pax_global_header00006660000000000000000000000064145341773110014520gustar00rootroot0000000000000052 comment=3176e7077391f4946c5bcbc0c47ca9b6f3a9c0eb hacf-fr-meteofrance-api-1d2b3b9/000077500000000000000000000000001453417731100164735ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/.cookiecutter.json000066400000000000000000000004501453417731100221430ustar00rootroot00000000000000{ "_template": "gh:cjolowicz/cookiecutter-hypermodern-python", "author": "oncleben31", "email": "oncleben31@gmail.com", "friendly_name": "M\u00e9t\u00e9o-France API", "github_user": "hacf-fr", "package_name": "meteofrance", "project_name": "meteofrance-api", "version": "0.1.1" } hacf-fr-meteofrance-api-1d2b3b9/.darglint000066400000000000000000000000351453417731100202760ustar00rootroot00000000000000[darglint] strictness = long hacf-fr-meteofrance-api-1d2b3b9/.flake8000066400000000000000000000004101453417731100176410ustar00rootroot00000000000000[flake8] select = B,B9,C,D,DAR,E,F,N,RST,S,W ignore = E203,E501,RST201,RST203,RST301,W503 max-line-length = 100 max-complexity = 10 docstring-convention = google per-file-ignores = tests/*:S101 rst-roles = class,const,func,meth,mod,ref rst-directives = deprecated hacf-fr-meteofrance-api-1d2b3b9/.gitattributes000066400000000000000000000000231453417731100213610ustar00rootroot00000000000000* text=auto eol=lf hacf-fr-meteofrance-api-1d2b3b9/.github/000077500000000000000000000000001453417731100200335ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/.github/CODEOWNERS000066400000000000000000000000361453417731100214250ustar00rootroot00000000000000* @oncleben31 @Quentame @dx44 hacf-fr-meteofrance-api-1d2b3b9/.github/ISSUE_TEMPLATE/000077500000000000000000000000001453417731100222165ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005631453417731100242120ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: HACF Discord url: https://discord.gg/PaZFEjX about: The HACF discord server - Join the community! This discord server is french speaking. - name: HACF Forum url: https://forum.hacf.fr/ about: The HACF forum - Use the forum to find tutorials, help other people, and communicate with others in french. hacf-fr-meteofrance-api-1d2b3b9/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000010571453417731100257460ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. hacf-fr-meteofrance-api-1d2b3b9/.github/ISSUE_TEMPLATE/issue.md000066400000000000000000000002561453417731100236730ustar00rootroot00000000000000--- name: Issue about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **log** ``` Add your logs here. ``` hacf-fr-meteofrance-api-1d2b3b9/.github/dependabot.yml000066400000000000000000000005741453417731100226710ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: interval: daily - package-ecosystem: pip directory: "/.github/workflows" schedule: interval: daily - package-ecosystem: pip directory: "/docs" schedule: interval: daily - package-ecosystem: pip directory: "/" schedule: interval: daily hacf-fr-meteofrance-api-1d2b3b9/.github/release-drafter.yml000066400000000000000000000013301453417731100236200ustar00rootroot00000000000000categories: - title: ":boom: Breaking Changes" label: "breaking" - title: ":rocket: Features" label: "enhancement" - title: ":fire: Removals and Deprecations" label: "removal" - title: ":beetle: Fixes" label: "bug" - title: ":racehorse: Performance" label: "performance" - title: ":rotating_light: Testing" label: "testing" - title: ":construction_worker: Continuous Integration" label: "ci" - title: ":books: Documentation" label: "documentation" - title: ":hammer: Refactoring" label: "refactoring" - title: ":lipstick: Style" label: "style" - title: ":package: Dependencies" labels: - "dependencies" - "build" template: | ## Changes $CHANGES hacf-fr-meteofrance-api-1d2b3b9/.github/stale.yml000066400000000000000000000012111453417731100216610ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 14 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale #exemptLabels: # - pinned # - security # Label to use when marking an issue as stale staleLabel: Stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false hacf-fr-meteofrance-api-1d2b3b9/.github/workflows/000077500000000000000000000000001453417731100220705ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/.github/workflows/constraints.txt000066400000000000000000000001171453417731100251770ustar00rootroot00000000000000pip==23.3.1 nox==2023.4.22 nox-poetry==1.0.3 poetry==1.6.1 virtualenv==20.24.6 hacf-fr-meteofrance-api-1d2b3b9/.github/workflows/release.yml000066400000000000000000000043311453417731100242340ustar00rootroot00000000000000name: Release on: push: branches: - main - master jobs: release: name: Release runs-on: ubuntu-latest steps: - name: Check out the repository uses: actions/checkout@v4.1.1 with: fetch-depth: 2 - name: Set up Python uses: actions/setup-python@v4.7.1 with: python-version: "3.11" - name: Upgrade pip run: | pip install --constraint=.github/workflows/constraints.txt pip pip --version - name: Install Poetry run: | pip install --constraint=.github/workflows/constraints.txt poetry poetry --version - name: Check if there is a parent commit id: check-parent-commit run: | echo "::set-output name=sha::$(git rev-parse --verify --quiet HEAD^)" - name: Detect and tag new version id: check-version if: steps.check-parent-commit.outputs.sha uses: salsify/action-detect-and-tag-new-version@v2.0.3 with: version-command: | bash -o pipefail -c "poetry version | awk '{ print \$2 }'" - name: Bump version for developmental release if: "! steps.check-version.outputs.tag" run: | poetry version patch && version=$(poetry version | awk '{ print $2 }') && poetry version $version.dev.$(date +%s) - name: Build package run: | poetry build --ansi - name: Publish package on PyPI if: steps.check-version.outputs.tag uses: pypa/gh-action-pypi-publish@v1.8.10 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - name: Publish package on TestPyPI if: "! steps.check-version.outputs.tag" uses: pypa/gh-action-pypi-publish@v1.8.10 with: user: __token__ password: ${{ secrets.TEST_PYPI_TOKEN }} repository_url: https://test.pypi.org/legacy/ - name: Publish release notes uses: release-drafter/release-drafter@v5.25.0 with: publish: ${{ steps.check-version.outputs.tag != '' }} tag: ${{ steps.check-version.outputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} hacf-fr-meteofrance-api-1d2b3b9/.github/workflows/tests.yml000066400000000000000000000111311453417731100237520ustar00rootroot00000000000000name: Tests on: push: pull_request: types: [opened, reopened] jobs: tests: name: ${{ matrix.session }} ${{ matrix.python-version }} / ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - { python-version: "3.11", os: ubuntu-latest, session: "pre-commit" } - { python-version: "3.11", os: ubuntu-latest, session: "safety" } - { python-version: "3.11", os: ubuntu-latest, session: "mypy" } - { python-version: "3.10", os: ubuntu-latest, session: "mypy" } - { python-version: "3.9", os: ubuntu-latest, session: "mypy" } - { python-version: "3.8", os: ubuntu-latest, session: "mypy" } - { python-version: "3.11", os: ubuntu-latest, session: "tests" } - { python-version: "3.10", os: ubuntu-latest, session: "tests" } - { python-version: "3.9", os: ubuntu-latest, session: "tests" } - { python-version: "3.8", os: ubuntu-latest, session: "tests" } - { python-version: "3.11", os: windows-latest, session: "tests" } - { python-version: "3.11", os: macos-latest, session: "tests" } - { python-version: "3.11", os: ubuntu-latest, session: "typeguard" } - { python-version: "3.11", os: ubuntu-latest, session: "xdoctest" } - { python-version: "3.11", os: ubuntu-latest, session: "docs-build" } env: NOXSESSION: ${{ matrix.session }} steps: - name: Check out the repository uses: actions/checkout@v4.1.1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4.7.1 with: python-version: ${{ matrix.python-version }} - name: Upgrade pip run: | pip install --constraint=.github/workflows/constraints.txt pip pip --version - name: Install Poetry run: | pip install --constraint=.github/workflows/constraints.txt poetry poetry --version - name: Install Nox run: | pip install --constraint=.github/workflows/constraints.txt nox nox-poetry nox --version - name: Compute pre-commit cache key if: matrix.session == 'pre-commit' id: pre-commit-cache shell: python run: | import hashlib import sys python = "py{}.{}".format(*sys.version_info[:2]) payload = sys.version.encode() + sys.executable.encode() digest = hashlib.sha256(payload).hexdigest() result = "${{ runner.os }}-{}-{}-pre-commit".format(python, digest[:8]) print("::set-output name=result::{}".format(result)) - name: Restore pre-commit cache uses: actions/cache@v3.3.2 if: matrix.session == 'pre-commit' with: path: ~/.cache/pre-commit key: ${{ steps.pre-commit-cache.outputs.result }}-${{ hashFiles('.pre-commit-config.yaml') }} restore-keys: | ${{ steps.pre-commit-cache.outputs.result }}- - name: Run Nox run: | nox --force-color --python=${{ matrix.python-version }} - name: Upload coverage data if: always() && matrix.session == 'tests' uses: "actions/upload-artifact@v3.1.3" with: name: coverage-data path: ".coverage.*" - name: Upload documentation if: matrix.session == 'docs-build' uses: actions/upload-artifact@v3.1.3 with: name: docs path: docs/_build coverage: runs-on: ubuntu-latest needs: tests steps: - name: Check out the repository uses: actions/checkout@v4.1.1 - name: Set up Python 3.11 uses: actions/setup-python@v4.7.1 with: python-version: "3.11" - name: Upgrade pip run: | pip install --constraint=.github/workflows/constraints.txt pip pip --version - name: Install Poetry run: | pip install --constraint=.github/workflows/constraints.txt poetry poetry --version - name: Install Nox run: | pip install --constraint=.github/workflows/constraints.txt nox nox-poetry nox --version - name: Download coverage data uses: actions/download-artifact@v3.0.2 with: name: coverage-data - name: Combine coverage data and display human readable report run: | nox --force-color --session=coverage - name: Create coverage report run: | nox --force-color --session=coverage -- xml - name: Upload coverage report uses: codecov/codecov-action@v3.1.4 hacf-fr-meteofrance-api-1d2b3b9/.gitignore000066400000000000000000000002151453417731100204610ustar00rootroot00000000000000.mypy_cache/ /.coverage /.coverage.* /.nox/ /.python-version /.pytype/ /dist/ /docs/_build/ /src/*.egg-info/ __pycache__/ # Editor /.vscode hacf-fr-meteofrance-api-1d2b3b9/.pre-commit-config.yaml000066400000000000000000000025471453417731100227640ustar00rootroot00000000000000repos: - repo: local hooks: - id: black name: black entry: black language: system types: [python] require_serial: true - id: check-added-large-files name: Check for added large files entry: check-added-large-files language: system - id: check-toml name: Check Toml entry: check-toml language: system types: [toml] - id: check-yaml name: Check Yaml entry: check-yaml language: system types: [yaml] - id: end-of-file-fixer name: Fix End of Files entry: end-of-file-fixer language: system types: [text] stages: [commit, push, manual] - id: flake8 name: flake8 entry: flake8 language: system types: [python] require_serial: true - id: reorder-python-imports name: Reorder python imports entry: reorder-python-imports language: system types: [python] args: [--application-directories=src] - id: trailing-whitespace name: Trim Trailing Whitespace entry: trailing-whitespace-fixer language: system types: [text] stages: [commit, push, manual] - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.3.0 hooks: - id: prettier hacf-fr-meteofrance-api-1d2b3b9/.readthedocs.yml000066400000000000000000000002771453417731100215670ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py formats: all python: install: - requirements: docs/requirements.txt - path: . build: os: ubuntu-22.04 tools: python: "3.11" hacf-fr-meteofrance-api-1d2b3b9/CODE_OF_CONDUCT.rst000066400000000000000000000124241453417731100215050ustar00rootroot00000000000000Contributor Covenant Code of Conduct ==================================== Our Pledge ---------- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. Our Standards ------------- Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting Enforcement Responsibilities ---------------------------- Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. Scope ----- This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at contact@hacf.fr. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. Enforcement Guidelines ---------------------- Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 1. Correction ~~~~~~~~~~~~~ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 2. Warning ~~~~~~~~~~ **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 3. Temporary Ban ~~~~~~~~~~~~~~~~ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 4. Permanent Ban ~~~~~~~~~~~~~~~~ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `__, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder `__. .. _homepage: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. hacf-fr-meteofrance-api-1d2b3b9/CONTRIBUTING.rst000066400000000000000000000057051453417731100211430ustar00rootroot00000000000000Contributor Guide ================= Thank you for your interest in improving this project. This project is open-source under the `MIT license`_ and welcomes contributions in the form of bug reports, feature requests, and pull requests. Here is a list of important resources for contributors: - `Source Code`_ - `Documentation`_ - `Issue Tracker`_ - `Code of Conduct`_ .. _MIT license: https://opensource.org/licenses/MIT .. _Source Code: https://github.com/hacf-fr/meteofrance-api .. _Documentation: https://meteofrance-api.readthedocs.io/ .. _Issue Tracker: https://github.com/hacf-fr/meteofrance-api/issues How to report a bug ------------------- Report bugs on the `Issue Tracker`_. When filing an issue, make sure to answer these questions: - Which operating system and Python version are you using? - Which version of this project are you using? - What did you do? - What did you expect to see? - What did you see instead? The best way to get your bug fixed is to provide a test case, and/or steps to reproduce the issue. How to request a feature ------------------------ Request features on the `Issue Tracker`_. How to set up your development environment ------------------------------------------ You need Python 3.8+ and the following tools: - Poetry_ - Nox_ - nox-poetry_ Install the package with development requirements: .. code:: console $ poetry install You can now run an interactive Python session: .. code:: console $ poetry run python .. _Poetry: https://python-poetry.org/ .. _Nox: https://nox.thea.codes/ .. _nox-poetry: https://nox-poetry.readthedocs.io/ How to test the project ----------------------- Run the full test suite: .. code:: console $ nox List the available Nox sessions: .. code:: console $ nox --list-sessions You can also run a specific Nox session. For example, invoke the unit test suite like this: .. code:: console $ nox --session=tests Unit tests are located in the ``tests`` directory, and are written using the pytest_ testing framework. .. _pytest: https://pytest.readthedocs.io/ How to submit changes --------------------- Open a `pull request`_ to submit changes to this project. Your pull request needs to meet the following guidelines for acceptance: - The Nox test suite must pass without errors and warnings. - Include unit tests. This project maintains 100% code coverage. - If your changes add functionality, update the documentation accordingly. Feel free to submit early, though—we can always iterate on this. To run linting and code formatting checks before committing your change, you can install pre-commit as a Git hook by running the following command: .. code:: console $ nox --session=pre-commit -- install It is recommended to open an issue before starting work on anything. This will allow a chance to talk it over with the owners and validate your approach. .. _pull request: https://github.com/hacf-fr/meteofrance-api/pulls .. github-only .. _Code of Conduct: CODE_OF_CONDUCT.rst hacf-fr-meteofrance-api-1d2b3b9/LICENSE.rst000066400000000000000000000021341453417731100203070ustar00rootroot00000000000000MIT License =========== Copyright (c) 2020 HACF Home Assistant Communauté Francophone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. **The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.** hacf-fr-meteofrance-api-1d2b3b9/README.rst000066400000000000000000000117131453417731100201650ustar00rootroot00000000000000Météo-France Python API ======================= Client Python pour l'API Météo-France. | Python client for Météo-France API. |PyPI| |GitHub Release| |Python Version| |License| |Read the Docs| |Tests| |Codecov| |GitHub Activity| |pre-commit| |Black| .. |PyPI| image:: https://img.shields.io/pypi/v/meteofrance-api :target: https://pypi.org/project/meteofrance-api/ :alt: PyPI .. |GitHub Release| image:: https://img.shields.io/github/release/hacf-fr/meteofrance-api.svg :target: https://github.com/hacf-fr/meteofrance-api/releases :alt: GitHub Release .. |Python Version| image:: https://img.shields.io/pypi/pyversions/meteofrance-api :target: https://pypi.org/project/meteofrance-api/ :alt: Python Version .. |License| image:: https://img.shields.io/pypi/l/meteofrance-api :target: https://opensource.org/licenses/MIT :alt: License .. |Read the Docs| image:: https://img.shields.io/readthedocs/meteofrance-api/latest.svg?label=Read%20the%20Docs :target: https://meteofrance-api.readthedocs.io/ :alt: Read the documentation at https://meteofrance-api.readthedocs.io/ .. |Tests| image:: https://github.com/hacf-fr/meteofrance-api/workflows/Tests/badge.svg :target: https://github.com/hacf-fr/meteofrance-api/actions?workflow=Tests :alt: Tests .. |Codecov| image:: https://codecov.io/gh/hacf-fr/meteofrance-api/branch/master/graph/badge.svg :target: https://codecov.io/gh/hacf-fr/meteofrance-api :alt: Codecov .. |GitHub Activity| image:: https://img.shields.io/github/commit-activity/y/hacf-fr/meteofrance-api.svg :target: https://github.com/hacf-fr/meteofrance-api/commits/master :alt: GitHub Activity .. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white :target: https://github.com/pre-commit/pre-commit :alt: pre-commit .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Black You will find English README content in the section `For English speaking users`_. Vous trouverez le contenu francophone du README dans la section `Pour les francophones`_. Pour les francophones --------------------- Description ^^^^^^^^^^^ Ce package Python permet de gérer la communication avec l'API non publique de Météo-France utilisée par les applications mobiles officielles. Le client permet: * Rechercher des lieux de prévisions. * Accéder aux prévisions météorologiques horaires ou quotidiennes. * Accéder aux prévisions de pluie dans l'heure quand disponibles. * Accéder aux alertes météo pour chaque département français et d'Andorre. Deux bulletins sont disponibles : un synthétique et un second avec l'évolution des alertes pour les prochaines 24 heures (exemple `ici `_). Ce package a été développé avec l'intention d'être utilisé par `Home-Assistant `_ mais il peut être utilisé dans d'autres contextes. Installation ^^^^^^^^^^^^ Pour utiliser le module Python ``meteofrance`` vous devez en premier installer le package en utilisant pip_ depuis PyPI_: .. code:: console $ pip install meteofrance-api Vous pouvez trouver un exemple d'usage dans un module Python en regardant `le test d'intégration `_. Contribuer ^^^^^^^^^^ Les contributions sont les bienvenues. Veuillez consulter les bonnes pratiques détaillées dans `CONTRIBUTING.rst`_. For English speaking users -------------------------- Description ^^^^^^^^^^^^ This Python package manages the communication with the private Météo-France API used by the official mobile applications. The client allows: * Search a forecast location. * Fetch daily or hourly weather forecast. * Fetch rain forecast within the next hour if available. * Fetch the weather alerts or phenomenoms for each French department or Andorre. Two bulletin are availabe: one basic and an other advanced with the timelaps evolution for the next 24 hours (example `here `_). This package have been developed to be used with `Home-Assistant `_ but it can be used in other contexts. Installation ^^^^^^^^^^^^ To use the ``meteofrance`` Python module, you have to install this package first via pip_ from PyPI_: .. code:: console $ pip install meteofrance-api You will find an example ot usage in a Python program in the `integration test `_. Contributing ^^^^^^^^^^^^ Contributions are welcomed. Please check the guidelines in `CONTRIBUTING.rst`_. Credits ------- This project was generated from `@cjolowicz`_'s `Hypermodern Python Cookiecutter`_ template. .. _@cjolowicz: https://github.com/cjolowicz .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _PyPI: https://pypi.org/ .. _Hypermodern Python Cookiecutter: https://github.com/cjolowicz/cookiecutter-hypermodern-python .. _pip: https://pip.pypa.io/ .. github-only .. _CONTRIBUTING.rst: CONTRIBUTING.rst hacf-fr-meteofrance-api-1d2b3b9/codecov.yml000066400000000000000000000002051453417731100206350ustar00rootroot00000000000000comment: false coverage: status: project: default: target: "100" patch: default: target: "100" hacf-fr-meteofrance-api-1d2b3b9/docs/000077500000000000000000000000001453417731100174235ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/docs/codeofconduct.rst000066400000000000000000000000441453417731100227720ustar00rootroot00000000000000.. include:: ../CODE_OF_CONDUCT.rst hacf-fr-meteofrance-api-1d2b3b9/docs/conf.py000066400000000000000000000004671453417731100207310ustar00rootroot00000000000000"""Sphinx configuration.""" from datetime import datetime project = "meteofrance-api" author = "HACF" copyright = f"{datetime.now().year}, {author}" extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_rtd_theme", ] autodoc_typehints = "description" html_theme = "sphinx_rtd_theme" hacf-fr-meteofrance-api-1d2b3b9/docs/contributing.rst000066400000000000000000000001461453417731100226650ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst :end-before: github-only .. _Code of Conduct: codeofconduct.html hacf-fr-meteofrance-api-1d2b3b9/docs/index.rst000066400000000000000000000004441453417731100212660ustar00rootroot00000000000000.. include:: ../README.rst :end-before: github-only .. _CONTRIBUTING.rst: contributing.html .. toctree:: :hidden: :maxdepth: 1 reference contributing Code of Conduct License Changelog hacf-fr-meteofrance-api-1d2b3b9/docs/license.rst000066400000000000000000000000341453417731100215740ustar00rootroot00000000000000.. include:: ../LICENSE.rst hacf-fr-meteofrance-api-1d2b3b9/docs/reference.rst000066400000000000000000000003471453417731100221170ustar00rootroot00000000000000Reference ========= .. contents:: :local: :backlinks: none meteofrance ----------- .. automodule:: meteofrance_api :members: meteofrance.model ----------------- .. automodule:: meteofrance_api.model :members: hacf-fr-meteofrance-api-1d2b3b9/docs/requirements.txt000066400000000000000000000000461453417731100227070ustar00rootroot00000000000000sphinx==7.2.6 sphinx-rtd-theme==1.3.0 hacf-fr-meteofrance-api-1d2b3b9/noxfile.py000066400000000000000000000127431453417731100205200ustar00rootroot00000000000000"""Nox sessions.""" import shutil import sys from pathlib import Path from textwrap import dedent import nox try: from nox_poetry import Session from nox_poetry import session except ImportError: message = f"""\ Nox failed to import the 'nox-poetry' package. Please install it using the following command: {sys.executable} -m pip install nox-poetry""" raise SystemExit(dedent(message)) from None package = "meteofrance_api" python_versions = ["3.11", "3.10", "3.9", "3.8"] nox.needs_version = ">= 2021.6.6" nox.options.sessions = ( "pre-commit", "safety", "mypy", "tests", "typeguard", "xdoctest", "docs-build", ) def activate_virtualenv_in_precommit_hooks(session: Session) -> None: """Activate virtualenv in hooks installed by pre-commit. This function patches git hooks installed by pre-commit to activate the session's virtual environment. This allows pre-commit to locate hooks in that environment when invoked from git. Args: session: The Session object. """ if session.bin is None: return virtualenv = session.env.get("VIRTUAL_ENV") if virtualenv is None: return hookdir = Path(".git") / "hooks" if not hookdir.is_dir(): return for hook in hookdir.iterdir(): if hook.name.endswith(".sample") or not hook.is_file(): continue text = hook.read_text() bindir = repr(session.bin)[1:-1] # strip quotes if not ( Path("A") == Path("a") and bindir.lower() in text.lower() or bindir in text ): continue lines = text.splitlines() if not (lines[0].startswith("#!") and "python" in lines[0].lower()): continue header = dedent( f"""\ import os os.environ["VIRTUAL_ENV"] = {virtualenv!r} os.environ["PATH"] = os.pathsep.join(( {session.bin!r}, os.environ.get("PATH", ""), )) """ ) lines.insert(1, header) hook.write_text("\n".join(lines)) @session(name="pre-commit", python="3.11") def precommit(session: Session) -> None: """Lint using pre-commit.""" args = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] session.install( "black", "darglint", "flake8", "flake8-bandit", "flake8-bugbear", "flake8-docstrings", "flake8-rst-docstrings", "pep8-naming", "pre-commit", "pre-commit-hooks", "reorder-python-imports", ) session.run("pre-commit", *args) if args and args[0] == "install": activate_virtualenv_in_precommit_hooks(session) @session(python="3.11") def safety(session: Session) -> None: """Scan dependencies for insecure packages.""" requirements = session.poetry.export_requirements() session.install("safety") session.run("safety", "check", "--full-report", f"--file={requirements}") @session(python=python_versions) def mypy(session: Session) -> None: """Type-check using mypy.""" args = session.posargs or ["src", "tests", "docs/conf.py"] session.install(".") session.install("mypy", "pytest", "types-requests", "types-pytz") session.run("mypy", *args) if not session.posargs: session.run("mypy", f"--python-executable={sys.executable}", "noxfile.py") @session(python=python_versions) def tests(session: Session) -> None: """Run the test suite.""" session.install(".") session.install("coverage[toml]", "pytest", "pygments", "requests_mock") try: session.run("coverage", "run", "--parallel", "-m", "pytest", *session.posargs) finally: if session.interactive: session.notify("coverage", posargs=[]) @session def coverage(session: Session) -> None: """Produce the coverage report.""" args = session.posargs or ["report"] session.install("coverage[toml]") if not session.posargs and any(Path().glob(".coverage.*")): session.run("coverage", "combine") session.run("coverage", *args) @session(python=python_versions) def typeguard(session: Session) -> None: """Runtime type checking using Typeguard.""" session.install(".") session.install("pytest", "typeguard", "pygments", "requests_mock") session.run("pytest", f"--typeguard-packages={package}", *session.posargs) @session(python=python_versions) def xdoctest(session: Session) -> None: """Run examples with xdoctest.""" args = session.posargs or ["all"] session.install(".") session.install("xdoctest[colors]") session.run("python", "-m", "xdoctest", package, *args) @session(name="docs-build", python="3.11") def docs_build(session: Session) -> None: """Build the documentation.""" args = session.posargs or ["docs", "docs/_build"] session.install(".") session.install("sphinx", "sphinx-click", "sphinx-rtd-theme") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-build", *args) @session(python="3.11") def docs(session: Session) -> None: """Build and serve the documentation with live reloading on file changes.""" args = session.posargs or ["--open-browser", "docs", "docs/_build"] session.install(".") session.install("sphinx", "sphinx-autobuild", "sphinx-click", "sphinx-rtd-theme") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-autobuild", *args) hacf-fr-meteofrance-api-1d2b3b9/poetry.lock000066400000000000000000003230221453417731100206710ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] [[package]] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "babel" version = "2.13.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "bandit" version = "1.7.5" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.7" files = [ {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] [[package]] name = "black" version = "22.12.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.7" files = [ {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [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." 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 = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] name = "classify-imports" version = "4.2.0" description = "Utilities for refactoring imports in python-like syntax." optional = false python-versions = ">=3.7" files = [ {file = "classify_imports-4.2.0-py2.py3-none-any.whl", hash = "sha256:dbbc264b70a470ed8c6c95976a11dfb8b7f63df44ed1af87328bbed2663f5161"}, {file = "classify_imports-4.2.0.tar.gz", hash = "sha256:7abfb7ea92149b29d046bd34573d247ba6e68cc28100c801eba4af17964fc40e"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" 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." 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 = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." optional = false python-versions = ">=3.6,<4.0" files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, ] [[package]] name = "distlib" version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] [[package]] name = "dparse" version = "0.6.3" description = "A parser for Python dependency files" optional = false python-versions = ">=3.6" files = [ {file = "dparse-0.6.3-py3-none-any.whl", hash = "sha256:0d8fe18714056ca632d98b24fbfc4e9791d4e47065285ab486182288813a5318"}, {file = "dparse-0.6.3.tar.gz", hash = "sha256:27bb8b4bcaefec3997697ba3f6e06b2447200ba273c0b085c3d012a04571b528"}, ] [package.dependencies] packaging = "*" tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] conda = ["pyyaml"] pipenv = ["pipenv (<=2022.12.19)"] [[package]] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.12.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.6.1" files = [ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "flake8-bandit" version = "4.1.1" description = "Automated security testing with bandit and flake8." optional = false python-versions = ">=3.6" files = [ {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, ] [package.dependencies] bandit = ">=1.7.3" flake8 = ">=5.0.0" [[package]] name = "flake8-bugbear" version = "23.3.12" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." optional = false python-versions = ">=3.7" files = [ {file = "flake8-bugbear-23.3.12.tar.gz", hash = "sha256:e3e7f74c8a49ad3794a7183353026dabd68c74030d5f46571f84c1fb0eb79363"}, {file = "flake8_bugbear-23.3.12-py3-none-any.whl", hash = "sha256:beb5c7efcd7ccc2039ef66a77bb8db925e7be3531ff1cb4d0b7030d0e2113d72"}, ] [package.dependencies] attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"] [[package]] name = "flake8-docstrings" version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" optional = false python-versions = ">=3.7" files = [ {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, ] [package.dependencies] flake8 = ">=3" pydocstyle = ">=2.1" [[package]] name = "flake8-rst-docstrings" version = "0.3.0" description = "Python docstring reStructuredText (RST) validator for flake8" optional = false python-versions = ">=3.7" files = [ {file = "flake8-rst-docstrings-0.3.0.tar.gz", hash = "sha256:d1ce22b4bd37b73cd86b8d980e946ef198cfcc18ed82fedb674ceaa2f8d1afa4"}, {file = "flake8_rst_docstrings-0.3.0-py3-none-any.whl", hash = "sha256:f8c3c6892ff402292651c31983a38da082480ad3ba253743de52989bdc84ca1c"}, ] [package.dependencies] flake8 = ">=3" pygments = "*" restructuredtext-lint = "*" [package.extras] develop = ["build", "twine"] [[package]] name = "gitdb" version = "4.0.10" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.40" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] [[package]] name = "identify" version = "2.5.30" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" 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 = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" optional = false python-versions = "*" files = [ {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] [package.dependencies] six = "*" tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" 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 = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "mypy" version = "1.6.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." 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 = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] [package.dependencies] setuptools = "*" [[package]] name = "packaging" version = "21.3" description = "Core utilities for Python packages" optional = false python-versions = ">=3.6" files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." 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 = "pbr" version = "5.11.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" files = [ {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] [[package]] name = "pep8-naming" version = "0.13.3" description = "Check PEP-8 naming conventions, plugin for flake8" optional = false python-versions = ">=3.7" files = [ {file = "pep8-naming-0.13.3.tar.gz", hash = "sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971"}, {file = "pep8_naming-0.13.3-py3-none-any.whl", hash = "sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80"}, ] [package.dependencies] flake8 = ">=5.0.0" [[package]] name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [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 = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "pre-commit-hooks" version = "4.5.0" description = "Some out-of-the-box hooks for pre-commit." optional = false python-versions = ">=3.8" files = [ {file = "pre_commit_hooks-4.5.0-py2.py3-none-any.whl", hash = "sha256:b779d5c44ede9b1fda48e2d96b08e9aa5b1d2fdb8903ca09f0dbaca22d529edb"}, {file = "pre_commit_hooks-4.5.0.tar.gz", hash = "sha256:ffbe2af1c85ac9a7695866955680b4dee98822638b748a6f3debefad79748c8a"}, ] [package.dependencies] "ruamel.yaml" = ">=0.15" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" optional = false python-versions = ">=3.6" files = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] [[package]] name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" optional = false python-versions = ">=3.6" files = [ {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, ] [package.dependencies] snowballstemmer = ">=2.2.0" [package.extras] toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] [[package]] name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "reorder-python-imports" version = "3.12.0" description = "Tool for reordering python imports" optional = false python-versions = ">=3.8" files = [ {file = "reorder_python_imports-3.12.0-py2.py3-none-any.whl", hash = "sha256:930c23a42192b365e20e191a4d304d93e645bd44c242d8bc64accc4a3b2b0f3d"}, {file = "reorder_python_imports-3.12.0.tar.gz", hash = "sha256:f93106a662b0c034ca81c91fd1c2f21a1e94ece47c9f192672e2a13c8ec1856c"}, ] [package.dependencies] classify-imports = ">=4.1" [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." 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 = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" optional = false python-versions = "*" files = [ {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, ] [package.dependencies] requests = ">=2.3,<3" six = "*" [package.extras] fixture = ["fixtures"] test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] [[package]] name = "restructuredtext-lint" version = "1.4.0" description = "reStructuredText linter" optional = false python-versions = "*" files = [ {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, ] [package.dependencies] docutils = ">=0.11,<1.0" [[package]] name = "rich" version = "13.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruamel-yaml" version = "0.17.39" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ {file = "ruamel.yaml-0.17.39-py3-none-any.whl", hash = "sha256:8df81a51384f2e6af73d88bede63ec437df4854a5a74841f40e7622471298457"}, {file = "ruamel.yaml-0.17.39.tar.gz", hash = "sha256:ee9a9178a231306d2b260e67ba497c136d4639d58b08775ab67f5fb0f21e73f0"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} [package.extras] docs = ["mercurial (>5.7)", "ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.8" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] [[package]] name = "safety" version = "2.3.5" description = "Checks installed dependencies for known vulnerabilities and licenses." optional = false python-versions = "*" files = [ {file = "safety-2.3.5-py3-none-any.whl", hash = "sha256:2227fcac1b22b53c1615af78872b48348661691450aa25d6704a5504dbd1f7e2"}, {file = "safety-2.3.5.tar.gz", hash = "sha256:a60c11f8952f412cbb165d70cb1f673a3b43a2ba9a93ce11f97e6a4de834aa3a"}, ] [package.dependencies] Click = ">=8.0.2" dparse = ">=0.6.2" packaging = ">=21.0,<22.0" requests = "*" "ruamel.yaml" = ">=0.17.21" setuptools = ">=19.3" [package.extras] github = ["jinja2 (>=3.1.0)", "pygithub (>=1.43.3)"] gitlab = ["python-gitlab (>=1.3.0)"] [[package]] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [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)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "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] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "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" 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 = "smmap" version = "5.0.1" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" files = [ {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sphinx" version = "7.1.2" description = "Python documentation generator" optional = false python-versions = ">=3.8" files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.13" requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." optional = false python-versions = ">=3.6" files = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, ] [package.dependencies] colorama = "*" livereload = "*" sphinx = "*" [package.extras] test = ["pytest", "pytest-cov"] [[package]] name = "sphinx-click" version = "5.0.1" description = "Sphinx extension that automatically documents click applications" optional = false python-versions = ">=3.8" files = [ {file = "sphinx-click-5.0.1.tar.gz", hash = "sha256:fcc7df15e56e3ff17ebf446cdd316c2eb79580b37c49579fba11e5468802ef25"}, {file = "sphinx_click-5.0.1-py3-none-any.whl", hash = "sha256:31836ca22f746d3c26cbfdfe0c58edf0bca5783731a0b2e25bb6d59800bb75a1"}, ] [package.dependencies] click = ">=7.0" docutils = "*" sphinx = ">=2.0" [[package]] name = "sphinx-rtd-theme" version = "1.3.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] docutils = "<0.19" sphinx = ">=1.6,<8" sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, ] [package.dependencies] Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "stevedore" version = "5.1.0" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.8" files = [ {file = "stevedore-5.1.0-py3-none-any.whl", hash = "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d"}, {file = "stevedore-5.1.0.tar.gz", hash = "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"}, ] [package.dependencies] pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tornado" version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, ] [[package]] name = "typeguard" version = "4.1.5" description = "Run-time type checker for Python" optional = false python-versions = ">=3.8" files = [ {file = "typeguard-4.1.5-py3-none-any.whl", hash = "sha256:8923e55f8873caec136c892c3bed1f676eae7be57cdb94819281b3d3bc9c0953"}, {file = "typeguard-4.1.5.tar.gz", hash = "sha256:ea0a113bbc111bcffc90789ebb215625c963411f7096a7e9062d4e4630c155fd"}, ] [package.dependencies] importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""} [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] [[package]] name = "types-pytz" version = "2023.3.1.1" description = "Typing stubs for pytz" optional = false python-versions = "*" files = [ {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"}, {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"}, ] [[package]] name = "types-requests" version = "2.31.0.6" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" files = [ {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, ] [package.dependencies] types-urllib3 = "*" [[package]] name = "types-urllib3" version = "1.26.25.14" description = "Typing stubs for urllib3" optional = false python-versions = "*" files = [ {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, ] [[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" version = "20.24.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "xdoctest" version = "1.1.2" description = "A rewrite of the builtin doctest module" optional = false python-versions = ">=3.6" files = [ {file = "xdoctest-1.1.2-py3-none-any.whl", hash = "sha256:ebe133222534f09597cbe461f97cc5f95ad7b36e5d31f3437caffb9baaddbddb"}, {file = "xdoctest-1.1.2.tar.gz", hash = "sha256:267d3d4e362547fa917d3deabaf6888232bbf43c8d30298faeb957dbfa7e0ba3"}, ] [package.dependencies] colorama = {version = "*", optional = true, markers = "platform_system == \"Windows\" and extra == \"colors\""} Pygments = {version = "*", optional = true, markers = "python_version >= \"3.5.0\" and extra == \"colors\""} [package.extras] all = ["IPython (>=7.10.0)", "IPython (>=7.23.1)", "Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "attrs (>=19.2.0)", "colorama (>=0.4.1)", "debugpy (>=1.0.0)", "debugpy (>=1.0.0)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=5.2.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=6.1.5)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)", "pyflakes (>=2.2.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "tomli (>=0.2.0)", "typing (>=3.7.4)"] all-strict = ["IPython (==7.10.0)", "IPython (==7.23.1)", "Pygments (==2.0.0)", "Pygments (==2.4.1)", "attrs (==19.2.0)", "colorama (==0.4.1)", "debugpy (==1.0.0)", "debugpy (==1.0.0)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==5.2.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==6.1.5)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)", "pyflakes (==2.2.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "tomli (==0.2.0)", "typing (==3.7.4)"] colors = ["Pygments", "Pygments", "colorama"] jupyter = ["IPython", "IPython", "attrs", "debugpy", "debugpy", "debugpy", "debugpy", "debugpy", "ipykernel", "ipykernel", "ipykernel", "ipython-genutils", "jedi", "jinja2", "jupyter-client", "jupyter-client", "jupyter-core", "nbconvert", "nbconvert"] optional = ["IPython (>=7.10.0)", "IPython (>=7.23.1)", "Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "attrs (>=19.2.0)", "colorama (>=0.4.1)", "debugpy (>=1.0.0)", "debugpy (>=1.0.0)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=5.2.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=6.1.5)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)", "pyflakes (>=2.2.0)", "tomli (>=0.2.0)"] optional-strict = ["IPython (==7.10.0)", "IPython (==7.23.1)", "Pygments (==2.0.0)", "Pygments (==2.4.1)", "attrs (==19.2.0)", "colorama (==0.4.1)", "debugpy (==1.0.0)", "debugpy (==1.0.0)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==5.2.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==6.1.5)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)", "pyflakes (==2.2.0)", "tomli (==0.2.0)"] tests = ["pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "typing (>=3.7.4)"] tests-binary = ["cmake", "cmake", "ninja", "ninja", "pybind11", "pybind11", "scikit-build", "scikit-build"] tests-binary-strict = ["cmake (==3.21.2)", "cmake (==3.25.0)", "ninja (==1.10.2)", "ninja (==1.11.1)", "pybind11 (==2.10.3)", "pybind11 (==2.7.1)", "scikit-build (==0.11.1)", "scikit-build (==0.16.1)"] tests-strict = ["pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "typing (==3.7.4)"] [[package]] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.8.18" content-hash = "cb16b4ce4c842c029da7f03c57b858b9003578eaad0420aaca065d4891553a57" hacf-fr-meteofrance-api-1d2b3b9/pyproject.toml000066400000000000000000000044651453417731100214200ustar00rootroot00000000000000[tool.poetry] name = "meteofrance-api" version = "1.3.0" description = "Python client for Météo-France API." authors = ["oncleben31 ", "quentame ", "HACF "] license = "MIT" readme = "README.rst" homepage = "https://github.com/hacf-fr/meteofrance-api" repository = "https://github.com/hacf-fr/meteofrance-api" documentation = "https://meteofrance-api.readthedocs.io" keywords = ["weather", "weather-api", "meteo"] classifiers = [ "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "License :: OSI Approved :: MIT License", ] [tool.poetry.urls] Changelog = "https://github.com/hacf-fr/meteofrance-api/releases" [tool.poetry.dependencies] python = "^3.8.18" requests = "^2.25.1" urllib3 = "^1.26.18" pytz = ">=2020.4" typing-extensions = {version = ">=3.8.18,<5.0.0", python = "~3.8 || ~3.9 || ~3.10 || ~3.11"} [tool.poetry.dev-dependencies] pytest = "^7.4.3" requests_mock = "^1.9.2" coverage = {extras = ["toml"], version = "^7.2"} safety = "^2.3.5" mypy = "^1.6" typeguard = "^4.1.5" xdoctest = {extras = ["colors"], version = "^1.1.2"} sphinx = "^7.1.0" sphinx-autobuild = "^2021.3.14" pre-commit = "^3.5.0" flake8 = "^5.0.4" black = "^22.12.0" flake8-bandit = "^4.1.1" flake8-bugbear = "^23.3.12" flake8-docstrings = "^1.6.0" flake8-rst-docstrings = "^0.3.0" pep8-naming = "^0.13.3" darglint = "^1.8.1" reorder-python-imports = "^3.9.0" pre-commit-hooks = "^4.4.0" sphinx-rtd-theme = "^1.3.0" sphinx-click = "^5.0.1" Pygments = "^2.15.0" types-requests = "^2.28.11" types-pytz = "^2023.3.0" [tool.poetry.scripts] meteofrance-api = "meteofrance_api.__main__:main" [tool.coverage.paths] source = ["src", "*/site-packages"] [tool.coverage.run] branch = true source = ["meteofrance_api"] [tool.coverage.report] show_missing = true # fail_under = 100 reduce targetwaiting for tests creation fail_under = 90 [tool.mypy] strict = true pretty = true show_column_numbers = true show_error_codes = true show_error_context = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" hacf-fr-meteofrance-api-1d2b3b9/src/000077500000000000000000000000001453417731100172625ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/000077500000000000000000000000001453417731100224035ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/__init__.py000066400000000000000000000001411453417731100245100ustar00rootroot00000000000000"""Météo-France API.""" from .client import MeteoFranceClient __all__ = ["MeteoFranceClient"] hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/__main__.py000066400000000000000000000000631453417731100244740ustar00rootroot00000000000000"""CLI usage of the API.""" # TODO: to be complete hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/client.py000066400000000000000000000323101453417731100242320ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Client for the Météo-France REST API.""" from typing import List from typing import Optional from .const import COASTAL_DEPARTMENT_LIST from .const import METEOFRANCE_API_TOKEN from .const import METEOFRANCE_API_URL from .model import CurrentPhenomenons from .model import Forecast from .model import Full from .model import Observation from .model import PictureOfTheDay from .model import Place from .model import Rain from .model import WarningDictionary from .session import MeteoFranceSession # TODO: investigate bulletincote, montagne, etc... # http://ws.meteofrance.com/ws//getDetail/france/330630.json # TODO: add protection for warning if domain not valid # TODO: strategy for HTTP errors # TODO: next rain in minute. Necessary ? # TODO: forecast/metadata from ID to get gps ? class MeteoFranceClient: """Proxy to the Météo-France REST API. You will find methods and helpers to request weather forecast, rain forecast and weather alert bulletin. """ def __init__(self, access_token: Optional[str] = None) -> None: """Initialize the API and store the auth so we can make requests. Args: access_token: a string containing the authentication token for the REST API. """ self.session = MeteoFranceSession(access_token) # # Place # def search_places( self, search_query: str, latitude: Optional[str] = None, longitude: Optional[str] = None, ) -> List[Place]: """Search the places (cities) linked to a query by name. You can add GPS coordinates in parameter to search places arround a given location. Args: search_query: A complete name, only a part of a name or a postal code (for France only) corresponding to a city in the world. latitude: Optional; Latitude in degree of a reference point to order results. The nearest places first. longitude: Optional; Longitude in degree of a reference point to order results. The nearest places first. Returns: A list of places (Place instance) corresponding to the query. """ # Construct the list of the GET parameters params = {"q": search_query} if latitude is not None: params["lat"] = latitude if longitude is not None: params["lon"] = longitude # Send the API resuest resp = self.session.request("get", "places", params=params) return [Place(place_data) for place_data in resp.json()] # # Observation # def get_observation( self, latitude: float, longitude: float, language: str = "fr", ) -> Observation: """Retrieve the weather observation for a given GPS location. Results can be fetched in french or english according to the language parameter. Args: latitude: Latitude in degree of the GPS point corresponding to the weather forecast. longitude: Longitude in degree of the GPS point corresponding to the weather forecast. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: An Observation instance. """ resp = self.session.request( "get", "v2/observation", params={"lat": latitude, "lon": longitude, "lang": language}, ) return Observation(resp.json()) def get_observation_for_place( self, place: Place, language: str = "fr", ) -> Observation: """Retrieve the weather observation for a given Place instance. Results can be fetched in french or english according to the language parameter. Args: place: Place class instance corresponding to a location. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: An Observation intance. """ return self.get_observation(place.latitude, place.longitude, language) # # Forecast # def get_forecast( self, latitude: float, longitude: float, language: str = "fr", ) -> Forecast: """Retrieve the weather forecast for a given GPS location. Results can be fetched in french or english according to the language parameter. Args: latitude: Latitude in degree of the GPS point corresponding to the weather forecast. longitude: Longitude in degree of the GPS point corresponding to the weather forecast. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: A Forecast intance representing the hourly and daily weather forecast. """ # TODO: add possibility to request forecast from id # Send the API request resp = self.session.request( "get", "forecast", params={"lat": latitude, "lon": longitude, "lang": language}, ) return Forecast(resp.json()) def get_forecast_for_place( self, place: Place, language: str = "fr", ) -> Forecast: """Retrieve the weather forecast for a given Place instance. Results can be fetched in french or english according to the language parameter. Args: place: Place class instance corresponding to a location. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: A Forecast intance representing the hourly and daily weather forecast. """ return self.get_forecast(place.latitude, place.longitude, language) # # Rain # def get_rain(self, latitude: float, longitude: float, language: str = "fr") -> Rain: """Retrieve the next 1 hour rain forecast for a given GPS the location. Results can be fetched in french or english according to the language parameter. Args: latitude: Latitude in degree of the GPS point corresponding to the rain forecast. longitude: Longitude in degree of the GPS point corresponding to the rain forecast. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: A Rain instance representing the next hour rain forecast. """ # TODO: add protection if no rain forecast for this position # Send the API request resp = self.session.request( "get", "rain", params={"lat": latitude, "lon": longitude, "lang": language} ) return Rain(resp.json()) # # Warning # def get_warning_current_phenomenoms( self, domain: str, depth: int = 0, with_coastal_bulletin: bool = False ) -> CurrentPhenomenons: """Return the current weather phenomenoms (or alerts) for a given domain. Args: domain: could be `france` or any metropolitan France department numbers on two digits. For some departments you can access an additional bulletin for coastal phenomenoms. To access it add `10` after the domain id (example: `1310`). depth: Optional; To be used with domain = 'france'. With depth = 0 the results will show only natinal sum up of the weather alerts. If depth = 1, you will have in addition, the bulletin for all metropolitan France department and Andorre with_coastal_bulletin: Optional; If set to True (default is False), you can get the basic bulletin and coastal bulletin merged. Returns: A warning.CurrentPhenomenons instance representing the weather alert bulletin. """ # Send the API request resp = self.session.request( "get", "v3/warning/currentphenomenons", params={"domain": domain, "depth": depth}, ) # Create object with API response phenomenoms = CurrentPhenomenons(resp.json()) # if user ask to have the coastal bulletin merged if with_coastal_bulletin: if domain in COASTAL_DEPARTMENT_LIST: resp = self.session.request( "get", "v3/warning/currentphenomenons", params={"domain": domain + "10"}, ) phenomenoms.merge_with_coastal_phenomenons( CurrentPhenomenons(resp.json()) ) return phenomenoms def get_warning_full( self, domain: str, with_coastal_bulletin: bool = False ) -> Full: """Retrieve a complete bulletin of the weather phenomenons for a given domain. For a given domain we can access the maximum alert, a timelaps of the alert evolution for the next 24 hours, a list of alerts and other metadatas. Args: domain: could be `france` or any metropolitan France department numbers on two digits. For some departments you can access an additional bulletin for coastal phenomenoms. To access it add `10` after the domain id (example: `1310`). with_coastal_bulletin: Optional; If set to True (default is False), you can get the basic bulletin and coastal bulletin merged. Returns: A warning.Full instance representing the complete weather alert bulletin. """ # TODO: add formatDate parameter # Send the API request resp = self.session.request( "get", "/v3/warning/full", params={"domain": domain} ) # Create object with API response full_phenomenoms = Full(resp.json()) # if user ask to have the coastal bulletin merged if with_coastal_bulletin: if domain in COASTAL_DEPARTMENT_LIST: resp = self.session.request( "get", "v3/warning/full", params={"domain": domain + "10"}, ) full_phenomenoms.merge_with_coastal_phenomenons(Full(resp.json())) return full_phenomenoms def get_warning_thumbnail(self, domain: str = "france") -> str: """Retrieve the thumbnail URL of the weather phenomenoms or alerts map. Args: domain: could be `france` or any metropolitan France department numbers on two digits. Returns: The URL of the thumbnail representing the weather alert status. """ # Return directly the URL of the gif image return ( f"{METEOFRANCE_API_URL}/v3/warning/thumbnail?&token={METEOFRANCE_API_TOKEN}" f"&domain={domain}" ) def get_warning_dictionary(self, language: str = "fr") -> WarningDictionary: """Retrieves the meteorological dictionary from the Météo-France API. This dictionary includes information about various meteorological phenomena and color codes used for weather warnings. Args: language (str): The language in which to retrieve the dictionary data. Default is 'fr' for French. Other language codes can be used if supported by the API. Returns: WarningDictionary: An object containing structured data about meteorological phenomena and warning color codes. It has two main attributes: 'phenomenons' (list of PhenomenonDictionaryEntry) and 'colors' (list of ColorDictionaryEntry). """ resp = self.session.request( "get", "v3/warning/dictionary", params={"lang": language} ) dictionary = WarningDictionary(resp.json()) return dictionary # # Picture of the day # def get_picture_of_the_day(self, domain: str = "france") -> PictureOfTheDay: """Retrieve the picture of the day image URL & description. Args: domain: could be `france` Returns: PictureOfTheDay instance with the URL and the description of the picture of the day. """ # Send the API request # TODO: check if other value of domain are usable resp = self.session.request( "get", "v2/report", params={ "domain": domain, "report_type": "observation", "report_subtype": "image du jour", "format": "txt", }, ) image_url = ( f"{METEOFRANCE_API_URL}/v2/report" f"?domain={domain}" f"&report_type=observation&report_subtype=image%20du%20jour&format=jpg" f"&token={METEOFRANCE_API_TOKEN}" ) return PictureOfTheDay({"image_url": image_url, "description": resp.text}) hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/const.py000066400000000000000000000066731453417731100241170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Consts for Météo-France weather forecast python API.""" METEOFRANCE_API_URL = "https://webservice.meteofrance.com" METEOFRANCE_API_TOKEN = "__Wj7dVSTjV9YGu1guveLyDq0g7S7TfTjaHBTPTpO0kj8__" # noqa: S105 # TODO: # new token ? eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzRjdFOTVGOS04QjIxLTQwMDctOTFCQi0wQ0M3QjlBNTQxQzQiLCJjbGFzcyI6Im1vYmlsZSIsImlhdCI6MTYzNDg0NjM1Mi40NzU0MTE5fQ.F02c4y95-HqMsMbQeJ5Cx-qQ4LDJgaYwl47YsNvJM0E # noqa: B950 # new api v2 # https://webservice.meteofrance.com/v2/report?domain=france&report_type=observation&report_subtype=image%20du%20jour&format=jpg&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzRjdFOTVGOS04QjIxLTQwMDctOTFCQi0wQ0M3QjlBNTQxQzQiLCJjbGFzcyI6Im1vYmlsZSIsImlhdCI6MTYzNDg0NjM1Mi40NzU0MTE5fQ.F02c4y95-HqMsMbQeJ5Cx-qQ4LDJgaYwl47YsNvJM0E # noqa: B950 # http://webservice.meteofrance.com/v2/report?domain=france&report_type=observation&report_subtype=image%20du%20jour&format=txt&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzRjdFOTVGOS04QjIxLTQwMDctOTFCQi0wQ0M3QjlBNTQxQzQiLCJjbGFzcyI6Im1vYmlsZSIsImlhd # noqa: B950 # http://webservice.meteofrance.com/v2/ephemeris?lat=43.566079&lon=1.057237&lang=fr&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzRjdFOTVGOS04QjIxLTQwMDctOTFCQi0wQ0M3QjlBNTQxQzQiLCJjbGFzcyI6Im1vYmlsZSIsImlhdCI6MTYzNDg0NjM1Mi40NzU0 # noqa: B950 # enums used in all Warning classes. First indice is 0 # Weather alert criticity ALERT_COLOR_LIST_FR = [None, "Vert", "Jaune", "Orange", "Rouge"] ALERT_COLOR_LIST_EN = [None, "Green", "Yellow", "Orange", "Red"] # Weather alert type ALERT_TYPE_DICTIONARY_FR = { "0": None, "1": "Vent violent", "2": "Pluie-inondation", "3": "Orages", "4": "Inondation", "5": "Neige-verglas", "6": "Canicule", "7": "Grand-froid", "8": "Avalanches", "9": "Vagues-submersion", } ALERT_TYPE_DICTIONARY_EN = { "0": None, "1": "Wind", "2": "Rain-Flood", "3": "Thunderstorms", "4": "Flood", "5": "Snow/Ice", "6": "Extreme high temperature", "7": "Extreme low temperature", "8": "Avalanches", "9": "Coastal Event", } # Valide departments list for weather alert bulletin VALID_DEPARTMENT_LIST = [ "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "99", ] # Area code list for Coastal Departments COASTAL_DEPARTMENT_LIST = [ "06", "11", "13", "14", "17", "22", "29", "2A", "2B", "30", "33", "34", "35", "40", "44", "50", "56", "59", "62", "64", "66", "76", "80", "83", "85", ] hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/exceptions.py000066400000000000000000000002211453417731100251310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Météo-France API module exceptions.""" class MeteoFranceError(Exception): """Generic Météo-France Error.""" hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/helpers.py000066400000000000000000000134451453417731100244260ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Helpers to be used with the Météo-France REST API .""" import math from datetime import datetime from typing import Dict from typing import List from typing import Optional from typing import Tuple from pytz import timezone from pytz import utc from .const import ALERT_COLOR_LIST_EN from .const import ALERT_COLOR_LIST_FR from .const import ALERT_TYPE_DICTIONARY_EN from .const import ALERT_TYPE_DICTIONARY_FR from .const import COASTAL_DEPARTMENT_LIST from .const import VALID_DEPARTMENT_LIST from .model.place import Place from .model.warning import PhenomenonMaxColor def get_warning_text_status_from_indice_color( int_color: int, lang: str = "fr" ) -> Optional[str]: """Convert the color code (in int) in readable text (Helper). Args: int_color: Color status in int. Value expected between 1 and 4. lang: Optional; If language is equal 'fr' (default value) results will be in French. All other value will give results in English. Returns: Color status in text. French or English according to the lang parameter. """ if lang == "fr": return ALERT_COLOR_LIST_FR[int_color] return ALERT_COLOR_LIST_EN[int_color] def get_phenomenon_name_from_indice( int_phenomenon: str, lang: str = "fr" ) -> Optional[str]: """Convert the phenomenom code in readable text (Hepler). Args: int_phenomenon: ID of the phenomenom in int. Value expected between 1 and 9. lang: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: Phenomenom in text. French or English according to the lang parameter. """ if lang == "fr": return ALERT_TYPE_DICTIONARY_FR[int_phenomenon] return ALERT_TYPE_DICTIONARY_EN[int_phenomenon] def is_coastal_department(department_number: str) -> bool: """Identify when a second bulletin is availabe for coastal risks (Helper). Args: department_number: Department number on 2 characters Returns: True if the department have an additional coastal bulletin. False otherwise. """ return department_number in COASTAL_DEPARTMENT_LIST def is_valid_warning_department(department_number: str) -> bool: """Identify if there is a weather alert bulletin for this department (Helper). Weather alert buletins are available only for metropolitan France and Andorre. Args: department_number: Department number on 2 characters. Returns: True if a department is metropolitan France or Andorre. """ return department_number in VALID_DEPARTMENT_LIST def readeable_phenomenoms_dict( list_phenomenoms: List[PhenomenonMaxColor], language: str = "fr" ) -> Dict[Optional[str], Optional[str]]: """Create a dictionary with human readable keys and values (Helper). Args: list_phenomenoms: Dictionary with phenomenon ID and color code of status. language: Optional; If language is equal "fr" (default value) results will be in French. All other value will give results in English. Returns: Dictionary with keys and value human readable. """ # Init empty dictionary readable_dict = {} # Translate phenomenom name and alert level for phenomenom in list_phenomenoms: readable_dict[ get_phenomenon_name_from_indice(phenomenom["phenomenon_id"], language) ] = get_warning_text_status_from_indice_color( phenomenom["phenomenon_max_color_id"], language ) return readable_dict def haversine(coord1: Tuple[float, float], coord2: Tuple[float, float]) -> float: """Compute distance in meters between to GPS coordinates using Harvesine formula. source: https://janakiev.com/blog/gps-points-distance-python/ Args: coord1: Tuple with latitude and longitude in degrees for first point coord2: Tuple with latitude and longitude in degrees for second point Returns: Distance in meters between the two points """ radius = 6372800 # Earth radius in meters lat1, lon1 = coord1 lat2, lon2 = coord2 phi1, phi2 = math.radians(lat1), math.radians(lat2) dphi = math.radians(lat2 - lat1) dlambda = math.radians(lon2 - lon1) a = ( math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2) ** 2 ) return 2 * radius * math.atan2(math.sqrt(a), math.sqrt(1 - a)) def sort_places_versus_distance_from_coordinates( list_places: List[Place], gps_coord: Tuple[float, float] ) -> List[Place]: """Oder list of places according to the distance to a reference coordinates. Note: this helper is compensating the bad results of the API. Results in the API are generally sorted, but lot of cases identified where the order is inconsistent (example: Montréal) Args: list_places: List of Place instances to be ordered gps_coord: Tuple with latitude and longitude in degrees for the reference point Returns: List of Place instances ordered by distance to the reference point (nearest first) """ sorted_places = sorted( list_places, key=lambda x: haversine((float(x.latitude), float(x.longitude)), gps_coord), ) return sorted_places def timestamp_to_dateime_with_locale_tz(timestamp: int, local_tz: str) -> datetime: """Convert timestamp in datetime (Helper). Args: timestamp: Timestamp. local_tz: Name of the timezone to be used to convert the timestamp. Returns: Datetime instance corresponding to the timestamp with a timezone. """ # convert timestamp in datetime with UTC timezone dt_utc = utc.localize(datetime.utcfromtimestamp(timestamp)) # convert datetime to local timezone return dt_utc.astimezone(timezone(local_tz)) hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/000077500000000000000000000000001453417731100235035ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/__init__.py000066400000000000000000000007401453417731100256150ustar00rootroot00000000000000"""Météo-France models for the REST API.""" from .dictionary import WarningDictionary from .forecast import Forecast from .observation import Observation from .picture_of_the_day import PictureOfTheDay from .place import Place from .rain import Rain from .warning import CurrentPhenomenons from .warning import Full __all__ = [ "Forecast", "Observation", "Place", "PictureOfTheDay", "Rain", "CurrentPhenomenons", "Full", "WarningDictionary", ] hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/dictionary.py000066400000000000000000000073351453417731100262320ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Dictionary Python model for the Météo-France REST API.""" from typing import List from typing import Optional from typing import TypedDict class PhenomenonDictionaryEntry(TypedDict): """Represents a single meteorological phenomenon entry. Attributes: id: An integer representing the unique identifier of the phenomenon. name: A string representing the name of the phenomenon. """ id: int name: str class ColorDictionaryEntry(TypedDict): """Represents a single color entry used in meteorological warnings. Attributes: id: An integer representing the unique identifier of the color. level: An integer representing the severity level associated with the color. name: A string representing the name of the color. hexaCode: A string representing the hexadecimal code of the color. """ id: int level: int name: str hexaCode: str # noqa: N815 class WarningDictionaryData(TypedDict): """Structured data representing the meteorological dictionary. Attributes: phenomenons: A list of PhenomenonDictionaryEntry instances. colors: A list of ColorDictionaryEntry instances. """ phenomenons: List[PhenomenonDictionaryEntry] colors: List[ColorDictionaryEntry] class WarningDictionary: """A class to represent and manipulate the Meteo France meteorological dictionary data. Methods: get_phenomenon_name_by_id(phenomenon_id: int): Returns the name of the phenomenon for the given ID. get_color_name_by_id(color_id: int): Returns the name of the color for the given ID. """ def __init__(self, raw_data: WarningDictionaryData) -> None: """Initializes the WarningDictionary with raw dictionary data. Args: raw_data: A dictionary representing the JSON response from the Meteo France API. """ self.raw_data = raw_data def get_phenomenon_by_id( self, phenomenon_id: int ) -> Optional[PhenomenonDictionaryEntry]: """Retrieves a meteorological phenomenon based on its ID. Args: phenomenon_id: The ID of the meteorological phenomenon. Returns: The phenomenon if found, otherwise returns None. """ for phenomenon in self.raw_data["phenomenons"]: if phenomenon["id"] == phenomenon_id: return phenomenon return None def get_phenomenon_name_by_id(self, phenomenon_id: int) -> Optional[str]: """Retrieves the name of a meteorological phenomenon based on its ID. Args: phenomenon_id: The ID of the meteorological phenomenon. Returns: The name of the phenomenon if found, otherwise returns None. """ phenomenon = self.get_phenomenon_by_id(phenomenon_id) if phenomenon is not None: return phenomenon["name"] return None def get_color_by_id(self, color_id: int) -> Optional[ColorDictionaryEntry]: """Retrieves a warning color based on its ID. Args: color_id: The ID of the color. Returns: The the color object if found, otherwise returns None. """ for color in self.raw_data["colors"]: if color["id"] == color_id: return color return None def get_color_name_by_id(self, color_id: int) -> Optional[str]: """Retrieves the name of a warning color based on its ID. Args: color_id: The ID of the color. Returns: The name of the color if found, otherwise returns None. """ color = self.get_color_by_id(color_id) if color is not None: return color["name"] return None hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/forecast.py000066400000000000000000000107121453417731100256640ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Weather forecast Python model for the Météo-France REST API.""" from datetime import datetime from typing import Any from typing import Dict from typing import List from typing import TypedDict from pytz import utc from meteofrance_api.helpers import timestamp_to_dateime_with_locale_tz class ForecastData(TypedDict, total=False): """Describing the data structure of the forecast object returned by the REST API.""" position: Dict[str, Any] updated_on: int daily_forecast: List[Dict[str, Any]] forecast: List[Dict[str, Any]] probability_forecast: List[Dict[str, Any]] class Forecast: """Class to access the results of a `forecast` API request. Attributes: position: A dictionary with metadata about the position of the forecast place. updated_on: A timestamp as int corresponding to the latest update date. daily_forecast: A list of dictionaries to describe the daily forecast for the next 15 days. forecast: A list of dictionaries to describe the hourly forecast for the next days. probability_forecast: A list of dictionaries to describe the event probability forecast (rain, snow, freezing) for next 10 days. today_forecast: A dictionary corresponding to the daily forecast for the current day. nearest_forecast: A dictionary corresponding to the nearest hourly forecast. current_forecast: A dictionary corresponding to the hourly forecast for the current hour. """ def __init__(self, raw_data: ForecastData) -> None: """Initialize a Forecast object. Args: raw_data: A dictionary representing the JSON response from 'forecast' REST API request. The structure is described by the ForecastData class. """ self.raw_data = raw_data @property def position(self) -> Dict[str, Any]: """Return the position information of the forecast.""" return self.raw_data["position"] @property def updated_on(self) -> int: """Return the update timestamp of the forecast.""" return self.raw_data["updated_on"] @property def daily_forecast(self) -> List[Dict[str, Any]]: """Return the daily forecast for the following days.""" return self.raw_data["daily_forecast"] @property def forecast(self) -> List[Dict[str, Any]]: """Return the hourly forecast.""" return self.raw_data["forecast"] @property def probability_forecast(self) -> List[Dict[str, Any]]: """Return the wheather event forecast.""" return self.raw_data.get("probability_forecast", []) @property def today_forecast(self) -> Dict[str, Any]: """Return the forecast for today.""" return self.daily_forecast[0] @property def nearest_forecast(self) -> Dict[str, Any]: """Return the nearest hourly forecast.""" # get timestamp for current time now_timestamp = int(utc.localize(datetime.utcnow()).timestamp()) # sort list of forecast by distance between current timestamp and # forecast timestamp sorted_forecast = sorted( self.forecast, key=lambda x: abs(x["dt"] - now_timestamp), ) return sorted_forecast[0] @property def current_forecast(self) -> Dict[str, Any]: """Return the forecast of the current hour.""" # Get the timestamp for the current hour. current_hour_timestamp = int( utc.localize( datetime.utcnow().replace(minute=0, second=0, microsecond=0) ).timestamp() ) # create a dict using timestamp as keys forecast_by_datetime = {item["dt"]: item for item in self.forecast} # Return the forecast corresponding to the timestamp of the current hour if # exists. If not exists, returns the nearest forecast (not France countries) return forecast_by_datetime.get(current_hour_timestamp, self.nearest_forecast) def timestamp_to_locale_time(self, timestamp: int) -> datetime: """Convert timestamp in datetime in the forecast location timezone (Helper). Args: timestamp: An integer to describe the UNIX timestamp. Returns: Datetime instance corresponding to the timestamp with the timezone of the forecast location. """ return timestamp_to_dateime_with_locale_tz(timestamp, self.position["timezone"]) hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/observation.py000066400000000000000000000104021453417731100264050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Weather observation Python model for the Météo-France REST API.""" from datetime import datetime from typing import Any from typing import Dict from typing import Optional from typing import TypedDict class ObservationDataPropertiesGridded(TypedDict, total=False): """Data structure of the observation gridded properties object from the REST API.""" time: str T: float wind_speed: float wind_direction: int wind_icon: str weather_icon: str weather_description: str class ObservationDataProperties(TypedDict, total=False): """Data structure of the observation properties object from the REST API.""" timezone: str gridded: ObservationDataPropertiesGridded class ObservationData(TypedDict, total=False): """Data structure of the observation object from the REST API.""" update_time: str geometry: Dict[str, Any] type: str properties: ObservationDataProperties class Observation: """Class to access the results of an `observation` API request. Attributes: timezone: The observation timezone time: The time at which the observation was made temperature: The observed temperature (°C) wind_speed: The observed wind speed (km/h) wind_direction: The observed wind direction (°) wind_icon: An icon ID illustrating the observed wind direction weather_icon: An icon ID illustrating the observed weather condition weather_description: A description of the observed weather condition """ def __init__(self, raw_data: ObservationData) -> None: """Initialize an Observation object. Args: raw_data: A dictionary representing the JSON response from 'observation' REST API request. The structure is described by the ObservationData class. """ self.properties = raw_data.get("properties", {}) @property def timezone(self) -> Optional[str]: """Returns the observation timezone.""" return self.properties.get("timezone") @property def _gridded(self) -> ObservationDataPropertiesGridded: """Returns the observation gridded properties.""" return self.properties.get("gridded", {}) @property def time_as_string(self) -> Optional[str]: """Returns the time at which the observation was made.""" return self._gridded.get("time") @property def time_as_datetime(self) -> Optional[datetime]: """Returns the time at which the observation was made.""" time = self.time_as_string return ( None if time is None else datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%f%z") ) @property def temperature(self) -> Optional[float]: """Returns the observed temp (°C).""" return self._gridded.get("T") @property def wind_speed(self) -> Optional[float]: """Returns the observed wind speed (km/h).""" return self._gridded.get("wind_speed") @property def wind_direction(self) -> Optional[int]: """Returns the observed wind direction (°).""" return self._gridded.get("wind_direction") @property def wind_icon(self) -> Optional[str]: """Returns an icon ID illustrating the observed wind direction.""" return self._gridded.get("wind_icon") @property def weather_icon(self) -> Optional[str]: """Returns an icon ID illustrating the observed weather condition.""" return self._gridded.get("weather_icon") @property def weather_description(self) -> Optional[str]: """Returns a description of the observed weather condition.""" return self._gridded.get("weather_description") def __repr__(self) -> str: """Returns a stringified version of the object.""" return ( f"Observation(" f" timezone={self.timezone}," f" time={self.time_as_string}," f" temperature={self.temperature}°C," f" wind_speed={self.wind_speed} km/h," f" wind_direction={self.wind_direction}°," f" wind_icon={self.wind_icon}," f" weather_icon={self.weather_icon}," f" weather_description={self.weather_description}" ")" ) hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/picture_of_the_day.py000066400000000000000000000025051453417731100277130ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Picture of the Day Python model for the Météo-France REST API.""" from typing import TypedDict class PictureOfTheDayData(TypedDict): """Describing the data structure of ImageJour object.""" image_url: str # noqa: N815 description: str class PictureOfTheDay: """Class to access the results of a `ImageJour/last` REST API request. Attributes: image_url: A string corresponding to the picture of the day URL. image_hd_url: A string corresponding to the URL for the HD version of the picture of the day. descritpion: A string with the description of the picture of the day. """ def __init__(self, raw_data: PictureOfTheDayData) -> None: """Initialize a PictureOfTheDay object. Args: raw_data: A dictionary representing the JSON response from 'ImageJour/last' REST API request. The structure is described by the PictureOfTheDayData class. """ self.raw_data = raw_data @property def image_url(self) -> str: """Return the image URL of the picture of the day.""" return self.raw_data["image_url"] @property def description(self) -> str: """Return the description of the picture of the day.""" return self.raw_data["description"] hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/place.py000066400000000000000000000070341453417731100251450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Place Python model for the Météo-France REST API.""" from typing import Optional from typing import TypedDict class PlaceData(TypedDict): """Describing the data structure of place object returned by the REST API.""" insee: Optional[str] name: str lat: float lon: float country: str admin: str admin2: Optional[str] postCode: Optional[str] # noqa: N815 class Place: """Class to access the results of 'places' REST API request. Attributes: insee: A string corresponding to the INSEE ID of the place. name: Name of the place. lat: A float with the latitude in degree of the place. lon: A float with the longitude in degree of the place country: A string corresponding to the country code of the place. admin: A string with the name of the administrative area ('Département' for France and Region for other countries). admin2: A string correponding to an administrative code ( 'Département' number for France) postCode: A string corresponding to the ZIP code of location. """ def __init__(self, raw_data: PlaceData) -> None: """Initialize a Place object. Args: raw_data: A dictionary representing the JSON response from 'places' REST API request. The structure is described by the PlaceData class. """ self.raw_data = raw_data def __repr__(self) -> str: """Return string representation of this class. Returns: A string to represent the instance of the Place class using the name, country and admin area of the location. Example: """ return "<{}(name={}, country={}, admin={})>".format( self.__class__.__name__, self.name, self.country, self.admin ) def __str__(self) -> str: """Provide an easy way to identify the Place. Returns: A string to represent a Place instance with city name, Region name, department ID and the country name. For Examples: `Marseille - Provence-Alpes-Côte d'Azur (13) - FR` or `Montréal - Quebec - CA` """ if self.country == "FR": return f"{self.name} - {self.admin} ({self.admin2}) - {self.country}" return f"{self.name} - {self.admin} - {self.country}" @property def insee(self) -> Optional[str]: """Return the INSEE ID of the place.""" return self.raw_data.get("insee") @property def name(self) -> str: """Return the name of the place.""" return self.raw_data["name"] @property def latitude(self) -> float: """Return the latitude of the place.""" return self.raw_data["lat"] @property def longitude(self) -> float: """Return the longitude of the place.""" return self.raw_data["lon"] @property def country(self) -> str: """Return the country code of the place.""" return self.raw_data["country"] @property def admin(self) -> Optional[str]: """Return the admin of the place.""" return self.raw_data.get("admin") @property def admin2(self) -> Optional[str]: """Return the admin2 of the place.""" return self.raw_data.get("admin2") @property def postal_code(self) -> Optional[str]: """Return the postal code of the place.""" return self.raw_data.get("postCode") hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/rain.py000066400000000000000000000065551453417731100250210ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Rain in the next hour Python model for the Météo-France REST API.""" from datetime import datetime from typing import Any from typing import Dict from typing import List from typing import Optional from typing import TypedDict from meteofrance_api.helpers import timestamp_to_dateime_with_locale_tz class RainData(TypedDict): """Describing the data structure of rain object returned by the REST API.""" position: Dict[str, Any] updated_on: int forecast: List[Dict[str, Any]] quality: int class Rain: """Class to access the results of 'rain' REST API request. Attributes: position: A dictionary with metadata about the position of the forecast place. updated_on: A timestamp as int corresponding to the latest update date. forecast: A list of dictionaries to describe the following next hour rain forecast. quality: An integer. Don't know yet the usage. """ def __init__(self, raw_data: RainData) -> None: """Initialize a Rain object. Args: raw_data: A dictionary representing the JSON response from 'rain' REST API request. The structure is described by the RainData class. """ self.raw_data = raw_data @property def position(self) -> Dict[str, Any]: """Return the position information of the rain forecast.""" return self.raw_data["position"] @property def updated_on(self) -> int: """Return the update timestamp of the rain forecast.""" return self.raw_data["updated_on"] @property def forecast(self) -> List[Dict[str, Any]]: """Return the rain forecast.""" return self.raw_data["forecast"] @property def quality(self) -> int: """Return the quality of the rain forecast.""" # TODO: don't know yet what is the usage return self.raw_data["quality"] def next_rain_date_locale(self) -> Optional[datetime]: """Estimate the date of the next rain in the Place timezone (Helper). Returns: A datetime instance representing the date estimation of the next rain within the next hour. If no rain is expected in the following hour 'None' is returned. The datetime use the location timezone. """ # search first cadran with rain next_rain = next( (cadran for cadran in self.forecast if cadran["rain"] > 1), None ) next_rain_dt_local: Optional[datetime] = None if next_rain is not None: # get the time stamp of the first cadran with rain next_rain_timestamp = next_rain["dt"] # convert timestamp in datetime with local timezone next_rain_dt_local = timestamp_to_dateime_with_locale_tz( next_rain_timestamp, self.position["timezone"] ) return next_rain_dt_local def timestamp_to_locale_time(self, timestamp: int) -> datetime: """Convert timestamp in datetime with rain forecast location timezone (Helper). Args: timestamp: An integer representing the UNIX timestamp. Returns: A datetime instance corresponding to the timestamp with the timezone of the rain forecast location. """ return timestamp_to_dateime_with_locale_tz(timestamp, self.position["timezone"]) hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/model/warning.py000066400000000000000000000201131453417731100255170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Weather alert buletin Python model for the Météo-France REST API. For getting weather alerts in France Metropole and Andorre. """ from typing import Any from typing import Dict from typing import List from typing import Optional from typing import TypedDict # Define a custom type for items in the phenomenons_max_colors list class PhenomenonMaxColor(TypedDict): """A type definition for representing a meteorological phenomenon and its maximum color code. Attributes: phenomenon_id (str): A unique identifier for the meteorological phenomenon. This is kept as a string to match the format provided by the API and could represent various types of weather phenomena (e.g., storms, heavy rain, etc.). phenomenon_max_color_id (int): An integer representing the maximum alert color code associated with the phenomenon. The color codes typically indicate the severity or urgency of the weather-related alerts or warnings, with each color corresponding to a specific level of alert. """ phenomenon_id: str phenomenon_max_color_id: int class WarnningCurrentPhenomenonsData(TypedDict): """Describing the data structure of CurrentPhenomenons object from the REST API.""" update_time: int end_validity_time: int domain_id: str phenomenons_max_colors: List[PhenomenonMaxColor] class WarnningFullData(TypedDict): """Describing the data structure of full object from the REST API.""" update_time: int end_validity_time: int domain_id: str color_max: int timelaps: List[Dict[str, Any]] phenomenons_items: List[PhenomenonMaxColor] advices: Optional[List[Dict[str, Any]]] consequences: Optional[List[Dict[str, Any]]] max_count_items: Any # Didn't see any value yet comments: Dict[str, Any] text: Optional[Dict[str, Any]] text_avalanche: Any # Didn't see any value yet class CurrentPhenomenons: """Class to access the results of a `warning/currentPhenomenons` REST API request. For coastal department two bulletins are avalaible corresponding to two different domains. Attributes: update_time: A timestamp (as integer) corresponding to the latest update of the pheomenoms. end_validity_time: A timestamp (as integer) corresponding to expiration date of the phenomenoms. domain_id: A string corresponding do the domain ID of the bulletin. Value is 'France' or a department number. phenomenons_max_colors: A list of dictionnaries with type of phenomenoms and the current alert level. """ def __init__(self, raw_data: WarnningCurrentPhenomenonsData) -> None: """Initialize a CurrentPhenomenons object. Args: raw_data: A dictionary representing the JSON response from 'warning/currentPhenomenons' REST API request. The structure is described by the WarnningCurrentPhenomenonsData class. """ self.raw_data = raw_data @property def update_time(self) -> int: """Return the update time of the phenomenoms.""" return self.raw_data["update_time"] @property def end_validity_time(self) -> int: """Return the end of validty time of the phenomenoms.""" return self.raw_data["end_validity_time"] @property def domain_id(self) -> str: """Return the domain ID of the phenomenoms.""" return self.raw_data["domain_id"] @property def phenomenons_max_colors(self) -> List[PhenomenonMaxColor]: """Return the list and colors of the phenomenoms.""" return self.raw_data["phenomenons_max_colors"] def merge_with_coastal_phenomenons( self, coastal_phenomenoms: "CurrentPhenomenons" ) -> None: """Merge the classical phenomenoms bulleting with the coastal one. Extend the phenomenomes_max_colors property with the content of the coastal weather alert bulletin. Args: coastal_phenomenoms: CurrentPhenomenons instance corresponding to the coastal weather alert bulletin. """ # TODO: Add consitency check self.raw_data["phenomenons_max_colors"].extend( coastal_phenomenoms.phenomenons_max_colors ) def get_domain_max_color(self) -> int: """Get the maximum level of alert of a given domain (class helper). Returns: An integer corresponding to the status code representing the maximum alert. """ max_int_color = max( x["phenomenon_max_color_id"] for x in self.phenomenons_max_colors ) return max_int_color class Full: """This class allows to access the results of a `warning/full` API command. For a given domain we can access the maximum alert, a timelaps of the alert evolution for the next 24 hours, and a list of alerts. For coastal department two bulletins are avalaible corresponding to two different domains. Attributes: update_time: A timestamp (as integer) corresponding to the latest update of the pheomenoms. end_validity_time: A timestamp (as integer) corresponding to expiration date of the phenomenoms. domain_id: A string corresponding do the domain ID of the bulletin. Value is 'France' or a department number. color_max: An integer representing the maximum alert level in the domain. timelaps: A list of dictionnaries corresponding to the schedule of each phenomenoms in the next 24 hours. phenomenons_items: list of dictionnaries corresponding the alert level for each phenomenoms type. """ def __init__(self, raw_data: WarnningFullData) -> None: """Initialize a Full object. Args: raw_data: A dictionary representing the JSON response from'warning/full' REST API request. The structure is described by the WarnningFullData class. """ self.raw_data = raw_data @property def update_time(self) -> int: """Return the update time of the full bulletin.""" return self.raw_data["update_time"] @property def end_validity_time(self) -> int: """Return the end of validty time of the full bulletin.""" return self.raw_data["end_validity_time"] @property def domain_id(self) -> str: """Return the domain ID of the the full bulletin.""" return self.raw_data["domain_id"] @property def color_max(self) -> int: """Return the color max of the domain.""" return self.raw_data["color_max"] @property def timelaps(self) -> List[Dict[str, Any]]: """Return the timelaps of each phenomenom for the domain.""" return self.raw_data["timelaps"] @property def phenomenons_items(self) -> List[PhenomenonMaxColor]: """Return the phenomenom list of the domain.""" return self.raw_data["phenomenons_items"] def merge_with_coastal_phenomenons(self, coastal_phenomenoms: "Full") -> None: """Merge the classical phenomenon bulletin with the coastal one. Extend the color_max, timelaps and phenomenons_items properties with the content of the coastal weather alert bulletin. Args: coastal_phenomenoms: Full instance corresponding to the coastal weather alert bulletin. """ # TODO: Add consitency check # TODO: Check if other data need to be merged # Merge color_max property self.raw_data["color_max"] = max(self.color_max, coastal_phenomenoms.color_max) # Merge timelaps self.raw_data["timelaps"].extend(coastal_phenomenoms.timelaps) # Merge phenomenons_items self.raw_data["phenomenons_items"].extend(coastal_phenomenoms.phenomenons_items) # TODO: check opportunity to complete class hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/py.typed000066400000000000000000000000001453417731100240700ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/src/meteofrance_api/session.py000066400000000000000000000032031453417731100244360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Session managers for the Météo-France REST API.""" from typing import Any from typing import Optional from requests import Response from requests import Session from .const import METEOFRANCE_API_TOKEN from .const import METEOFRANCE_API_URL class MeteoFranceSession(Session): """HTTP session manager for Météo-France. This session object allows to manage the authentication in the API using a token. """ host: str = METEOFRANCE_API_URL def __init__(self, access_token: Optional[str] = None) -> None: """Initialize the authentication. Args: access_token: a string containing the authentication token for the REST API. """ self.access_token = access_token or METEOFRANCE_API_TOKEN Session.__init__(self) def request( # type: ignore self, method: str, path: str, *args: Any, **kwargs: Any ) -> Response: """Make a request using token authentication. Args: method: Method for the HTTP request (example "get"). path: path of the REST API endpoint. args: all other non-keyword arguments. kwargs: all other keyword arguments. Returns: the Response object corresponding to the result of the API request. """ params_inputs = kwargs.pop("params", None) params = {"token": self.access_token} if params_inputs: params.update(params_inputs) kwargs["params"] = params response = super().request(method, f"{self.host}/{path}", *args, **kwargs) response.raise_for_status() return response hacf-fr-meteofrance-api-1d2b3b9/tests/000077500000000000000000000000001453417731100176355ustar00rootroot00000000000000hacf-fr-meteofrance-api-1d2b3b9/tests/__init__.py000066400000000000000000000000561453417731100217470ustar00rootroot00000000000000"""Test suite for the meteofrance package.""" hacf-fr-meteofrance-api-1d2b3b9/tests/const.py000066400000000000000000000004621453417731100213370ustar00rootroot00000000000000"""Météo-France API test constants.""" from meteofrance_api.model.place import PlaceData MOUNTAIN_CITY: PlaceData = { "insee": "74080", "name": "La Clusaz", "lat": 45.90417, "lon": 6.42306, "country": "FR", "admin": "Rhône-Alpes", "admin2": "74", "postCode": "74220", } hacf-fr-meteofrance-api-1d2b3b9/tests/test_exception.py000066400000000000000000000005731453417731100232510ustar00rootroot00000000000000# coding: utf-8 """Tests for meteofrance module. Exception classes.""" import pytest from meteofrance_api.exceptions import MeteoFranceError def test_meteofrance_exception() -> None: """Tests MeteoFranceError exception.""" with pytest.raises(MeteoFranceError): # TODO test for coverage. To be update in the future. raise MeteoFranceError("Test Error") hacf-fr-meteofrance-api-1d2b3b9/tests/test_forecast.py000066400000000000000000000060601453417731100230560ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. Forecast class.""" import time from datetime import datetime from .const import MOUNTAIN_CITY from meteofrance_api import MeteoFranceClient from meteofrance_api.model import Place def test_forecast_france() -> None: """Test weather forecast results from API.""" client = MeteoFranceClient() weather_forecast = client.get_forecast(latitude=48.8075, longitude=2.24028) now_ts = int(time.time()) assert isinstance(weather_forecast.position, dict) assert isinstance(weather_forecast.updated_on, int) assert "T" in weather_forecast.daily_forecast[0].keys() assert "humidity" in weather_forecast.daily_forecast[0].keys() assert "rain" in weather_forecast.probability_forecast[0].keys() assert "clouds" in weather_forecast.forecast[0].keys() assert isinstance( weather_forecast.timestamp_to_locale_time( weather_forecast.daily_forecast[0]["dt"] ), datetime, ) assert abs(weather_forecast.nearest_forecast["dt"] - now_ts) <= 30 * 60 assert now_ts - 3600 <= weather_forecast.current_forecast["dt"] <= now_ts assert ( weather_forecast.today_forecast["dt"] == weather_forecast.daily_forecast[0]["dt"] ) def test_forecast_world() -> None: """Test weather forecast results from API.""" client = MeteoFranceClient() weather_forecast = client.get_forecast(latitude=45.5016889, longitude=73.567256) now_ts = int(time.time()) assert isinstance(weather_forecast.position, dict) assert isinstance(weather_forecast.updated_on, int) assert "T" in weather_forecast.daily_forecast[0].keys() assert "humidity" in weather_forecast.daily_forecast[0].keys() assert not weather_forecast.probability_forecast assert "clouds" in weather_forecast.forecast[0].keys() assert isinstance( weather_forecast.timestamp_to_locale_time( weather_forecast.daily_forecast[0]["dt"] ), datetime, ) assert abs(weather_forecast.nearest_forecast["dt"] - now_ts) == min( abs(x["dt"] - now_ts) for x in weather_forecast.forecast ) assert ( weather_forecast.current_forecast["dt"] == weather_forecast.nearest_forecast["dt"] ) assert ( weather_forecast.today_forecast["dt"] == weather_forecast.daily_forecast[0]["dt"] ) def test_forecast_place() -> None: """Test weather forecast results from API.""" client = MeteoFranceClient() weather_forecast = client.get_forecast_for_place(place=Place(MOUNTAIN_CITY)) assert isinstance(weather_forecast.position, dict) assert isinstance(weather_forecast.updated_on, int) assert "T" in weather_forecast.daily_forecast[0].keys() assert "humidity" in weather_forecast.daily_forecast[0].keys() assert "rain" in weather_forecast.probability_forecast[0].keys() assert "clouds" in weather_forecast.forecast[0].keys() assert isinstance( weather_forecast.timestamp_to_locale_time( weather_forecast.daily_forecast[0]["dt"] ), datetime, ) hacf-fr-meteofrance-api-1d2b3b9/tests/test_helpers.py000066400000000000000000000146661453417731100227250ustar00rootroot00000000000000# coding: utf-8 """Tests for meteofrance module. Helpers.""" from typing import List import pytest from meteofrance_api.helpers import get_phenomenon_name_from_indice from meteofrance_api.helpers import get_warning_text_status_from_indice_color from meteofrance_api.helpers import is_coastal_department from meteofrance_api.helpers import is_valid_warning_department from meteofrance_api.helpers import readeable_phenomenoms_dict from meteofrance_api.helpers import sort_places_versus_distance_from_coordinates from meteofrance_api.model import Place from meteofrance_api.model.place import PlaceData from meteofrance_api.model.warning import PhenomenonMaxColor def test_text_helpers_fr() -> None: """Test helpers to have readable alert type and alert level in French.""" assert get_warning_text_status_from_indice_color(1) == "Vert" assert get_phenomenon_name_from_indice("2") == "Pluie-inondation" def test_get_warning_text_status_from_indice_color_en() -> None: """Test helpers to have readable alert type and alert level in English.""" assert get_warning_text_status_from_indice_color(4, "en") == "Red" assert get_phenomenon_name_from_indice("4", "en") == "Flood" @pytest.mark.parametrize( "dep, res", [ ("03", False), ("06", True), ("69", False), ("74", False), ("98", False), ("2B", True), ], ) def test_is_coastal_department(dep: str, res: bool) -> None: """Test the helper checking if an additional coastal departement bulletin exist.""" assert is_coastal_department(dep) == res @pytest.mark.parametrize( "dep, res", [ ("03", True), ("69", True), ("74", True), ("98", False), ("2B", True), ], ) def test_is_valid_warning_department(dep: str, res: bool) -> None: """Test the helper checking if departent has a weather alert bulletin.""" assert is_valid_warning_department(dep) == res def test_readeable_phenomenoms_dict() -> None: """Test the helper constructing a human readable dictionary for phenomenom.""" api_list = [ PhenomenonMaxColor(phenomenon_id="4", phenomenon_max_color_id=1), PhenomenonMaxColor(phenomenon_id="5", phenomenon_max_color_id=1), PhenomenonMaxColor(phenomenon_id="3", phenomenon_max_color_id=2), PhenomenonMaxColor(phenomenon_id="2", phenomenon_max_color_id=1), PhenomenonMaxColor(phenomenon_id="1", phenomenon_max_color_id=3), ] expected_dictionary = { "Inondation": "Vert", "Neige-verglas": "Vert", "Pluie-inondation": "Vert", "Orages": "Jaune", "Vent violent": "Orange", } assert readeable_phenomenoms_dict(api_list) == expected_dictionary def test_sort_places_versus_distance_from_coordinates() -> None: """Test the helper to order the Places list return by the search.""" json_places: List[PlaceData] = [ { "insee": "11254", "name": "Montréal", "lat": 43.2, "lon": 2.14083, "country": "FR", "admin": "Languedoc-Roussillon", "admin2": "11", "postCode": "11290", }, { "insee": "32290", "name": "Montréal", "lat": 43.95, "lon": 0.20222, "country": "FR", "admin": "Midi-Pyrénées", "admin2": "32", "postCode": "32250", }, { "insee": "07162", "name": "Montréal", "lat": 44.5284, "lon": 4.2938, "country": "FR", "admin": "Rhône-Alpes", "admin2": "07", "postCode": "07110", }, { "insee": "89267", "name": "Montréal", "lat": 47.54222, "lon": 4.03611, "country": "FR", "admin": "Bourgogne", "admin2": "89", "postCode": "89420", }, { "insee": "null", "name": "Montréal", "lat": 45.50884, "lon": -73.58781, "country": "CA", "admin": "Quebec", "admin2": "06", "postCode": "null", }, { "insee": "01265", "name": "Montréal-la-Cluse", "lat": 46.1871, "lon": 5.5709, "country": "FR", "admin": "Rhône-Alpes", "admin2": "01", "postCode": "01460", }, { "insee": "26209", "name": "Montréal-les-Sources", "lat": 44.40139, "lon": 5.3, "country": "FR", "admin": "Rhône-Alpes", "admin2": "26", "postCode": "26510", }, { "insee": "null", "name": "Montréal-Ouest", "lat": 45.45286, "lon": -73.64918, "country": "CA", "admin": "Quebec", "admin2": "null", "postCode": "null", }, { "insee": "null", "name": "Montréal-Est", "lat": 45.63202, "lon": -73.5075, "country": "CA", "admin": "Quebec", "admin2": "null", "postCode": "null", }, { "insee": "11432", "name": "Villeneuve-lès-Montréal", "lat": 43.18, "lon": 2.11139, "country": "FR", "admin": "Languedoc-Roussillon", "admin2": "11", "postCode": "11290", }, { "insee": "null", "name": "Montereale Valcellina", "lat": 46.1511, "lon": 12.64771, "country": "IT", "admin": "Friuli Venezia Giulia", "admin2": "PN", "postCode": "null", }, { "insee": "null", "name": "Mont-ral", "lat": 41.28333, "lon": 1.1, "country": "ES", "admin": "Catalonia", "admin2": "T", "postCode": "null", }, ] list_places = [Place(place_data) for place_data in json_places] # Sort Places by distance from Auch (32) coordinates. list_places_ordered = sort_places_versus_distance_from_coordinates( list_places, (43.64528, 0.58861) ) # first one should be in Gers assert list_places_ordered[0].admin2 == "32" # second in Aude assert list_places_ordered[1].admin2 == "11" hacf-fr-meteofrance-api-1d2b3b9/tests/test_integrations.py000066400000000000000000000031641453417731100237600ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module.""" import pytest from meteofrance_api import MeteoFranceClient from meteofrance_api.helpers import readeable_phenomenoms_dict @pytest.mark.parametrize("city", ["Montréal", "Foix"]) def test_workflow(city: str) -> None: """Test classical workflow usage with the Python library.""" # Init client client = MeteoFranceClient() # Search a location from name. list_places = client.search_places(city) my_place = list_places[0] # Fetch weather forecast for the location my_place_weather_forecast = client.get_forecast_for_place(my_place) # Get the daily forecast my_place_daily_forecast = my_place_weather_forecast.daily_forecast # If rain in the hour forecast is available, get it. if my_place_weather_forecast.position["rain_product_available"] == 1: my_place_rain_forecast = client.get_rain(my_place.latitude, my_place.longitude) next_rain_dt = my_place_rain_forecast.next_rain_date_locale() if not next_rain_dt: rain_status = "No rain expected in the following hour." else: rain_status = next_rain_dt.strftime("%H:%M") else: rain_status = "No rain forecast available." # Fetch weather alerts. if my_place.admin2: my_place_weather_alerts = client.get_warning_current_phenomenoms( my_place.admin2 ) readable_warnings = readeable_phenomenoms_dict( my_place_weather_alerts.phenomenons_max_colors ) assert isinstance(my_place_daily_forecast, list) assert rain_status assert isinstance(readable_warnings, dict) hacf-fr-meteofrance-api-1d2b3b9/tests/test_observation.py000066400000000000000000000051241453417731100236030ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. Observation class.""" from datetime import datetime from datetime import timedelta from datetime import timezone from .const import MOUNTAIN_CITY from meteofrance_api import MeteoFranceClient from meteofrance_api.model import Observation from meteofrance_api.model import Place def assert_types(observation: Observation) -> None: """Check observation types.""" assert isinstance(observation.timezone, str) assert isinstance(observation.time_as_string, str) assert isinstance(observation.time_as_datetime, datetime) assert isinstance(observation.temperature, float) or isinstance( observation.temperature, int ) assert isinstance(observation.wind_speed, float) or isinstance( observation.wind_speed, int ) assert isinstance(observation.wind_direction, int) assert isinstance(observation.wind_icon, str) assert isinstance(observation.weather_icon, str) assert isinstance(observation.weather_description, str) def assert_datetime(observation: Observation) -> None: """Check observation time is before now but after now - 1h.""" now = datetime.now(timezone.utc) assert ( True if observation.time_as_datetime is None else now - timedelta(hours=1) < observation.time_as_datetime < now ) def test_observation_france() -> None: """Test weather observation results from API (valid result, from lat/lon).""" client = MeteoFranceClient() observation = client.get_observation(latitude=48.8075, longitude=2.24028) assert str(observation) assert_types(observation) assert_datetime(observation) def test_observation_world() -> None: """Test weather observation results from API (null result).""" client = MeteoFranceClient() observation = client.get_observation(latitude=45.5016889, longitude=73.567256) assert str(observation) assert observation.timezone is None assert observation.time_as_string is None assert observation.time_as_datetime is None assert observation.temperature is None assert observation.wind_speed is None assert observation.wind_direction is None assert observation.wind_icon is None assert observation.weather_icon is None assert observation.weather_description is None def test_observation_place() -> None: """Test weather observation results from API (valid result, from place).""" client = MeteoFranceClient() observation = client.get_observation_for_place(place=Place(MOUNTAIN_CITY)) assert str(observation) assert_types(observation) assert_datetime(observation) hacf-fr-meteofrance-api-1d2b3b9/tests/test_picture_of_the_day.py000066400000000000000000000012201453417731100250750ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. PictureOfTheDay class.""" from meteofrance_api import const from meteofrance_api import MeteoFranceClient def test_picture_of_the_day() -> None: """Test weather picture of the day results from API.""" client = MeteoFranceClient() potd = client.get_picture_of_the_day() assert potd.description params = "?domain=france&report_type=observation&report_subtype=image%20du%20jour&format=jpg" token_param = f"&token={const.METEOFRANCE_API_TOKEN}" path = "/v2/report" assert_url = f"{const.METEOFRANCE_API_URL}{path}{params}{token_param}" assert potd.image_url == assert_url hacf-fr-meteofrance-api-1d2b3b9/tests/test_place.py000066400000000000000000000034451453417731100223400ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. Place class.""" from meteofrance_api import MeteoFranceClient def test_places() -> None: """Test for simple seach of Place.""" client = MeteoFranceClient() list_places = client.search_places("montreal") assert list_places place = list_places[0] assert place.insee assert place.latitude assert place.longitude assert place.postal_code assert place.name == "Montréal" assert place.country == "FR" assert place.admin == "Languedoc-Roussillon" assert place.admin2 == "11" def test_places_with_gps() -> None: """Test a place search by specifying a GPS point to search arround.""" client = MeteoFranceClient() list_places = client.search_places("montreal", "45.50884", "-73.58") assert list_places place = list_places[0] assert place.name == "Montréal" assert place.country == "CA" assert place.admin == "Quebec" assert place.admin2 == "06" def test_places_not_found() -> None: """Test when no places are found.""" client = MeteoFranceClient() list_places = client.search_places("sqdmfkjdsmkf") assert not list_places def test_places_print() -> None: """Test different way to print Places class.""" client = MeteoFranceClient() place_in_france = client.search_places("Montréal")[0] place_not_in_france = client.search_places("Montréal", "45.50884", "-73.58")[0] assert ( repr(place_in_france) == "" ) assert str(place_in_france) == "Montréal - Languedoc-Roussillon (11) - FR" assert ( repr(place_not_in_france) == "" ) assert str(place_not_in_france) == "Montréal - Quebec - CA" hacf-fr-meteofrance-api-1d2b3b9/tests/test_rain.py000066400000000000000000000073271453417731100222100ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. Forecast class.""" from unittest.mock import Mock import pytest import requests from meteofrance_api import MeteoFranceClient from meteofrance_api.const import METEOFRANCE_API_URL def test_rain() -> None: """Test rain forecast on a covered zone.""" client = MeteoFranceClient() rain = client.get_rain(latitude=48.8075, longitude=2.24028) assert isinstance(rain.position, dict) assert isinstance(rain.updated_on, int) assert isinstance(rain.quality, int) assert "rain" in rain.forecast[0].keys() def test_rain_not_covered() -> None: """Test rain forecast result on a non covered zone.""" client = MeteoFranceClient() with pytest.raises(requests.HTTPError, match=r"400 .*"): client.get_rain(latitude=45.508, longitude=-73.58) def test_rain_expected(requests_mock: Mock) -> None: """Test datecomputation when rain is expected within the hour.""" client = MeteoFranceClient() requests_mock.request( "get", f"{METEOFRANCE_API_URL}/rain", json={ "position": { "lat": 48.807166, "lon": 2.239895, "alti": 76, "name": "Meudon", "country": "FR - France", "dept": "92", "timezone": "Europe/Paris", }, "updated_on": 1589995200, "quality": 0, "forecast": [ {"dt": 1589996100, "rain": 1, "desc": "Temps sec"}, {"dt": 1589996400, "rain": 1, "desc": "Temps sec"}, {"dt": 1589996700, "rain": 1, "desc": "Temps sec"}, {"dt": 1589997000, "rain": 2, "desc": "Pluie faible"}, {"dt": 1589997300, "rain": 3, "desc": "Pluie modérée"}, {"dt": 1589997600, "rain": 2, "desc": "Pluie faible"}, {"dt": 1589998200, "rain": 1, "desc": "Temps sec"}, {"dt": 1589998800, "rain": 1, "desc": "Temps sec"}, {"dt": 1589999400, "rain": 1, "desc": "Temps sec"}, ], }, ) rain = client.get_rain(latitude=48.8075, longitude=2.24028) date_rain = rain.next_rain_date_locale() assert str(date_rain) == "2020-05-20 19:50:00+02:00" assert ( str(rain.timestamp_to_locale_time(rain.forecast[3]["dt"])) == "2020-05-20 19:50:00+02:00" ) def test_no_rain_expected(requests_mock: Mock) -> None: """Test datecomputation when rain is expected within the hour.""" client = MeteoFranceClient() requests_mock.request( "get", f"{METEOFRANCE_API_URL}/rain", json={ "position": { "lat": 48.807166, "lon": 2.239895, "alti": 76, "name": "Meudon", "country": "FR - France", "dept": "92", "timezone": "Europe/Paris", }, "updated_on": 1589995200, "quality": 0, "forecast": [ {"dt": 1589996100, "rain": 1, "desc": "Temps sec"}, {"dt": 1589996400, "rain": 1, "desc": "Temps sec"}, {"dt": 1589996700, "rain": 1, "desc": "Temps sec"}, {"dt": 1589997000, "rain": 1, "desc": "Temps sec"}, {"dt": 1589997300, "rain": 1, "desc": "Temps sec"}, {"dt": 1589997600, "rain": 1, "desc": "Temps sec"}, {"dt": 1589998200, "rain": 1, "desc": "Temps sec"}, {"dt": 1589998800, "rain": 1, "desc": "Temps sec"}, {"dt": 1589999400, "rain": 1, "desc": "Temps sec"}, ], }, ) rain = client.get_rain(latitude=48.8075, longitude=2.24028) assert rain.next_rain_date_locale() is None hacf-fr-meteofrance-api-1d2b3b9/tests/test_session.py000066400000000000000000000011401453417731100227250ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. MeteoFranceSession class.""" import pytest from requests.exceptions import RequestException from meteofrance_api.session import MeteoFranceSession def test_session() -> None: """Test generic session.""" session = MeteoFranceSession() resp = session.request("get", "places", params={"q": "Montréal"}) assert resp.status_code == 200 def test_session_wrong_token() -> None: """Test exceptions raised.""" session = MeteoFranceSession("fake_token") with pytest.raises(RequestException): session.request("get", "places") hacf-fr-meteofrance-api-1d2b3b9/tests/test_warning.py000066400000000000000000000103121453417731100227100ustar00rootroot00000000000000# coding: utf-8 """Tests Météo-France module. Warning classes.""" from unittest.mock import Mock import pytest from meteofrance_api import MeteoFranceClient from meteofrance_api.const import METEOFRANCE_API_URL WARNING_COLOR_LIST = [1, 2, 3, 4] def test_currentphenomenons(requests_mock: Mock) -> None: """Test basic weather alert results from API.""" client = MeteoFranceClient() requests_mock.request( "get", f"{METEOFRANCE_API_URL}/v3/warning/currentphenomenons", json={ "update_time": 1591279200, "end_validity_time": 1591365600, "domain_id": "32", "phenomenons_max_colors": [ {"phenomenon_id": "6", "phenomenon_max_color_id": 1}, {"phenomenon_id": "4", "phenomenon_max_color_id": 1}, {"phenomenon_id": "5", "phenomenon_max_color_id": 3}, {"phenomenon_id": "2", "phenomenon_max_color_id": 1}, {"phenomenon_id": "1", "phenomenon_max_color_id": 1}, {"phenomenon_id": "3", "phenomenon_max_color_id": 2}, ], }, ) current_phenomenoms = client.get_warning_current_phenomenoms(domain="32", depth=1) assert isinstance(current_phenomenoms.update_time, int) assert isinstance(current_phenomenoms.end_validity_time, int) assert isinstance(current_phenomenoms.domain_id, str) assert "phenomenon_id" in current_phenomenoms.phenomenons_max_colors[0].keys() assert current_phenomenoms.get_domain_max_color() == 3 def test_fulls() -> None: """Test advanced weather alert results from API.""" client = MeteoFranceClient() warning_full = client.get_warning_full(domain="31") assert isinstance(warning_full.update_time, int) assert isinstance(warning_full.end_validity_time, int) assert isinstance(warning_full.domain_id, str) assert warning_full.domain_id == "31" assert warning_full.color_max in WARNING_COLOR_LIST assert ( warning_full.timelaps[0]["timelaps_items"][0]["color_id"] in WARNING_COLOR_LIST ) assert ( warning_full.phenomenons_items[0]["phenomenon_max_color_id"] in WARNING_COLOR_LIST ) def test_thumbnail() -> None: """Test getting France status weather alert map.""" client = MeteoFranceClient() thumbnail_url = client.get_warning_thumbnail() assert thumbnail_url == ( "https://webservice.meteofrance.com/v3/warning/thumbnail" "?&token=__Wj7dVSTjV9YGu1guveLyDq0g7S7TfTjaHBTPTpO0kj8__&" "domain=france" ) @pytest.mark.parametrize( "dep, res_coastal, res_avalanche", [("13", True, False), ("32", False, False), ("74", False, True)], ) def test_currentphenomenons_with_coastal_bulletin( dep: str, res_coastal: bool, res_avalanche: bool ) -> None: """Test getting a complete basic bulletin for coastal department.""" client = MeteoFranceClient() current_phenomenoms = client.get_warning_current_phenomenoms( domain=dep, depth=1, with_coastal_bulletin=True ) has_avalanche_phenomenom = any( phenomenom["phenomenon_id"] == "8" for phenomenom in current_phenomenoms.phenomenons_max_colors ) has_coastal_phenomenom = any( phenomenom["phenomenon_id"] == "9" for phenomenom in current_phenomenoms.phenomenons_max_colors ) assert has_avalanche_phenomenom == res_avalanche assert has_coastal_phenomenom == res_coastal @pytest.mark.parametrize( "dep, res_coastal, res_avalanche", [("13", True, False), ("32", False, False), ("74", False, True)], ) def test_full_with_coastal_bulletin( dep: str, res_coastal: bool, res_avalanche: bool ) -> None: """Test getting a complete advanced bulletin for coastal department.""" client = MeteoFranceClient() full_phenomenoms = client.get_warning_full(domain=dep, with_coastal_bulletin=True) has_avalanche_phenomenom = any( phenomenom["phenomenon_id"] == "8" for phenomenom in full_phenomenoms.phenomenons_items ) has_coastal_phenomenom = any( phenomenom["phenomenon_id"] == "9" for phenomenom in full_phenomenoms.phenomenons_items ) assert has_avalanche_phenomenom == res_avalanche assert has_coastal_phenomenom == res_coastal hacf-fr-meteofrance-api-1d2b3b9/tests/test_warning_dictionary.py000066400000000000000000000066221453417731100251460ustar00rootroot00000000000000# coding: utf-8 """Tests for WarningDictionary class in the Météo-France module.""" import pytest from meteofrance_api import MeteoFranceClient from meteofrance_api.model import WarningDictionary from meteofrance_api.model.dictionary import WarningDictionaryData class TestWarningDictionary: """Tests for WarningDictionary class in the Météo-France module.""" @pytest.fixture def sample_dictionary_data(self) -> WarningDictionaryData: """Provide sample data for WarningDictionary.""" return { "phenomenons": [ {"id": 1, "name": "Wind"}, {"id": 2, "name": "Rain-flood"}, {"id": 3, "name": "Thunderstorms"}, {"id": 4, "name": "Flood"}, {"id": 5, "name": "Snow-ice"}, {"id": 6, "name": "Heat wave"}, {"id": 7, "name": "Extreme Cold"}, {"id": 8, "name": "Avalanche"}, {"id": 9, "name": "Waves-flooding"}, ], "colors": [ {"id": 1, "level": 1, "name": "green", "hexaCode": "#31aa35"}, {"id": 2, "level": 2, "name": "yellow", "hexaCode": "#fff600"}, {"id": 3, "level": 3, "name": "orange", "hexaCode": "#ffb82b"}, {"id": 4, "level": 4, "name": "red", "hexaCode": "#CC0000"}, ], } def test_dictionary(self) -> None: """Test dictionary.""" client = MeteoFranceClient() dictionary = client.get_warning_dictionary() assert isinstance(dictionary, WarningDictionary) color = dictionary.get_color_name_by_id(1) assert isinstance(color, str) phenomenon = dictionary.get_phenomenon_name_by_id(1) assert isinstance(phenomenon, str) def test_get_phenomenon_by_id( self, sample_dictionary_data: WarningDictionaryData ) -> None: """Test get_phenomenon_by_id method.""" dictionary = WarningDictionary(sample_dictionary_data) phenomenon = dictionary.get_phenomenon_by_id(1) assert phenomenon == {"id": 1, "name": "Wind"} # Test for non-existing ID assert dictionary.get_phenomenon_by_id(99) is None def test_get_phenomenon_name_by_id( self, sample_dictionary_data: WarningDictionaryData ) -> None: """Test get_phenomenon_name_by_id method.""" dictionary = WarningDictionary(sample_dictionary_data) name = dictionary.get_phenomenon_name_by_id(2) assert name == "Rain-flood" # Test for non-existing ID assert dictionary.get_phenomenon_name_by_id(99) is None def test_get_color_by_id( self, sample_dictionary_data: WarningDictionaryData ) -> None: """Test get_color_by_id method.""" dictionary = WarningDictionary(sample_dictionary_data) color = dictionary.get_color_by_id(2) assert color == {"id": 2, "level": 2, "name": "yellow", "hexaCode": "#fff600"} # Test for non-existing ID assert dictionary.get_color_by_id(99) is None def test_get_color_name_by_id( self, sample_dictionary_data: WarningDictionaryData ) -> None: """Test get_color_name_by_id method.""" dictionary = WarningDictionary(sample_dictionary_data) name = dictionary.get_color_name_by_id(1) assert name == "green" # Test for non-existing ID assert dictionary.get_color_name_by_id(99) is None