diff --git a/.github/workflows/docker_ghpr.yml b/.github/workflows/docker_ghpr.yml deleted file mode 100644 index fa96faa..0000000 --- a/.github/workflows/docker_ghpr.yml +++ /dev/null @@ -1,53 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Create and publish a Docker image to GitHub Packages Repository - -on: workflow_dispatch - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - with: - platforms: 'arm64' - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Log in to the Container registry - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f33237 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/result* +/.venv +__pycache__ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b906fa2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "python.analysis.typeCheckingMode": "off", + "python.formatting.provider": "none", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4ebbd94..0000000 --- a/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM python:3.9 -LABEL maintainer="neurocis " - -RUN true && \ -\ -ARCH=`uname -m`; \ -if [ "$ARCH" = "armv7l" ]; then \ - NOBIN_OPT="--no-binary=grpcio"; \ -else \ - NOBIN_OPT=""; \ -fi; \ -# Install python prerequisites -pip3 install --no-cache-dir $NOBIN_OPT \ - grpcio==1.50.0 six==1.16.0 \ - influxdb==5.3.1 certifi==2022.9.24 charset-normalizer==2.1.1 idna==3.4 \ - msgpack==1.0.4 python-dateutil==2.8.2 pytz==2022.6 requests==2.28.1 \ - urllib3==1.26.12 \ - influxdb-client==1.34.0 reactivex==4.0.4 \ - paho-mqtt==1.6.1 \ - pypng==0.20220715.0 \ - typing_extensions==4.4.0 \ - yagrc==1.1.1 grpcio-reflection==1.50.0 protobuf==4.21.9 - -COPY dish_*.py starlink_*.py entrypoint.sh /app/ -WORKDIR /app - -ENTRYPOINT ["/bin/sh", "/app/entrypoint.sh"] -CMD ["dish_grpc_influx.py status alert_detail"] - -# docker run -d --name='starlink-grpc-tools' -e INFLUXDB_HOST=192.168.1.34 -e INFLUXDB_PORT=8086 -e INFLUXDB_DB=starlink -# --net='br0' --ip='192.168.1.39' ghcr.io/sparky8512/starlink-grpc-tools dish_grpc_influx.py status alert_detail diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 92ae7cb..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -printenv >> /etc/environment -ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -exec /usr/local/bin/python3 $@ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..efc6a5f --- /dev/null +++ b/flake.lock @@ -0,0 +1,169 @@ +{ + "nodes": { + "bash": { + "locked": { + "lastModified": 1678247195, + "narHash": "sha256-m/wSwlSket+hob3JED4XUvoWJLtW7yhtOiZrlRDMShs=", + "ref": "refs/heads/main", + "rev": "e7a00dcc0e75bc3ef6856bdd94d7d809245f5636", + "revCount": 1, + "type": "git", + "url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git" + }, + "original": { + "type": "git", + "url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "make-shell": { + "locked": { + "lastModified": 1634940815, + "narHash": "sha256-P69OmveboXzS+es1vQGS4bt+ckwbeIExqxfGLjGuJqA=", + "owner": "ursi", + "repo": "nix-make-shell", + "rev": "8add91681170924e4d0591b22f294aee3f5516f9", + "type": "github" + }, + "original": { + "owner": "ursi", + "repo": "nix-make-shell", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1693341273, + "narHash": "sha256-wrsPjsIx2767909MPGhSIOmkpGELM9eufqLQOPxmZQg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2ab91c8d65c00fd22a441c69bbf1bc9b420d5ea1", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils_2", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1693051011, + "narHash": "sha256-HNbuVCS/Fnl1YZOjBk9/MlIem+wM8fvIzTH0CVQrLSQ=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "5b3a5151cf212021ff8d424f215fb030e4ff2837", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + "root": { + "inputs": { + "bash": "bash", + "flake-utils": "flake-utils", + "make-shell": "make-shell", + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..24600ed --- /dev/null +++ b/flake.nix @@ -0,0 +1,87 @@ +{ + description = "Starling gRPC Tools"; + + inputs = { + nixpkgs = { + url = "nixpkgs/nixos-23.05"; + }; + poetry2nix = { + url = "github:nix-community/poetry2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + bash = { + url = "git+https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git"; + }; + make-shell = { + url = "github:ursi/nix-make-shell"; + }; + }; + outputs = { self, nixpkgs, poetry2nix, flake-utils, bash, make-shell, ... }@inputs: + flake-utils.lib.eachDefaultSystem + (system: + let + inherit (poetry2nix.legacyPackages.${system}) mkPoetryApplication overrides; + pkgs = import nixpkgs { + inherit system; + }; + python = pkgs.python311.withPackages (ps: with ps; [ + poetry-core + ]); + in + { + devShells.default = + let + make-shell = import inputs.make-shell { + inherit system; + pkgs = pkgs; + }; + project = "starlink"; + prompt = ( + bash.build_prompt + bash.ansi_normal_blue + "${project} - ${bash.username}@${bash.hostname_short}: ${bash.current_working_directory}" + "${project}:${bash.current_working_directory}" + ); + in + make-shell { + packages = [ + python + pkgs.poetry + ]; + env = { + POETRY_VIRTUALENVS_IN_PROJECT = "true"; + PS1 = prompt; + }; + setup = '' + export PATH=''$(pwd)/.venv/bin:$PATH + ''; + }; + packages = { + starlink-grpc-tools = mkPoetryApplication { + python = pkgs.python311; + projectDir = ./.; + groups = [ ]; + overrides = overrides.withDefaults ( + self: super: { + yagrc = super.yagrc.overridePythonAttrs ( + old: { + buildInputs = old.buildInputs ++ [ self.setuptools ]; + } + ); + } + ); + meta = with pkgs.lib; { + homepage = "https://github.com/sparky8512/starlink-grpc-tools"; + description = ""; + longDescription = ''''; + license = licenses.unlicense; + }; + }; + }; + default = self.packages.${system}.starlink-grpc-tools; + } + ); +} diff --git a/packaging/README.md b/packaging/README.md deleted file mode 100644 index a698bf4..0000000 --- a/packaging/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# starlink-grpc-tools Core Module - -This project packages up the `starlink_grpc` module from the [starlink-grpc-tools](https://github.com/sparky8512/starlink-grpc-tools) project and exports it as an installable package for use by other projects. It is not needed to install this project in order to use the scripts in starlink-grpc-tools, as those have their own copy of `starlink_grpc.py`. - -`starlink_grpc.py` is the only part of the scripts in starlink-grpc-tools that is designed to have a stable enough interface to be directly callable from other projects without having to go through a clunky command line interface. It provides the low(er) level core functionality available via the [gRPC](https://grpc.io/) service implemented on the Starlink user terminal. - -# Installation - -The most recently published version of this project can be installed by itself using pip: -```shell script -pip install starlink-grpc-core -``` -However, it is really meant to be installed as a dependency by other projects. - -# Usage - -The installation process places the `starlink_grpc.py` module in the top-level of your Python lib directory or virtual environment, so it can be used simply by doing: -```python -import starlink_grpc -``` -and then calling whatever functions you need. For details, see the doc strings in `starlink_grpc.py`. - -# Examples - -For example usage, see calling scripts in the [starlink-grpc-tools](https://github.com/sparky8512/starlink-grpc-tools) project, most of which are hopelessly convoluted, but some of which show simple usage of the `starlink_grpc` functions. - diff --git a/packaging/pyproject.toml b/packaging/pyproject.toml deleted file mode 100644 index 1c7ad35..0000000 --- a/packaging/pyproject.toml +++ /dev/null @@ -1,10 +0,0 @@ -[build-system] -requires = [ - "setuptools>=42", - "setuptools_scm[toml]>=3.4", - "wheel" -] -build-backend = "setuptools.build_meta" - -[tool.setuptools_scm] -root = ".." diff --git a/packaging/setup.py b/packaging/setup.py deleted file mode 100644 index b908cbe..0000000 --- a/packaging/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -import setuptools - -setuptools.setup() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..927753e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,773 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +description = "Flake8 plug-in loading the configuration from pyproject.toml" +category = "dev" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, +] + +[package.dependencies] +Flake8 = ">=5" + +[package.extras] +dev = ["pyTest", "pyTest-cov"] + +[[package]] +name = "grpcio" +version = "1.57.0" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.57.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:092fa155b945015754bdf988be47793c377b52b88d546e45c6a9f9579ac7f7b6"}, + {file = "grpcio-1.57.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f7349786da979a94690cc5c2b804cab4e8774a3cf59be40d037c4342c906649"}, + {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:82640e57fb86ea1d71ea9ab54f7e942502cf98a429a200b2e743d8672171734f"}, + {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40b72effd4c789de94ce1be2b5f88d7b9b5f7379fe9645f198854112a6567d9a"}, + {file = "grpcio-1.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f708a6a17868ad8bf586598bee69abded4996b18adf26fd2d91191383b79019"}, + {file = "grpcio-1.57.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:60fe15288a0a65d5c1cb5b4a62b1850d07336e3ba728257a810317be14f0c527"}, + {file = "grpcio-1.57.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6907b1cf8bb29b058081d2aad677b15757a44ef2d4d8d9130271d2ad5e33efca"}, + {file = "grpcio-1.57.0-cp310-cp310-win32.whl", hash = "sha256:57b183e8b252825c4dd29114d6c13559be95387aafc10a7be645462a0fc98bbb"}, + {file = "grpcio-1.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7b400807fa749a9eb286e2cd893e501b110b4d356a218426cb9c825a0474ca56"}, + {file = "grpcio-1.57.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c6ebecfb7a31385393203eb04ed8b6a08f5002f53df3d59e5e795edb80999652"}, + {file = "grpcio-1.57.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:00258cbe3f5188629828363ae8ff78477ce976a6f63fb2bb5e90088396faa82e"}, + {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:23e7d8849a0e58b806253fd206ac105b328171e01b8f18c7d5922274958cc87e"}, + {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5371bcd861e679d63b8274f73ac281751d34bd54eccdbfcd6aa00e692a82cd7b"}, + {file = "grpcio-1.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aed90d93b731929e742967e236f842a4a2174dc5db077c8f9ad2c5996f89f63e"}, + {file = "grpcio-1.57.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe752639919aad9ffb0dee0d87f29a6467d1ef764f13c4644d212a9a853a078d"}, + {file = "grpcio-1.57.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fada6b07ec4f0befe05218181f4b85176f11d531911b64c715d1875c4736d73a"}, + {file = "grpcio-1.57.0-cp311-cp311-win32.whl", hash = "sha256:bb396952cfa7ad2f01061fbc7dc1ad91dd9d69243bcb8110cf4e36924785a0fe"}, + {file = "grpcio-1.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:e503cb45ed12b924b5b988ba9576dc9949b2f5283b8e33b21dcb6be74a7c58d0"}, + {file = "grpcio-1.57.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:fd173b4cf02b20f60860dc2ffe30115c18972d7d6d2d69df97ac38dee03be5bf"}, + {file = "grpcio-1.57.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:d7f8df114d6b4cf5a916b98389aeaf1e3132035420a88beea4e3d977e5f267a5"}, + {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:76c44efa4ede1f42a9d5b2fed1fe9377e73a109bef8675fb0728eb80b0b8e8f2"}, + {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4faea2cfdf762a664ab90589b66f416274887641ae17817de510b8178356bf73"}, + {file = "grpcio-1.57.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60b83c43faeb6d0a9831f0351d7787a0753f5087cc6fa218d78fdf38e5acef0"}, + {file = "grpcio-1.57.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b363bbb5253e5f9c23d8a0a034dfdf1b7c9e7f12e602fc788c435171e96daccc"}, + {file = "grpcio-1.57.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f1fb0fd4a1e9b11ac21c30c169d169ef434c6e9344ee0ab27cfa6f605f6387b2"}, + {file = "grpcio-1.57.0-cp37-cp37m-win_amd64.whl", hash = "sha256:34950353539e7d93f61c6796a007c705d663f3be41166358e3d88c45760c7d98"}, + {file = "grpcio-1.57.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:871f9999e0211f9551f368612460442a5436d9444606184652117d6a688c9f51"}, + {file = "grpcio-1.57.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:a8a8e560e8dbbdf29288872e91efd22af71e88b0e5736b0daf7773c1fecd99f0"}, + {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2313b124e475aa9017a9844bdc5eafb2d5abdda9d456af16fc4535408c7d6da6"}, + {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4098b6b638d9e0ca839a81656a2fd4bc26c9486ea707e8b1437d6f9d61c3941"}, + {file = "grpcio-1.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e5b58e32ae14658085c16986d11e99abd002ddbf51c8daae8a0671fffb3467f"}, + {file = "grpcio-1.57.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0f80bf37f09e1caba6a8063e56e2b87fa335add314cf2b78ebf7cb45aa7e3d06"}, + {file = "grpcio-1.57.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5b7a4ce8f862fe32b2a10b57752cf3169f5fe2915acfe7e6a1e155db3da99e79"}, + {file = "grpcio-1.57.0-cp38-cp38-win32.whl", hash = "sha256:9338bacf172e942e62e5889b6364e56657fbf8ac68062e8b25c48843e7b202bb"}, + {file = "grpcio-1.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:e1cb52fa2d67d7f7fab310b600f22ce1ff04d562d46e9e0ac3e3403c2bb4cc16"}, + {file = "grpcio-1.57.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fee387d2fab144e8a34e0e9c5ca0f45c9376b99de45628265cfa9886b1dbe62b"}, + {file = "grpcio-1.57.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b53333627283e7241fcc217323f225c37783b5f0472316edcaa4479a213abfa6"}, + {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f19ac6ac0a256cf77d3cc926ef0b4e64a9725cc612f97228cd5dc4bd9dbab03b"}, + {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fdf04e402f12e1de8074458549337febb3b45f21076cc02ef4ff786aff687e"}, + {file = "grpcio-1.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5613a2fecc82f95d6c51d15b9a72705553aa0d7c932fad7aed7afb51dc982ee5"}, + {file = "grpcio-1.57.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b670c2faa92124b7397b42303e4d8eb64a4cd0b7a77e35a9e865a55d61c57ef9"}, + {file = "grpcio-1.57.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a635589201b18510ff988161b7b573f50c6a48fae9cb567657920ca82022b37"}, + {file = "grpcio-1.57.0-cp39-cp39-win32.whl", hash = "sha256:d78d8b86fcdfa1e4c21f8896614b6cc7ee01a2a758ec0c4382d662f2a62cf766"}, + {file = "grpcio-1.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:20ec6fc4ad47d1b6e12deec5045ec3cd5402d9a1597f738263e98f490fe07056"}, + {file = "grpcio-1.57.0.tar.gz", hash = "sha256:4b089f7ad1eb00a104078bab8015b0ed0ebcb3b589e527ab009c53893fd4e613"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.57.0)"] + +[[package]] +name = "grpcio-reflection" +version = "1.57.0" +description = "Standard Protobuf Reflection Service for gRPC" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "grpcio-reflection-1.57.0.tar.gz", hash = "sha256:8f63a18729cba995a172f8325235f5094cb066febec75f9a3b1b2e28328aa166"}, + {file = "grpcio_reflection-1.57.0-py3-none-any.whl", hash = "sha256:d7deb8587f9d0095fb5d367c2aa5ce1380e3f23b0f8bca6c00bc404c5429cb6a"}, +] + +[package.dependencies] +grpcio = ">=1.57.0" +protobuf = ">=4.21.6" + +[[package]] +name = "grpcio-tools" +version = "1.57.0" +description = "Protobuf code generator for gRPC" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-tools-1.57.0.tar.gz", hash = "sha256:2f16130d869ce27ecd623194547b649dd657333ec7e8644cc571c645781a9b85"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:4fb8a8468031f858381a576078924af364a08833d8f8f3237018252c4573a802"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:35bf0dad8a3562043345236c26d0053a856fb06c04d7da652f2ded914e508ae7"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:ec9aab2fb6783c7fc54bc28f58eb75f1ca77594e6b0fd5e5e7a8114a95169fe0"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cf5fc0a1c23f8ea34b408b72fb0e90eec0f404ad4dba98e8f6da3c9ce34e2ed"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26e69d08a515554e0cfe1ec4d31568836f4b17f0ff82294f957f629388629eb9"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c39a3656576b6fdaaf28abe0467f7a7231df4230c1bee132322dbc3209419e7f"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f64f8ab22d27d4a5693310748d35a696061c3b5c7b8c4fb4ab3b4bc1068b6b56"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-win32.whl", hash = "sha256:d2a134756f4db34759a5cc7f7e43f7eb87540b68d1cca62925593c6fb93924f7"}, + {file = "grpcio_tools-1.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a3d60fb8d46ede26c1907c146561b3a9caa20a7aff961bc661ef8226f85a2e9"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:aac98ecad8f7bd4301855669d42a5d97ef7bb34bec2b1e74c7a0641d47e313cf"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:cdd020cb68b51462983b7c2dfbc3eb6ede032b8bf438d4554df0c3f08ce35c76"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:f54081b08419a39221cd646363b5708857c696b3ad4784f1dcf310891e33a5f7"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed85a0291fff45b67f2557fe7f117d3bc7af8b54b8619d27bf374b5c8b7e3ca2"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e868cd6feb3ef07d4b35be104fe1fd0657db05259ff8f8ec5e08f4f89ca1191d"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:dfb6f6120587b8e228a3cae5ee4985b5bdc18501bad05c49df61965dfc9d70a9"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a7ad7f328e28fc97c356d0f10fb10d8b5151bb65aa7cf14bf8084513f0b7306"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-win32.whl", hash = "sha256:9867f2817b1a0c93c523f89ac6c9d8625548af4620a7ce438bf5a76e23327284"}, + {file = "grpcio_tools-1.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:1f9e917a9f18087f6c14b4d4508fb94fca5c2f96852363a89232fb9b2124ac1f"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:9f2aefa8a37bd2c4db1a3f1aca11377e2766214520fb70e67071f4ff8d8b0fa5"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:850cbda0ec5d24c39e7215ede410276040692ca45d105fbbeada407fa03f0ac0"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:6fa52972c9647876ea35f6dc2b51002a74ed900ec7894586cbb2fe76f64f99de"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0eea89d7542719594e50e2283f51a072978b953e8b3e9fd7c59a2c762d4c1"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3da5240211252fc70a6451fe00c143e2ab2f7bfc2445695ad2ed056b8e48d96"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a0256f8786ac9e4db618a1aa492bb3472569a0946fd3ee862ffe23196323da55"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c026bdf5c1366ce88b7bbe2d8207374d675afd3fd911f60752103de3da4a41d2"}, + {file = "grpcio_tools-1.57.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9053c2f655589545be08b9d6a673e92970173a4bf11a4b9f18cd6e9af626b587"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:81ec4dbb696e095057b2528d11a8da04be6bbe2b967fa07d4ea9ba6354338cbf"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:495e2946406963e0b9f063f76d5af0f2a19517dac2b367b5b044432ac9194296"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:7b46fc6aa8eb7edd18cafcd21fd98703cb6c09e46b507de335fca7f0161dfccb"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb81ff861692111fa81bd85f64584e624cb4013bd66fbce8a209b8893f5ce398"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a42dc220eb5305f470855c9284f4c8e85ae59d6d742cd07946b0cbe5e9ca186"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90d10d9038ba46a595a223a34f136c9230e3d6d7abc2433dbf0e1c31939d3a8b"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5bc3e6d338aefb052e19cedabe00452be46d0c10a4ed29ee77abb00402e438fe"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-win32.whl", hash = "sha256:34b36217b17b5bea674a414229913e1fd80ede328be51e1b531fcc62abd393b0"}, + {file = "grpcio_tools-1.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbde4004a0688400036342ff73e3706e8940483e2871547b1354d59e93a38277"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:784574709b9690dc28696617ea69352e2132352fdfc9bc89afa8e39f99ae538e"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:85ac4e62eb44428cde025fd9ab7554002315fc7880f791c553fc5a0015cc9931"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:dc771d4db5701f280957bbcee91745e0686d00ed1c6aa7e05ba30a58b02d70a1"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3ac06703c412f8167a9062eaf6099409967e33bf98fa5b02be4b4689b6bdf39"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02d78c034109f46032c7217260066d49d41e6bcaf588fa28fa40fe2f83445347"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2db25f15ed44327f2e02d0c4fe741ac966f9500e407047d8a7c7fccf2df65616"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b417c97936d94874a3ce7ed8deab910f2233e3612134507cfee4af8735c38a6"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-win32.whl", hash = "sha256:f717cce5093e6b6049d9ea6d12fdf3658efdb1a80772f7737db1f8510b876df6"}, + {file = "grpcio_tools-1.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:1c0e8a1a32973a5d59fbcc19232f925e5c48116e9411f788033a31c5ca5130b4"}, +] + +[package.dependencies] +grpcio = ">=1.57.0" +protobuf = ">=4.21.6,<5.0dev" +setuptools = "*" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "influxdb" +version = "5.3.1" +description = "InfluxDB client" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "influxdb-5.3.1-py2.py3-none-any.whl", hash = "sha256:65040a1f53d1a2a4f88a677e89e3a98189a7d30cf2ab61c318aaa89733280747"}, + {file = "influxdb-5.3.1.tar.gz", hash = "sha256:46f85e7b04ee4b3dee894672be6a295c94709003a7ddea8820deec2ac4d8b27a"}, +] + +[package.dependencies] +msgpack = "*" +python-dateutil = ">=2.6.0" +pytz = "*" +requests = ">=2.17.0" +six = ">=1.10.0" + +[package.extras] +test = ["mock", "nose", "nose-cov", "requests-mock"] + +[[package]] +name = "influxdb-client" +version = "1.37.0" +description = "InfluxDB 2.0 Python client library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "influxdb_client-1.37.0-py3-none-any.whl", hash = "sha256:30b03888846fab6fb740936b1a08af24841b791cf1b99773e3894dd1d64edf2f"}, + {file = "influxdb_client-1.37.0.tar.gz", hash = "sha256:01ac44d6a16a965ae2e0fa3238e2edeb147c11935a89b61439c9a752458001da"}, +] + +[package.dependencies] +certifi = ">=14.05.14" +python-dateutil = ">=2.5.3" +reactivex = ">=4.0.4" +setuptools = ">=21.0.0" +urllib3 = ">=1.26.0" + +[package.extras] +async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"] +ciso = ["ciso8601 (>=2.1.1)"] +extra = ["numpy", "pandas (>=0.25.3)"] +test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.2)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=2.1.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "msgpack" +version = "1.0.5" +description = "MessagePack serializer" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "paho-mqtt" +version = "1.6.1" +description = "MQTT version 5.0/3.1.1 client class" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, +] + +[package.extras] +proxy = ["PySocks"] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "protobuf" +version = "4.24.2" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.2-cp310-abi3-win32.whl", hash = "sha256:58e12d2c1aa428ece2281cef09bbaa6938b083bcda606db3da4e02e991a0d924"}, + {file = "protobuf-4.24.2-cp310-abi3-win_amd64.whl", hash = "sha256:77700b55ba41144fc64828e02afb41901b42497b8217b558e4a001f18a85f2e3"}, + {file = "protobuf-4.24.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:237b9a50bd3b7307d0d834c1b0eb1a6cd47d3f4c2da840802cd03ea288ae8880"}, + {file = "protobuf-4.24.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:25ae91d21e3ce8d874211110c2f7edd6384816fb44e06b2867afe35139e1fd1c"}, + {file = "protobuf-4.24.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:c00c3c7eb9ad3833806e21e86dca448f46035242a680f81c3fe068ff65e79c74"}, + {file = "protobuf-4.24.2-cp37-cp37m-win32.whl", hash = "sha256:4e69965e7e54de4db989289a9b971a099e626f6167a9351e9d112221fc691bc1"}, + {file = "protobuf-4.24.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c5cdd486af081bf752225b26809d2d0a85e575b80a84cde5172a05bbb1990099"}, + {file = "protobuf-4.24.2-cp38-cp38-win32.whl", hash = "sha256:6bd26c1fa9038b26c5c044ee77e0ecb18463e957fefbaeb81a3feb419313a54e"}, + {file = "protobuf-4.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb7aa97c252279da65584af0456f802bd4b2de429eb945bbc9b3d61a42a8cd16"}, + {file = "protobuf-4.24.2-cp39-cp39-win32.whl", hash = "sha256:2b23bd6e06445699b12f525f3e92a916f2dcf45ffba441026357dea7fa46f42b"}, + {file = "protobuf-4.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:839952e759fc40b5d46be319a265cf94920174d88de31657d5622b5d8d6be5cd"}, + {file = "protobuf-4.24.2-py3-none-any.whl", hash = "sha256:3b7b170d3491ceed33f723bbf2d5a260f8a4e23843799a3906f16ef736ef251e"}, + {file = "protobuf-4.24.2.tar.gz", hash = "sha256:7fda70797ddec31ddfa3576cbdcc3ddbb6b3078b737a1a87ab9136af0570cd6e"}, +] + +[[package]] +name = "pycodestyle" +version = "2.11.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, + {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pypng" +version = "0.20220715.0" +description = "Pure Python library for saving and loading PNG images" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, + {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "reactivex" +version = "4.0.4" +description = "ReactiveX (Rx) for Python" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"}, + {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}, +] + +[package.dependencies] +typing-extensions = ">=4.1.1,<5.0.0" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "68.1.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "yagrc" +version = "1.1.2" +description = "Yet another gRPC reflection client" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yagrc-1.1.2-py3-none-any.whl", hash = "sha256:ef7fc6da021c10f3cac2ea21147931bca97e90341645d28edd6c90b68b0ef3f5"}, + {file = "yagrc-1.1.2.tar.gz", hash = "sha256:ef264bf98bfbc8f9932dca85048838e6f541db87de4937610ea15e035afe1251"}, +] + +[package.dependencies] +grpcio = ">=1.12.0" +grpcio-reflection = ">=1.7.3" +protobuf = ">=4.22.0" + +[package.extras] +test = ["pytest", "pytest-grpc"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "d0558513b225264653cde8153abe3425004ee3298c1407cbb2dc672fe356b2f3" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7ae143e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[tool.poetry] +name = "starlink-grpc-tools" +version = "0.1.0" +description = "" +authors = ["Jeffrey C. Ollie "] +readme = "README.md" +packages = [{ include = "starlink_grpc_tools" }] + +[tool.poetry.dependencies] +python = "^3.11" +grpcio = "^1.57.0" +grpcio-tools = "^1.57.0" +protobuf = "^4.24.2" +yagrc = "*" +paho-mqtt = "^1.6.1" +influxdb = "^5.3.1" +influxdb-client = "^1.37.0" +pypng = "^0.20220715.0" +typing-extensions = "^4.7.1" + +[tool.poetry.group.dev.dependencies] +black = "^23.7.0" +flake8 = "^6.1.0" +flake8-pyproject = "^1.2.3" +isort = "^5.12.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] + +[tool.isort] +profile = "black" +line_length = 88 +force_single_line = true +force_sort_within_sections = true +from_first = false + +[tool.flake8] +max-line-length = 120 +extend-ignore = "E203" + +[tool.poetry.scripts] +dish_grpc_prometheus = "starlink_grpc_tools.dish_grpc_prometheus:main" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f401847..0000000 --- a/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -grpcio>=1.12.0 -grpcio-tools>=1.20.0 -protobuf>=3.6.0 -yagrc>=1.1.1 -paho-mqtt>=1.5.1 -influxdb>=5.3.1 -influxdb_client>=1.23.0 -pypng>=0.0.20 -typing-extensions>=4.3.0 \ No newline at end of file diff --git a/starlink_grpc_tools/__init__.py b/starlink_grpc_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dish_common.py b/starlink_grpc_tools/dish_common.py similarity index 75% rename from dish_common.py rename to starlink_grpc_tools/dish_common.py index 6701a7a..1bd5b92 100644 --- a/dish_common.py +++ b/starlink_grpc_tools/dish_common.py @@ -20,13 +20,17 @@ from typing import List import grpc -import starlink_grpc +import starlink_grpc_tools.starlink_grpc as starlink_grpc BRACKETS_RE = re.compile(r"([^[]*)(\[((\d+),|)(\d*)\]|)$") LOOP_TIME_DEFAULT = 0 STATUS_MODES: List[str] = ["status", "obstruction_detail", "alert_detail", "location"] HISTORY_STATS_MODES: List[str] = [ - "ping_drop", "ping_run_length", "ping_latency", "ping_loaded_latency", "usage" + "ping_drop", + "ping_run_length", + "ping_latency", + "ping_loaded_latency", + "usage", ] UNGROUPED_MODES: List[str] = [] @@ -34,60 +38,79 @@ UNGROUPED_MODES: List[str] = [] def create_arg_parser(output_description, bulk_history=True): """Create an argparse parser and add the common command line options.""" parser = argparse.ArgumentParser( - description="Collect status and/or history data from a Starlink user terminal and " + - output_description, + description="Collect status and/or history data from a Starlink user terminal and " + + output_description, epilog="Additional arguments can be read from a file by including @FILENAME as an " "option, where FILENAME is a path to a file that contains arguments, one per line.", fromfile_prefix_chars="@", - add_help=False) + add_help=False, + ) # need to remember this for later parser.bulk_history = bulk_history group = parser.add_argument_group(title="General options") - group.add_argument("-g", - "--target", - help="host:port of dish to query, default is the standard IP address " - "and port (192.168.100.1:9200)") + group.add_argument( + "-g", + "--target", + help="host:port of dish to query, default is the standard IP address " + "and port (192.168.100.1:9200)", + ) group.add_argument("-h", "--help", action="help", help="Be helpful") - group.add_argument("-N", - "--numeric", - action="store_true", - help="Record boolean values as 1 and 0 instead of True and False") - group.add_argument("-t", - "--loop-interval", - type=float, - default=float(LOOP_TIME_DEFAULT), - help="Loop interval in seconds or 0 for no loop, default: " + - str(LOOP_TIME_DEFAULT)) + group.add_argument( + "-N", + "--numeric", + action="store_true", + help="Record boolean values as 1 and 0 instead of True and False", + ) + group.add_argument( + "-t", + "--loop-interval", + type=float, + default=float(LOOP_TIME_DEFAULT), + help="Loop interval in seconds or 0 for no loop, default: " + + str(LOOP_TIME_DEFAULT), + ) group.add_argument("-v", "--verbose", action="store_true", help="Be verbose") group = parser.add_argument_group(title="History mode options") - group.add_argument("-a", - "--all-samples", - action="store_const", - const=-1, - dest="samples", - help="Parse all valid samples") - group.add_argument("-o", - "--poll-loops", - type=int, - help="Poll history for N loops and aggregate data before computing history " - "stats; this allows for a smaller loop interval with less loss of data " - "when the dish reboots", - metavar="N") + group.add_argument( + "-a", + "--all-samples", + action="store_const", + const=-1, + dest="samples", + help="Parse all valid samples", + ) + group.add_argument( + "-o", + "--poll-loops", + type=int, + help="Poll history for N loops and aggregate data before computing history " + "stats; this allows for a smaller loop interval with less loss of data " + "when the dish reboots", + metavar="N", + ) if bulk_history: - sample_help = ("Number of data samples to parse; normally applies to first loop " - "iteration only, default: all in bulk mode, loop interval if loop " - "interval set, else all available samples") - no_counter_help = ("Don't track sample counter across loop iterations in non-bulk " - "modes; keep using samples option value instead") + sample_help = ( + "Number of data samples to parse; normally applies to first loop " + "iteration only, default: all in bulk mode, loop interval if loop " + "interval set, else all available samples" + ) + no_counter_help = ( + "Don't track sample counter across loop iterations in non-bulk " + "modes; keep using samples option value instead" + ) else: - sample_help = ("Number of data samples to parse; normally applies to first loop " - "iteration only, default: loop interval, if set, else all available " + - "samples") - no_counter_help = ("Don't track sample counter across loop iterations; keep using " - "samples option value instead") + sample_help = ( + "Number of data samples to parse; normally applies to first loop " + "iteration only, default: loop interval, if set, else all available " + + "samples" + ) + no_counter_help = ( + "Don't track sample counter across loop iterations; keep using " + "samples option value instead" + ) group.add_argument("-s", "--samples", type=int, help=sample_help) group.add_argument("-j", "--no-counter", action="store_true", help=no_counter_help) @@ -114,11 +137,13 @@ def run_arg_parser(parser, need_id=False, no_stdout_errors=False, modes=None): modes = STATUS_MODES + HISTORY_STATS_MODES + UNGROUPED_MODES if parser.bulk_history: modes.append("bulk_history") - parser.add_argument("mode", - nargs="+", - choices=modes, - help="The data group to record, one or more of: " + ", ".join(modes), - metavar="mode") + parser.add_argument( + "mode", + nargs="+", + choices=modes, + help="The data group to record, one or more of: " + ", ".join(modes), + metavar="mode", + ) opts = parser.parse_args() @@ -163,6 +188,7 @@ def conn_error(opts, msg, *args): class GlobalState: """A class for keeping state across loop iterations.""" + def __init__(self, target=None): # counter, timestamp for bulk_history: self.counter = None @@ -227,7 +253,9 @@ def get_data(opts, gstate, add_item, add_sequence, add_bulk=None, flush_history= rc, status_ts = get_status_data(opts, gstate, add_item, add_sequence) if opts.history_stats_mode and (not rc or opts.poll_loops > 1): - hist_rc, hist_ts = get_history_stats(opts, gstate, add_item, add_sequence, flush_history) + hist_rc, hist_ts = get_history_stats( + opts, gstate, add_item, add_sequence, flush_history + ) if not rc: rc = hist_rc @@ -252,10 +280,12 @@ def add_data_numeric(data, category, add_item, add_sequence): if seq is None: add_item(name, int(val) if isinstance(val, int) else val, category) else: - add_sequence(name, - [int(subval) if isinstance(subval, int) else subval for subval in val], - category, - int(start) if start else 0) + add_sequence( + name, + [int(subval) if isinstance(subval, int) else subval for subval in val], + category, + int(start) if start else 0, + ) def get_status_data(opts, gstate, add_item, add_sequence): @@ -269,7 +299,10 @@ def get_status_data(opts, gstate, add_item, add_sequence): except starlink_grpc.GrpcError as e: if "status" in opts.mode: if opts.need_id and gstate.dish_id is None: - conn_error(opts, "Dish unreachable and ID unknown, so not recording state") + conn_error( + opts, + "Dish unreachable and ID unknown, so not recording state", + ) return 1, None if opts.verbose: print("Dish unreachable") @@ -293,7 +326,9 @@ def get_status_data(opts, gstate, add_item, add_sequence): conn_error(opts, "Failure getting location: %s", str(e)) return 1, None if location["latitude"] is None and gstate.warn_once_location: - logging.warning("Location data not enabled. See README for more details.") + logging.warning( + "Location data not enabled. See README for more details." + ) gstate.warn_once_location = False add_data(location, "status", add_item, add_sequence) return 0, timestamp @@ -319,7 +354,9 @@ def get_history_stats(opts, gstate, add_item, add_sequence, flush_history): history = starlink_grpc.get_history(context=gstate.context) gstate.timestamp_stats = timestamp except (AttributeError, ValueError, grpc.RpcError) as e: - conn_error(opts, "Failure getting history: %s", str(starlink_grpc.GrpcError(e))) + conn_error( + opts, "Failure getting history: %s", str(starlink_grpc.GrpcError(e)) + ) history = None parse_samples = opts.samples if gstate.counter_stats is None else -1 @@ -329,11 +366,13 @@ def get_history_stats(opts, gstate, add_item, add_sequence, flush_history): # was a dish reboot. if gstate.accum_history: if history is not None: - gstate.accum_history = starlink_grpc.concatenate_history(gstate.accum_history, - history, - samples1=parse_samples, - start1=start, - verbose=opts.verbose) + gstate.accum_history = starlink_grpc.concatenate_history( + gstate.accum_history, + history, + samples1=parse_samples, + start1=start, + verbose=opts.verbose, + ) # Counter tracking gets too complicated to handle across reboots # once the data has been accumulated, so just have concatenate # handle it on the first polled loop and use a value of 0 to @@ -354,7 +393,9 @@ def get_history_stats(opts, gstate, add_item, add_sequence, flush_history): new_samples = gstate.accum_history.current if new_samples > len(gstate.accum_history.pop_ping_drop_rate): new_samples = len(gstate.accum_history.pop_ping_drop_rate) - gstate.poll_count = max(gstate.poll_count, int((new_samples-1) / opts.loop_interval)) + gstate.poll_count = max( + gstate.poll_count, int((new_samples - 1) / opts.loop_interval) + ) gstate.first_poll = False if gstate.poll_count < opts.poll_loops - 1 and not flush_history: @@ -366,10 +407,9 @@ def get_history_stats(opts, gstate, add_item, add_sequence, flush_history): if gstate.accum_history is None: return (0, None) if flush_history else (1, None) - groups = starlink_grpc.history_stats(parse_samples, - start=start, - verbose=opts.verbose, - history=gstate.accum_history) + groups = starlink_grpc.history_stats( + parse_samples, start=start, verbose=opts.verbose, history=gstate.accum_history + ) general, ping, runlen, latency, loaded, usage = groups[0:6] add_data = add_data_numeric if opts.numeric else add_data_normal add_data(general, "ping_stats", add_item, add_sequence) @@ -400,10 +440,9 @@ def get_bulk_data(opts, gstate, add_bulk): start = gstate.counter parse_samples = opts.bulk_samples if start is None else -1 try: - general, bulk = starlink_grpc.history_bulk_data(parse_samples, - start=start, - verbose=opts.verbose, - context=gstate.context) + general, bulk = starlink_grpc.history_bulk_data( + parse_samples, start=start, verbose=opts.verbose, context=gstate.context + ) except starlink_grpc.GrpcError as e: conn_error(opts, "Failure getting history: %s", str(e)) return 1 @@ -417,16 +456,26 @@ def get_bulk_data(opts, gstate, add_bulk): timestamp = None # Allow up to 2 seconds of time drift before forcibly re-syncing, since # +/- 1 second can happen just due to scheduler timing. - if timestamp is not None and not before - 2.0 <= timestamp + parsed_samples <= after + 2.0: + if ( + timestamp is not None + and not before - 2.0 <= timestamp + parsed_samples <= after + 2.0 + ): if opts.verbose: - print("Lost sample time sync at: " + - str(datetime.fromtimestamp(timestamp + parsed_samples, tz=timezone.utc))) + print( + "Lost sample time sync at: " + + str( + datetime.fromtimestamp(timestamp + parsed_samples, tz=timezone.utc) + ) + ) timestamp = None if timestamp is None: timestamp = int(before) if opts.verbose: - print("Establishing new time base: {0} -> {1}".format( - new_counter, datetime.fromtimestamp(timestamp, tz=timezone.utc))) + print( + "Establishing new time base: {0} -> {1}".format( + new_counter, datetime.fromtimestamp(timestamp, tz=timezone.utc) + ) + ) timestamp -= parsed_samples if opts.numeric: @@ -434,7 +483,11 @@ def get_bulk_data(opts, gstate, add_bulk): { k: [int(subv) if isinstance(subv, int) else subv for subv in v] for k, v in bulk.items() - }, parsed_samples, timestamp, new_counter - parsed_samples) + }, + parsed_samples, + timestamp, + new_counter - parsed_samples, + ) else: add_bulk(bulk, parsed_samples, timestamp, new_counter - parsed_samples) diff --git a/dish_control.py b/starlink_grpc_tools/dish_control.py similarity index 70% rename from dish_control.py rename to starlink_grpc_tools/dish_control.py index 08a66d6..e1911e6 100644 --- a/dish_control.py +++ b/starlink_grpc_tools/dish_control.py @@ -11,11 +11,13 @@ from yagrc import reflector as yagrc_reflector def parse_args(): parser = argparse.ArgumentParser(description="Starlink user terminal state control") - parser.add_argument("-e", - "--target", - default="192.168.100.1:9200", - help="host:port of dish to query, default is the standard IP address " - "and port (192.168.100.1:9200)") + parser.add_argument( + "-e", + "--target", + default="192.168.100.1:9200", + help="host:port of dish to query, default is the standard IP address " + "and port (192.168.100.1:9200)", + ) subs = parser.add_subparsers(dest="command", required=True) subs.add_parser("reboot", help="Reboot the user terminal") subs.add_parser("stow", help="Set user terminal to stow position") @@ -23,15 +25,14 @@ def parse_args(): sleep_parser = subs.add_parser( "set_sleep", help="Show, set, or disable power save configuration", - description="Run without arguments to show current configuration") - sleep_parser.add_argument("start", - nargs="?", - type=int, - help="Start time in minutes past midnight UTC") - sleep_parser.add_argument("duration", - nargs="?", - type=int, - help="Duration in minutes, or 0 to disable") + description="Run without arguments to show current configuration", + ) + sleep_parser.add_argument( + "start", nargs="?", type=int, help="Start time in minutes past midnight UTC" + ) + sleep_parser.add_argument( + "duration", nargs="?", type=int, help="Duration in minutes, or 0 to disable" + ) opts = parser.parse_args() if opts.command == "set_sleep" and opts.start is not None: @@ -70,23 +71,35 @@ def main(): dish_power_save={ "power_save_start_minutes": opts.start, "power_save_duration_minutes": opts.duration, - "enable_power_save": True - }) + "enable_power_save": True, + } + ) else: # duration of 0 not allowed, even when disabled - request = request_class(dish_power_save={ - "power_save_duration_minutes": 1, - "enable_power_save": False - }) + request = request_class( + dish_power_save={ + "power_save_duration_minutes": 1, + "enable_power_save": False, + } + ) response = stub.Handle(request, timeout=10) - if opts.command == "set_sleep" and opts.start is None and opts.duration is None: + if ( + opts.command == "set_sleep" + and opts.start is None + and opts.duration is None + ): config = response.dish_get_config.dish_config if config.power_save_mode: - print("Sleep start:", config.power_save_start_minutes, - "minutes past midnight UTC") - print("Sleep duration:", config.power_save_duration_minutes, "minutes") + print( + "Sleep start:", + config.power_save_start_minutes, + "minutes past midnight UTC", + ) + print( + "Sleep duration:", config.power_save_duration_minutes, "minutes" + ) else: print("Sleep disabled") except (AttributeError, ValueError, grpc.RpcError) as e: diff --git a/dish_grpc_influx.py b/starlink_grpc_tools/dish_grpc_influx.py similarity index 69% rename from dish_grpc_influx.py rename to starlink_grpc_tools/dish_grpc_influx.py index 6408367..6d2d8a5 100644 --- a/dish_grpc_influx.py +++ b/starlink_grpc_tools/dish_grpc_influx.py @@ -31,7 +31,7 @@ import warnings from influxdb import InfluxDBClient -import dish_common +import starlink_grpc_tools.dish_common as dish_common HOST_DEFAULT = "localhost" DATABASE_DEFAULT = "starlinkstats" @@ -52,41 +52,58 @@ def handle_sigterm(signum, frame): def parse_args(): parser = dish_common.create_arg_parser( - output_description="write it to an InfluxDB 1.x database") + output_description="write it to an InfluxDB 1.x database" + ) group = parser.add_argument_group(title="InfluxDB 1.x database options") - group.add_argument("-n", - "--hostname", - default=HOST_DEFAULT, - dest="host", - help="Hostname of InfluxDB server, default: " + HOST_DEFAULT) - group.add_argument("-p", "--port", type=int, help="Port number to use on InfluxDB server") - group.add_argument("-P", "--password", help="Set password for username/password authentication") + group.add_argument( + "-n", + "--hostname", + default=HOST_DEFAULT, + dest="host", + help="Hostname of InfluxDB server, default: " + HOST_DEFAULT, + ) + group.add_argument( + "-p", "--port", type=int, help="Port number to use on InfluxDB server" + ) + group.add_argument( + "-P", "--password", help="Set password for username/password authentication" + ) group.add_argument("-U", "--username", help="Set username for authentication") - group.add_argument("-D", - "--database", - default=DATABASE_DEFAULT, - help="Database name to use, default: " + DATABASE_DEFAULT) + group.add_argument( + "-D", + "--database", + default=DATABASE_DEFAULT, + help="Database name to use, default: " + DATABASE_DEFAULT, + ) group.add_argument("-R", "--retention-policy", help="Retention policy name to use") - group.add_argument("-k", - "--skip-query", - action="store_true", - help="Skip querying for prior sample write point in bulk mode") - group.add_argument("-C", - "--ca-cert", - dest="verify_ssl", - help="Enable SSL/TLS using specified CA cert to verify server", - metavar="FILENAME") - group.add_argument("-I", - "--insecure", - action="store_false", - dest="verify_ssl", - help="Enable SSL/TLS but disable certificate verification (INSECURE!)") - group.add_argument("-S", - "--secure", - action="store_true", - dest="verify_ssl", - help="Enable SSL/TLS using default CA cert") + group.add_argument( + "-k", + "--skip-query", + action="store_true", + help="Skip querying for prior sample write point in bulk mode", + ) + group.add_argument( + "-C", + "--ca-cert", + dest="verify_ssl", + help="Enable SSL/TLS using specified CA cert to verify server", + metavar="FILENAME", + ) + group.add_argument( + "-I", + "--insecure", + action="store_false", + dest="verify_ssl", + help="Enable SSL/TLS but disable certificate verification (INSECURE!)", + ) + group.add_argument( + "-S", + "--secure", + action="store_true", + dest="verify_ssl", + help="Enable SSL/TLS using default CA cert", + ) env_map = ( ("INFLUXDB_HOST", "host"), @@ -130,16 +147,20 @@ def parse_args(): def flush_points(opts, gstate): try: while len(gstate.points) > MAX_BATCH: - gstate.influx_client.write_points(gstate.points[:MAX_BATCH], - time_precision="s", - retention_policy=opts.retention_policy) + gstate.influx_client.write_points( + gstate.points[:MAX_BATCH], + time_precision="s", + retention_policy=opts.retention_policy, + ) if opts.verbose: print("Data points written: " + str(MAX_BATCH)) del gstate.points[:MAX_BATCH] if gstate.points: - gstate.influx_client.write_points(gstate.points, - time_precision="s", - retention_policy=opts.retention_policy) + gstate.influx_client.write_points( + gstate.points, + time_precision="s", + retention_policy=opts.retention_policy, + ) if opts.verbose: print("Data points written: " + str(len(gstate.points))) gstate.points.clear() @@ -159,12 +180,13 @@ def flush_points(opts, gstate): def query_counter(gstate, start, end): try: # fetch the latest point where counter field was recorded - result = gstate.influx_client.query("SELECT counter FROM \"{0}\" " - "WHERE time>={1}s AND time<{2}s AND id=$id " - "ORDER by time DESC LIMIT 1;".format( - BULK_MEASUREMENT, start, end), - bind_params={"id": gstate.dish_id}, - epoch="s") + result = gstate.influx_client.query( + 'SELECT counter FROM "{0}" ' + "WHERE time>={1}s AND time<{2}s AND id=$id " + "ORDER by time DESC LIMIT 1;".format(BULK_MEASUREMENT, start, end), + bind_params={"id": gstate.dish_id}, + epoch="s", + ) points = list(result.get_points()) if points: counter = points[0].get("counter", None) @@ -177,22 +199,28 @@ def query_counter(gstate, start, end): # query(), so just skip this functionality. logging.error( "Failed running query, probably due to influxdb-python version too old. " - "Skipping resumption from prior counter value. Reported error was: %s", str(e)) + "Skipping resumption from prior counter value. Reported error was: %s", + str(e), + ) return None, 0 def sync_timebase(opts, gstate): try: - db_counter, db_timestamp = query_counter(gstate, gstate.start_timestamp, gstate.timestamp) + db_counter, db_timestamp = query_counter( + gstate, gstate.start_timestamp, gstate.timestamp + ) except Exception as e: # could be temporary outage, so try again next time - dish_common.conn_error(opts, "Failed querying InfluxDB for prior count: %s", str(e)) + dish_common.conn_error( + opts, "Failed querying InfluxDB for prior count: %s", str(e) + ) return gstate.timebase_synced = True if db_counter and gstate.start_counter <= db_counter: - del gstate.deferred_points[:db_counter - gstate.start_counter] + del gstate.deferred_points[: db_counter - gstate.start_counter] if gstate.deferred_points: delta_timestamp = db_timestamp - (gstate.deferred_points[0]["time"] - 1) # to prevent +/- 1 second timestamp drift when the script restarts, @@ -203,8 +231,12 @@ def sync_timebase(opts, gstate): print("Exactly synced with database time base") elif -2 <= delta_timestamp <= 2: if opts.verbose: - print("Replacing with existing time base: {0} -> {1}".format( - db_counter, datetime.fromtimestamp(db_timestamp, tz=timezone.utc))) + print( + "Replacing with existing time base: {0} -> {1}".format( + db_counter, + datetime.fromtimestamp(db_timestamp, tz=timezone.utc), + ) + ) for point in gstate.deferred_points: db_timestamp += 1 if point["time"] + delta_timestamp == db_timestamp: @@ -216,7 +248,11 @@ def sync_timebase(opts, gstate): gstate.timestamp = db_timestamp else: if opts.verbose: - print("Database time base out of sync by {0} seconds".format(delta_timestamp)) + print( + "Database time base out of sync by {0} seconds".format( + delta_timestamp + ) + ) gstate.points.extend(gstate.deferred_points) gstate.deferred_points.clear() @@ -239,38 +275,42 @@ def loop_body(opts, gstate, shutdown=False): points = gstate.points if gstate.timebase_synced else gstate.deferred_points for i in range(count): timestamp += 1 - points.append({ - "measurement": BULK_MEASUREMENT, - "tags": { - "id": gstate.dish_id - }, - "time": timestamp, - "fields": {key: val[i] for key, val in bulk.items() if val[i] is not None}, - }) + points.append( + { + "measurement": BULK_MEASUREMENT, + "tags": {"id": gstate.dish_id}, + "time": timestamp, + "fields": { + key: val[i] for key, val in bulk.items() if val[i] is not None + }, + } + ) if points: # save off counter value for script restart points[-1]["fields"]["counter"] = counter + count - rc, status_ts, hist_ts = dish_common.get_data(opts, - gstate, - cb_add_item, - cb_add_sequence, - add_bulk=cb_add_bulk, - flush_history=shutdown) + rc, status_ts, hist_ts = dish_common.get_data( + opts, + gstate, + cb_add_item, + cb_add_sequence, + add_bulk=cb_add_bulk, + flush_history=shutdown, + ) if rc: return rc for category, cat_fields in fields.items(): if cat_fields: timestamp = status_ts if category == "status" else hist_ts - gstate.points.append({ - "measurement": "spacex.starlink.user_terminal." + category, - "tags": { - "id": gstate.dish_id - }, - "time": timestamp, - "fields": cat_fields, - }) + gstate.points.append( + { + "measurement": "spacex.starlink.user_terminal." + category, + "tags": {"id": gstate.dish_id}, + "time": timestamp, + "fields": cat_fields, + } + ) # This is here and not before the points being processed because if the # query previously failed, there will be points that were processed in @@ -306,7 +346,9 @@ def main(): signal.signal(signal.SIGTERM, handle_sigterm) try: # attempt to hack around breakage between influxdb-python client and 2.0 server: - gstate.influx_client = InfluxDBClient(**opts.icargs, headers={"Accept": "application/json"}) + gstate.influx_client = InfluxDBClient( + **opts.icargs, headers={"Accept": "application/json"} + ) except TypeError: # ...unless influxdb-python package version is too old gstate.influx_client = InfluxDBClient(**opts.icargs) diff --git a/dish_grpc_influx2.py b/starlink_grpc_tools/dish_grpc_influx2.py similarity index 69% rename from dish_grpc_influx2.py rename to starlink_grpc_tools/dish_grpc_influx2.py index 3023c9c..1f47fa0 100644 --- a/dish_grpc_influx2.py +++ b/starlink_grpc_tools/dish_grpc_influx2.py @@ -29,9 +29,11 @@ import sys import time import warnings -from influxdb_client import InfluxDBClient, WriteOptions, WritePrecision +from influxdb_client import InfluxDBClient +from influxdb_client import WriteOptions +from influxdb_client import WritePrecision -import dish_common +import starlink_grpc_tools.dish_common as dish_common URL_DEFAULT = "http://localhost:8086" BUCKET_DEFAULT = "starlinkstats" @@ -52,34 +54,45 @@ def handle_sigterm(signum, frame): def parse_args(): parser = dish_common.create_arg_parser( - output_description="write it to an InfluxDB 2.x database") + output_description="write it to an InfluxDB 2.x database" + ) group = parser.add_argument_group(title="InfluxDB 2.x database options") - group.add_argument("-u", - "--url", - default=URL_DEFAULT, - dest="url", - help="URL of the InfluxDB 2.x server, default: " + URL_DEFAULT) + group.add_argument( + "-u", + "--url", + default=URL_DEFAULT, + dest="url", + help="URL of the InfluxDB 2.x server, default: " + URL_DEFAULT, + ) group.add_argument("-T", "--token", help="Token to access the bucket") - group.add_argument("-B", - "--bucket", - default=BUCKET_DEFAULT, - help="Bucket name to use, default: " + BUCKET_DEFAULT) + group.add_argument( + "-B", + "--bucket", + default=BUCKET_DEFAULT, + help="Bucket name to use, default: " + BUCKET_DEFAULT, + ) group.add_argument("-O", "--org", help="Organisation name") - group.add_argument("-k", - "--skip-query", - action="store_true", - help="Skip querying for prior sample write point in bulk mode") - group.add_argument("-C", - "--ca-cert", - dest="ssl_ca_cert", - help="Use specified CA cert to verify HTTPS server", - metavar="FILENAME") - group.add_argument("-I", - "--insecure", - action="store_false", - dest="verify_ssl", - help="Disable certificate verification of HTTPS server (INSECURE!)") + group.add_argument( + "-k", + "--skip-query", + action="store_true", + help="Skip querying for prior sample write point in bulk mode", + ) + group.add_argument( + "-C", + "--ca-cert", + dest="ssl_ca_cert", + help="Use specified CA cert to verify HTTPS server", + metavar="FILENAME", + ) + group.add_argument( + "-I", + "--insecure", + action="store_false", + dest="verify_ssl", + help="Disable certificate verification of HTTPS server (INSECURE!)", + ) env_map = ( ("INFLUXDB_URL", "url"), @@ -112,8 +125,9 @@ def parse_args(): if val is not None: opts.icargs[key] = val - if (not opts.verify_ssl - or opts.ssl_ca_cert is not None) and not opts.url.lower().startswith("https:"): + if ( + not opts.verify_ssl or opts.ssl_ca_cert is not None + ) and not opts.url.lower().startswith("https:"): parser.error("SSL options only apply to HTTPS URLs") return opts @@ -122,25 +136,32 @@ def parse_args(): def flush_points(opts, gstate): try: write_api = gstate.influx_client.write_api( - write_options=WriteOptions(batch_size=len(gstate.points), - flush_interval=10_000, - jitter_interval=2_000, - retry_interval=5_000, - max_retries=5, - max_retry_delay=30_000, - exponential_base=2)) + write_options=WriteOptions( + batch_size=len(gstate.points), + flush_interval=10_000, + jitter_interval=2_000, + retry_interval=5_000, + max_retries=5, + max_retry_delay=30_000, + exponential_base=2, + ) + ) while len(gstate.points) > MAX_BATCH: - write_api.write(record=gstate.points[:MAX_BATCH], - write_precision=WritePrecision.S, - bucket=opts.bucket) + write_api.write( + record=gstate.points[:MAX_BATCH], + write_precision=WritePrecision.S, + bucket=opts.bucket, + ) if opts.verbose: print("Data points written: " + str(MAX_BATCH)) del gstate.points[:MAX_BATCH] if gstate.points: - write_api.write(record=gstate.points, - write_precision=WritePrecision.S, - bucket=opts.bucket) + write_api.write( + record=gstate.points, + write_precision=WritePrecision.S, + bucket=opts.bucket, + ) if opts.verbose: print("Data points written: " + str(len(gstate.points))) gstate.points.clear() @@ -161,14 +182,18 @@ def flush_points(opts, gstate): def query_counter(opts, gstate, start, end): query_api = gstate.influx_client.query_api() - result = query_api.query(''' + result = query_api.query( + """ from(bucket: "{0}") |> range(start: {1}, stop: {2}) |> filter(fn: (r) => r["_measurement"] == "{3}") |> filter(fn: (r) => r["_field"] == "counter") |> last() |> yield(name: "last") - '''.format(opts.bucket, str(start), str(end), BULK_MEASUREMENT)) + """.format( + opts.bucket, str(start), str(end), BULK_MEASUREMENT + ) + ) if result: counter = result[0].records[0]["_value"] timestamp = result[0].records[0]["_time"].timestamp() @@ -180,16 +205,19 @@ def query_counter(opts, gstate, start, end): def sync_timebase(opts, gstate): try: - db_counter, db_timestamp = query_counter(opts, gstate, gstate.start_timestamp, - gstate.timestamp) + db_counter, db_timestamp = query_counter( + opts, gstate, gstate.start_timestamp, gstate.timestamp + ) except Exception as e: # could be temporary outage, so try again next time - dish_common.conn_error(opts, "Failed querying InfluxDB for prior count: %s", str(e)) + dish_common.conn_error( + opts, "Failed querying InfluxDB for prior count: %s", str(e) + ) return gstate.timebase_synced = True if db_counter and gstate.start_counter <= db_counter: - del gstate.deferred_points[:db_counter - gstate.start_counter] + del gstate.deferred_points[: db_counter - gstate.start_counter] if gstate.deferred_points: delta_timestamp = db_timestamp - (gstate.deferred_points[0]["time"] - 1) # to prevent +/- 1 second timestamp drift when the script restarts, @@ -200,8 +228,12 @@ def sync_timebase(opts, gstate): print("Exactly synced with database time base") elif -2 <= delta_timestamp <= 2: if opts.verbose: - print("Replacing with existing time base: {0} -> {1}".format( - db_counter, datetime.fromtimestamp(db_timestamp, tz=timezone.utc))) + print( + "Replacing with existing time base: {0} -> {1}".format( + db_counter, + datetime.fromtimestamp(db_timestamp, tz=timezone.utc), + ) + ) for point in gstate.deferred_points: db_timestamp += 1 if point["time"] + delta_timestamp == db_timestamp: @@ -213,7 +245,11 @@ def sync_timebase(opts, gstate): gstate.timestamp = db_timestamp else: if opts.verbose: - print("Database time base out of sync by {0} seconds".format(delta_timestamp)) + print( + "Database time base out of sync by {0} seconds".format( + delta_timestamp + ) + ) gstate.points.extend(gstate.deferred_points) gstate.deferred_points.clear() @@ -236,38 +272,42 @@ def loop_body(opts, gstate, shutdown=False): points = gstate.points if gstate.timebase_synced else gstate.deferred_points for i in range(count): timestamp += 1 - points.append({ - "measurement": BULK_MEASUREMENT, - "tags": { - "id": gstate.dish_id - }, - "time": timestamp, - "fields": {key: val[i] for key, val in bulk.items() if val[i] is not None}, - }) + points.append( + { + "measurement": BULK_MEASUREMENT, + "tags": {"id": gstate.dish_id}, + "time": timestamp, + "fields": { + key: val[i] for key, val in bulk.items() if val[i] is not None + }, + } + ) if points: # save off counter value for script restart points[-1]["fields"]["counter"] = counter + count - rc, status_ts, hist_ts = dish_common.get_data(opts, - gstate, - cb_add_item, - cb_add_sequence, - add_bulk=cb_add_bulk, - flush_history=shutdown) + rc, status_ts, hist_ts = dish_common.get_data( + opts, + gstate, + cb_add_item, + cb_add_sequence, + add_bulk=cb_add_bulk, + flush_history=shutdown, + ) if rc: return rc for category, cat_fields in fields.items(): if cat_fields: timestamp = status_ts if category == "status" else hist_ts - gstate.points.append({ - "measurement": "spacex.starlink.user_terminal." + category, - "tags": { - "id": gstate.dish_id - }, - "time": timestamp, - "fields": cat_fields, - }) + gstate.points.append( + { + "measurement": "spacex.starlink.user_terminal." + category, + "tags": {"id": gstate.dish_id}, + "time": timestamp, + "fields": cat_fields, + } + ) # This is here and not before the points being processed because if the # query previously failed, there will be points that were processed in diff --git a/dish_grpc_mqtt.py b/starlink_grpc_tools/dish_grpc_mqtt.py similarity index 73% rename from dish_grpc_mqtt.py rename to starlink_grpc_tools/dish_grpc_mqtt.py index 154bd7a..eb440d5 100644 --- a/dish_grpc_mqtt.py +++ b/starlink_grpc_tools/dish_grpc_mqtt.py @@ -29,13 +29,14 @@ import time try: import ssl + ssl_ok = True except ImportError: ssl_ok = False import paho.mqtt.publish -import dish_common +import starlink_grpc_tools.dish_common as dish_common HOST_DEFAULT = "localhost" @@ -50,16 +51,23 @@ def handle_sigterm(signum, frame): def parse_args(): - parser = dish_common.create_arg_parser(output_description="publish it to a MQTT broker", - bulk_history=False) + parser = dish_common.create_arg_parser( + output_description="publish it to a MQTT broker", bulk_history=False + ) group = parser.add_argument_group(title="MQTT broker options") - group.add_argument("-n", - "--hostname", - default=HOST_DEFAULT, - help="Hostname of MQTT broker, default: " + HOST_DEFAULT) - group.add_argument("-p", "--port", type=int, help="Port number to use on MQTT broker") - group.add_argument("-P", "--password", help="Set password for username/password authentication") + group.add_argument( + "-n", + "--hostname", + default=HOST_DEFAULT, + help="Hostname of MQTT broker, default: " + HOST_DEFAULT, + ) + group.add_argument( + "-p", "--port", type=int, help="Port number to use on MQTT broker" + ) + group.add_argument( + "-P", "--password", help="Set password for username/password authentication" + ) group.add_argument("-U", "--username", help="Set username for authentication") group.add_argument("-J", "--json", action="store_true", help="Publish data as JSON") if ssl_ok: @@ -67,24 +75,30 @@ def parse_args(): def wrap_ca_arg(arg): return {"ca_certs": arg} - group.add_argument("-C", - "--ca-cert", - type=wrap_ca_arg, - dest="tls", - help="Enable SSL/TLS using specified CA cert to verify broker", - metavar="FILENAME") - group.add_argument("-I", - "--insecure", - action="store_const", - const={"cert_reqs": ssl.CERT_NONE}, - dest="tls", - help="Enable SSL/TLS but disable certificate verification (INSECURE!)") - group.add_argument("-S", - "--secure", - action="store_const", - const={}, - dest="tls", - help="Enable SSL/TLS using default CA cert") + group.add_argument( + "-C", + "--ca-cert", + type=wrap_ca_arg, + dest="tls", + help="Enable SSL/TLS using specified CA cert to verify broker", + metavar="FILENAME", + ) + group.add_argument( + "-I", + "--insecure", + action="store_const", + const={"cert_reqs": ssl.CERT_NONE}, + dest="tls", + help="Enable SSL/TLS but disable certificate verification (INSECURE!)", + ) + group.add_argument( + "-S", + "--secure", + action="store_const", + const={}, + dest="tls", + help="Enable SSL/TLS using default CA cert", + ) else: parser.epilog += "\nSSL support options not available due to missing ssl module" @@ -135,7 +149,6 @@ def loop_body(opts, gstate): msgs = [] if opts.json: - data = {} def cb_add_item(key, val, category): @@ -155,12 +168,24 @@ def loop_body(opts, gstate): else: def cb_add_item(key, val, category): - msgs.append(("starlink/dish_{0}/{1}/{2}".format(category, gstate.dish_id, - key), val, 0, False)) + msgs.append( + ( + "starlink/dish_{0}/{1}/{2}".format(category, gstate.dish_id, key), + val, + 0, + False, + ) + ) def cb_add_sequence(key, val, category, _): - msgs.append(("starlink/dish_{0}/{1}/{2}".format(category, gstate.dish_id, key), - ",".join("" if x is None else str(x) for x in val), 0, False)) + msgs.append( + ( + "starlink/dish_{0}/{1}/{2}".format(category, gstate.dish_id, key), + ",".join("" if x is None else str(x) for x in val), + 0, + False, + ) + ) rc = dish_common.get_data(opts, gstate, cb_add_item, cb_add_sequence)[0] diff --git a/dish_grpc_prometheus.py b/starlink_grpc_tools/dish_grpc_prometheus.py similarity index 88% rename from dish_grpc_prometheus.py rename to starlink_grpc_tools/dish_grpc_prometheus.py index 417b800..a77cac5 100644 --- a/dish_grpc_prometheus.py +++ b/starlink_grpc_tools/dish_grpc_prometheus.py @@ -6,13 +6,14 @@ history data and makes it available via HTTP in the format Prometheus expects. """ from http import HTTPStatus -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from http.server import BaseHTTPRequestHandler +from http.server import ThreadingHTTPServer import logging import signal import sys import threading -import dish_common +import starlink_grpc_tools.dish_common as dish_common class Terminated(Exception): @@ -128,14 +129,18 @@ class MetricValue: def __str__(self): label_str = "" if self.labels: - label_str = ("{" + str.join(",", [f'{v[0]}="{v[1]}"' - for v in self.labels.items()]) + "}") + label_str = ( + "{" + + str.join(",", [f'{v[0]}="{v[1]}"' for v in self.labels.items()]) + + "}" + ) return f"{label_str} {self.value}" def parse_args(): - parser = dish_common.create_arg_parser(output_description="Prometheus exporter", - bulk_history=False) + parser = dish_common.create_arg_parser( + output_description="Prometheus exporter", bulk_history=False + ) group = parser.add_argument_group(title="HTTP server options") group.add_argument("--address", default="0.0.0.0", help="IP address to listen on") @@ -155,8 +160,9 @@ def prometheus_export(opts, gstate): raise NotImplementedError("Did not expect sequence data") with gstate.lock: - rc, status_ts, hist_ts = dish_common.get_data(opts, gstate, data_add_item, - data_add_sequencem) + rc, status_ts, hist_ts = dish_common.get_data( + opts, gstate, data_add_item, data_add_sequencem + ) metrics = [] @@ -173,9 +179,11 @@ def prometheus_export(opts, gstate): MetricValue( value=int(raw_data["status_state"] == state_value), labels={"state": state_value}, - ) for state_value in STATE_VALUES + ) + for state_value in STATE_VALUES ], - )) + ) + ) del raw_data["status_state"] info_metrics = ["status_id", "status_hardware_version", "status_software_version"] @@ -191,12 +199,14 @@ def prometheus_export(opts, gstate): MetricValue( value=1, labels={ - x.replace("status_", ""): raw_data.pop(x) for x in info_metrics + x.replace("status_", ""): raw_data.pop(x) + for x in info_metrics if x in raw_data }, ) ], - )) + ) + ) for name, metric_info in METRICS_INFO.items(): if name in raw_data: @@ -206,7 +216,8 @@ def prometheus_export(opts, gstate): timestamp=status_ts, kind=metric_info.kind, values=[MetricValue(value=float(raw_data.pop(name) or 0))], - )) + ) + ) else: metrics_not_found.append(name) @@ -215,17 +226,22 @@ def prometheus_export(opts, gstate): name="starlink_exporter_unprocessed_metrics", timestamp=status_ts, values=[MetricValue(value=1, labels={"metric": name}) for name in raw_data], - )) + ) + ) metrics.append( Metric( name="starlink_exporter_missing_metrics", timestamp=status_ts, - values=[MetricValue( - value=1, - labels={"metric": name}, - ) for name in metrics_not_found], - )) + values=[ + MetricValue( + value=1, + labels={"metric": name}, + ) + for name in metrics_not_found + ], + ) + ) return str.join("\n", [str(metric) for metric in metrics]) diff --git a/dish_grpc_sqlite.py b/starlink_grpc_tools/dish_grpc_sqlite.py similarity index 79% rename from dish_grpc_sqlite.py rename to starlink_grpc_tools/dish_grpc_sqlite.py index 4cf6aca..4733224 100644 --- a/dish_grpc_sqlite.py +++ b/starlink_grpc_tools/dish_grpc_sqlite.py @@ -43,8 +43,8 @@ import sqlite3 import sys import time -import dish_common -import starlink_grpc +import starlink_grpc_tools.dish_common as dish_common +import starlink_grpc_tools.starlink_grpc as starlink_grpc SCHEMA_VERSION = 4 @@ -59,20 +59,26 @@ def handle_sigterm(signum, frame): def parse_args(): - parser = dish_common.create_arg_parser(output_description="write it to a sqlite database") + parser = dish_common.create_arg_parser( + output_description="write it to a sqlite database" + ) parser.add_argument("database", help="Database file to use") group = parser.add_argument_group(title="sqlite database options") - group.add_argument("-f", - "--force", - action="store_true", - help="Force schema conversion, even if it results in downgrade; may " - "result in discarded data") - group.add_argument("-k", - "--skip-query", - action="store_true", - help="Skip querying for prior sample write point in history modes") + group.add_argument( + "-f", + "--force", + action="store_true", + help="Force schema conversion, even if it results in downgrade; may " + "result in discarded data", + ) + group.add_argument( + "-k", + "--skip-query", + action="store_true", + help="Skip querying for prior sample write point in history modes", + ) opts = dish_common.run_arg_parser(parser, need_id=True) @@ -86,14 +92,19 @@ def query_counter(opts, gstate, column, table): cur = gstate.sql_conn.cursor() cur.execute( 'SELECT "time", "{0}" FROM "{1}" WHERE "time" {1}".format( - row[1], datetime.fromtimestamp(row[0], tz=timezone.utc))) + print( + "Existing time base: {0} -> {1}".format( + row[1], datetime.fromtimestamp(row[0], tz=timezone.utc) + ) + ) return row else: return 0, None @@ -108,7 +119,9 @@ def loop_body(opts, gstate, shutdown=False): tables[category][key] = val def cb_add_sequence(key, val, category, start): - tables[category][key] = ",".join(str(subv) if subv is not None else "" for subv in val) + tables[category][key] = ",".join( + str(subv) if subv is not None else "" for subv in val + ) def cb_add_bulk(bulk, count, timestamp, counter): if len(hist_cols) == 2: @@ -127,19 +140,26 @@ def loop_body(opts, gstate, shutdown=False): hist_ts = None if not shutdown: - rc, status_ts = dish_common.get_status_data(opts, gstate, cb_add_item, cb_add_sequence) + rc, status_ts = dish_common.get_status_data( + opts, gstate, cb_add_item, cb_add_sequence + ) if opts.history_stats_mode and (not rc or opts.poll_loops > 1): if gstate.counter_stats is None and not opts.skip_query and opts.samples < 0: - _, gstate.counter_stats = query_counter(opts, gstate, "end_counter", "ping_stats") - hist_rc, hist_ts = dish_common.get_history_stats(opts, gstate, cb_add_item, cb_add_sequence, - shutdown) + _, gstate.counter_stats = query_counter( + opts, gstate, "end_counter", "ping_stats" + ) + hist_rc, hist_ts = dish_common.get_history_stats( + opts, gstate, cb_add_item, cb_add_sequence, shutdown + ) if not rc: rc = hist_rc if not shutdown and opts.bulk_mode and not rc: if gstate.counter is None and not opts.skip_query and opts.bulk_samples < 0: - gstate.timestamp, gstate.counter = query_counter(opts, gstate, "counter", "history") + gstate.timestamp, gstate.counter = query_counter( + opts, gstate, "counter", "history" + ) rc = dish_common.get_bulk_data(opts, gstate, cb_add_bulk) rows_written = 0 @@ -150,9 +170,10 @@ def loop_body(opts, gstate, shutdown=False): if fields: timestamp = status_ts if category == "status" else hist_ts sql = 'INSERT OR REPLACE INTO "{0}" ("time","id",{1}) VALUES ({2})'.format( - category, ",".join('"' + x + '"' for x in fields), - ",".join(repeat("?", - len(fields) + 2))) + category, + ",".join('"' + x + '"' for x in fields), + ",".join(repeat("?", len(fields) + 2)), + ) values = [timestamp, gstate.dish_id] values.extend(fields.values()) cur.execute(sql, values) @@ -160,7 +181,9 @@ def loop_body(opts, gstate, shutdown=False): if hist_rows: sql = 'INSERT OR REPLACE INTO "history" ({0}) VALUES({1})'.format( - ",".join('"' + x + '"' for x in hist_cols), ",".join(repeat("?", len(hist_cols)))) + ",".join('"' + x + '"' for x in hist_cols), + ",".join(repeat("?", len(hist_cols))), + ) cur.executemany(sql, hist_rows) rows_written += len(hist_rows) @@ -191,7 +214,9 @@ def ensure_schema(opts, conn, context): print("Initializing new database") create_tables(conn, context, "") elif version[0] > SCHEMA_VERSION and not opts.force: - logging.error("Cowardly refusing to downgrade from schema version %s", version[0]) + logging.error( + "Cowardly refusing to downgrade from schema version %s", version[0] + ) return 1 else: print("Converting from schema version:", version[0]) @@ -208,10 +233,12 @@ def ensure_schema(opts, conn, context): def create_tables(conn, context, suffix): tables = {} - name_groups = (starlink_grpc.status_field_names(context=context) + - (starlink_grpc.location_field_names(),)) - type_groups = (starlink_grpc.status_field_types(context=context) + - (starlink_grpc.location_field_types(),)) + name_groups = starlink_grpc.status_field_names(context=context) + ( + starlink_grpc.location_field_names(), + ) + type_groups = starlink_grpc.status_field_types(context=context) + ( + starlink_grpc.location_field_types(), + ) tables["status"] = zip(name_groups, type_groups) name_groups = starlink_grpc.history_stats_field_names() @@ -248,7 +275,8 @@ def create_tables(conn, context, suffix): column_names.append(name_item) cur.execute('DROP TABLE IF EXISTS "{0}{1}"'.format(table, suffix)) sql = 'CREATE TABLE "{0}{1}" ({2}, PRIMARY KEY("time","id"))'.format( - table, suffix, ", ".join(columns)) + table, suffix, ", ".join(columns) + ) cur.execute(sql) column_info[table] = column_names cur.close() @@ -266,9 +294,13 @@ def convert_tables(conn, context): old_columns = set(x[0] for x in old_cur.description) new_columns = tuple(x for x in new_columns if x in old_columns) sql = 'INSERT OR REPLACE INTO "{0}_new" ({1}) VALUES ({2})'.format( - table, ",".join('"' + x + '"' for x in new_columns), - ",".join(repeat("?", len(new_columns)))) - new_cur.executemany(sql, (tuple(row[col] for col in new_columns) for row in old_cur)) + table, + ",".join('"' + x + '"' for x in new_columns), + ",".join(repeat("?", len(new_columns))), + ) + new_cur.executemany( + sql, (tuple(row[col] for col in new_columns) for row in old_cur) + ) new_cur.execute('DROP TABLE "{0}"'.format(table)) new_cur.execute('ALTER TABLE "{0}_new" RENAME TO "{0}"'.format(table)) old_cur.close() diff --git a/dish_grpc_text.py b/starlink_grpc_tools/dish_grpc_text.py similarity index 79% rename from dish_grpc_text.py rename to starlink_grpc_tools/dish_grpc_text.py index a0db7e9..014e0fc 100644 --- a/dish_grpc_text.py +++ b/starlink_grpc_tools/dish_grpc_text.py @@ -18,15 +18,14 @@ import signal import sys import time -import dish_common -import starlink_grpc +import starlink_grpc_tools.dish_common as dish_common +import starlink_grpc_tools.starlink_grpc as starlink_grpc COUNTER_FIELD = "end_counter" VERBOSE_FIELD_MAP = { # status fields (the remainder are either self-explanatory or I don't # know with confidence what they mean) "alerts": "Alerts bit field", - # ping_drop fields "samples": "Parsed samples", "end_counter": "Sample counter", @@ -38,22 +37,18 @@ VERBOSE_FIELD_MAP = { "count_unscheduled": "Unscheduled", "total_unscheduled_ping_drop": "Unscheduled ping drop", "count_full_unscheduled_ping_drop": "Unscheduled drop == 1", - # ping_run_length fields "init_run_fragment": "Initial drop run fragment", "final_run_fragment": "Final drop run fragment", "run_seconds": "Per-second drop runs", "run_minutes": "Per-minute drop runs", - # ping_latency fields "mean_all_ping_latency": "Mean RTT, drop < 1", "deciles_all_ping_latency": "RTT deciles, drop < 1", "mean_full_ping_latency": "Mean RTT, drop == 0", "deciles_full_ping_latency": "RTT deciles, drop == 0", "stdev_full_ping_latency": "RTT standard deviation, drop == 0", - # ping_loaded_latency is still experimental, so leave those unexplained - # usage fields "download_usage": "Bytes downloaded", "upload_usage": "Bytes uploaded", @@ -71,34 +66,52 @@ def handle_sigterm(signum, frame): def parse_args(): parser = dish_common.create_arg_parser( - output_description="print it in text format; by default, will print in CSV format") + output_description="print it in text format; by default, will print in CSV format" + ) group = parser.add_argument_group(title="CSV output options") - group.add_argument("-H", - "--print-header", - action="store_true", - help="Print CSV header instead of parsing data") - group.add_argument("-O", - "--out-file", - default="-", - help="Output file path; if set, can also be used to resume from prior " - "history sample counter, default: write to standard output") - group.add_argument("-k", - "--skip-query", - action="store_true", - help="Skip querying for prior sample write point in history modes") + group.add_argument( + "-H", + "--print-header", + action="store_true", + help="Print CSV header instead of parsing data", + ) + group.add_argument( + "-O", + "--out-file", + default="-", + help="Output file path; if set, can also be used to resume from prior " + "history sample counter, default: write to standard output", + ) + group.add_argument( + "-k", + "--skip-query", + action="store_true", + help="Skip querying for prior sample write point in history modes", + ) opts = dish_common.run_arg_parser(parser) - if (opts.history_stats_mode or opts.status_mode) and opts.bulk_mode and not opts.verbose: + if ( + (opts.history_stats_mode or opts.status_mode) + and opts.bulk_mode + and not opts.verbose + ): parser.error("bulk_history cannot be combined with other modes for CSV output") # Technically possible, but a pain to implement, so just disallow it. User # probably doesn't realize how weird it would be, anyway, given that stats # data reports at a different rate from status data in this case. - if opts.history_stats_mode and opts.status_mode and not opts.verbose and opts.poll_loops > 1: - parser.error("usage of --poll-loops with history stats modes cannot be mixed with status " - "modes for CSV output") + if ( + opts.history_stats_mode + and opts.status_mode + and not opts.verbose + and opts.poll_loops > 1 + ): + parser.error( + "usage of --poll-loops with history stats modes cannot be mixed with status " + "modes for CSV output" + ) opts.skip_query |= opts.no_counter | opts.verbose if opts.out_file == "-": @@ -133,7 +146,9 @@ def print_header(opts, print_file): try: name_groups = starlink_grpc.status_field_names(context=context) except starlink_grpc.GrpcError as e: - dish_common.conn_error(opts, "Failure reflecting status field names: %s", str(e)) + dish_common.conn_error( + opts, "Failure reflecting status field names: %s", str(e) + ) return 1 if "status" in opts.mode: header_add(name_groups[0]) @@ -196,8 +211,9 @@ def loop_body(opts, gstate, print_file, shutdown=False): def cb_data_add_item(name, val, category): if opts.verbose: - csv_data.append("{0:22} {1}".format( - VERBOSE_FIELD_MAP.get(name, name) + ":", xform(val))) + csv_data.append( + "{0:22} {1}".format(VERBOSE_FIELD_MAP.get(name, name) + ":", xform(val)) + ) else: # special case for get_status failure: this will be the lone item added if name == "state" and val == "DISH_UNREACHABLE": @@ -207,21 +223,31 @@ def loop_body(opts, gstate, print_file, shutdown=False): def cb_data_add_sequence(name, val, category, start): if opts.verbose: - csv_data.append("{0:22} {1}".format( - VERBOSE_FIELD_MAP.get(name, name) + ":", - ", ".join(xform(subval) for subval in val))) + csv_data.append( + "{0:22} {1}".format( + VERBOSE_FIELD_MAP.get(name, name) + ":", + ", ".join(xform(subval) for subval in val), + ) + ) else: csv_data.extend(xform(subval) for subval in val) def cb_add_bulk(bulk, count, timestamp, counter): if opts.verbose: - print("Time range (UTC): {0} -> {1}".format( - datetime.utcfromtimestamp(timestamp).isoformat(), - datetime.utcfromtimestamp(timestamp + count).isoformat()), - file=print_file) + print( + "Time range (UTC): {0} -> {1}".format( + datetime.utcfromtimestamp(timestamp).isoformat(), + datetime.utcfromtimestamp(timestamp + count).isoformat(), + ), + file=print_file, + ) for key, val in bulk.items(): - print("{0:22} {1}".format(key + ":", ", ".join(xform(subval) for subval in val)), - file=print_file) + print( + "{0:22} {1}".format( + key + ":", ", ".join(xform(subval) for subval in val) + ), + file=print_file, + ) if opts.loop_interval > 0.0: print(file=print_file) else: @@ -231,12 +257,14 @@ def loop_body(opts, gstate, print_file, shutdown=False): fields.extend([xform(val[i]) for val in bulk.values()]) print(",".join(fields), file=print_file) - rc, status_ts, hist_ts = dish_common.get_data(opts, - gstate, - cb_data_add_item, - cb_data_add_sequence, - add_bulk=cb_add_bulk, - flush_history=shutdown) + rc, status_ts, hist_ts = dish_common.get_data( + opts, + gstate, + cb_data_add_item, + cb_data_add_sequence, + add_bulk=cb_add_bulk, + flush_history=shutdown, + ) if opts.verbose: if csv_data: diff --git a/dish_json_text.py b/starlink_grpc_tools/dish_json_text.py similarity index 72% rename from dish_json_text.py rename to starlink_grpc_tools/dish_json_text.py index 52d9184..b5477d1 100644 --- a/dish_json_text.py +++ b/starlink_grpc_tools/dish_json_text.py @@ -18,12 +18,16 @@ import re import sys import time -import starlink_json +import starlink_grpc_tools.starlink_json as starlink_json BRACKETS_RE = re.compile(r"([^[]*)(\[((\d+),|)(\d*)\]|)$") SAMPLES_DEFAULT = 3600 HISTORY_STATS_MODES = [ - "ping_drop", "ping_run_length", "ping_latency", "ping_loaded_latency", "usage" + "ping_drop", + "ping_run_length", + "ping_latency", + "ping_loaded_latency", + "usage", ] VERBOSE_FIELD_MAP = { # ping_drop fields @@ -37,22 +41,18 @@ VERBOSE_FIELD_MAP = { "count_unscheduled": "Unscheduled", "total_unscheduled_ping_drop": "Unscheduled ping drop", "count_full_unscheduled_ping_drop": "Unscheduled drop == 1", - # ping_run_length fields "init_run_fragment": "Initial drop run fragment", "final_run_fragment": "Final drop run fragment", "run_seconds": "Per-second drop runs", "run_minutes": "Per-minute drop runs", - # ping_latency fields "mean_all_ping_latency": "Mean RTT, drop < 1", "deciles_all_ping_latency": "RTT deciles, drop < 1", "mean_full_ping_latency": "Mean RTT, drop == 0", "deciles_full_ping_latency": "RTT deciles, drop == 0", "stdev_full_ping_latency": "RTT standard deviation, drop == 0", - # ping_loaded_latency is still experimental, so leave those unexplained - # usage fields "download_usage": "Bytes downloaded", "upload_usage": "Bytes uploaded", @@ -63,42 +63,55 @@ def parse_args(): parser = argparse.ArgumentParser( description="Collect status and/or history data from a Starlink user terminal and " "print it to standard output in text format; by default, will print in CSV format", - add_help=False) + add_help=False, + ) group = parser.add_argument_group(title="General options") - group.add_argument("-f", "--filename", default="-", help="The file to parse, default: stdin") + group.add_argument( + "-f", "--filename", default="-", help="The file to parse, default: stdin" + ) group.add_argument("-h", "--help", action="help", help="Be helpful") - group.add_argument("-t", - "--timestamp", - help="UTC time history data was pulled, as YYYY-MM-DD_HH:MM:SS or as " - "seconds since Unix epoch, default: current time") + group.add_argument( + "-t", + "--timestamp", + help="UTC time history data was pulled, as YYYY-MM-DD_HH:MM:SS or as " + "seconds since Unix epoch, default: current time", + ) group.add_argument("-v", "--verbose", action="store_true", help="Be verbose") group = parser.add_argument_group(title="History mode options") - group.add_argument("-a", - "--all-samples", - action="store_const", - const=-1, - dest="samples", - help="Parse all valid samples") - group.add_argument("-s", - "--samples", - type=int, - help="Number of data samples to parse, default: all in bulk mode, " - "else " + str(SAMPLES_DEFAULT)) + group.add_argument( + "-a", + "--all-samples", + action="store_const", + const=-1, + dest="samples", + help="Parse all valid samples", + ) + group.add_argument( + "-s", + "--samples", + type=int, + help="Number of data samples to parse, default: all in bulk mode, " + "else " + str(SAMPLES_DEFAULT), + ) group = parser.add_argument_group(title="CSV output options") - group.add_argument("-H", - "--print-header", - action="store_true", - help="Print CSV header instead of parsing data") + group.add_argument( + "-H", + "--print-header", + action="store_true", + help="Print CSV header instead of parsing data", + ) all_modes = HISTORY_STATS_MODES + ["bulk_history"] - parser.add_argument("mode", - nargs="+", - choices=all_modes, - help="The data group to record, one or more of: " + ", ".join(all_modes), - metavar="mode") + parser.add_argument( + "mode", + nargs="+", + choices=all_modes, + help="The data group to record, one or more of: " + ", ".join(all_modes), + metavar="mode", + ) opts = parser.parse_args() @@ -120,11 +133,15 @@ def parse_args(): except ValueError: try: opts.history_time = int( - datetime.strptime(opts.timestamp, "%Y-%m-%d_%H:%M:%S").timestamp()) + datetime.strptime(opts.timestamp, "%Y-%m-%d_%H:%M:%S").timestamp() + ) except ValueError: parser.error("Could not parse timestamp") if opts.verbose: - print("Using timestamp", datetime.fromtimestamp(opts.history_time, tz=timezone.utc)) + print( + "Using timestamp", + datetime.fromtimestamp(opts.history_time, tz=timezone.utc), + ) return opts @@ -177,7 +194,9 @@ def get_data(opts, add_item, add_sequence, add_bulk): if opts.history_stats_mode: try: - groups = starlink_json.history_stats(opts.filename, opts.samples, verbose=opts.verbose) + groups = starlink_json.history_stats( + opts.filename, opts.samples, verbose=opts.verbose + ) except starlink_json.JsonError as e: logging.error("Failure getting history stats: %s", str(e)) return 1 @@ -197,17 +216,20 @@ def get_data(opts, add_item, add_sequence, add_bulk): if opts.bulk_mode and add_bulk: timestamp = int(time.time()) if opts.history_time is None else opts.history_time try: - general, bulk = starlink_json.history_bulk_data(opts.filename, - opts.samples, - verbose=opts.verbose) + general, bulk = starlink_json.history_bulk_data( + opts.filename, opts.samples, verbose=opts.verbose + ) except starlink_json.JsonError as e: logging.error("Failure getting bulk history: %s", str(e)) return 1 parsed_samples = general["samples"] new_counter = general["end_counter"] if opts.verbose: - print("Establishing time base: {0} -> {1}".format( - new_counter, datetime.fromtimestamp(timestamp, tz=timezone.utc))) + print( + "Establishing time base: {0} -> {1}".format( + new_counter, datetime.fromtimestamp(timestamp, tz=timezone.utc) + ) + ) timestamp -= parsed_samples add_bulk(bulk, parsed_samples, timestamp, new_counter - parsed_samples) @@ -219,12 +241,16 @@ def loop_body(opts): if opts.verbose: csv_data = [] else: - history_time = int(time.time()) if opts.history_time is None else opts.history_time + history_time = ( + int(time.time()) if opts.history_time is None else opts.history_time + ) csv_data = [datetime.utcfromtimestamp(history_time).isoformat()] def cb_data_add_item(name, val): if opts.verbose: - csv_data.append("{0:22} {1}".format(VERBOSE_FIELD_MAP.get(name, name) + ":", val)) + csv_data.append( + "{0:22} {1}".format(VERBOSE_FIELD_MAP.get(name, name) + ":", val) + ) else: # special case for get_status failure: this will be the lone item added if name == "state" and val == "DISH_UNREACHABLE": @@ -234,23 +260,36 @@ def loop_body(opts): def cb_data_add_sequence(name, val): if opts.verbose: - csv_data.append("{0:22} {1}".format( - VERBOSE_FIELD_MAP.get(name, name) + ":", ", ".join(str(subval) for subval in val))) + csv_data.append( + "{0:22} {1}".format( + VERBOSE_FIELD_MAP.get(name, name) + ":", + ", ".join(str(subval) for subval in val), + ) + ) else: csv_data.extend(str(subval) for subval in val) def cb_add_bulk(bulk, count, timestamp, counter): if opts.verbose: - print("Time range (UTC): {0} -> {1}".format( - datetime.utcfromtimestamp(timestamp).isoformat(), - datetime.utcfromtimestamp(timestamp + count).isoformat())) + print( + "Time range (UTC): {0} -> {1}".format( + datetime.utcfromtimestamp(timestamp).isoformat(), + datetime.utcfromtimestamp(timestamp + count).isoformat(), + ) + ) for key, val in bulk.items(): - print("{0:22} {1}".format(key + ":", ", ".join(str(subval) for subval in val))) + print( + "{0:22} {1}".format( + key + ":", ", ".join(str(subval) for subval in val) + ) + ) else: for i in range(count): timestamp += 1 fields = [datetime.utcfromtimestamp(timestamp).isoformat()] - fields.extend(["" if val[i] is None else str(val[i]) for val in bulk.values()]) + fields.extend( + ["" if val[i] is None else str(val[i]) for val in bulk.values()] + ) print(",".join(fields)) rc = get_data(opts, cb_data_add_item, cb_data_add_sequence, cb_add_bulk) diff --git a/dish_obstruction_map.py b/starlink_grpc_tools/dish_obstruction_map.py similarity index 62% rename from dish_obstruction_map.py rename to starlink_grpc_tools/dish_obstruction_map.py index 7fabfff..879d261 100644 --- a/dish_obstruction_map.py +++ b/starlink_grpc_tools/dish_obstruction_map.py @@ -9,11 +9,12 @@ import argparse from datetime import datetime import logging import os -import png import sys import time -import starlink_grpc +import png + +import starlink_grpc_tools.starlink_grpc as starlink_grpc DEFAULT_OBSTRUCTED_COLOR = "FFFF0000" DEFAULT_UNOBSTRUCTED_COLOR = "FFFFFFFF" @@ -39,18 +40,28 @@ def loop_body(opts, context): if point >= 0.0: if opts.greyscale: - yield round(point * opts.unobstructed_color_g + - (1.0-point) * opts.obstructed_color_g) + yield round( + point * opts.unobstructed_color_g + + (1.0 - point) * opts.obstructed_color_g + ) else: - yield round(point * opts.unobstructed_color_r + - (1.0-point) * opts.obstructed_color_r) - yield round(point * opts.unobstructed_color_g + - (1.0-point) * opts.obstructed_color_g) - yield round(point * opts.unobstructed_color_b + - (1.0-point) * opts.obstructed_color_b) + yield round( + point * opts.unobstructed_color_r + + (1.0 - point) * opts.obstructed_color_r + ) + yield round( + point * opts.unobstructed_color_g + + (1.0 - point) * opts.obstructed_color_g + ) + yield round( + point * opts.unobstructed_color_b + + (1.0 - point) * opts.obstructed_color_b + ) if not opts.no_alpha: - yield round(point * opts.unobstructed_color_a + - (1.0-point) * opts.obstructed_color_a) + yield round( + point * opts.unobstructed_color_a + + (1.0 - point) * opts.obstructed_color_a + ) else: if opts.greyscale: yield opts.no_data_color_g @@ -67,17 +78,20 @@ def loop_body(opts, context): else: now = int(time.time()) filename = opts.filename.replace("%u", str(now)) - filename = filename.replace("%d", - datetime.utcfromtimestamp(now).strftime("%Y_%m_%d_%H_%M_%S")) + filename = filename.replace( + "%d", datetime.utcfromtimestamp(now).strftime("%Y_%m_%d_%H_%M_%S") + ) filename = filename.replace("%s", str(opts.sequence)) out_file = open(filename, "wb") if not snr_data or not snr_data[0]: logging.error("Invalid SNR map data: Zero-length") return 1 - writer = png.Writer(len(snr_data[0]), - len(snr_data), - alpha=(not opts.no_alpha), - greyscale=opts.greyscale) + writer = png.Writer( + len(snr_data[0]), + len(snr_data), + alpha=(not opts.no_alpha), + greyscale=opts.greyscale, + ) writer.write(out_file, (bytes(pixel_bytes(row)) for row in snr_data)) out_file.close() @@ -88,62 +102,89 @@ def loop_body(opts, context): def parse_args(): parser = argparse.ArgumentParser( description="Collect directional obstruction map data from a Starlink user terminal and " - "emit it as a PNG image") + "emit it as a PNG image" + ) parser.add_argument( "filename", help="The image file to write, or - to write to stdout; may be a template with the " "following to be filled in per loop iteration: %%s for sequence number, %%d for UTC date " - "and time, %%u for seconds since Unix epoch.") + "and time, %%u for seconds since Unix epoch.", + ) parser.add_argument( "-o", "--obstructed-color", - help="Color of obstructed areas, in RGB, ARGB, L, or AL hex notation, default: " + - DEFAULT_OBSTRUCTED_COLOR + " or " + DEFAULT_OBSTRUCTED_GREYSCALE) + help="Color of obstructed areas, in RGB, ARGB, L, or AL hex notation, default: " + + DEFAULT_OBSTRUCTED_COLOR + + " or " + + DEFAULT_OBSTRUCTED_GREYSCALE, + ) parser.add_argument( "-u", "--unobstructed-color", - help="Color of unobstructed areas, in RGB, ARGB, L, or AL hex notation, default: " + - DEFAULT_UNOBSTRUCTED_COLOR + " or " + DEFAULT_UNOBSTRUCTED_GREYSCALE) + help="Color of unobstructed areas, in RGB, ARGB, L, or AL hex notation, default: " + + DEFAULT_UNOBSTRUCTED_COLOR + + " or " + + DEFAULT_UNOBSTRUCTED_GREYSCALE, + ) parser.add_argument( "-n", "--no-data-color", - help="Color of areas with no data, in RGB, ARGB, L, or AL hex notation, default: " + - DEFAULT_NO_DATA_COLOR + " or " + DEFAULT_NO_DATA_GREYSCALE) + help="Color of areas with no data, in RGB, ARGB, L, or AL hex notation, default: " + + DEFAULT_NO_DATA_COLOR + + " or " + + DEFAULT_NO_DATA_GREYSCALE, + ) parser.add_argument( "-g", "--greyscale", action="store_true", help="Emit a greyscale image instead of the default full color image; greyscale images " - "use L or AL hex notation for the color options") + "use L or AL hex notation for the color options", + ) parser.add_argument( "-z", "--no-alpha", action="store_true", help="Emit an image without alpha (transparency) channel instead of the default that " - "includes alpha channel") - parser.add_argument("-e", - "--target", - help="host:port of dish to query, default is the standard IP address " - "and port (192.168.100.1:9200)") - parser.add_argument("-t", - "--loop-interval", - type=float, - default=float(LOOP_TIME_DEFAULT), - help="Loop interval in seconds or 0 for no loop, default: " + - str(LOOP_TIME_DEFAULT)) - parser.add_argument("-s", - "--sequence", - type=int, - default=1, - help="Starting sequence number for templatized filenames, default: 1") + "includes alpha channel", + ) + parser.add_argument( + "-e", + "--target", + help="host:port of dish to query, default is the standard IP address " + "and port (192.168.100.1:9200)", + ) + parser.add_argument( + "-t", + "--loop-interval", + type=float, + default=float(LOOP_TIME_DEFAULT), + help="Loop interval in seconds or 0 for no loop, default: " + + str(LOOP_TIME_DEFAULT), + ) + parser.add_argument( + "-s", + "--sequence", + type=int, + default=1, + help="Starting sequence number for templatized filenames, default: 1", + ) opts = parser.parse_args() if opts.obstructed_color is None: - opts.obstructed_color = DEFAULT_OBSTRUCTED_GREYSCALE if opts.greyscale else DEFAULT_OBSTRUCTED_COLOR + opts.obstructed_color = ( + DEFAULT_OBSTRUCTED_GREYSCALE if opts.greyscale else DEFAULT_OBSTRUCTED_COLOR + ) if opts.unobstructed_color is None: - opts.unobstructed_color = DEFAULT_UNOBSTRUCTED_GREYSCALE if opts.greyscale else DEFAULT_UNOBSTRUCTED_COLOR + opts.unobstructed_color = ( + DEFAULT_UNOBSTRUCTED_GREYSCALE + if opts.greyscale + else DEFAULT_UNOBSTRUCTED_COLOR + ) if opts.no_data_color is None: - opts.no_data_color = DEFAULT_NO_DATA_GREYSCALE if opts.greyscale else DEFAULT_NO_DATA_COLOR + opts.no_data_color = ( + DEFAULT_NO_DATA_GREYSCALE if opts.greyscale else DEFAULT_NO_DATA_COLOR + ) for option in ("obstructed_color", "unobstructed_color", "no_data_color"): try: diff --git a/dump_dish_status.py b/starlink_grpc_tools/dump_dish_status.py similarity index 85% rename from dump_dish_status.py rename to starlink_grpc_tools/dump_dish_status.py index 157dd03..8dced35 100644 --- a/dump_dish_status.py +++ b/starlink_grpc_tools/dump_dish_status.py @@ -9,8 +9,10 @@ try: from spacex.api.device import device_pb2 from spacex.api.device import device_pb2_grpc except ModuleNotFoundError: - print("This script requires the generated gRPC protocol modules. See README file for details.", - file=sys.stderr) + print( + "This script requires the generated gRPC protocol modules. See README file for details.", + file=sys.stderr, + ) sys.exit(1) # Note that if you remove the 'with' clause here, you need to separately diff --git a/extract_protoset.py b/starlink_grpc_tools/extract_protoset.py similarity index 75% rename from extract_protoset.py rename to starlink_grpc_tools/extract_protoset.py index 6bd0e92..c5ee746 100644 --- a/extract_protoset.py +++ b/starlink_grpc_tools/extract_protoset.py @@ -39,32 +39,43 @@ RETRY_DELAY_DEFAULT = 0 def parse_args(): parser = argparse.ArgumentParser( description="Poll a gRPC reflection server and record a serialized " - "FileDescriptorSet (protoset) of the reflected information") + "FileDescriptorSet (protoset) of the reflected information" + ) - parser.add_argument("outdir", - nargs="?", - metavar="OUTDIR", - help="Directory in which to write protoset files") - parser.add_argument("-g", - "--target", - default=TARGET_DEFAULT, - help="host:port of device to query, default: " + TARGET_DEFAULT) - parser.add_argument("-n", - "--print-only", - action="store_true", - help="Print the protoset filename instead of writing the data") - parser.add_argument("-r", - "--retry-delay", - type=float, - default=float(RETRY_DELAY_DEFAULT), - help="Time in seconds to wait before retrying after network " - "error or 0 for no retry, default: " + str(RETRY_DELAY_DEFAULT)) - parser.add_argument("-t", - "--loop-interval", - type=float, - default=float(LOOP_TIME_DEFAULT), - help="Loop interval in seconds or 0 for no loop, default: " + - str(LOOP_TIME_DEFAULT)) + parser.add_argument( + "outdir", + nargs="?", + metavar="OUTDIR", + help="Directory in which to write protoset files", + ) + parser.add_argument( + "-g", + "--target", + default=TARGET_DEFAULT, + help="host:port of device to query, default: " + TARGET_DEFAULT, + ) + parser.add_argument( + "-n", + "--print-only", + action="store_true", + help="Print the protoset filename instead of writing the data", + ) + parser.add_argument( + "-r", + "--retry-delay", + type=float, + default=float(RETRY_DELAY_DEFAULT), + help="Time in seconds to wait before retrying after network " + "error or 0 for no retry, default: " + str(RETRY_DELAY_DEFAULT), + ) + parser.add_argument( + "-t", + "--loop-interval", + type=float, + default=float(LOOP_TIME_DEFAULT), + help="Loop interval in seconds or 0 for no loop, default: " + + str(LOOP_TIME_DEFAULT), + ) parser.add_argument("-v", "--verbose", action="store_true", help="Be verbose") opts = parser.parse_args() diff --git a/poll_history.py b/starlink_grpc_tools/poll_history.py similarity index 86% rename from poll_history.py rename to starlink_grpc_tools/poll_history.py index cf85853..0b10520 100644 --- a/poll_history.py +++ b/starlink_grpc_tools/poll_history.py @@ -14,7 +14,7 @@ from datetime import datetime from datetime import timezone import time -import starlink_grpc +import starlink_grpc_tools.starlink_grpc as starlink_grpc INITIAL_SAMPLES = 20 LOOP_SLEEP_TIME = 4 @@ -34,7 +34,9 @@ def run_loop(context): # On the other hand, `starlink_grpc.history_bulk_data` will always # return 2 dicts, because that's all the data there is. - general, bulk = starlink_grpc.history_bulk_data(samples, start=counter, context=context) + general, bulk = starlink_grpc.history_bulk_data( + samples, start=counter, context=context + ) except starlink_grpc.GrpcError: # Dish rebooting maybe, or LAN connectivity error. Just ignore it # and hope it goes away. @@ -44,14 +46,17 @@ def run_loop(context): # be replaced with something more useful. # This computes a trigger detecting any packet loss (ping drop): - #triggered = any(x > 0 for x in bulk["pop_ping_drop_rate"]) + # triggered = any(x > 0 for x in bulk["pop_ping_drop_rate"]) # This computes a trigger detecting samples marked as obstructed: - #triggered = any(bulk["obstructed"]) + # triggered = any(bulk["obstructed"]) # This computes a trigger detecting samples not marked as scheduled: triggered = not all(bulk["scheduled"]) if triggered or prev_triggered: - print("Triggered" if triggered else "Continued", "at:", - datetime.now(tz=timezone.utc)) + print( + "Triggered" if triggered else "Continued", + "at:", + datetime.now(tz=timezone.utc), + ) print("status:", status) print("history:", bulk) if not triggered: diff --git a/starlink_grpc.py b/starlink_grpc_tools/starlink_grpc.py similarity index 86% rename from starlink_grpc.py rename to starlink_grpc_tools/starlink_grpc.py index be6b4e4..666fabc 100644 --- a/starlink_grpc.py +++ b/starlink_grpc_tools/starlink_grpc.py @@ -370,13 +370,21 @@ period. from itertools import chain import math import statistics -from typing import Dict, Iterable, List, Optional, Sequence, Tuple, get_type_hints -from typing_extensions import TypedDict, get_args +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import get_type_hints import grpc +from typing_extensions import TypedDict +from typing_extensions import get_args try: from yagrc import importer + importer.add_lazy_packages(["spacex.api.device"]) imports_pending = True except (ImportError, AttributeError): @@ -390,11 +398,16 @@ from spacex.api.device import dish_pb2 # prevent hang if the connection goes dead without closing. REQUEST_TIMEOUT = 10 -HISTORY_FIELDS = ("pop_ping_drop_rate", "pop_ping_latency_ms", "downlink_throughput_bps", - "uplink_throughput_bps") +HISTORY_FIELDS = ( + "pop_ping_drop_rate", + "pop_ping_latency_ms", + "downlink_throughput_bps", + "uplink_throughput_bps", +) StatusDict = TypedDict( - "StatusDict", { + "StatusDict", + { "id": str, "hardware_version": str, "software_version": str, @@ -415,30 +428,40 @@ StatusDict = TypedDict( "direction_azimuth": float, "direction_elevation": float, "is_snr_above_noise_floor": bool, - }) + }, +) ObstructionDict = TypedDict( - "ObstructionDict", { + "ObstructionDict", + { "wedges_fraction_obstructed[]": Sequence[Optional[float]], "raw_wedges_fraction_obstructed[]": Sequence[Optional[float]], "valid_s": float, - }) + }, +) AlertDict = Dict[str, bool] -LocationDict = TypedDict("LocationDict", { - "latitude": Optional[float], - "longitude": Optional[float], - "altitude": Optional[float], -}) +LocationDict = TypedDict( + "LocationDict", + { + "latitude": Optional[float], + "longitude": Optional[float], + "altitude": Optional[float], + }, +) -HistGeneralDict = TypedDict("HistGeneralDict", { - "samples": int, - "end_counter": int, -}) +HistGeneralDict = TypedDict( + "HistGeneralDict", + { + "samples": int, + "end_counter": int, + }, +) HistBulkDict = TypedDict( - "HistBulkDict", { + "HistBulkDict", + { "pop_ping_drop_rate": Sequence[float], "pop_ping_latency_ms": Sequence[Optional[float]], "downlink_throughput_bps": Sequence[float], @@ -446,10 +469,12 @@ HistBulkDict = TypedDict( "snr": Sequence[Optional[float]], "scheduled": Sequence[Optional[bool]], "obstructed": Sequence[Optional[bool]], - }) + }, +) PingDropDict = TypedDict( - "PingDropDict", { + "PingDropDict", + { "total_ping_drop": float, "count_full_ping_drop": int, "count_obstructed": int, @@ -458,37 +483,47 @@ PingDropDict = TypedDict( "count_unscheduled": int, "total_unscheduled_ping_drop": float, "count_full_unscheduled_ping_drop": int, - }) + }, +) PingDropRlDict = TypedDict( - "PingDropRlDict", { + "PingDropRlDict", + { "init_run_fragment": int, "final_run_fragment": int, "run_seconds[1,]": Sequence[int], "run_minutes[1,]": Sequence[int], - }) + }, +) PingLatencyDict = TypedDict( - "PingLatencyDict", { + "PingLatencyDict", + { "mean_all_ping_latency": float, "deciles_all_ping_latency[]": Sequence[float], "mean_full_ping_latency": float, "deciles_full_ping_latency[]": Sequence[float], "stdev_full_ping_latency": Optional[float], - }) + }, +) LoadedLatencyDict = TypedDict( - "LoadedLatencyDict", { + "LoadedLatencyDict", + { "load_bucket_samples[]": Sequence[int], "load_bucket_min_latency[]": Sequence[Optional[float]], "load_bucket_median_latency[]": Sequence[Optional[float]], "load_bucket_max_latency[]": Sequence[Optional[float]], - }) + }, +) -UsageDict = TypedDict("UsageDict", { - "download_usage": int, - "upload_usage": int, -}) +UsageDict = TypedDict( + "UsageDict", + { + "download_usage": int, + "upload_usage": int, + }, +) # For legacy reasons, there is a slight difference between the field names # returned in the actual data vs the *_field_names functions. This is a map of @@ -534,6 +569,7 @@ def resolve_imports(channel: grpc.Channel): class GrpcError(Exception): """Provides error info when something went wrong with a gRPC call.""" + def __init__(self, e, *args, **kwargs): # grpc.RpcError is too verbose to print in whole, but it may also be # a Call object, and that class has some minimally useful info. @@ -560,6 +596,7 @@ class ChannelContext: `close()` should be called on the object when it is no longer in use. """ + def __init__(self, target: Optional[str] = None) -> None: self.channel = None self.target = "192.168.100.1:9200" if target is None else target @@ -577,7 +614,9 @@ class ChannelContext: self.channel = None -def call_with_channel(function, *args, context: Optional[ChannelContext] = None, **kwargs): +def call_with_channel( + function, *args, context: Optional[ChannelContext] = None, **kwargs +): """Call a function with a channel object. Args: @@ -662,7 +701,11 @@ def status_field_types(context: Optional[ChannelContext] = None): num_alerts = len(dish_pb2.DishAlerts.DESCRIPTOR.fields) except AttributeError: pass - return (_field_types(StatusDict), _field_types(ObstructionDict), [bool] * num_alerts) + return ( + _field_types(StatusDict), + _field_types(ObstructionDict), + [bool] * num_alerts, + ) def get_status(context: Optional[ChannelContext] = None): @@ -680,11 +723,14 @@ def get_status(context: Optional[ChannelContext] = None): Starlink user terminal or the grpc protocol has changed in a way this module cannot handle. """ + def grpc_call(channel): if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - response = stub.Handle(device_pb2.Request(get_status={}), timeout=REQUEST_TIMEOUT) + response = stub.Handle( + device_pb2.Request(get_status={}), timeout=REQUEST_TIMEOUT + ) return response.dish_get_status return call_with_channel(grpc_call, context=context) @@ -712,7 +758,8 @@ def get_id(context: Optional[ChannelContext] = None) -> str: def status_data( - context: Optional[ChannelContext] = None) -> Tuple[StatusDict, ObstructionDict, AlertDict]: + context: Optional[ChannelContext] = None, +) -> Tuple[StatusDict, ObstructionDict, AlertDict]: """Fetch current status data. Args: @@ -768,40 +815,61 @@ def status_data( obstruction_stats = getattr(status, "obstruction_stats", None) if obstruction_stats is not None: try: - if (obstruction_stats.avg_prolonged_obstruction_duration_s > 0.0 - and not math.isnan(obstruction_stats.avg_prolonged_obstruction_interval_s)): - obstruction_duration = obstruction_stats.avg_prolonged_obstruction_duration_s - obstruction_interval = obstruction_stats.avg_prolonged_obstruction_interval_s + if ( + obstruction_stats.avg_prolonged_obstruction_duration_s > 0.0 + and not math.isnan( + obstruction_stats.avg_prolonged_obstruction_interval_s + ) + ): + obstruction_duration = ( + obstruction_stats.avg_prolonged_obstruction_duration_s + ) + obstruction_interval = ( + obstruction_stats.avg_prolonged_obstruction_interval_s + ) except AttributeError: pass device_info = getattr(status, "device_info", None) - return { - "id": getattr(device_info, "id", None), - "hardware_version": getattr(device_info, "hardware_version", None), - "software_version": getattr(device_info, "software_version", None), - "state": state, - "uptime": getattr(getattr(status, "device_state", None), "uptime_s", None), - "snr": None, # obsoleted in grpc service - "seconds_to_first_nonempty_slot": getattr(status, "seconds_to_first_nonempty_slot", None), - "pop_ping_drop_rate": getattr(status, "pop_ping_drop_rate", None), - "downlink_throughput_bps": getattr(status, "downlink_throughput_bps", None), - "uplink_throughput_bps": getattr(status, "uplink_throughput_bps", None), - "pop_ping_latency_ms": getattr(status, "pop_ping_latency_ms", None), - "alerts": alert_bits, - "fraction_obstructed": getattr(obstruction_stats, "fraction_obstructed", None), - "currently_obstructed": getattr(obstruction_stats, "currently_obstructed", None), - "seconds_obstructed": None, # obsoleted in grpc service - "obstruction_duration": obstruction_duration, - "obstruction_interval": obstruction_interval, - "direction_azimuth": getattr(status, "boresight_azimuth_deg", None), - "direction_elevation": getattr(status, "boresight_elevation_deg", None), - "is_snr_above_noise_floor": getattr(status, "is_snr_above_noise_floor", None), - }, { - "wedges_fraction_obstructed[]": [None] * 12, # obsoleted in grpc service - "raw_wedges_fraction_obstructed[]": [None] * 12, # obsoleted in grpc service - "valid_s": getattr(obstruction_stats, "valid_s", None), - }, alerts + return ( + { + "id": getattr(device_info, "id", None), + "hardware_version": getattr(device_info, "hardware_version", None), + "software_version": getattr(device_info, "software_version", None), + "state": state, + "uptime": getattr(getattr(status, "device_state", None), "uptime_s", None), + "snr": None, # obsoleted in grpc service + "seconds_to_first_nonempty_slot": getattr( + status, "seconds_to_first_nonempty_slot", None + ), + "pop_ping_drop_rate": getattr(status, "pop_ping_drop_rate", None), + "downlink_throughput_bps": getattr(status, "downlink_throughput_bps", None), + "uplink_throughput_bps": getattr(status, "uplink_throughput_bps", None), + "pop_ping_latency_ms": getattr(status, "pop_ping_latency_ms", None), + "alerts": alert_bits, + "fraction_obstructed": getattr( + obstruction_stats, "fraction_obstructed", None + ), + "currently_obstructed": getattr( + obstruction_stats, "currently_obstructed", None + ), + "seconds_obstructed": None, # obsoleted in grpc service + "obstruction_duration": obstruction_duration, + "obstruction_interval": obstruction_interval, + "direction_azimuth": getattr(status, "boresight_azimuth_deg", None), + "direction_elevation": getattr(status, "boresight_elevation_deg", None), + "is_snr_above_noise_floor": getattr( + status, "is_snr_above_noise_floor", None + ), + }, + { + "wedges_fraction_obstructed[]": [None] * 12, # obsoleted in grpc service + "raw_wedges_fraction_obstructed[]": [None] + * 12, # obsoleted in grpc service + "valid_s": getattr(obstruction_stats, "valid_s", None), + }, + alerts, + ) def location_field_names(): @@ -839,11 +907,14 @@ def get_location(context: Optional[ChannelContext] = None): Starlink user terminal or the grpc protocol has changed in a way this module cannot handle. """ + def grpc_call(channel): if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - response = stub.Handle(device_pb2.Request(get_location={}), timeout=REQUEST_TIMEOUT) + response = stub.Handle( + device_pb2.Request(get_location={}), timeout=REQUEST_TIMEOUT + ) return response.get_location return call_with_channel(grpc_call, context=context) @@ -935,8 +1006,14 @@ def history_stats_field_names(): additional data groups, so it not recommended for the caller to assume exactly 6 elements. """ - return (_field_names(HistGeneralDict), _field_names(PingDropDict), _field_names(PingDropRlDict), - _field_names(PingLatencyDict), _field_names(LoadedLatencyDict), _field_names(UsageDict)) + return ( + _field_names(HistGeneralDict), + _field_names(PingDropDict), + _field_names(PingDropRlDict), + _field_names(PingLatencyDict), + _field_names(LoadedLatencyDict), + _field_names(UsageDict), + ) def history_stats_field_types(): @@ -955,8 +1032,14 @@ def history_stats_field_types(): additional data groups, so it not recommended for the caller to assume exactly 6 elements. """ - return (_field_types(HistGeneralDict), _field_types(PingDropDict), _field_types(PingDropRlDict), - _field_types(PingLatencyDict), _field_types(LoadedLatencyDict), _field_types(UsageDict)) + return ( + _field_types(HistGeneralDict), + _field_types(PingDropDict), + _field_types(PingDropRlDict), + _field_types(PingLatencyDict), + _field_types(LoadedLatencyDict), + _field_types(UsageDict), + ) def get_history(context: Optional[ChannelContext] = None): @@ -974,20 +1057,22 @@ def get_history(context: Optional[ChannelContext] = None): Starlink user terminal or the grpc protocol has changed in a way this module cannot handle. """ + def grpc_call(channel: grpc.Channel): if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - response = stub.Handle(device_pb2.Request(get_history={}), timeout=REQUEST_TIMEOUT) + response = stub.Handle( + device_pb2.Request(get_history={}), timeout=REQUEST_TIMEOUT + ) return response.dish_get_history return call_with_channel(grpc_call, context=context) -def _compute_sample_range(history, - parse_samples: int, - start: Optional[int] = None, - verbose: bool = False): +def _compute_sample_range( + history, parse_samples: int, start: Optional[int] = None, verbose: bool = False +): try: current = int(history.current) samples = len(history.pop_ping_drop_rate) @@ -1021,7 +1106,7 @@ def _compute_sample_range(history, # Not a ring buffer is simple case. if hasattr(history, "unwrapped"): - return range(samples - (current-start), samples), current - start, current + return range(samples - (current - start), samples), current - start, current # This is ring buffer offset, so both index to oldest data sample and # index to next data sample after the newest one. @@ -1039,11 +1124,13 @@ def _compute_sample_range(history, return sample_range, current - start, current -def concatenate_history(history1, - history2, - samples1: int = -1, - start1: Optional[int] = None, - verbose: bool = False): +def concatenate_history( + history1, + history2, + samples1: int = -1, + start1: Optional[int] = None, + verbose: bool = False, +): """Append the sample-dependent fields of one history object to another. Note: @@ -1085,7 +1172,9 @@ def concatenate_history(history1, # but this layer of the code tries not to make that sort of logging # policy decision, so honor requested verbosity. if verbose: - print("WARNING: Appending discontiguous samples. Polling interval probably too short.") + print( + "WARNING: Appending discontiguous samples. Polling interval probably too short." + ) new_samples = size2 unwrapped = UnwrappedHistory() @@ -1094,8 +1183,13 @@ def concatenate_history(history1, setattr(unwrapped, field, []) unwrapped.unwrapped = True - sample_range, ignore1, ignore2 = _compute_sample_range( # pylint: disable=unused-variable - history1, samples1, start=start1) + ( + sample_range, + ignore1, + ignore2, + ) = _compute_sample_range( # pylint: disable=unused-variable + history1, samples1, start=start1 + ) for i in sample_range: for field in HISTORY_FIELDS: if hasattr(unwrapped, field): @@ -1104,7 +1198,9 @@ def concatenate_history(history1, except (IndexError, TypeError): pass - sample_range, ignore1, ignore2 = _compute_sample_range(history2, new_samples) # pylint: disable=unused-variable + sample_range, ignore1, ignore2 = _compute_sample_range( + history2, new_samples + ) # pylint: disable=unused-variable for i in sample_range: for field in HISTORY_FIELDS: if hasattr(unwrapped, field): @@ -1117,11 +1213,13 @@ def concatenate_history(history1, return unwrapped -def history_bulk_data(parse_samples: int, - start: Optional[int] = None, - verbose: bool = False, - context: Optional[ChannelContext] = None, - history=None) -> Tuple[HistGeneralDict, HistBulkDict]: +def history_bulk_data( + parse_samples: int, + start: Optional[int] = None, + verbose: bool = False, + context: Optional[ChannelContext] = None, + history=None, +) -> Tuple[HistGeneralDict, HistBulkDict]: """Fetch history data for a range of samples. Args: @@ -1164,10 +1262,9 @@ def history_bulk_data(parse_samples: int, except (AttributeError, ValueError, grpc.RpcError) as e: raise GrpcError(e) from e - sample_range, parsed_samples, current = _compute_sample_range(history, - parse_samples, - start=start, - verbose=verbose) + sample_range, parsed_samples, current = _compute_sample_range( + history, parse_samples, start=start, verbose=verbose + ) pop_ping_drop_rate = [] pop_ping_latency_ms = [] @@ -1214,10 +1311,9 @@ def history_bulk_data(parse_samples: int, } -def history_ping_stats(parse_samples: int, - verbose: bool = False, - context: Optional[ChannelContext] = None - ) -> Tuple[HistGeneralDict, PingDropDict, PingDropRlDict]: +def history_ping_stats( + parse_samples: int, verbose: bool = False, context: Optional[ChannelContext] = None +) -> Tuple[HistGeneralDict, PingDropDict, PingDropRlDict]: """Deprecated. Use history_stats instead.""" return history_stats(parse_samples, verbose=verbose, context=context)[0:3] @@ -1227,9 +1323,15 @@ def history_stats( start: Optional[int] = None, verbose: bool = False, context: Optional[ChannelContext] = None, - history=None -) -> Tuple[HistGeneralDict, PingDropDict, PingDropRlDict, PingLatencyDict, LoadedLatencyDict, - UsageDict]: + history=None, +) -> Tuple[ + HistGeneralDict, + PingDropDict, + PingDropRlDict, + PingLatencyDict, + LoadedLatencyDict, + UsageDict, +]: """Fetch, parse, and compute ping and usage stats. Note: @@ -1268,10 +1370,9 @@ def history_stats( except (AttributeError, ValueError, grpc.RpcError) as e: raise GrpcError(e) from e - sample_range, parsed_samples, current = _compute_sample_range(history, - parse_samples, - start=start, - verbose=verbose) + sample_range, parsed_samples, current = _compute_sample_range( + history, parse_samples, start=start, verbose=verbose + ) tot = 0.0 count_full_drop = 0 @@ -1308,7 +1409,7 @@ def history_stats( if run_length <= 60: second_runs[run_length - 1] += run_length else: - minute_runs[min((run_length-1) // 60 - 1, 59)] += run_length + minute_runs[min((run_length - 1) // 60 - 1, 59)] += run_length run_length = 0 elif init_run_length is None: init_run_length = 0 @@ -1337,7 +1438,7 @@ def history_stats( if d == 0.0: rtt_full.append(rtt) if down + up > 500000: - rtt_buckets[min(14, int(math.log2((down+up) / 500000)))].append(rtt) + rtt_buckets[min(14, int(math.log2((down + up) / 500000)))].append(rtt) else: rtt_buckets[0].append(rtt) if d < 1.0: @@ -1353,7 +1454,7 @@ def history_stats( def weighted_mean_and_quantiles(data, n): if not data: - return None, [None] * (n+1) + return None, [None] * (n + 1) total_weight = sum(x[1] for x in data) result = [] items = iter(data) @@ -1392,40 +1493,51 @@ def history_stats( rtt_all.sort(key=lambda x: x[0]) wmean_all, wdeciles_all = weighted_mean_and_quantiles(rtt_all, 10) rtt_full.sort() - mean_full, deciles_full = weighted_mean_and_quantiles(tuple((x, 1.0) for x in rtt_full), 10) + mean_full, deciles_full = weighted_mean_and_quantiles( + tuple((x, 1.0) for x in rtt_full), 10 + ) - return { - "samples": parsed_samples, - "end_counter": current, - }, { - "total_ping_drop": tot, - "count_full_ping_drop": count_full_drop, - "count_obstructed": count_obstruct, - "total_obstructed_ping_drop": total_obstruct_drop, - "count_full_obstructed_ping_drop": count_full_obstruct, - "count_unscheduled": count_unsched, - "total_unscheduled_ping_drop": total_unsched_drop, - "count_full_unscheduled_ping_drop": count_full_unsched, - }, { - "init_run_fragment": init_run_length, - "final_run_fragment": run_length, - "run_seconds[1,]": second_runs, - "run_minutes[1,]": minute_runs, - }, { - "mean_all_ping_latency": wmean_all, - "deciles_all_ping_latency[]": wdeciles_all, - "mean_full_ping_latency": mean_full, - "deciles_full_ping_latency[]": deciles_full, - "stdev_full_ping_latency": statistics.pstdev(rtt_full) if rtt_full else None, - }, { - "load_bucket_samples[]": bucket_samples, - "load_bucket_min_latency[]": bucket_min, - "load_bucket_median_latency[]": bucket_median, - "load_bucket_max_latency[]": bucket_max, - }, { - "download_usage": int(round(usage_down / 8)), - "upload_usage": int(round(usage_up / 8)), - } + return ( + { + "samples": parsed_samples, + "end_counter": current, + }, + { + "total_ping_drop": tot, + "count_full_ping_drop": count_full_drop, + "count_obstructed": count_obstruct, + "total_obstructed_ping_drop": total_obstruct_drop, + "count_full_obstructed_ping_drop": count_full_obstruct, + "count_unscheduled": count_unsched, + "total_unscheduled_ping_drop": total_unsched_drop, + "count_full_unscheduled_ping_drop": count_full_unsched, + }, + { + "init_run_fragment": init_run_length, + "final_run_fragment": run_length, + "run_seconds[1,]": second_runs, + "run_minutes[1,]": minute_runs, + }, + { + "mean_all_ping_latency": wmean_all, + "deciles_all_ping_latency[]": wdeciles_all, + "mean_full_ping_latency": mean_full, + "deciles_full_ping_latency[]": deciles_full, + "stdev_full_ping_latency": statistics.pstdev(rtt_full) + if rtt_full + else None, + }, + { + "load_bucket_samples[]": bucket_samples, + "load_bucket_min_latency[]": bucket_min, + "load_bucket_median_latency[]": bucket_median, + "load_bucket_max_latency[]": bucket_max, + }, + { + "download_usage": int(round(usage_down / 8)), + "upload_usage": int(round(usage_up / 8)), + }, + ) def get_obstruction_map(context: Optional[ChannelContext] = None): @@ -1443,12 +1555,14 @@ def get_obstruction_map(context: Optional[ChannelContext] = None): Starlink user terminal or the grpc protocol has changed in a way this module cannot handle. """ + def grpc_call(channel: grpc.Channel): if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - response = stub.Handle(device_pb2.Request(dish_get_obstruction_map={}), - timeout=REQUEST_TIMEOUT) + response = stub.Handle( + device_pb2.Request(dish_get_obstruction_map={}), timeout=REQUEST_TIMEOUT + ) return response.dish_get_obstruction_map return call_with_channel(grpc_call, context=context) @@ -1478,7 +1592,10 @@ def obstruction_map(context: Optional[ChannelContext] = None): try: cols = map_data.num_cols - return tuple((map_data.snr[i:i + cols]) for i in range(0, cols * map_data.num_rows, cols)) + return tuple( + (map_data.snr[i : i + cols]) + for i in range(0, cols * map_data.num_rows, cols) + ) except (AttributeError, IndexError, TypeError) as e: raise GrpcError(e) from e @@ -1495,6 +1612,7 @@ def reboot(context: Optional[ChannelContext] = None) -> None: Raises: GrpcError: Communication or service error. """ + def grpc_call(channel: grpc.Channel) -> None: if imports_pending: resolve_imports(channel) @@ -1508,7 +1626,9 @@ def reboot(context: Optional[ChannelContext] = None) -> None: raise GrpcError(e) from e -def set_stow_state(unstow: bool = False, context: Optional[ChannelContext] = None) -> None: +def set_stow_state( + unstow: bool = False, context: Optional[ChannelContext] = None +) -> None: """Request dish stow or unstow operation. Args: @@ -1522,11 +1642,14 @@ def set_stow_state(unstow: bool = False, context: Optional[ChannelContext] = Non Raises: GrpcError: Communication or service error. """ + def grpc_call(channel: grpc.Channel) -> None: if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - stub.Handle(device_pb2.Request(dish_stow={"unstow": unstow}), timeout=REQUEST_TIMEOUT) + stub.Handle( + device_pb2.Request(dish_stow={"unstow": unstow}), timeout=REQUEST_TIMEOUT + ) # response is empty message in this case, so just ignore it try: @@ -1535,10 +1658,12 @@ def set_stow_state(unstow: bool = False, context: Optional[ChannelContext] = Non raise GrpcError(e) from e -def set_sleep_config(start: int, - duration: int, - enable: bool = True, - context: Optional[ChannelContext] = None) -> None: +def set_sleep_config( + start: int, + duration: int, + enable: bool = True, + context: Optional[ChannelContext] = None, +) -> None: """Set sleep mode configuration. Args: @@ -1565,13 +1690,16 @@ def set_sleep_config(start: int, if imports_pending: resolve_imports(channel) stub = device_pb2_grpc.DeviceStub(channel) - stub.Handle(device_pb2.Request( - dish_power_save={ - "power_save_start_minutes": start, - "power_save_duration_minutes": duration, - "enable_power_save": enable - }), - timeout=REQUEST_TIMEOUT) + stub.Handle( + device_pb2.Request( + dish_power_save={ + "power_save_start_minutes": start, + "power_save_duration_minutes": duration, + "enable_power_save": enable, + } + ), + timeout=REQUEST_TIMEOUT, + ) # response is empty message in this case, so just ignore it try: diff --git a/starlink_json.py b/starlink_grpc_tools/starlink_json.py similarity index 76% rename from starlink_json.py rename to starlink_grpc_tools/starlink_json.py index 70cfc20..5a6fd74 100644 --- a/starlink_json.py +++ b/starlink_grpc_tools/starlink_json.py @@ -9,13 +9,12 @@ response does. See the starlink_grpc module docstring for descriptions of the stat elements. """ +from itertools import chain import json import math import statistics import sys -from itertools import chain - class JsonError(Exception): """Provides error info when something went wrong with JSON parsing.""" @@ -66,38 +65,45 @@ def history_stats_field_names(): additional data groups, so it not recommended for the caller to assume exactly 6 elements. """ - return [ - "samples", - "end_counter", - ], [ - "total_ping_drop", - "count_full_ping_drop", - "count_obstructed", - "total_obstructed_ping_drop", - "count_full_obstructed_ping_drop", - "count_unscheduled", - "total_unscheduled_ping_drop", - "count_full_unscheduled_ping_drop", - ], [ - "init_run_fragment", - "final_run_fragment", - "run_seconds[1,61]", - "run_minutes[1,61]", - ], [ - "mean_all_ping_latency", - "deciles_all_ping_latency[11]", - "mean_full_ping_latency", - "deciles_full_ping_latency[11]", - "stdev_full_ping_latency", - ], [ - "load_bucket_samples[15]", - "load_bucket_min_latency[15]", - "load_bucket_median_latency[15]", - "load_bucket_max_latency[15]", - ], [ - "download_usage", - "upload_usage", - ] + return ( + [ + "samples", + "end_counter", + ], + [ + "total_ping_drop", + "count_full_ping_drop", + "count_obstructed", + "total_obstructed_ping_drop", + "count_full_obstructed_ping_drop", + "count_unscheduled", + "total_unscheduled_ping_drop", + "count_full_unscheduled_ping_drop", + ], + [ + "init_run_fragment", + "final_run_fragment", + "run_seconds[1,61]", + "run_minutes[1,61]", + ], + [ + "mean_all_ping_latency", + "deciles_all_ping_latency[11]", + "mean_full_ping_latency", + "deciles_full_ping_latency[11]", + "stdev_full_ping_latency", + ], + [ + "load_bucket_samples[15]", + "load_bucket_min_latency[15]", + "load_bucket_median_latency[15]", + "load_bucket_max_latency[15]", + ], + [ + "download_usage", + "upload_usage", + ], + ) def get_history(filename): @@ -184,9 +190,9 @@ def history_bulk_data(filename, parse_samples, verbose=False): except Exception as e: raise JsonError(e) - sample_range, parsed_samples, current = _compute_sample_range(history, - parse_samples, - verbose=verbose) + sample_range, parsed_samples, current = _compute_sample_range( + history, parse_samples, verbose=verbose + ) pop_ping_drop_rate = [] pop_ping_latency_ms = [] @@ -196,7 +202,10 @@ def history_bulk_data(filename, parse_samples, verbose=False): for i in sample_range: pop_ping_drop_rate.append(history["popPingDropRate"][i]) pop_ping_latency_ms.append( - history["popPingLatencyMs"][i] if history["popPingDropRate"][i] < 1 else None) + history["popPingLatencyMs"][i] + if history["popPingDropRate"][i] < 1 + else None + ) downlink_throughput_bps.append(history["downlinkThroughputBps"][i]) uplink_throughput_bps.append(history["uplinkThroughputBps"][i]) @@ -250,9 +259,9 @@ def history_stats(filename, parse_samples, verbose=False): except Exception as e: raise JsonError(e) - sample_range, parsed_samples, current = _compute_sample_range(history, - parse_samples, - verbose=verbose) + sample_range, parsed_samples, current = _compute_sample_range( + history, parse_samples, verbose=verbose + ) tot = 0.0 count_full_drop = 0 @@ -289,7 +298,7 @@ def history_stats(filename, parse_samples, verbose=False): if run_length <= 60: second_runs[run_length - 1] += run_length else: - minute_runs[min((run_length-1) // 60 - 1, 59)] += run_length + minute_runs[min((run_length - 1) // 60 - 1, 59)] += run_length run_length = 0 elif init_run_length is None: init_run_length = 0 @@ -305,7 +314,7 @@ def history_stats(filename, parse_samples, verbose=False): if d == 0.0: rtt_full.append(rtt) if down + up > 500000: - rtt_buckets[min(14, int(math.log2((down+up) / 500000)))].append(rtt) + rtt_buckets[min(14, int(math.log2((down + up) / 500000)))].append(rtt) else: rtt_buckets[0].append(rtt) if d < 1.0: @@ -321,7 +330,7 @@ def history_stats(filename, parse_samples, verbose=False): def weighted_mean_and_quantiles(data, n): if not data: - return None, [None] * (n+1) + return None, [None] * (n + 1) total_weight = sum(x[1] for x in data) result = [] items = iter(data) @@ -360,37 +369,48 @@ def history_stats(filename, parse_samples, verbose=False): rtt_all.sort(key=lambda x: x[0]) wmean_all, wdeciles_all = weighted_mean_and_quantiles(rtt_all, 10) rtt_full.sort() - mean_full, deciles_full = weighted_mean_and_quantiles(tuple((x, 1.0) for x in rtt_full), 10) + mean_full, deciles_full = weighted_mean_and_quantiles( + tuple((x, 1.0) for x in rtt_full), 10 + ) - return { - "samples": parsed_samples, - "end_counter": current, - }, { - "total_ping_drop": tot, - "count_full_ping_drop": count_full_drop, - "count_obstructed": count_obstruct, - "total_obstructed_ping_drop": total_obstruct_drop, - "count_full_obstructed_ping_drop": count_full_obstruct, - "count_unscheduled": count_unsched, - "total_unscheduled_ping_drop": total_unsched_drop, - "count_full_unscheduled_ping_drop": count_full_unsched, - }, { - "init_run_fragment": init_run_length, - "final_run_fragment": run_length, - "run_seconds[1,]": second_runs, - "run_minutes[1,]": minute_runs, - }, { - "mean_all_ping_latency": wmean_all, - "deciles_all_ping_latency[]": wdeciles_all, - "mean_full_ping_latency": mean_full, - "deciles_full_ping_latency[]": deciles_full, - "stdev_full_ping_latency": statistics.pstdev(rtt_full) if rtt_full else None, - }, { - "load_bucket_samples[]": bucket_samples, - "load_bucket_min_latency[]": bucket_min, - "load_bucket_median_latency[]": bucket_median, - "load_bucket_max_latency[]": bucket_max, - }, { - "download_usage": int(round(usage_down / 8)), - "upload_usage": int(round(usage_up / 8)), - } + return ( + { + "samples": parsed_samples, + "end_counter": current, + }, + { + "total_ping_drop": tot, + "count_full_ping_drop": count_full_drop, + "count_obstructed": count_obstruct, + "total_obstructed_ping_drop": total_obstruct_drop, + "count_full_obstructed_ping_drop": count_full_obstruct, + "count_unscheduled": count_unsched, + "total_unscheduled_ping_drop": total_unsched_drop, + "count_full_unscheduled_ping_drop": count_full_unsched, + }, + { + "init_run_fragment": init_run_length, + "final_run_fragment": run_length, + "run_seconds[1,]": second_runs, + "run_minutes[1,]": minute_runs, + }, + { + "mean_all_ping_latency": wmean_all, + "deciles_all_ping_latency[]": wdeciles_all, + "mean_full_ping_latency": mean_full, + "deciles_full_ping_latency[]": deciles_full, + "stdev_full_ping_latency": statistics.pstdev(rtt_full) + if rtt_full + else None, + }, + { + "load_bucket_samples[]": bucket_samples, + "load_bucket_min_latency[]": bucket_min, + "load_bucket_median_latency[]": bucket_median, + "load_bucket_max_latency[]": bucket_max, + }, + { + "download_usage": int(round(usage_down / 8)), + "upload_usage": int(round(usage_up / 8)), + }, + )