pax_global_header00006660000000000000000000000064147367112020014515gustar00rootroot0000000000000052 comment=49e324ebd3ad0b9d07f355f472c2ad9f58c6b7a6 renault-api-0.2.9/000077500000000000000000000000001473671120200137465ustar00rootroot00000000000000renault-api-0.2.9/.cookiecutter.json000066400000000000000000000004111473671120200174130ustar00rootroot00000000000000{ "_template": "gh:cjolowicz/cookiecutter-hypermodern-python", "author": "epenet", "email": "mail@tbd.com", "friendly_name": "Renault API", "github_user": "hacf-fr", "package_name": "renault_api", "project_name": "renault-api", "version": "0.0.0" } renault-api-0.2.9/.darglint000066400000000000000000000000361473671120200155520ustar00rootroot00000000000000[darglint] strictness = short renault-api-0.2.9/.gitattributes000066400000000000000000000000231473671120200166340ustar00rootroot00000000000000* text=auto eol=lf renault-api-0.2.9/.github/000077500000000000000000000000001473671120200153065ustar00rootroot00000000000000renault-api-0.2.9/.github/dependabot.yml000066400000000000000000000007321473671120200201400ustar00rootroot00000000000000version: 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 # Allow up to 10 open pull requests for pip dependencies open-pull-requests-limit: 99 renault-api-0.2.9/.github/release-drafter.yml000066400000000000000000000014101473671120200210720ustar00rootroot00000000000000categories: - title: ":boom: Breaking Changes" label: "breaking" - title: ":rocket: Features" label: "enhancement" - title: ":fire: Removals and Deprecations" label: "removal" - title: ":bug: Fixes" label: "bug" - title: ":racehorse: Performance" label: "performance" - title: ":books: Documentation" label: "documentation" - title: ":construction_worker: Continuous Integration" label: "ci" - title: ":rotating_light: Testing" label: "testing" - title: ":wave: Fixtures" label: "fixtures" - title: ":hammer: Refactoring" label: "refactoring" - title: ":lipstick: Style" label: "style" - title: ":package: Dependencies" labels: - "dependencies" - "build" template: | ## Changes $CHANGES renault-api-0.2.9/.github/workflows/000077500000000000000000000000001473671120200173435ustar00rootroot00000000000000renault-api-0.2.9/.github/workflows/constraints.txt000066400000000000000000000001171473671120200224520ustar00rootroot00000000000000pip==24.3.1 nox==2024.10.9 nox-poetry==1.0.3 poetry==1.8.5 virtualenv==20.28.1 renault-api-0.2.9/.github/workflows/release.yml000066400000000000000000000043051473671120200215100ustar00rootroot00000000000000name: Release on: push: branches: - main jobs: release: name: Release runs-on: ubuntu-latest steps: - name: Check out the repository uses: actions/checkout@v4 with: fetch-depth: 2 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - 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.12.3 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.12.3 with: user: __token__ password: ${{ secrets.TEST_PYPI_TOKEN }} repository_url: https://test.pypi.org/legacy/ - name: Publish the release notes uses: release-drafter/release-drafter@v6.0.0 with: publish: ${{ steps.check-version.outputs.tag != '' }} tag: ${{ steps.check-version.outputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} renault-api-0.2.9/.github/workflows/tests.yml000066400000000000000000000117101473671120200212300ustar00rootroot00000000000000name: Tests on: pull_request: push: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true 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.12", os: ubuntu-latest, session: "safety" } - { python-version: "3.12", os: ubuntu-latest, session: "mypy" } - { 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.12", os: ubuntu-latest, session: "tests" } - { 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.12", os: windows-latest, session: "tests" } - { python-version: "3.12", os: macos-latest, session: "tests" } - { python-version: "3.12", os: ubuntu-latest, session: "typeguard" } - { python-version: "3.12", os: ubuntu-latest, session: "xdoctest" } - { python-version: "3.12", os: ubuntu-latest, session: "docs-build" } env: NOXSESSION: ${{ matrix.session }} steps: - name: Check out the repository uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 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@v4 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@v4.5.0 with: name: coverage-data-${{ matrix.python-version }}-${{ matrix.os }} path: ".coverage.*" include-hidden-files: true - name: Upload documentation if: matrix.session == 'docs-build' uses: actions/upload-artifact@v4.5.0 with: name: docs path: docs/_build coverage: runs-on: ubuntu-latest needs: tests steps: - name: Check out the repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - 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@v4.1.8 with: pattern: coverage-data-* merge-multiple: true - 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@v5.1.2 renault-api-0.2.9/.gitignore000066400000000000000000000002021473671120200157300ustar00rootroot00000000000000.mypy_cache/ /.coverage /.nox/ /.python-version /.pytype/ /dist/ /docs/_build/ /src/*.egg-info/ __pycache__/ # Virtual env .venv renault-api-0.2.9/.pre-commit-config.yaml000066400000000000000000000023031473671120200202250ustar00rootroot00000000000000repos: - repo: local hooks: - 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: ruff-check name: Lint code with Ruff entry: ruff check language: system types: [python] require_serial: true - id: ruff-format name: Format code with Ruff entry: ruff format language: system types: [python] require_serial: true - id: trailing-whitespace name: Trim Trailing Whitespace entry: trailing-whitespace-fixer language: system types: [text] stages: [commit, push, manual] - repo: https://github.com/prettier/pre-commit rev: v2.1.2 hooks: - id: prettier renault-api-0.2.9/.readthedocs.yml000066400000000000000000000002651473671120200170370ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.12" sphinx: configuration: docs/conf.py formats: all python: install: - requirements: docs/requirements.txt renault-api-0.2.9/.vscode/000077500000000000000000000000001473671120200153075ustar00rootroot00000000000000renault-api-0.2.9/.vscode/extensions.json000066400000000000000000000001101473671120200203710ustar00rootroot00000000000000{ "recommendations": ["esbenp.prettier-vscode", "ms-python.python"] } renault-api-0.2.9/.vscode/settings.json000066400000000000000000000000541473671120200200410ustar00rootroot00000000000000{ "python.formatting.provider": "black" } renault-api-0.2.9/.vscode/tasks.json000066400000000000000000000011661473671120200173330ustar00rootroot00000000000000{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "Pre-commit all files", "type": "shell", "command": "poetry run pre-commit run --all-files", "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] }, { "label": "Run tests with coverage", "type": "shell", "command": "poetry run pytest --cov", "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] } ] } renault-api-0.2.9/CODE_OF_CONDUCT.rst000066400000000000000000000124211473671120200167550ustar00rootroot00000000000000Contributor 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 mail@tbd.com. 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. renault-api-0.2.9/CONTRIBUTING.rst000066400000000000000000000100441473671120200164060ustar00rootroot00000000000000Contributor 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/renault-api .. _Documentation: https://renault-api.readthedocs.io/ .. _Issue Tracker: https://github.com/hacf-fr/renault-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`_. Providing samples ----------------- Providing samples for more vehicles helps us improve the library. If you have a vehicle for which we do not have the specifications yet, or if you have found a new feature for an existing vehicle, then please send us the samples. - Generate a trace. - Add the json file to the correct `test/fixtures/kamereon` subfolder. - Ensure that `vin` starts with `VF1AAAA` _(we do not want the real VIN)_ - Ensure that `vehicleDetails.vin` also starts with `VF1AAAA` _(we do not want the real VIN)_ - Ensure that `vehicleDetails.registrationNumber` starts with `REG-` _(we do not want the real registration number)_ - Ensure that `vehicleDetails.radioCode` is equal to `1234` _(we do not want the real radio code)_ - Ensure that the json file passes pre-commit (can be parsed online via https://codebeautify.org/jsonviewer) - Create a pull request How to set up your development environment ------------------------------------------ You need Python 3.8+ and the following tools: - Poetry_ - Nox_ - nox-poetry_ **WARNING**: due to an open issue with Poetry, we recommand that you use the 1.0.10 version. You can install it with the commmand: .. code:: console $ pipx install poetry==1.0.10 Install the package with development requirements: .. code:: console $ poetry install --extras "cli" You can now run an interactive Python session, or the command-line interface: .. code:: console $ poetry run python $ poetry run renault-api .. _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 commiting 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/renault-api/pulls .. github-only .. _Code of Conduct: CODE_OF_CONDUCT.rst renault-api-0.2.9/LICENSE.rst000066400000000000000000000020661473671120200155660ustar00rootroot00000000000000MIT License =========== Copyright © 2020 epenet 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.** renault-api-0.2.9/README.rst000066400000000000000000000122051473671120200154350ustar00rootroot00000000000000Renault API =========== |PyPI| |Python Version| |License| |Read the Docs| |Tests| |Codecov| |pre-commit| |Ruff| .. |PyPI| image:: https://img.shields.io/pypi/v/renault-api.svg :target: https://pypi.org/project/renault-api/ :alt: PyPI .. |Python Version| image:: https://img.shields.io/pypi/pyversions/renault-api :target: https://pypi.org/project/renault-api :alt: Python Version .. |License| image:: https://img.shields.io/pypi/l/renault-api :target: https://opensource.org/licenses/MIT :alt: License .. |Read the Docs| image:: https://img.shields.io/readthedocs/renault-api/latest.svg?label=Read%20the%20Docs :target: https://renault-api.readthedocs.io/ :alt: Read the documentation at https://renault-api.readthedocs.io/ .. |Tests| image:: https://github.com/hacf-fr/renault-api/workflows/Tests/badge.svg :target: https://github.com/hacf-fr/renault-api/actions?workflow=Tests :alt: Tests .. |Codecov| image:: https://codecov.io/gh/hacf-fr/renault-api/branch/main/graph/badge.svg :target: https://codecov.io/gh/hacf-fr/renault-api :alt: Codecov .. |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 .. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff Features -------- This Python package manages the communication with the private Renault API used by the official MyRenault application. The client is able to read various vehicle attributes, such as: * mileage * GPS location * fuel autonomy (for fuel vehicles) * battery autonomy (for electric vehicles) * contracts associated to the vehicle (warranty and connected services) For some vehicles, it is also possible to manage: * hvac/pre-conditionning of the vehicle * charge schedule This package has been developed to be used with Home-Assistant, but it can be used in other contexts Requirements ------------ * Python (>= 3.8) API Usage --------- You can install *Renault API* via pip_ from PyPI_: .. code:: console $ pip install renault-api .. code:: python import aiohttp import asyncio from renault_api.renault_client import RenaultClient async def main(): async with aiohttp.ClientSession() as websession: client = RenaultClient(websession=websession, locale="fr_FR") await client.session.login('email', 'password') print(f"Accounts: {await client.get_person()}") # List available accounts, make a note of kamereon account id account_id = "Your Kamereon account id" account = await client.get_api_account(account_id) print(f"Vehicles: {await account.get_vehicles()}") # List available vehicles, make a note of vehicle VIN vin = "Your vehicle VIN" vehicle = await account.get_api_vehicle(vin) print(f"Cockpit information: {await vehicle.get_cockpit()}") print(f"Battery status information: {await vehicle.get_battery_status()}") loop = asyncio.get_event_loop() loop.run_until_complete(main()) CLI Usage --------- The renault-api is also available through a CLI, which requires additional dependencies. For the added dependencies, you can install *Renault API* via pip_ from PyPI_: .. code:: console $ pip install renault-api[cli] Once installed, the following command prompts for credentials and settings, displays basic vehicle status information, and generates traces: .. code:: console $ renault-api --log status * Credentials will automatically be stored in the user home directory (~/.credentials/renault-api.json) * Logs will automatically be generated in `logs` subfolder Please see the `Command-line Reference `_ for full details. Contributing ------------ Contributions are very welcome. To learn more, see the `Contributor Guide`_. License ------- Distributed under the terms of the MIT_ license, *Renault API* is free and open source software. Disclaimer ---------- This project is not affiliated with, endorsed by, or connected to Renault. I accept no responsibility for any consequences, intended or accidental, as a as a result of interacting with Renault's API using this project. Issues ------ If you encounter any problems, please `file an issue`_ along with a detailed description. Credits ------- This project was generated from `@cjolowicz`_'s `Hypermodern Python Cookiecutter`_ template. This project was heavily based on `@jamesremuscat`_'s `PyZE`_ python client for the Renault ZE API. .. _@cjolowicz: https://github.com/cjolowicz .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _@jamesremuscat: https://github.com/jamesremuscat .. _PyZE: https://github.com/jamesremuscat/pyze .. _MIT: http://opensource.org/licenses/MIT .. _PyPI: https://pypi.org/ .. _Hypermodern Python Cookiecutter: https://github.com/cjolowicz/cookiecutter-hypermodern-python .. _file an issue: https://github.com/hacf-fr/renault-api/issues .. _pip: https://pip.pypa.io/ .. github-only .. _Contributor Guide: CONTRIBUTING.rst .. _Usage: https://renault-api.readthedocs.io/en/latest/usage.html renault-api-0.2.9/codecov.yml000066400000000000000000000002051473671120200161100ustar00rootroot00000000000000comment: false coverage: status: project: default: target: "100" patch: default: target: "100" renault-api-0.2.9/docs/000077500000000000000000000000001473671120200146765ustar00rootroot00000000000000renault-api-0.2.9/docs/codeofconduct.rst000066400000000000000000000000441473671120200202450ustar00rootroot00000000000000.. include:: ../CODE_OF_CONDUCT.rst renault-api-0.2.9/docs/conf.py000066400000000000000000000005111473671120200161720ustar00rootroot00000000000000"""Sphinx configuration.""" from datetime import datetime project = "Renault API" author = "epenet" copyright = f"{datetime.now().year}, {author}" extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_click", "sphinx_rtd_theme", ] autodoc_typehints = "description" html_theme = "sphinx_rtd_theme" renault-api-0.2.9/docs/contributing.rst000066400000000000000000000001461473671120200201400ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst :end-before: github-only .. _Code of Conduct: codeofconduct.html renault-api-0.2.9/docs/endpoints.rst000066400000000000000000000031251473671120200174340ustar00rootroot00000000000000Renault endpoints ================= .. contents:: :local: :backlinks: none This is a list of the endpoints available, and their characteristics. .. _fixtures: https://github.com/hacf-fr/renault-api/blob/main/tests/fixtures/kamereon/vehicle_data/ .. _chargestatus: https://github.com/hacf-fr/renault-api/blob/main/src/renault_api/kamereon/enums.py .. _Contributor Guide: contributing.html Vehicle data endpoints ---------------------- .. include:: endpoints/vehicle_data.battery-status.rst .. include:: endpoints/vehicle_data.charge-history.rst .. include:: endpoints/vehicle_data.charge-mode.rst .. include:: endpoints/vehicle_data.charges.rst .. include:: endpoints/vehicle_data.charging-settings.rst .. include:: endpoints/vehicle_data.cockpit.rst .. include:: endpoints/vehicle_data.hvac-history.rst .. include:: endpoints/vehicle_data.hvac-sessions.rst .. include:: endpoints/vehicle_data.hvac-status.rst .. include:: endpoints/vehicle_data.hvac-settings.rst .. include:: endpoints/vehicle_data.location.rst .. include:: endpoints/vehicle_data.lock-status.rst .. include:: endpoints/vehicle_data.notification-settings.rst .. include:: endpoints/vehicle_data.res-state.rst Action endpoints ---------------- .. include:: endpoints/vehicle_action.charge-mode.rst .. include:: endpoints/vehicle_action.charge-schedule.rst .. include:: endpoints/vehicle_action.charging-start.rst .. include:: endpoints/vehicle_action.hvac-start.rst .. include:: endpoints/vehicle_action.hvac-schedule.rst KCM Action endpoints ------------------------- .. include:: endpoints/vehicle_kcm_actions.charge-pause-resume.rst renault-api-0.2.9/docs/endpoints/000077500000000000000000000000001473671120200167015ustar00rootroot00000000000000renault-api-0.2.9/docs/endpoints/vehicle_action.charge-mode.rst000066400000000000000000000020541473671120200245620ustar00rootroot00000000000000actions/charge-mode ''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/actions/charge-mode`` Sample payload: Use instant charging: .. code-block:: JavaScript { "data": { "type": "ChargeMode", "attributes": {"action": "always_charging"} } } Use scheduled charging: .. code-block:: JavaScript { "data": { "type": "ChargeMode", "attributes": {"action": "schedule_mode"} } } Please check the `Contributor Guide`_ to provide extra samples. .. note:: All vehicles seem to use `always_charging` and `schedule_mode`. On older vehicles, such as Zoe40 (model code X101VE): This matches the ``vehicle_data.charge-mode`` return values: ``always_charging`` and ``schedule_mode``. On newer vehicles, such as Zoe50 (model code X102VE): This DOES NOT match the ``vehicle_data.charge-mode`` return values which are: ``always`` and ``scheduled``. renault-api-0.2.9/docs/endpoints/vehicle_action.charge-schedule.rst000066400000000000000000000005071473671120200254330ustar00rootroot00000000000000actions/charge-schedule ''''''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v2/cars/{vin}/actions/charge-schedule`` Sample payload: Sample payload is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. renault-api-0.2.9/docs/endpoints/vehicle_action.charging-start.rst000066400000000000000000000011631473671120200253240ustar00rootroot00000000000000actions/charging-start '''''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/actions/charging-start`` Sample payload: Start charge: .. code-block:: JavaScript { "data": { "type": "ChargingStart", "attributes": {"action": "start"} } } Stop charge: .. code-block:: JavaScript { "data": { "type": "ChargingStart", "attributes": {"action": "stop"} } } Please check the `Contributor Guide`_ to provide extra samples. renault-api-0.2.9/docs/endpoints/vehicle_action.hvac-schedule.rst000066400000000000000000000005011473671120200251150ustar00rootroot00000000000000actions/hvac-schedule ''''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v2/cars/{vin}/actions/hvac-schedule`` Sample payload: Sample payload is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. renault-api-0.2.9/docs/endpoints/vehicle_action.hvac-start.rst000066400000000000000000000007531473671120200244670ustar00rootroot00000000000000actions/hvac-start '''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/actions/hvac-start`` Sample payload: Sample payload is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. .. note:: On Zoe50 (model code X102VE): Payload ``{'action': 'cancel'}`` to stop HVAC does not create errors but has no effect on the vehicle (Renault side limitation). renault-api-0.2.9/docs/endpoints/vehicle_data.battery-status.rst000066400000000000000000000015271473671120200250420ustar00rootroot00000000000000battery-status '''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v2/cars/{vin}/battery-status`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/battery-status.2.json :language: JSON .. note:: * ``batteryTemperature`` is not always present. * ``batteryCapacity`` appears to always return ``0``. On Zoe40 (model code X101VE): * ``chargingInstantaneousPower`` gives value in watts. * ``chargingStatus`` uses only a subset of ``ChargeStatus``\_ enum (NOT_IN_CHARGE = 0.0, CHARGE_IN_PROGRESS = 1.0, CHARGE_ERROR = -1.0) On Zoe50 (model code X102VE): * ``batteryTemperature`` appears completely wrong. * ``chargingInstantaneousPower`` seems to return values in kilowatts, but the values still appear completely wrong. renault-api-0.2.9/docs/endpoints/vehicle_data.charge-history.rst000066400000000000000000000004401473671120200247700ustar00rootroot00000000000000charge-history '''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/charge-history`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/charge-history.day.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.charge-mode.rst000066400000000000000000000012561473671120200242210ustar00rootroot00000000000000charge-mode ''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/charge-mode`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/charge-mode.json :language: JSON .. note:: On older vehicles, such as Zoe40 (model code X101VE): The return values appear to be ``always_charging`` and ``schedule_mode``. This matches the ``vehicle_action.charge-mode`` action attributes. On newer vehicles, such as Zoe50 (model code X102VE): The return values appear to be ``always`` and ``scheduled``. This DOES NOT match the ``vehicle_action.charge-mode`` action attributes. renault-api-0.2.9/docs/endpoints/vehicle_data.charges.rst000066400000000000000000000004001473671120200234500ustar00rootroot00000000000000charges ''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/charges`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/charges.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.charging-settings.rst000066400000000000000000000006431473671120200254650ustar00rootroot00000000000000charging-settings ''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/charging-settings`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/charging-settings.single.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/charging-settings.multi.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.cockpit.rst000066400000000000000000000011101473671120200234670ustar00rootroot00000000000000cockpit ''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v2/cars/{vin}/cockpit`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/cockpit.captur_ii.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/cockpit.spring.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/cockpit.zoe.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/cockpit.zoe_50.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.hvac-history.rst000066400000000000000000000004421473671120200244620ustar00rootroot00000000000000hvac-history '''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/hvac-history`` Sample return: Sample data is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. renault-api-0.2.9/docs/endpoints/vehicle_data.hvac-sessions.rst000066400000000000000000000004451473671120200246320ustar00rootroot00000000000000hvac-sessions ''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/hvac-sessions`` Sample return: Sample data is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. renault-api-0.2.9/docs/endpoints/vehicle_data.hvac-settings.rst000066400000000000000000000004301473671120200246160ustar00rootroot00000000000000hvac-settings ''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/hvac-settings`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/hvac-settings.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.hvac-status.rst000066400000000000000000000014711473671120200243070ustar00rootroot00000000000000hvac-status ''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/hvac-status`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/hvac-status.spring.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/hvac-status.zoe.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/hvac-status.zoe_50.json :language: JSON .. note:: On Zoe40 (model code X101VE): ``hvacStatus`` seems to always report ``off``, even when preconditioning is in progress. On Zoe50 (model code X102VE): This endpoint seem to be unavailable and returns an error ``'err.func.403': 'Operation not supported Operation not supported for this can (C1A)'``. renault-api-0.2.9/docs/endpoints/vehicle_data.location.rst000066400000000000000000000005551473671120200236570ustar00rootroot00000000000000location '''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/location`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/location.1.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/location.2.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.lock-status.rst000066400000000000000000000005741473671120200243210ustar00rootroot00000000000000lock-status ''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/lock-status`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/lock-status.1.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/lock-status.2.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_data.notification-settings.rst000066400000000000000000000004751473671120200263740ustar00rootroot00000000000000notification-settings ''''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/notification-settings`` Sample return: Sample data is not yet available for this endpoint. Please check the `Contributor Guide`_ to provide a sample. renault-api-0.2.9/docs/endpoints/vehicle_data.res-state.rst000066400000000000000000000005641473671120200237560ustar00rootroot00000000000000res-state ''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kca/car-adapter/v1/cars/{vin}/res-state`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/res-state.1.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_data/res-state.2.json :language: JSON renault-api-0.2.9/docs/endpoints/vehicle_kcm_actions.charge-pause-resume.rst000066400000000000000000000006641473671120200272730ustar00rootroot00000000000000charge/pause-resume '''''''''''''''''''''' .. rst-class:: endpoint Base url: ``/commerce/v1/accounts/{account_id}/kamereon/kcm/v1/vehicles/{vin}/charge/pause-resume`` Sample return: .. literalinclude:: /../tests/fixtures/kamereon/vehicle_kcm_action/charge-pause-resume.resume.json :language: JSON .. literalinclude:: /../tests/fixtures/kamereon/vehicle_kcm_action/charge-pause-resume.pause.json :language: JSON renault-api-0.2.9/docs/index.rst000066400000000000000000000006011473671120200165340ustar00rootroot00000000000000.. include:: ../README.rst :end-before: github-only .. _Contributor Guide: contributing.html .. _Usage: usage.html .. toctree:: :hidden: :maxdepth: 1 usage reference/renault_api reference/kamereon reference/gigya endpoints contributing Code of Conduct License Changelog renault-api-0.2.9/docs/license.rst000066400000000000000000000000341473671120200170470ustar00rootroot00000000000000.. include:: ../LICENSE.rst renault-api-0.2.9/docs/reference/000077500000000000000000000000001473671120200166345ustar00rootroot00000000000000renault-api-0.2.9/docs/reference/gigya.rst000066400000000000000000000004261473671120200204700ustar00rootroot00000000000000Gigya low-level authentication API Reference ============================================ .. contents:: :local: :backlinks: none Core ---- .. automodule:: renault_api.gigya :members: Data models ----------- .. automodule:: renault_api.gigya.models :members: renault-api-0.2.9/docs/reference/kamereon.rst000066400000000000000000000004041473671120200211650ustar00rootroot00000000000000Kamereon low-level API Reference ================================ .. contents:: :local: :backlinks: none Core ---- .. automodule:: renault_api.kamereon :members: Data models ----------- .. automodule:: renault_api.kamereon.models :members: renault-api-0.2.9/docs/reference/renault_api.rst000066400000000000000000000006741473671120200217000ustar00rootroot00000000000000Renault high-level API Reference ================================ .. contents:: :local: :backlinks: none Session --------------- .. automodule:: renault_api.renault_session :members: Client -------------- .. automodule:: renault_api.renault_client :members: Account --------------- .. automodule:: renault_api.renault_account :members: Vehicle --------------- .. automodule:: renault_api.renault_vehicle :members: renault-api-0.2.9/docs/requirements.txt000066400000000000000000000001511473671120200201570ustar00rootroot00000000000000sphinx==8.1.3 sphinx-click==6.0.0 sphinx-rtd-theme==3.0.2 click==8.1.8 tabulate==0.9.0 dateparser==1.2.0 renault-api-0.2.9/docs/usage.rst000066400000000000000000000001451473671120200165340ustar00rootroot00000000000000CLI Usage ========= .. click:: renault_api.cli.__main__:main :prog: renault-api :nested: full renault-api-0.2.9/noxfile.py000066400000000000000000000133111473671120200157630ustar00rootroot00000000000000"""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 as err: 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 err package = "renault_api" python_versions = ["3.12", "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. """ 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( "darglint", "ruff", "pre-commit", "pre-commit-hooks", ) session.run("pre-commit", *args) if args and args[0] == "install": activate_virtualenv_in_precommit_hooks(session) @session(python="3.12") 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}", "-i 70612", # Disputed - no fix available https://github.com/pyupio/safety/issues/527 ) @session(python=python_versions) def mypy(session: Session) -> None: """Type-check using mypy.""" args = session.posargs or ["src", "tests", "docs/conf.py"] session.install(".[cli]") session.install( "mypy", "pytest", "types-dateparser", "types-tabulate", "types-tzlocal", "typeguard", "pytest-asyncio", "aioresponses", ) 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(".[cli]") session.install( "coverage[toml]", "pytest", "pygments", "pytest-asyncio", "aioresponses", "typeguard", ) 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(".[cli]") session.install("pytest", "typeguard", "pygments", "pytest-asyncio", "aioresponses") 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.12") def docs_build(session: Session) -> None: """Build the documentation.""" args = session.posargs or ["docs", "docs/_build"] session.run("python", "-m", "pip", "install", "-r", "docs/requirements.txt") session.install(".[cli]") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-build", *args) @session(python="3.12") 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.run("python", "-m", "pip", "install", "-r", "docs/requirements.txt") session.install(".[cli]") session.install("sphinx-autobuild") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-autobuild", *args) renault-api-0.2.9/poetry.lock000066400000000000000000006715471473671120200161660ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" version = "2.4.0" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, ] [[package]] name = "aiohttp" version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, ] [package.dependencies] aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aioresponses" version = "0.7.7" description = "Mock out requests made by ClientSession from aiohttp package" optional = false python-versions = "*" files = [ {file = "aioresponses-0.7.7-py2.py3-none-any.whl", hash = "sha256:6975f31fe5e7f2113a41bd387221f31854f285ecbc05527272cd8ba4c50764a3"}, {file = "aioresponses-0.7.7.tar.gz", hash = "sha256:66292f1d5c94a3cb984f3336d806446042adb17347d3089f2d3962dd6e5ba55a"}, ] [package.dependencies] aiohttp = ">=3.3.0,<4.0.0" packaging = ">=22.0" [[package]] name = "aiosignal" version = "1.2.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.6" files = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] [package.dependencies] frozenlist = ">=1.1.0" [[package]] name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = "*" files = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "async-timeout" version = "4.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.6" files = [ {file = "async-timeout-4.0.1.tar.gz", hash = "sha256:b930cb161a39042f9222f6efb7301399c87eeab394727ec5437924a36d6eef51"}, {file = "async_timeout-4.0.1-py3-none-any.whl", hash = "sha256:a22c0b311af23337eb05fcf05a8b51c3ea53729d46fb5460af62bee033cec690"}, ] [package.dependencies] typing-extensions = ">=3.6.5" [[package]] name = "attrs" version = "20.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] [package.extras] dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "authlib" version = "1.3.1" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.8" files = [ {file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"}, {file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"}, ] [package.dependencies] cryptography = "*" [[package]] name = "babel" version = "2.9.1" description = "Internationalization utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] [package.dependencies] pytz = ">=2015.7" [[package]] name = "certifi" version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] pycparser = "*" [[package]] name = "cfgv" version = "3.2.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.6.1" files = [ {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, ] [[package]] name = "charset-normalizer" version = "2.0.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.5.0" files = [ {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] [package.extras] unicode-backport = ["unicodedata2"] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [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.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[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 = "dateparser" version = "1.2.0" description = "Date parsing library designed to parse dates from HTML pages" optional = true python-versions = ">=3.7" files = [ {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"}, {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"}, ] [package.dependencies] python-dateutil = "*" pytz = "*" regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" tzlocal = "*" [package.extras] calendars = ["convertdate", "hijri-converter"] fasttext = ["fasttext"] langdetect = ["langdetect"] [[package]] name = "distlib" version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[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.4" description = "A parser for Python dependency files" optional = false python-versions = ">=3.7" files = [ {file = "dparse-0.6.4-py3-none-any.whl", hash = "sha256:fbab4d50d54d0e739fbb4dedfc3d92771003a5b9aa8545ca7a7045e3b174af57"}, {file = "dparse-0.6.4.tar.gz", hash = "sha256:90b29c39e3edc36c6284c82c4132648eaf28a01863eb3c231c2512196132201a"}, ] [package.dependencies] packaging = "*" tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] all = ["pipenv", "poetry", "pyyaml"] conda = ["pyyaml"] pipenv = ["pipenv"] poetry = ["poetry"] [[package]] name = "exceptiongroup" version = "1.0.0rc9" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.0.0rc9-py3-none-any.whl", hash = "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337"}, {file = "exceptiongroup-1.0.0rc9.tar.gz", hash = "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "identify" version = "1.5.10" description = "File identification library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ {file = "identify-1.5.10-py2.py3-none-any.whl", hash = "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"}, {file = "identify-1.5.10.tar.gz", hash = "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5"}, ] [package.extras] license = ["editdistance"] [[package]] name = "idna" version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[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 = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" optional = false python-versions = "*" files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] [[package]] name = "jinja2" version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [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 = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.7" files = [ {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [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 = ["attrs", "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 = "marshmallow" version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "marshmallow-dataclass" version = "8.7.1" description = "Python library to convert dataclasses into marshmallow schemas." optional = false python-versions = ">=3.8" files = [ {file = "marshmallow_dataclass-8.7.1-py3-none-any.whl", hash = "sha256:405cbaaad9cea56b3de2f85eff32a9880e3bf849f652e7f6de7395e4b1ddc072"}, {file = "marshmallow_dataclass-8.7.1.tar.gz", hash = "sha256:4fb80e1bf7b31ce1b192aa87ffadee2cedb3f6f37bb0042f8500b07e6fad59c4"}, ] [package.dependencies] marshmallow = ">=3.18.0" typeguard = ">=4.0,<5" typing-extensions = {version = ">=4.2.0", markers = "python_version < \"3.11\""} typing-inspect = ">=0.9.0" [package.extras] dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx"] docs = ["sphinx"] lint = ["pre-commit (>=2.17,<3.0)"] tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)"] [[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 = "multidict" version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] name = "mypy" version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] 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.5.0" description = "Node.js virtual environment builder" optional = false python-versions = "*" files = [ {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, ] [[package]] name = "packaging" version = "23.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] [[package]] name = "platformdirs" version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] type = ["mypy (>=1.8)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [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 = "5.0.0" description = "Some out-of-the-box hooks for pre-commit." optional = false python-versions = ">=3.8" files = [ {file = "pre_commit_hooks-5.0.0-py2.py3-none-any.whl", hash = "sha256:8d71cfb582c5c314a5498d94e0104b6567a8b93fb35903ea845c491f4e290a7a"}, {file = "pre_commit_hooks-5.0.0.tar.gz", hash = "sha256:10626959a9eaf602fbfc22bc61b6e75801436f82326bfcee82bb1f2fc4bc646e"}, ] [package.dependencies] "ruamel.yaml" = ">=0.15" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "propcache" version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, ] [[package]] name = "psutil" version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, ] [package.extras] dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] [[package]] name = "pydantic" version = "2.7.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, ] [package.dependencies] annotated-types = ">=0.4.0" pydantic-core = "2.18.4" typing-extensions = ">=4.6.1" [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic" version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, ] [package.dependencies] annotated-types = ">=0.6.0" pydantic-core = "2.23.4" typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] timezone = ["tzdata"] [[package]] name = "pydantic-core" version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-core" version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "python-dateutil" version = "2.8.1" description = "Extensions to the standard Python datetime module" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] [package.dependencies] six = ">=1.5" [[package]] name = "pytz" version = "2020.4" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, ] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "regex" version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = true python-versions = ">=3.8" files = [ {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [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 = "rich" version = "13.3.5" 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.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"}, {file = "rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0,<3.0.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.21" 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.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} [package.extras] docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel.yaml.clib" version = "0.2.6" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.5" files = [ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d3c620a54748a3d4cf0bcfe623e388407c8e85a4b06b8188e126302bcab93ea8"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:210c8fcfeff90514b7133010bf14e3bad652c8efde6b20e00c43854bf94fa5a6"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:61bc5e5ca632d95925907c569daa559ea194a4d16084ba86084be98ab1cec1c6"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1b4139a6ffbca8ef60fdaf9b33dec05143ba746a6f0ae0f9d11d38239211d335"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, ] [[package]] name = "ruff" version = "0.8.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, {file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"}, {file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"}, {file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"}, {file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"}, {file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"}, {file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"}, {file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"}, {file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"}, {file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"}, {file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"}, ] [[package]] name = "safety" version = "3.2.14" description = "Checks installed dependencies for known vulnerabilities and licenses." optional = false python-versions = ">=3.8" files = [ {file = "safety-3.2.14-py3-none-any.whl", hash = "sha256:23ceeb06038ff65607c7f1311bffa3e92b029148b367b360ad8287d9f3395194"}, {file = "safety-3.2.14.tar.gz", hash = "sha256:7a45d88b1903c5b7c370eaeb6ca131a52f147e0b8a0b302265f82824ef92adc7"}, ] [package.dependencies] Authlib = ">=1.2.0" Click = ">=8.0.2" dparse = ">=0.6.4" filelock = ">=3.16.1,<3.17.0" jinja2 = ">=3.1.0" marshmallow = ">=3.15.0" packaging = ">=21.0" psutil = ">=6.1.0,<6.2.0" pydantic = ">=2.6.0,<2.10.0" requests = "*" rich = "*" "ruamel.yaml" = ">=0.17.21" safety_schemas = "0.0.10" setuptools = ">=65.5.1" typer = ">=0.12.1" typing-extensions = ">=4.7.1" urllib3 = ">=1.26.5" [package.extras] github = ["pygithub (>=1.43.3)"] gitlab = ["python-gitlab (>=1.3.0)"] spdx = ["spdx-tools (>=0.8.2)"] [[package]] name = "safety-schemas" version = "0.0.10" description = "Schemas for Safety tools" optional = false python-versions = ">=3.7" files = [ {file = "safety_schemas-0.0.10-py3-none-any.whl", hash = "sha256:83978c14fcf598f00a6d98e70450e635d3deb33b3abbb5a886004ade7ca84b7f"}, {file = "safety_schemas-0.0.10.tar.gz", hash = "sha256:5ec83bb19e17003748d2a4b11e43e1f2b4471c9434329e9a0d80d1069966b96c"}, ] [package.dependencies] dparse = ">=0.6.4" packaging = ">=21.0" pydantic = ">=2.6.0,<2.10.0" ruamel-yaml = ">=0.17.21" typing-extensions = ">=4.7.1" [[package]] name = "setuptools" version = "70.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shellingham" version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "snowballstemmer" version = "2.0.0" description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] [[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 = "6.0.0" description = "Sphinx extension that automatically documents click applications" optional = false python-versions = ">=3.8" files = [ {file = "sphinx_click-6.0.0-py3-none-any.whl", hash = "sha256:1e0a3c83bcb7c55497751b19d07ebe56b5d7b85eb76dd399cf9061b497adc317"}, {file = "sphinx_click-6.0.0.tar.gz", hash = "sha256:f5d664321dc0c6622ff019f1e1c84e58ce0cecfddeb510e004cf60c2a3ab465b"}, ] [package.dependencies] click = ">=8.0" docutils = "*" sphinx = ">=4.0" [[package]] name = "sphinx-rtd-theme" version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, ] [package.dependencies] docutils = ">0.18,<0.22" sphinx = ">=6,<9" sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "transifex-client", "twine", "wheel"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, ] [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.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.6" files = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] [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 = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" optional = true python-versions = ">=3.7" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, ] [package.extras] widechars = ["wcwidth"] [[package]] name = "tomli" version = "1.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.6" files = [ {file = "tomli-1.1.0-py3-none-any.whl", hash = "sha256:f4a182048010e89cbec0ae4686b21f550a7f2903f665e34a6de58ec15424f919"}, {file = "tomli-1.1.0.tar.gz", hash = "sha256:33d7984738f8bb699c9b0a816eb646a8178a69eaa792d258486776a5d21b8ca5"}, ] [[package]] name = "tornado" version = "6.4.2" 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.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, {file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"}, {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946"}, {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf"}, {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"}, {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"}, {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"}, {file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"}, {file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"}, {file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"}, ] [[package]] name = "typeguard" version = "4.4.0" description = "Run-time type checker for Python" optional = false python-versions = ">=3.8" files = [ {file = "typeguard-4.4.0-py3-none-any.whl", hash = "sha256:8ca34c14043f53b2caae7040549ba431770869bcd6287cfa8239db7ecb882b4a"}, {file = "typeguard-4.4.0.tar.gz", hash = "sha256:463bd8697a65a4aa576a63767c369b1ecfba8a5ba735edfe3223127b6ecfa28c"}, ] [package.dependencies] importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} typing-extensions = ">=4.10.0" [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] [[package]] name = "typer" version = "0.15.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, ] [package.dependencies] click = ">=8.0.0" rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] name = "typing-inspect" version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, ] [package.dependencies] mypy-extensions = ">=0.3.0" typing-extensions = ">=3.7.4" [[package]] name = "tzlocal" version = "2.1" description = "tzinfo object for the local timezone" optional = true python-versions = "*" files = [ {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] [package.dependencies] pytz = "*" [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.28.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ {file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"}, {file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "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.2.0" description = "A rewrite of the builtin doctest module" optional = false python-versions = ">=3.8" files = [ {file = "xdoctest-1.2.0-py3-none-any.whl", hash = "sha256:0f1ecf5939a687bd1fc8deefbff1743c65419cce26dff908f8b84c93fbe486bc"}, {file = "xdoctest-1.2.0.tar.gz", hash = "sha256:d8cfca6d8991e488d33f756e600d35b9fdf5efd5c3a249d644efcbbbd2ed5863"}, ] [package.dependencies] colorama = {version = ">=0.4.1", optional = true, markers = "platform_system == \"Windows\" and extra == \"colors\""} Pygments = {version = ">=2.4.1", optional = true, markers = "python_version >= \"3.5.0\" and extra == \"colors\""} [package.extras] all = ["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.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "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 (>=6.2.5)", "pytest-cov (>=3.0.0)", "tomli (>=0.2.0)"] all-strict = ["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.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "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 (==6.2.5)", "pytest-cov (==3.0.0)", "tomli (==0.2.0)"] colors = ["Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "colorama (>=0.4.1)"] colors-strict = ["Pygments (==2.0.0)", "Pygments (==2.4.1)", "colorama (==0.4.1)"] docs = ["Pygments (>=2.9.0)", "myst-parser (>=0.18.0)", "sphinx (>=5.0.1)", "sphinx-autoapi (>=1.8.4)", "sphinx-autobuild (>=2021.3.14)", "sphinx-reredirects (>=0.0.1)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-napoleon (>=0.7)"] docs-strict = ["Pygments (==2.9.0)", "myst-parser (==0.18.0)", "sphinx (==5.0.1)", "sphinx-autoapi (==1.8.4)", "sphinx-autobuild (==2021.3.14)", "sphinx-reredirects (==0.0.1)", "sphinx-rtd-theme (==1.0.0)", "sphinxcontrib-napoleon (==0.7)"] jupyter = ["IPython (>=7.23.1)", "attrs (>=19.2.0)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)"] jupyter-strict = ["IPython (==7.23.1)", "attrs (==19.2.0)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)"] optional = ["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.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "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.23.1)", "Pygments (==2.0.0)", "Pygments (==2.4.1)", "attrs (==19.2.0)", "colorama (==0.4.1)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "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 (>=6.2.5)", "pytest-cov (>=3.0.0)"] tests-binary = ["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-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 (==6.2.5)", "pytest-cov (==3.0.0)"] [[package]] name = "yarl" version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.0" [[package]] name = "zipp" version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] cli = ["click", "dateparser", "tabulate"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" content-hash = "8cdf1116f7cf9d37a261d0b56ad307ec0f2717bb054dadd242f2b97701f3d149" renault-api-0.2.9/poetry.toml000066400000000000000000000000401473671120200161570ustar00rootroot00000000000000[virtualenvs] in-project = true renault-api-0.2.9/pyproject.toml000066400000000000000000000103231473671120200166610ustar00rootroot00000000000000[tool.poetry] name = "renault-api" version = "0.2.9" description = "Renault API" authors = ["epenet"] license = "MIT" readme = "README.rst" homepage = "https://github.com/hacf-fr/renault-api" repository = "https://github.com/hacf-fr/renault-api" documentation = "https://renault-api.readthedocs.io" classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] [tool.poetry.urls] Changelog = "https://github.com/hacf-fr/renault-api/releases" [tool.poetry.dependencies] python = ">=3.8,<4.0" # Warning: as of 2024-04-23, aiohttp is pinned to 3.9.5 on HA-core aiohttp = ">=3.9.5" # Warning: as of 2023-07-19, pyjwt is pinned to 2.8.0 on HA-core PyJWT = ">=2.8.0" #ensure cryptography (for pyjwt) is greater than 42.0.5 to account for PVE-2024-65647 cryptography = ">=42.0.5" marshmallow-dataclass = ">=8.2.0" click = { version = ">=8.0.1", optional = true } tabulate = { version = ">=0.8.7", optional = true } dateparser = {version = ">=1.0.0", optional = true} #ensure six (for dateparser) is greater than 1.16 for python 3.12 compatibility six = {version = "^1.16", optional = true} [tool.poetry.group.dev.dependencies] pytest = ">=7.3.1,<9.0.0" coverage = {extras = ["toml"], version = "^7.2"} safety = ">=2.3.5,<4.0.0" mypy = "^1.2" typeguard = ">=4.1.3,<5.0.0" xdoctest = {extras = ["colors"], version = "^1.1.1"} sphinx = ">=4.3.2,<8.0.0" sphinx-autobuild = "^2021.3.14" pre-commit = ">=2.21,<4.0" ruff = ">=0.3.7" darglint = "^1.8.1" pre-commit-hooks = ">=4.4,<6.0" sphinx-rtd-theme = ">=1.2,<4.0" sphinx-click = ">=4.4,<7.0" Pygments = "^2.15.0" pytest-asyncio = ">=0.21,<0.25" aioresponses = "^0.7.4" pytest-cov = ">=4,<6" #ensure urllib3 (for requests/sphinx) is greater than 1.26.5 to account for CVE-2021-33503 urllib3 = ">=1.26.15,<3.0.0" #ensure certifi (for requests/sphinx) is greater than 2023.7.22 to account for CVE-2023-37920 certifi = ">=2023.7.22,<2025.0.0" #ensure requests (for sphinx) is greater than 2.31.0 to account for CVE-2023-32681 requests = "^2.31.0" #ensure tornado (for sphinx) is greater than 6.3.3 to account for GHSA-qppv-j76h-2rpx tornado = "^6.3.3" #ensure PyYAML (for sphinx) is greater than 6.0.1 to fix Cython wheel PyYAML = "^6.0.1" #ensure virtualenv (for pre-commit) is greater than 20.21.0 to account for PVE-2024-68477 virtualenv = ">=20.21.0" [tool.poetry.extras] cli = ["click", "tabulate", "dateparser"] [tool.poetry.scripts] renault-api = "renault_api.cli.__main__:main" [tool.coverage.paths] source = ["src", "*/site-packages"] [tool.coverage.run] branch = true source = ["renault_api"] [tool.coverage.report] show_missing = true fail_under = 100 [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_untyped_defs = true no_implicit_optional = true no_implicit_reexport = true pretty = true show_column_numbers = true show_error_codes = true show_error_context = true strict_equality = true warn_redundant_casts = true warn_return_any = true warn_unreachable = true warn_unused_configs = true warn_unused_ignores = true [tool.ruff] line-length = 88 target-version = "py38" [tool.ruff.lint] ignore = [ "A001", # Variable is shadowing a Python builtin "A002", # Argument is shadowing a Python builtin "N815", # Variable in class scope should not be mixedCase "PLR0911", # Too many return statements "PLR0913", # Too many arguments in function definition ] select = [ "A", # flake8-builtins "B", # flake8-bugbear "C4", # flake8-comprehensions "C90", # mccabe "E", # pycodestyle error "ERA", # eradicate "F", # Pyflakes "I", # isort "N", # pep8-naming "PL", # Pylint "PT", # flake8-pytest-style "UP", # pyupgrade ] [tool.ruff.lint.isort] force-single-line = true known-local-folder = [ "renault_api", ] [tool.ruff.lint.mccabe] max-complexity = 10 [tool.ruff.lint.pydocstyle] convention = "google" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" renault-api-0.2.9/src/000077500000000000000000000000001473671120200145355ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/000077500000000000000000000000001473671120200170405ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/__init__.py000066400000000000000000000000231473671120200211440ustar00rootroot00000000000000"""Renault API.""" renault-api-0.2.9/src/renault_api/cli/000077500000000000000000000000001473671120200176075ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/cli/__init__.py000066400000000000000000000000231473671120200217130ustar00rootroot00000000000000"""Renault CLI.""" renault-api-0.2.9/src/renault_api/cli/__main__.py000066400000000000000000000160121473671120200217010ustar00rootroot00000000000000"""Command-line interface.""" import errno import json import logging import os from datetime import datetime from io import TextIOWrapper from typing import Any from typing import Dict from typing import Optional import aiohttp import click from click.core import Context from . import helpers from . import renault_account from . import renault_client from . import renault_settings from . import renault_vehicle from .charge import commands as charge_commands from .hvac import commands as hvac_commands from renault_api.credential_store import FileCredentialStore _WARNING_DEBUG_ENABLED = ( "Debug output enabled. Logs may contain personally identifiable " "information and account credentials! Be sure to sanitise these logs " "before sending them to a third party or posting them online." ) def _check_for_debug(debug: bool, log: bool) -> None: """Renault CLI.""" if debug or log: renault_log = logging.getLogger("renault_api") renault_log.setLevel(logging.DEBUG) if log: # create directory try: os.makedirs("logs") except OSError as e: # pragma: no cover if e.errno != errno.EEXIST: raise # create formatter and add it to the handlers formatter = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) # create file handler which logs even debug messages fh = logging.FileHandler(f"logs/{datetime.today():%Y-%m-%d}.log") fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) # And enable our own debug logging renault_log.addHandler(fh) if debug: logging.basicConfig() renault_log.warning(_WARNING_DEBUG_ENABLED) @click.group() @click.version_option() @click.option("--debug", is_flag=True, help="Display debug traces.") @click.option("--log", is_flag=True, help="Log debug traces to file.") @click.option("--json", is_flag=True, help="Return data as JSON") @click.option("--locale", default=None, help="API locale (eg. fr_FR)") @click.option( "--account", default=None, help="Kamereon account ID to use", ) @click.option("--vin", default=None, help="Vehicle VIN to use") @click.pass_context def main( ctx: Context, *, debug: bool, log: bool, json: bool, locale: Optional[str] = None, account: Optional[str] = None, vin: Optional[str] = None, ) -> None: """Main entry point for the Renault CLI.""" ctx.ensure_object(dict) ctx.obj["credential_store"] = FileCredentialStore( os.path.expanduser(renault_settings.CREDENTIAL_PATH) ) _check_for_debug(debug, log) ctx.obj["json"] = json if locale: ctx.obj["locale"] = locale if account: ctx.obj["account"] = account if vin: # pragma: no branch ctx.obj["vin"] = vin main.add_command(charge_commands.charge) main.add_command(hvac_commands.hvac) @main.command() @click.pass_obj @helpers.coro_with_websession async def accounts( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display list of accounts.""" await renault_client.display_accounts(websession, ctx_data) @main.command() @click.option("--user", prompt=True) @click.option("--password", prompt=True, hide_input=True) @click.pass_obj @helpers.coro_with_websession async def login( ctx_data: Dict[str, Any], *, user: str, password: str, websession: aiohttp.ClientSession, ) -> None: """Login to Renault.""" await renault_client.login(websession, ctx_data, user, password) @main.command() def reset() -> None: """Clear all credentials/settings from the credential store.""" renault_settings.reset() @main.command() @click.option("--locale", default=None, help="API locale (eg. fr_FR)") @click.option( "--account", default=None, help="Kamereon account ID to use for future calls" ) @click.option("--vin", default=None, help="Vehicle VIN to use for future calls") @click.pass_obj @helpers.coro_with_websession async def set( ctx_data: Dict[str, Any], *, locale: Optional[str] = None, account: Optional[str] = None, vin: Optional[str] = None, websession: aiohttp.ClientSession, ) -> None: """Store specified settings into credential store.""" await renault_settings.set_options(websession, ctx_data, locale, account, vin) @main.command() @click.pass_obj def settings(ctx_data: Dict[str, Any]) -> None: """Display the current configuration keys.""" renault_settings.display_settings(ctx_data) @main.command() @click.pass_obj @helpers.coro_with_websession async def status( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display vehicle status.""" await renault_vehicle.display_status(websession, ctx_data) @main.command() @click.pass_obj @helpers.coro_with_websession async def vehicles( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display list of vehicles.""" await renault_account.display_vehicles(websession, ctx_data) @main.command() @click.pass_obj @helpers.coro_with_websession async def vehicle( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display vehicle details.""" await renault_vehicle.display_vehicle(websession, ctx_data) @main.command() @click.pass_obj @helpers.coro_with_websession async def contracts( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display vehicle contracts.""" await renault_vehicle.display_contracts(websession, ctx_data) @main.group() def http() -> None: """Raw HTTP.""" pass @http.command(name="get") @click.argument("endpoint") @click.pass_obj @helpers.coro_with_websession async def http_get( ctx_data: Dict[str, Any], *, endpoint: str, websession: aiohttp.ClientSession, ) -> None: """Process HTTP GET request on endpoint.""" await renault_client.http_request(websession, ctx_data, "GET", endpoint) @http.command(name="post-file") @click.argument("endpoint") @click.argument("json-body", type=click.File("rb")) @click.pass_obj @helpers.coro_with_websession async def http_post_file( ctx_data: Dict[str, Any], *, endpoint: str, json_body: TextIOWrapper, websession: aiohttp.ClientSession, ) -> None: """Process HTTP POST request on endpoint.""" await renault_client.http_request( websession, ctx_data, "POST", endpoint, json.load(json_body) ) @http.command(name="post") @click.argument("endpoint") @click.argument("json-body") @click.pass_obj @helpers.coro_with_websession async def http_post( ctx_data: Dict[str, Any], *, endpoint: str, json_body: str, websession: aiohttp.ClientSession, ) -> None: """Process HTTP POST request on endpoint.""" await renault_client.http_request( websession, ctx_data, "POST", endpoint, json.loads(json_body) ) if __name__ == "__main__": # pragma: no cover main(prog_name="renault-api") renault-api-0.2.9/src/renault_api/cli/charge/000077500000000000000000000000001473671120200210405ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/cli/charge/__init__.py000066400000000000000000000000231473671120200231440ustar00rootroot00000000000000"""Renault CLI.""" renault-api-0.2.9/src/renault_api/cli/charge/commands.py000066400000000000000000000006251473671120200232160ustar00rootroot00000000000000"""Command-line interface.""" import click from . import control from . import history from . import schedule @click.group() def charge() -> None: """Charge functionality.""" pass charge.add_command(control.start) charge.add_command(control.stop) charge.add_command(control.mode) charge.add_command(history.history) charge.add_command(history.sessions) charge.add_command(schedule.schedule) renault-api-0.2.9/src/renault_api/cli/charge/control.py000066400000000000000000000031751473671120200231000ustar00rootroot00000000000000"""CLI function for a vehicle.""" from typing import Any from typing import Dict from typing import Optional import aiohttp import click from renault_api.cli import helpers from renault_api.cli import renault_vehicle @click.command() @click.option( "--set", help="Target charge mode (schedule_mode/always/always_schedule)", ) @click.pass_obj @helpers.coro_with_websession async def mode( ctx_data: Dict[str, Any], *, set: Optional[str] = None, websession: aiohttp.ClientSession, ) -> None: """Display or set charge mode.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) if set: write_response = await vehicle.set_charge_mode(set) click.echo(write_response.raw_data) else: read_response = await vehicle.get_charge_mode() click.echo(f"Charge mode: {read_response.chargeMode}") @click.command() @click.pass_obj @helpers.coro_with_websession async def start( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Start charge.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.set_charge_start() click.echo(response.raw_data) @click.command() @click.pass_obj @helpers.coro_with_websession async def stop( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Stop charge.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.set_charge_stop() click.echo(response.raw_data) renault-api-0.2.9/src/renault_api/cli/charge/history.py000066400000000000000000000074751473671120200231300ustar00rootroot00000000000000"""CLI function for a vehicle.""" from typing import Any from typing import Dict from typing import List from typing import Optional import aiohttp import click from tabulate import tabulate from renault_api.cli import helpers from renault_api.cli import renault_vehicle from renault_api.kamereon.models import KamereonVehicleDetails @click.command() @helpers.start_end_option(False) @click.pass_obj @helpers.coro_with_websession async def sessions( ctx_data: Dict[str, Any], *, start: str, end: str, websession: aiohttp.ClientSession, ) -> None: """Display charge sessions.""" parsed_start, parsed_end = helpers.parse_dates(start, end) vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) details = await vehicle.get_details() response = await vehicle.get_charges(start=parsed_start, end=parsed_end) charges: List[Dict[str, Any]] = response.raw_data["charges"] if not charges: # pragma: no cover click.echo("No data available.") return headers = [ "Charge start", "Charge end", "Duration", "Power (kW)", "Started at", "Finished at", "Charge gained", "Energy gained", "Power level", "Status", ] click.echo( tabulate( [_format_charges_item(item, details) for item in charges], headers=headers ) ) def _format_charges_item( item: Dict[str, Any], details: KamereonVehicleDetails ) -> List[str]: duration_unit = ( "minutes" if details.reports_charge_session_durations_in_minutes() else "seconds" ) return [ helpers.get_display_value(item.get("chargeStartDate"), "tzdatetime"), helpers.get_display_value(item.get("chargeEndDate"), "tzdatetime"), helpers.get_display_value(item.get("chargeDuration"), duration_unit), helpers.get_display_value(item.get("chargeStartInstantaneousPower"), "kW"), helpers.get_display_value(item.get("chargeStartBatteryLevel"), "%"), helpers.get_display_value(item.get("chargeEndBatteryLevel"), "%"), helpers.get_display_value(item.get("chargeBatteryLevelRecovered"), "%"), helpers.get_display_value(item.get("chargeEnergyRecovered"), "kWh"), helpers.get_display_value(item.get("chargePower")), helpers.get_display_value(item.get("chargeEndStatus")), ] @click.command() @helpers.start_end_option(True) @click.pass_obj @helpers.coro_with_websession async def history( ctx_data: Dict[str, Any], *, start: str, end: str, period: Optional[str], websession: aiohttp.ClientSession, ) -> None: """Display charge history.""" parsed_start, parsed_end = helpers.parse_dates(start, end) period = period or "month" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.get_charge_history( start=parsed_start, end=parsed_end, period=period ) charge_summaries: List[Dict[str, Any]] = response.raw_data["chargeSummaries"] if not charge_summaries: # pragma: no cover click.echo("No data available.") return headers = [ period.capitalize(), "Number of charges", "Total time charging", "Errors", ] click.echo( tabulate( [_format_charge_history_item(item, period) for item in charge_summaries], headers=headers, ) ) def _format_charge_history_item(item: Dict[str, Any], period: str) -> List[str]: return [ helpers.get_display_value(item.get(period)), helpers.get_display_value(item.get("totalChargesNumber")), helpers.get_display_value(item.get("totalChargesDuration"), "minutes"), helpers.get_display_value(item.get("totalChargesErrors")), ] renault-api-0.2.9/src/renault_api/cli/charge/schedule.py000066400000000000000000000155651473671120200232220ustar00rootroot00000000000000"""CLI function for a vehicle.""" import re from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Tuple import aiohttp import click from tabulate import tabulate from renault_api.cli import helpers from renault_api.cli import renault_vehicle from renault_api.kamereon.helpers import DAYS_OF_WEEK from renault_api.kamereon.models import ChargeDaySchedule from renault_api.kamereon.models import ChargeSchedule from renault_api.renault_vehicle import RenaultVehicle _DAY_SCHEDULE_REGEX = re.compile( "(?PT?)" "(?P[0-2][0-9])" ":" "(?P[0-5][0-9])" "(?PZ?)" "," "(?P[0-9]+)" ) _HOURS_PER_DAY = 24 @click.group() def schedule() -> None: """Display or update charge schedules.""" pass @schedule.command() @click.pass_obj @helpers.coro_with_websession async def show( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Display charge schedules.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.get_charging_settings() # Display mode click.echo(f"Mode: {response.mode}") if not response.schedules: # pragma: no cover click.echo("\nNo schedules found.") return for schedule in response.schedules: click.echo( f"\nSchedule ID: {schedule.id}{' [Active]' if schedule.activated else ''}" ) headers = [ "Day", "Start time", "End time", "Duration", ] click.echo( tabulate( [_format_charge_schedule(schedule, key) for key in DAYS_OF_WEEK], headers=headers, ) ) def _format_charge_schedule(schedule: ChargeSchedule, key: str) -> List[str]: details: Optional[ChargeDaySchedule] = getattr(schedule, key) if not details: # pragma: no cover return [key.capitalize(), "-", "-", "-"] return [ key.capitalize(), helpers.get_display_value(details.startTime, "tztime"), helpers.get_display_value(details.get_end_time(), "tztime"), helpers.get_display_value(details.duration, "minutes"), ] async def _get_schedule( ctx_data: Dict[str, Any], websession: aiohttp.ClientSession, id: int, ) -> Tuple[RenaultVehicle, List[ChargeSchedule], ChargeSchedule]: """Get the given schedules activated-flag to given state.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.get_charging_settings() if not response.schedules: # pragma: no cover raise ValueError("No schedules found.") schedule = next( # pragma: no branch (schedule for schedule in response.schedules if id == schedule.id), None ) if schedule: return (vehicle, response.schedules, schedule) raise IndexError(f"Schedule id {id} not found.") # pragma: no cover @schedule.command() @click.argument("id", type=int) @helpers.days_of_week_option( helptext="{} schedule in format `hh:mm,duration` (for local timezone) " "or `Thh:mmZ,duration` (for utc) or `clear` to unset." ) @click.pass_obj @helpers.coro_with_websession async def set( ctx_data: Dict[str, Any], *, id: int, websession: aiohttp.ClientSession, **kwargs: Any, ) -> None: """Update charging schedule {ID}.""" vehicle, schedules, schedule = await _get_schedule( websession=websession, ctx_data=ctx_data, id=id ) update_settings(schedule, **kwargs) write_response = await vehicle.set_charge_schedules(schedules) click.echo(write_response.raw_data) @schedule.command() @click.argument("id", type=int) @click.pass_obj @helpers.coro_with_websession async def activate( ctx_data: Dict[str, Any], *, id: int, websession: aiohttp.ClientSession, ) -> None: """Activate charging schedule {ID}.""" vehicle, schedules, schedule = await _get_schedule( websession=websession, ctx_data=ctx_data, id=id ) schedule.activated = True write_response = await vehicle.set_charge_schedules(schedules) click.echo(write_response.raw_data) @schedule.command() @click.argument("id", type=int) @click.pass_obj @helpers.coro_with_websession async def deactivate( ctx_data: Dict[str, Any], *, id: int, websession: aiohttp.ClientSession, ) -> None: """Deactivate charging schedule {ID}.""" vehicle, schedules, schedule = await _get_schedule( websession=websession, ctx_data=ctx_data, id=id ) schedule.activated = False write_response = await vehicle.set_charge_schedules(schedules) click.echo(write_response.raw_data) def update_settings( schedule: ChargeSchedule, **kwargs: Any, ) -> None: """Update charging settings.""" for day in DAYS_OF_WEEK: if day in kwargs: # pragma: no branch day_value = kwargs.pop(day) if day_value == "clear": setattr(schedule, day, None) elif day_value: start_time, duration = _parse_day_schedule(str(day_value)) setattr( schedule, day, ChargeDaySchedule( raw_data={}, startTime=start_time, duration=duration ), ) def _parse_day_schedule(raw: str) -> Tuple[str, int]: match = _DAY_SCHEDULE_REGEX.match(raw) if not match: # pragma: no cover raise ValueError( f"Invalid specification for charge schedule: `{raw}`. " "Should be of the form HH:MM,DURATION or THH:MMZ,DURATION" ) hours = int(match.group("hours")) if hours >= _HOURS_PER_DAY: # pragma: no cover raise ValueError( f"Invalid specification for charge schedule: `{raw}`. " "Hours should be less than 24." ) minutes = int(match.group("minutes")) if (minutes % 15) != 0: # pragma: no cover raise ValueError( f"Invalid specification for charge schedule: `{raw}`. " "Minutes should be a multiple of 15." ) duration = int(match.group("duration")) if (duration % 15) != 0: # pragma: no cover raise ValueError( f"Invalid specification for charge schedule: `{raw}`. " "Duration should be a multiple of 15." ) if match.group("prefix") and match.group("suffix"): formatted_start_time = f"T{hours:02g}:{minutes:02g}Z" elif not (match.group("prefix") or match.group("suffix")): formatted_start_time = helpers.convert_minutes_to_tztime(hours * 60 + minutes) else: # pragma: no cover raise ValueError( f"Invalid specification for charge schedule: `{raw}`. " "If provided, both T and Z must be set." ) return (formatted_start_time, duration) renault-api-0.2.9/src/renault_api/cli/helpers.py000066400000000000000000000144171473671120200216320ustar00rootroot00000000000000"""Helpers for Renault API.""" import asyncio import functools from datetime import datetime from datetime import timedelta from typing import Any from typing import Callable from typing import Optional from typing import Tuple import aiohttp import click import dateparser import tzlocal from renault_api.exceptions import RenaultException from renault_api.kamereon.helpers import DAYS_OF_WEEK _DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" def coro_with_websession(func: Callable[..., Any]) -> Callable[..., Any]: """Ensure the routine runs on an event loop with a websession.""" async def run_command(func: Callable[..., Any], *args: Any, **kwargs: Any) -> None: async with aiohttp.ClientSession() as websession: try: kwargs["websession"] = websession await func(*args, **kwargs) except RenaultException as exc: # pragma: no cover raise click.ClickException(str(exc)) from exc finally: closed_event = create_aiohttp_closed_event(websession) await websession.close() await closed_event.wait() def wrapper(*args: Any, **kwargs: Any) -> None: asyncio.run(run_command(func, *args, **kwargs)) return functools.update_wrapper(wrapper, func) def days_of_week_option(helptext: str) -> Callable[..., Any]: """Add day of week string options.""" def decorator(func: Callable[..., Any]) -> Callable[..., Any]: for day in reversed(DAYS_OF_WEEK): func = click.option( f"--{day}", help=helptext.format(day.capitalize()), )(func) return func return decorator def start_end_option(add_period: bool) -> Callable[..., Any]: """Add start/end options.""" def decorator(func: Callable[..., Any]) -> Callable[..., Any]: func = click.option( "--from", "start", help="Date to start showing history from", required=True )(func) func = click.option( "--to", "end", help="Date to finish showing history at (cannot be in the future)", required=True, )(func) if add_period: func = click.option( "--period", default="month", help="Period over which to aggregate.", type=click.Choice(["day", "month"], case_sensitive=False), )(func) return func return decorator def create_aiohttp_closed_event( websession: aiohttp.ClientSession, ) -> asyncio.Event: # pragma: no cover """Work around aiohttp issue that doesn't properly close transports on exit. See https://github.com/aio-libs/aiohttp/issues/1925#issuecomment-639080209 Args: websession (aiohttp.ClientSession): session for which to generate the event. Returns: An event that will be set once all transports have been properly closed. """ transports = 0 all_is_lost = asyncio.Event() def connection_lost(exc, orig_lost): # type: ignore nonlocal transports try: orig_lost(exc) finally: transports -= 1 if transports == 0: all_is_lost.set() def eof_received(orig_eof_received): # type: ignore try: orig_eof_received() except AttributeError: # It may happen that eof_received() is called after # _app_protocol and _transport are set to None. pass for conn in websession.connector._conns.values(): # type: ignore for handler, _ in conn: proto = getattr(handler.transport, "_ssl_protocol", None) if proto is None: continue transports += 1 orig_lost = proto.connection_lost orig_eof_received = proto.eof_received proto.connection_lost = functools.partial( connection_lost, orig_lost=orig_lost ) proto.eof_received = functools.partial( eof_received, orig_eof_received=orig_eof_received ) if transports == 0: all_is_lost.set() return all_is_lost def parse_dates(start: str, end: str) -> Tuple[datetime, datetime]: """Convert start/end string arguments into datetime arguments.""" parsed_start = dateparser.parse(start) parsed_end = dateparser.parse(end) if not parsed_start: # pragma: no cover raise ValueError(f"Unable to parse `{start}` into start datetime.") if not parsed_end: # pragma: no cover raise ValueError(f"Unable to parse `{end}` into end datetime.") return (parsed_start, parsed_end) def _timezone_offset() -> int: """Return UTC offset in minutes.""" utcoffset = tzlocal.get_localzone().utcoffset(datetime.now()) if utcoffset: return int(utcoffset.total_seconds() / 60) return 0 # pragma: no cover def _format_tzdatetime(date_string: str) -> str: date = datetime.fromisoformat(date_string.replace("Z", "+00:00")) return str(date.astimezone(tzlocal.get_localzone()).strftime(_DATETIME_FORMAT)) def _format_tztime(time: str) -> str: total_minutes = int(time[1:3]) * 60 + int(time[4:6]) + _timezone_offset() hours, minutes = divmod(total_minutes, 60) hours = hours % 24 # Ensure it is 00-23 return f"{hours:02g}:{minutes:02g}" def convert_minutes_to_tztime(minutes: int) -> str: """Convert minutes to Thh:mmZ format.""" total_minutes = minutes - _timezone_offset() hours, minutes = divmod(total_minutes, 60) hours = hours % 24 # Ensure it is 00-23 return f"T{hours:02g}:{minutes:02g}Z" def _format_seconds(secs: float) -> str: d = timedelta(seconds=secs) return str(d) def get_display_value( value: Optional[Any] = None, unit: Optional[str] = None, ) -> str: """Get a display for value.""" if value is None: # pragma: no cover return "" if unit is None: return str(value) if unit == "tzdatetime": return _format_tzdatetime(value) if unit == "tztime": return _format_tztime(value) if unit == "minutes": return _format_seconds(value * 60) if unit == "seconds": return _format_seconds(value) if unit == "kW": value = value / 1000 return f"{value:.2f} {unit}" return f"{value} {unit}" renault-api-0.2.9/src/renault_api/cli/hvac/000077500000000000000000000000001473671120200205305ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/cli/hvac/__init__.py000066400000000000000000000000231473671120200226340ustar00rootroot00000000000000"""Renault CLI.""" renault-api-0.2.9/src/renault_api/cli/hvac/commands.py000066400000000000000000000004551473671120200227070ustar00rootroot00000000000000"""Command-line interface.""" import click from . import control from . import history @click.group() def hvac() -> None: """HVAC functionality.""" pass hvac.add_command(control.start) hvac.add_command(control.cancel) hvac.add_command(history.history) hvac.add_command(history.sessions) renault-api-0.2.9/src/renault_api/cli/hvac/control.py000066400000000000000000000027151473671120200225670ustar00rootroot00000000000000"""CLI function for a vehicle.""" from typing import Any from typing import Dict from typing import Optional import aiohttp import click import dateparser from renault_api.cli import helpers from renault_api.cli import renault_vehicle @click.command() @click.option( "--temperature", type=int, help="Target temperature (in Celsius)", required=True ) @click.option( "--at", default=None, help="Date/time at which to complete preconditioning" " (defaults to immediate if not given). You can use" " times like 'in 5 minutes' or 'tomorrow at 9am'.", ) @click.pass_obj @helpers.coro_with_websession async def start( ctx_data: Dict[str, Any], *, temperature: int, at: Optional[str], websession: aiohttp.ClientSession, ) -> None: """Start air conditioning.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) if at: when = dateparser.parse(at) else: when = None response = await vehicle.set_ac_start(temperature=temperature, when=when) click.echo(response.raw_data) @click.command() @click.pass_obj @helpers.coro_with_websession async def cancel( ctx_data: Dict[str, Any], *, websession: aiohttp.ClientSession, ) -> None: """Cancel air conditioning.""" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.set_ac_stop() click.echo(response.raw_data) renault-api-0.2.9/src/renault_api/cli/hvac/history.py000066400000000000000000000026311473671120200226050ustar00rootroot00000000000000"""CLI function for a vehicle.""" from typing import Any from typing import Dict from typing import Optional import aiohttp import click from renault_api.cli import helpers from renault_api.cli import renault_vehicle @click.command() @helpers.start_end_option(True) @click.pass_obj @helpers.coro_with_websession async def history( ctx_data: Dict[str, Any], *, start: str, end: str, period: Optional[str], websession: aiohttp.ClientSession, ) -> None: """Display air conditioning history.""" parsed_start, parsed_end = helpers.parse_dates(start, end) period = period or "month" vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.get_hvac_history( start=parsed_start, end=parsed_end, period=period ) click.echo(response.raw_data) @click.command() @helpers.start_end_option(False) @click.pass_obj @helpers.coro_with_websession async def sessions( ctx_data: Dict[str, Any], *, start: str, end: str, websession: aiohttp.ClientSession, ) -> None: """Display air conditioning sessions.""" parsed_start, parsed_end = helpers.parse_dates(start, end) vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) response = await vehicle.get_hvac_sessions(start=parsed_start, end=parsed_end) click.echo(response.raw_data) renault-api-0.2.9/src/renault_api/cli/renault_account.py000066400000000000000000000101531473671120200233470ustar00rootroot00000000000000"""CLI function for a vehicle.""" from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Tuple import aiohttp import click from tabulate import tabulate from . import renault_client from . import renault_settings from renault_api.credential import Credential from renault_api.credential_store import CredentialStore from renault_api.exceptions import RenaultException from renault_api.kamereon.models import KamereonPersonAccount from renault_api.renault_account import RenaultAccount from renault_api.renault_client import RenaultClient async def _get_account_id(ctx_data: Dict[str, Any], client: RenaultClient) -> str: """Prompt the user for account.""" # First, check context data if "account" in ctx_data: return str(ctx_data["account"]) # Second, check credential store credential_store: CredentialStore = ctx_data["credential_store"] account_id = credential_store.get_value(renault_settings.CONF_ACCOUNT_ID) if account_id: return account_id # Third, prompt the user response = await client.get_person() if not response.accounts: # pragma: no cover raise RenaultException("No account found.") prompt, default = await _get_account_prompt(response.accounts, client) while True: i = int( click.prompt( prompt, default=default, type=click.IntRange(min=1, max=len(response.accounts)), ) ) try: account_id = str(response.accounts[i - 1].accountId) except (KeyError, IndexError) as exc: # pragma: no cover click.echo(f"Invalid option: {exc}.", err=True) else: if click.confirm( # pragma: no branch "Do you want to save the account ID to the credential store?", default=False, ): credential_store[renault_settings.CONF_ACCOUNT_ID] = Credential( account_id ) # Add blank new line click.echo("") return account_id async def _get_account_prompt( accounts: List[KamereonPersonAccount], client: RenaultClient ) -> Tuple[str, Optional[str]]: """Get prompt for selecting account.""" account_table = [] default = None for i, account in enumerate(accounts): if not account.accountId: # pragma: no cover continue api_account = await client.get_api_account(account.accountId) vehicles = await api_account.get_vehicles() if account.accountType == "MYRENAULT": default = str(i + 1) account_table.append( [ i + 1, account.accountId, account.accountType, 0 if vehicles.vehicleLinks is None else len(vehicles.vehicleLinks), ] ) menu = tabulate(account_table, headers=["", "ID", "Type", "Vehicles"]) prompt = f"{menu}\n\nPlease select account" return (prompt, default) async def get_account( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> RenaultAccount: """Get RenaultAccount for use by CLI.""" client = await renault_client.get_logged_in_client( websession=websession, ctx_data=ctx_data ) account_id = await _get_account_id(ctx_data, client) return await client.get_api_account(account_id) async def display_vehicles( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> None: """Display vehicle status.""" account = await get_account(websession, ctx_data) response = await account.get_vehicles() if response.vehicleLinks is None: # pragma: no cover raise ValueError("response.vehicleLinks is None") vehicles = [ [ vehicle.raw_data["vehicleDetails"]["registrationNumber"], vehicle.raw_data["vehicleDetails"]["brand"]["label"], vehicle.raw_data["vehicleDetails"]["model"]["label"], vehicle.vin, ] for vehicle in response.vehicleLinks ] click.echo(tabulate(vehicles, headers=["Registration", "Brand", "Model", "VIN"])) renault-api-0.2.9/src/renault_api/cli/renault_client.py000066400000000000000000000130041473671120200231670ustar00rootroot00000000000000"""Singletons for the CLI.""" import json from locale import getdefaultlocale from typing import Any from typing import Dict from typing import Optional import aiohttp import click from tabulate import tabulate from renault_api.cli import renault_account from renault_api.cli import renault_vehicle from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential_store import CredentialStore from renault_api.exceptions import RenaultException from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.helpers import get_api_keys from renault_api.renault_client import RenaultClient from renault_api.renault_session import RenaultSession async def get_locale( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> str: """Prompt the user for locale.""" credential_store: CredentialStore = ctx_data["credential_store"] locale = credential_store.get_value(CONF_LOCALE) if locale: return locale default_locale = getdefaultlocale()[0] while True: locale = click.prompt("Please select a locale", default=default_locale) if locale: # pragma: no branch try: await get_api_keys(locale, websession=websession) except RenaultException as exc: # pragma: no cover click.echo(f"Locale `{locale}` is unknown: {exc}", err=True) else: if click.confirm( "Do you want to save the locale to the credential store?", default=False, ): credential_store[CONF_LOCALE] = Credential(locale) # Add blank new line click.echo("") return locale async def _create_renault_session( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> RenaultSession: """Get RenaultClient for use by CLI.""" credential_store: CredentialStore = ctx_data["credential_store"] locale = ctx_data.get("locale") if not locale: locale = await get_locale(websession, ctx_data) country = locale[-2:] locale_details = await get_api_keys(locale=locale, websession=websession) return RenaultSession( websession=websession, locale=locale, country=country, locale_details=locale_details, credential_store=credential_store, ) async def _get_logged_in_session( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> RenaultSession: """Get RenaultSession for use by CLI.""" session = await _create_renault_session(websession=websession, ctx_data=ctx_data) credential_store: CredentialStore = ctx_data["credential_store"] if GIGYA_LOGIN_TOKEN not in credential_store: await _prompt_login(session) return session async def get_logged_in_client( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> RenaultClient: """Get RenaultClient for use by CLI.""" session = await _get_logged_in_session(websession=websession, ctx_data=ctx_data) return RenaultClient(session=session) async def _prompt_login(session: RenaultSession) -> None: """Prompt the user for credentials.""" while True: user = click.prompt("User") password = click.prompt("Password", hide_input=True) try: await session.login(user, password) except RenaultException as exc: # pragma: no cover click.echo(f"Login failed: {exc}.", err=True) else: # Add blank new line click.echo("") return async def login( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any], user: str, password: str, ) -> None: """Attempt login.""" session = await _create_renault_session(websession=websession, ctx_data=ctx_data) await session.login(user, password) async def display_accounts( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> None: """Display accounts.""" client = await get_logged_in_client(websession=websession, ctx_data=ctx_data) response = await client.get_person() if response.accounts is None: # pragma: no cover raise ValueError("response.accounts is None") accounts = {account.accountType: account.accountId for account in response.accounts} click.echo(tabulate(accounts.items(), headers=["Type", "ID"])) async def http_get_endpoint( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any], endpoint: str ) -> str: """Run HTTP GET request.""" if "{account_id}" in endpoint: # pragma: no branch account = await renault_account.get_account( websession=websession, ctx_data=ctx_data ) endpoint = endpoint.replace("{account_id}", account.account_id) if "{vin}" in endpoint: # pragma: no branch vehicle = await renault_vehicle.get_vehicle( websession=websession, ctx_data=ctx_data ) endpoint = endpoint.replace("{vin}", vehicle.vin) return endpoint async def http_request( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any], method: str, endpoint: str, json_body: Optional[Dict[str, Any]] = None, ) -> None: """Run HTTP request.""" endpoint = await http_get_endpoint(websession, ctx_data, endpoint) session = await _get_logged_in_session(websession=websession, ctx_data=ctx_data) response = await session.http_request(method, endpoint, json_body) if ctx_data["json"]: # pragma: no cover click.echo(json.dumps(response.raw_data)) else: click.echo(response.raw_data) renault-api-0.2.9/src/renault_api/cli/renault_settings.py000066400000000000000000000034321473671120200235550ustar00rootroot00000000000000"""Singletons for the CLI.""" import os from textwrap import TextWrapper from typing import Any from typing import Dict from typing import Optional import aiohttp import click from tabulate import tabulate from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential_store import CredentialStore from renault_api.helpers import get_api_keys CONF_ACCOUNT_ID = "accound-id" CONF_VIN = "vin" CREDENTIAL_PATH = "~/.credentials/renault-api.json" async def set_options( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any], locale: Optional[str], account: Optional[str], vin: Optional[str], ) -> None: """Set configuration keys.""" credential_store: CredentialStore = ctx_data["credential_store"] if locale: # Ensure API keys are available api_keys = await get_api_keys(locale, websession=websession) credential_store[CONF_LOCALE] = Credential(locale) for k, v in api_keys.items(): credential_store[k] = Credential(v) if account: credential_store[CONF_ACCOUNT_ID] = Credential(account) if vin: credential_store[CONF_VIN] = Credential(vin) def display_settings(ctx_data: Dict[str, Any]) -> None: """Get the current configuration keys.""" credential_store: CredentialStore = ctx_data["credential_store"] wrapper = TextWrapper(width=80) items = [ [key, "\n".join(wrapper.wrap(credential_store.get_value(key) or "-"))] for key in credential_store._store.keys() ] click.echo(tabulate(items, headers=["Key", "Value"])) def reset() -> None: """Clear all credentials/settings from the credential store.""" try: os.remove(os.path.expanduser(CREDENTIAL_PATH)) except FileNotFoundError: pass renault-api-0.2.9/src/renault_api/cli/renault_vehicle.py000066400000000000000000000335461473671120200233450ustar00rootroot00000000000000"""CLI function for a vehicle.""" import json from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Tuple import aiohttp import click from tabulate import tabulate from . import helpers from . import renault_account from . import renault_settings from renault_api.credential import Credential from renault_api.credential_store import CredentialStore from renault_api.exceptions import RenaultException from renault_api.kamereon.exceptions import KamereonResponseException from renault_api.kamereon.exceptions import QuotaLimitException from renault_api.kamereon.models import KamereonVehiclesLink from renault_api.renault_account import RenaultAccount from renault_api.renault_vehicle import RenaultVehicle async def _get_vin(ctx_data: Dict[str, Any], account: RenaultAccount) -> str: """Prompt the user for vin.""" # First, check context data if "vin" in ctx_data: return str(ctx_data["vin"]) # Second, check credential store credential_store: CredentialStore = ctx_data["credential_store"] vin = credential_store.get_value(renault_settings.CONF_VIN) if vin: return vin # Third, prompt the user response = await account.get_vehicles() if not response.vehicleLinks: # pragma: no cover raise RenaultException("No vehicle found.") prompt, default = await _get_vehicle_prompt(response.vehicleLinks, account) while True: i = int( click.prompt( prompt, default=default, type=click.IntRange(min=1, max=len(response.vehicleLinks)), ) ) try: vin = str(response.vehicleLinks[i - 1].vin) except (KeyError, IndexError) as exc: # pragma: no cover click.echo(f"Invalid option: {exc}.", err=True) else: if click.confirm( # pragma: no branch "Do you want to save the VIN to the credential store?", default=False, ): credential_store[renault_settings.CONF_VIN] = Credential(vin) click.echo("") return vin async def _get_vehicle_prompt( vehicle_links: List[KamereonVehiclesLink], account: RenaultAccount ) -> Tuple[str, Optional[str]]: """Get prompt for selecting vehicle.""" vehicle_table = [] default = None for i, vehicle in enumerate(vehicle_links): if not vehicle.vehicleDetails: # pragma: no cover continue vehicle_details = vehicle.vehicleDetails vehicle_table.append( [ i + 1, vehicle_details.vin, vehicle_details.registrationNumber, vehicle_details.get_brand_label(), vehicle_details.get_model_label(), ] ) if len(vehicle_table) == 1: # pragma: no branch default = "1" menu = tabulate( vehicle_table, headers=["", "Vin", "Registration", "Brand", "Model"] ) prompt = f"{menu}\n\nPlease select vehicle" return (prompt, default) async def get_vehicle( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> RenaultVehicle: """Get RenaultVehicle for use by CLI.""" account = await renault_account.get_account(websession, ctx_data) vin = await _get_vin(ctx_data, account) return await account.get_api_vehicle(vin) async def display_vehicle( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> None: """Display vehicle status.""" vehicle = await get_vehicle(websession, ctx_data) response = await vehicle.get_details() vehicles = [ [ response.raw_data["registrationNumber"], response.raw_data["brand"]["label"], response.raw_data["model"]["label"], response.vin, ] ] click.echo(tabulate(vehicles, headers=["Registration", "Brand", "Model", "VIN"])) async def display_contracts( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> None: """Display vehicle contracts.""" vehicle = await get_vehicle(websession, ctx_data) response = await vehicle.get_contracts() contracts = [ [ contract.type, contract.code, contract.description, contract.startDate, contract.endDate, contract.statusLabel, ] for contract in response ] click.echo( tabulate( contracts, headers=["Type", "Code", "Description", "Start", "End", "Status"] ) ) async def display_status( websession: aiohttp.ClientSession, ctx_data: Dict[str, Any] ) -> None: """Display vehicle status.""" vehicle = await get_vehicle(websession, ctx_data) status_table: Dict[str, Any] = {} await update_battery_status(vehicle, status_table, ctx_data) await update_charge_mode(vehicle, status_table, ctx_data) await update_cockpit(vehicle, status_table, ctx_data) await update_location(vehicle, status_table, ctx_data) await update_lock_status(vehicle, status_table, ctx_data) await update_res_state(vehicle, status_table, ctx_data) await update_hvac_status(vehicle, status_table, ctx_data) await update_tyre_pressure(vehicle, status_table, ctx_data) if ctx_data["json"]: click.echo(json.dumps(status_table)) return click.echo(tabulate(status_table.items())) def update_status_table( status_table: Dict[str, Any], key: str, value: Optional[Any], unit: Optional[str], ) -> None: """Update statuses with formatted strings.""" if value is None: return status_table[key] = helpers.get_display_value(value, unit) async def update_battery_status( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_battery_status.""" try: if not (await vehicle.get_details()).uses_electricity(): return if not await vehicle.supports_endpoint("battery-status"): # pragma: no cover return response = await vehicle.get_battery_status() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"battery-status: {exc.error_details}", err=True) return if response.batteryAvailableEnergy == 0: # pragma: no branch response.batteryAvailableEnergy = None if ( # pragma: no branch response.chargingStatus == -1.0 and response.plugStatus == 0 ): response.chargingStatus = 0.0 if ctx_data["json"]: status_table["battery-status"] = response.raw_data return items = [ ("Battery level", response.batteryLevel, "%"), ("Last updated", response.timestamp, "tzdatetime"), ("Available energy", response.batteryAvailableEnergy, "kWh"), ("Range estimate", response.batteryAutonomy, "km"), ("Plug state", response.get_plug_status(), None), ("Charging state", response.get_charging_status(), None), ("Charge rate", response.chargingInstantaneousPower, "kW"), ("Time remaining", response.chargingRemainingTime, "min"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_tyre_pressure( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_tyre_pressure.""" try: if not await vehicle.supports_endpoint("pressure"): # pragma: no cover return response = await vehicle.get_tyre_pressure() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"pressure: {exc.error_details}", err=True) return if ctx_data["json"]: status_table["pressure"] = response.raw_data return items = [ ("Front left pressure", response.flPressure, "bar"), ("Front right pressure", response.frPressure, "bar"), ("Rear left pressure", response.rlPressure, "bar"), ("Rear right pressure", response.rrPressure, "bar"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_charge_mode( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_charge_mode.""" try: if not (await vehicle.get_details()).uses_electricity(): return if not await vehicle.supports_endpoint("charge-mode"): # pragma: no cover return response = await vehicle.get_charge_mode() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"charge-mode: {exc.error_details}", err=True) return if ctx_data["json"]: status_table["charge-mode"] = response.raw_data return items = [("Charge mode", response.chargeMode, None)] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_cockpit( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_cockpit.""" try: if not await vehicle.supports_endpoint("cockpit"): # pragma: no cover return response = await vehicle.get_cockpit() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"cockpit: {exc.error_details}", err=True) return if ctx_data["json"]: status_table["cockpit"] = response.raw_data return items = [ ("Total mileage", response.totalMileage, "km"), ("Fuel autonomy", response.fuelAutonomy, "km"), ("Fuel quantity", response.fuelQuantity, "L"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_location( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_location.""" try: if not await vehicle.supports_endpoint("location"): return response = await vehicle.get_location() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"location: {exc.error_details}", err=True) return if ctx_data["json"]: # pragma: no cover status_table["location"] = response.raw_data return items = [ ("GPS Latitude", response.gpsLatitude, None), ("GPS Longitude", response.gpsLongitude, None), ("GPS last updated", response.lastUpdateTime, "tzdatetime"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_lock_status( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_lock_status.""" try: if not await vehicle.supports_endpoint("lock-status"): return response = await vehicle.get_lock_status() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"lock status: {exc.error_details}", err=True) return if ctx_data["json"]: # pragma: no cover status_table["lock-status"] = response.raw_data return items = [ ("Lock status", response.lockStatus, None), ("Lock last updated", response.lastUpdateTime, "tzdatetime"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_res_state( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_res_state.""" try: if not await vehicle.supports_endpoint("res-state"): # pragma: no cover return response = await vehicle.get_res_state() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"res state: {exc.error_details}", err=True) return if ctx_data["json"]: status_table["res-state"] = response.raw_data return items = [ ("Engine state", response.details, None), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) async def update_hvac_status( vehicle: RenaultVehicle, status_table: Dict[str, Any], ctx_data: Dict[str, Any] ) -> None: """Update status table from get_vehicle_hvac_status.""" try: if not await vehicle.supports_endpoint("hvac-status"): return response = await vehicle.get_hvac_status() except QuotaLimitException as exc: # pragma: no cover raise click.ClickException(repr(exc)) from exc except KamereonResponseException as exc: # pragma: no cover click.echo(f"hvac-status: {exc.error_details}", err=True) return if ctx_data["json"]: status_table["hvac-status"] = response.raw_data return items = [ ("HVAC status", response.hvacStatus, None), ("HVAC start at", response.nextHvacStartDate, "tzdatetime"), ("External temperature", response.externalTemperature, "°C"), ] for key, value, unit in items: update_status_table(status_table, key, value, unit) renault-api-0.2.9/src/renault_api/const.py000066400000000000000000000200161473671120200205370ustar00rootroot00000000000000"""Constants for Renault API.""" CONF_COUNTRY = "country" CONF_LOCALE = "locale" CONF_GIGYA_APIKEY = "gigya-api-key" CONF_GIGYA_URL = "gigya-root-url" CONF_KAMEREON_APIKEY = "kamereon-api-key" CONF_KAMEREON_URL = "kamereon-root-url" PERMANENT_KEYS = [ CONF_COUNTRY, CONF_LOCALE, CONF_GIGYA_APIKEY, CONF_GIGYA_URL, CONF_KAMEREON_APIKEY, CONF_KAMEREON_URL, ] GIGYA_URL_EU = "https://accounts.eu1.gigya.com" GIGYA_URL_US = "https://accounts.us1.gigya.com" KAMEREON_APIKEY = "YjkKtHmGfaceeuExUDKGxrLZGGvtVS0J" KAMEREON_URL_EU = "https://api-wired-prod-1-euw1.wrd-aws.com" KAMEREON_URL_US = "https://api-wired-prod-1-usw2.wrd-aws.com" LOCALE_BASE_URL = ( "https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com" ) AVAILABLE_LOCALES = { "bg_BG": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3__3ER_6lFvXEXHTP_faLtq6eEdbKDXd9F5GoKwzRyZq37ZQ-db7mXcLzR1Jtls5sn", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "cs_CZ": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_oRlKr5PCVL_sPWUZdJ8c5NOl5Ej8nIZw7VKG7S9Rg36UkDszFzfHfxCaUAUU5or2", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "da_DK": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_5x-2C8b1R4MJPQXkwTPdIqgBpcw653Dakw_ZaEneQRkTBdg9UW9Qg_5G-tMNrTMc", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "de_DE": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_7PLksOyBRkHv126x5WhHb-5pqC1qFR8pQjxSeLB6nhAnPERTUlwnYoznHSxwX668", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "de_AT": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3__B4KghyeUb0GlpU62ZXKrjSfb7CPzwBS368wioftJUL5qXE0Z_sSy0rX69klXuHy", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "de_CH": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_UyiWZs_1UXYCUqK_1n7l7l44UiI_9N9hqwtREV0-UYA_5X7tOV-VKvnGxPBww4q2", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "en_GB": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_e8d4g4SE_Fo8ahyHwwP7ohLGZ79HKNN2T8NjQqoNnk6Epj6ilyYwKdHUyCw3wuxz", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "en_IE": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_Xn7tuOnT9raLEXuwSI1_sFFZNEJhSD0lv3gxkwFtGI-RY4AgiePBiJ9EODh8d9yo", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "es_ES": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_DyMiOwEaxLcPdBTu63Gv3hlhvLaLbW3ufvjHLeuU8U5bx3zx19t5rEKq7KMwk9f1", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "es_MX": { CONF_GIGYA_URL: GIGYA_URL_US, CONF_GIGYA_APIKEY: "3_BFzR-2wfhMhUs5OCy3R8U8IiQcHS-81vF8bteSe8eFrboMTjEWzbf4pY1aHQ7cW0", # noqa CONF_KAMEREON_URL: KAMEREON_URL_US, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "fi_FI": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_xSRCLDYhk1SwSeYQLI3DmA8t-etfAfu5un51fws125ANOBZHgh8Lcc4ReWSwaqNY", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "fr_FR": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "fr_BE": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_ZK9x38N8pzEvdiG7ojWHeOAAej43APkeJ5Av6VbTkeoOWR4sdkRc-wyF72HzUB8X", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "fr_CH": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_h3LOcrKZ9mTXxMI9clb2R1VGAWPke6jMNqMw4yYLz4N7PGjYyD0hqRgIFAIHusSn", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "fr_LU": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_zt44Wl_wT9mnqn-BHrR19PvXj3wYRPQKLcPbGWawlatFR837KdxSZZStbBTDaqnb", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "hr_HR": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_HcDC5GGZ89NMP1jORLhYNNCcXt7M3thhZ85eGrcQaM2pRwrgrzcIRWEYi_36cFj9", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "hu_HU": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_nGDWrkSGZovhnVFv5hdIxyuuCuJGZfNmlRGp7-5kEn9yb0bfIfJqoDa2opHOd3Mu", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "it_IT": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_js8th3jdmCWV86fKR3SXQWvXGKbHoWFv8NAgRbH7FnIBsi_XvCpN_rtLcI07uNuq", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "it_CH": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_gHkmHaGACxSLKXqD_uDDx415zdTw7w8HXAFyvh0qIP0WxnHPMF2B9K_nREJVSkGq", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "nl_NL": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_ZIOtjqmP0zaHdEnPK7h1xPuBYgtcOyUxbsTY8Gw31Fzy7i7Ltjfm-hhPh23fpHT5", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "nl_BE": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_yachztWczt6i1pIMhLIH9UA6DXK6vXXuCDmcsoA4PYR0g35RvLPDbp49YribFdpC", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "no_NO": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_QrPkEJr69l7rHkdCVls0owC80BB4CGz5xw_b0gBSNdn3pL04wzMBkcwtbeKdl1g9", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "pl_PL": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_2YBjydYRd1shr6bsZdrvA9z7owvSg3W5RHDYDp6AlatXw9hqx7nVoanRn8YGsBN8", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "pt_PT": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3__afxovspi2-Ip1E5kNsAgc4_35lpLAKCF6bq4_xXj2I2bFPjIWxAOAQJlIkreKTD", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "ro_RO": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_WlBp06vVHuHZhiDLIehF8gchqbfegDJADPQ2MtEsrc8dWVuESf2JCITRo5I2CIxs", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "ru_RU": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_N_ecy4iDyoRtX8v5xOxewwZLKXBjRgrEIv85XxI0KJk8AAdYhJIi17LWb086tGXR", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "sk_SK": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_e8d4g4SE_Fo8ahyHwwP7ohLGZ79HKNN2T8NjQqoNnk6Epj6ilyYwKdHUyCw3wuxz", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "sl_SI": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_QKt0ADYxIhgcje4F3fj9oVidHsx3JIIk-GThhdyMMQi8AJR0QoHdA62YArVjbZCt", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, "sv_SE": { CONF_GIGYA_URL: GIGYA_URL_EU, CONF_GIGYA_APIKEY: "3_EN5Hcnwanu9_Dqot1v1Aky1YelT5QqG4TxveO0EgKFWZYu03WkeB9FKuKKIWUXIS", # noqa CONF_KAMEREON_URL: KAMEREON_URL_EU, CONF_KAMEREON_APIKEY: KAMEREON_APIKEY, }, } renault-api-0.2.9/src/renault_api/credential.py000066400000000000000000000013711473671120200215260ustar00rootroot00000000000000"""Credential dataclass.""" import time from dataclasses import dataclass import jwt @dataclass class Credential: """Base credential that never expires.""" value: str def has_expired(self) -> bool: """Check if Credential has expired.""" return False @dataclass class JWTCredential(Credential): """JWT credential with expiry time.""" expiry: float def __init__(self, value: str) -> None: """Initialise JWTCredential.""" decoded_token = jwt.decode(value, options={"verify_signature": False}) super().__init__(value=value) self.expiry = decoded_token["exp"] def has_expired(self) -> bool: """Check if JWT token has expired.""" return self.expiry < time.time() renault-api-0.2.9/src/renault_api/credential_store.py000066400000000000000000000101361473671120200227410ustar00rootroot00000000000000"""Kamereon client for interaction with Renault servers.""" import json import os from typing import Dict from typing import List from typing import Optional import jwt from renault_api.const import PERMANENT_KEYS from renault_api.credential import Credential from renault_api.credential import JWTCredential class CredentialStore: """Credential store.""" def __init__(self) -> None: """Initialise the credential store.""" self._store: Dict[str, Credential] = {} def __getitem__(self, name: str) -> Credential: """Get a credential the credential store.""" if name in list(self._store.keys()): cred = self._store[name] if not cred.has_expired(): return cred raise KeyError(name) def get(self, name: str) -> Optional[Credential]: """Get a credential the credential store.""" if name in list(self._store.keys()): cred = self._store[name] if not cred.has_expired(): return cred return None def get_value(self, name: str) -> Optional[str]: """Get a credential value from the credential store.""" if name in list(self._store.keys()): cred = self._store[name] if not cred.has_expired(): return cred.value return None def __delitem__(self, name: str) -> None: """Remove a credential from the credential store.""" del self._store[name] self._write() def __setitem__(self, name: str, value: Credential) -> None: """Add a credential to the credential store.""" if not isinstance(name, str): # pragma: no cover raise TypeError("`name` must be a string") if not isinstance(value, Credential): # pragma: no cover raise TypeError("`value` must be a Credential") self._store[name] = value self._write() def __contains__(self, name: str) -> bool: """Check if a credential is in the credential store.""" if name in self._store: cred = self._store[name] if not cred.has_expired(): return True return False def _write(self) -> None: """Writes the content to fixed storage.""" pass def clear(self) -> None: """Remove all non-permanent keys from credential store.""" for key in list(self._store.keys()): if key not in PERMANENT_KEYS: del self._store[key] self._write() def clear_keys(self, to_delete: List[str]) -> None: """Remove specified keys from credential store.""" for key in list(self._store.keys()): if key in to_delete: del self._store[key] self._write() class CredentialEncoder(json.JSONEncoder): """Custom encoder for Credential class.""" def default(self, obj: Credential) -> str: """Store the value.""" return obj.value class FileCredentialStore(CredentialStore): """Credential store with items stored in a file.""" def __init__(self, store_location: str) -> None: """Initialise the credential store.""" super().__init__() self._store_location = store_location self._read() def _read(self) -> None: """Read data from store location.""" if not os.path.exists(self._store_location): return with open(self._store_location) as json_file: data = json.load(json_file) for key, value in data.items(): if key == "gigya_jwt": try: self[key] = JWTCredential(value) except jwt.ExpiredSignatureError: # pragma: no cover pass else: self[key] = Credential(value) def _write(self) -> None: """Write data to store location.""" dirname = os.path.dirname(self._store_location) if not os.path.isdir(dirname): os.makedirs(dirname) with open(self._store_location, "w") as json_file: json.dump(self._store, json_file, cls=CredentialEncoder) renault-api-0.2.9/src/renault_api/exceptions.py000066400000000000000000000004411473671120200215720ustar00rootroot00000000000000"""Exceptions for Renault API.""" class RenaultException(Exception): # noqa: N818 """Base exception for Renault API errors.""" pass class NotAuthenticatedException(RenaultException): # noqa: N818 """You are not authenticated, or authentication has expired.""" pass renault-api-0.2.9/src/renault_api/gigya/000077500000000000000000000000001473671120200201405ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/gigya/__init__.py000066400000000000000000000061061473671120200222540ustar00rootroot00000000000000"""Gigya API.""" import logging from json import JSONDecodeError from typing import Any from typing import Dict from typing import cast import aiohttp from marshmallow.schema import Schema from . import models from . import schemas from .exceptions import GigyaException GIGYA_JWT = "gigya_jwt" GIGYA_LOGIN_TOKEN = "gigya_login_token" # nosec GIGYA_PERSON_ID = "gigya_person_id" GIGYA_KEYS = [GIGYA_JWT, GIGYA_LOGIN_TOKEN, GIGYA_PERSON_ID] _LOGGER = logging.getLogger(__name__) async def request( websession: aiohttp.ClientSession, method: str, url: str, data: Dict[str, Any], schema: Schema, ) -> models.GigyaResponse: """Send request to Gigya.""" async with websession.request(method, url, data=data) as http_response: response_text = await http_response.text() # Don't log on Gigya, to avoid unnecessary exposure. try: gigya_response: models.GigyaResponse = schema.loads(response_text) except JSONDecodeError as err: raise GigyaException("Gigya responded with invalid JSON") from err # Check for Gigya error gigya_response.raise_for_error_code() # Check for HTTP error http_response.raise_for_status() return gigya_response async def login( websession: aiohttp.ClientSession, root_url: str, api_key: str, login_id: str, password: str, ) -> models.GigyaLoginResponse: """Send POST to /accounts.login.""" return cast( models.GigyaLoginResponse, await request( websession, "POST", f"{root_url}/accounts.login", data={ "ApiKey": api_key, "loginID": login_id, "password": password, }, schema=schemas.GigyaLoginResponseSchema, ), ) async def get_account_info( websession: aiohttp.ClientSession, root_url: str, api_key: str, login_token: str, ) -> models.GigyaGetAccountInfoResponse: """Send POST to /accounts.getAccountInfo.""" return cast( models.GigyaGetAccountInfoResponse, await request( websession, "POST", f"{root_url}/accounts.getAccountInfo", data={ "ApiKey": api_key, "login_token": login_token, }, schema=schemas.GigyaGetAccountInfoResponseSchema, ), ) async def get_jwt( websession: aiohttp.ClientSession, root_url: str, api_key: str, login_token: str, ) -> models.GigyaGetJWTResponse: """Send POST to /accounts.getJWT.""" return cast( models.GigyaGetJWTResponse, await request( websession, "POST", f"{root_url}/accounts.getJWT", data={ "ApiKey": api_key, "login_token": login_token, # gigyaDataCenter may be needed for future jwt validation "fields": "data.personId,data.gigyaDataCenter", "expiration": 900, }, schema=schemas.GigyaGetJWTResponseSchema, ), ) renault-api-0.2.9/src/renault_api/gigya/exceptions.py000066400000000000000000000011341473671120200226720ustar00rootroot00000000000000"""Gigya exceptions.""" from typing import Optional from renault_api.exceptions import RenaultException class GigyaException(RenaultException): """Base exception for Gigya errors.""" pass class GigyaResponseException(GigyaException): """Gigya returned a parsable errors.""" def __init__(self, error_code: int, error_details: Optional[str]): """Initialise GigyaResponseException.""" self.error_code = error_code self.error_details = error_details class InvalidCredentialsException(GigyaResponseException): """Invalid loginID or password.""" pass renault-api-0.2.9/src/renault_api/gigya/models.py000066400000000000000000000054161473671120200220030ustar00rootroot00000000000000"""Gigya models.""" from dataclasses import dataclass from typing import Any from typing import Dict from typing import List from typing import Optional from . import exceptions from renault_api.models import BaseModel COMMON_ERRRORS: List[Dict[str, Any]] = [ { "errorCode": 403042, "error_type": exceptions.InvalidCredentialsException, } ] @dataclass class GigyaResponse(BaseModel): """Gigya response.""" errorCode: int errorDetails: Optional[str] def raise_for_error_code(self) -> None: """Checks the response information.""" if self.errorCode > 0: for common_error in COMMON_ERRRORS: if self.errorCode == common_error["errorCode"]: error_type = common_error["error_type"] raise error_type(self.errorCode, self.errorDetails) raise exceptions.GigyaResponseException(self.errorCode, self.errorDetails) @dataclass class GigyaLoginSessionInfo(BaseModel): """Gigya Login sessionInfo details.""" cookieValue: Optional[str] @dataclass class GigyaLoginResponse(GigyaResponse): """Gigya response to POST on /accounts.login.""" sessionInfo: Optional[GigyaLoginSessionInfo] def get_session_cookie(self) -> str: """Return cookie value from session information.""" if not self.sessionInfo: # pragma: no cover raise exceptions.GigyaException("`sessionInfo` is None in Login response.") if not self.sessionInfo.cookieValue: # pragma: no cover raise exceptions.GigyaException( "`sessionInfo.cookieValue` is None in Login response." ) return self.sessionInfo.cookieValue @dataclass class GigyaGetAccountInfoData(BaseModel): """Gigya GetAccountInfo data details.""" personId: Optional[str] @dataclass class GigyaGetAccountInfoResponse(GigyaResponse): """Gigya response to POST on /accounts.getAccountInfo.""" data: Optional[GigyaGetAccountInfoData] def get_person_id(self) -> str: """Return person id.""" if not self.data: # pragma: no cover raise exceptions.GigyaException( "`data` is None in GetAccountInfo response." ) if not self.data.personId: # pragma: no cover raise exceptions.GigyaException( "`data.personId` is None in GetAccountInfo response." ) return self.data.personId @dataclass class GigyaGetJWTResponse(GigyaResponse): """Gigya response to POST on /accounts.getJWT.""" id_token: Optional[str] def get_jwt(self) -> str: """Return jwt token.""" if not self.id_token: # pragma: no cover raise exceptions.GigyaException("`id_token` is None in GetJWT response.") return self.id_token renault-api-0.2.9/src/renault_api/gigya/schemas.py000066400000000000000000000011501473671120200221320ustar00rootroot00000000000000"""Gigya schemas.""" import marshmallow_dataclass from . import models from renault_api.models import BaseSchema GigyaResponseSchema = marshmallow_dataclass.class_schema( models.GigyaResponse, base_schema=BaseSchema )() GigyaLoginResponseSchema = marshmallow_dataclass.class_schema( models.GigyaLoginResponse, base_schema=BaseSchema )() GigyaGetAccountInfoResponseSchema = marshmallow_dataclass.class_schema( models.GigyaGetAccountInfoResponse, base_schema=BaseSchema )() GigyaGetJWTResponseSchema = marshmallow_dataclass.class_schema( models.GigyaGetJWTResponse, base_schema=BaseSchema )() renault-api-0.2.9/src/renault_api/helpers.py000066400000000000000000000103031473671120200210510ustar00rootroot00000000000000"""Helpers for Renault API.""" import asyncio import functools import logging from typing import Dict from typing import Optional import aiohttp from .const import AVAILABLE_LOCALES from .const import CONF_GIGYA_APIKEY from .const import CONF_GIGYA_URL from .const import CONF_KAMEREON_APIKEY from .const import CONF_KAMEREON_URL from .const import LOCALE_BASE_URL from .exceptions import RenaultException _LOGGER = logging.getLogger(__package__) async def get_api_keys( locale: str, force_load: bool = False, websession: Optional[aiohttp.ClientSession] = None, ) -> Dict[str, str]: """Get the API keys for specified locale. Args: locale (str): locale code (preferrably from AVAILABLE_LOCALES.keys()) force_load (bool): bypass internal AVAILABLE_LOCALES websession (aiohttp.ClientSession): required if locale not in AVAILABLE_LOCALES Returns: Dict with gigya-api-key, gigya-api-url, kamereon-api-key and kamereon-api-url Raises: RenaultException: an issue occured loading the API keys """ if locale in AVAILABLE_LOCALES.keys() and not force_load: return AVAILABLE_LOCALES[locale] else: _LOGGER.warning( "Locale %s was not found in AVAILABLE_LOCALES " "(or force_load used). " "Attempting to load details from Renault servers.", locale, ) if websession is None: raise RenaultException("aiohttp_session is not set.") url = f"{LOCALE_BASE_URL}/configuration/android/config_{locale}.json" async with websession.get(url) as response: # pragma: no cover try: response.raise_for_status() except aiohttp.ClientResponseError as exc: raise RenaultException( f"Locale not found on Renault server (HTTPStatus = {exc.status})." ) from exc # Server sometimes returns invalid content-type # eg. application/octet-stream response_body = await response.json(content_type=None) _LOGGER.debug( "Received api keys from myrenault response: %s", response_body ) servers = response_body["servers"] return { CONF_GIGYA_APIKEY: servers["gigyaProd"]["apikey"], CONF_GIGYA_URL: servers["gigyaProd"]["target"], CONF_KAMEREON_APIKEY: servers["wiredProd"]["apikey"], CONF_KAMEREON_URL: servers["wiredProd"]["target"], } def create_aiohttp_closed_event( websession: aiohttp.ClientSession, ) -> asyncio.Event: # pragma: no cover """Work around aiohttp issue that doesn't properly close transports on exit. See https://github.com/aio-libs/aiohttp/issues/1925#issuecomment-639080209 Args: websession (aiohttp.ClientSession): session for which to generate the event. Returns: An event that will be set once all transports have been properly closed. """ transports = 0 all_is_lost = asyncio.Event() def connection_lost(exc, orig_lost): # type: ignore nonlocal transports try: orig_lost(exc) finally: transports -= 1 if transports == 0: all_is_lost.set() def eof_received(orig_eof_received): # type: ignore try: orig_eof_received() except AttributeError: # It may happen that eof_received() is called after # _app_protocol and _transport are set to None. pass for conn in websession.connector._conns.values(): # type: ignore for handler, _ in conn: proto = getattr(handler.transport, "_ssl_protocol", None) if proto is None: continue transports += 1 orig_lost = proto.connection_lost orig_eof_received = proto.eof_received proto.connection_lost = functools.partial( connection_lost, orig_lost=orig_lost ) proto.eof_received = functools.partial( eof_received, orig_eof_received=orig_eof_received ) if transports == 0: all_is_lost.set() return all_is_lost renault-api-0.2.9/src/renault_api/kamereon/000077500000000000000000000000001473671120200206415ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/kamereon/__init__.py000066400000000000000000000253301473671120200227550ustar00rootroot00000000000000"""Kamereon API.""" import logging from json import dumps as json_dumps from typing import Any from typing import Dict from typing import List from typing import Optional from typing import cast from warnings import warn import aiohttp from marshmallow.schema import Schema from . import models from . import schemas from .exceptions import KamereonResponseException _LOGGER = logging.getLogger(__name__) _KCA_GET_ENDPOINTS: Dict[str, Any] = { "": {"version": 2}, "battery-status": {"version": 2}, "charge-history": {"version": 1}, "charge-mode": {"version": 1}, "charges": {"version": 1}, "charging-settings": {"version": 1}, "cockpit": {"version": 1}, "hvac-history": {"version": 1}, "hvac-sessions": {"version": 1}, "hvac-status": {"version": 1}, "hvac-settings": {"version": 1}, "location": {"version": 1}, "lock-status": {"version": 1}, "notification-settings": {"version": 1}, "pressure": {"version": 1}, "res-state": {"version": 1}, } _KCA_POST_ENDPOINTS: Dict[str, Any] = { "actions/charge-mode": {"version": 1, "type": "ChargeMode"}, "actions/charge-schedule": {"version": 2, "type": "ChargeSchedule"}, "actions/charging-start": {"version": 1, "type": "ChargingStart"}, "actions/hvac-schedule": {"version": 2, "type": "HvacSchedule"}, "actions/hvac-start": {"version": 1, "type": "HvacStart"}, } _KCM_POST_ENDPOINTS: Dict[str, Any] = { "charge/pause-resume": {"version": 1, "type": "ChargePauseResume"}, } # Deprecated from 0.1.8 - kept for compatibility DATA_ENDPOINTS = _KCA_GET_ENDPOINTS ACTION_ENDPOINTS = _KCA_POST_ENDPOINTS def get_commerce_url(root_url: str) -> str: """Get the Kamereon base commerce url.""" return f"{root_url}/commerce/v1" def get_person_url(root_url: str, person_id: str) -> str: """Get the url to the person.""" return f"{get_commerce_url(root_url)}/persons/{person_id}" def get_account_url(root_url: str, account_id: str) -> str: """Get the url to the account.""" return f"{get_commerce_url(root_url)}/accounts/{account_id}" def get_car_adapter_url( root_url: str, account_id: str, version: int, vin: str, *, adapter_type: str = "kca" ) -> str: """Get the url to the car adapter.""" account_url = get_account_url(root_url, account_id) if adapter_type == "kcm": return f"{account_url}/kamereon/kcm/v{version}/vehicles/{vin}" return f"{account_url}/kamereon/kca/car-adapter/v{version}/cars/{vin}" def get_contracts_url(root_url: str, account_id: str, vin: str) -> str: """Get the url to the car contracts.""" account_url = get_account_url(root_url, account_id) return f"{account_url}/vehicles/{vin}/contracts" def get_required_contracts(endpoint: str) -> str: # pragma: no cover """Get the required contracts for the specified endpoint.""" # "Deprecated in 0.1.3, contract codes are country-specific" # " and can't be used to guess requirements." warn("This method is deprecated.", DeprecationWarning, stacklevel=2) return "" def has_required_contracts( contracts: List[models.KamereonVehicleContract], endpoint: str ) -> bool: """Check if vehicle has contract for endpoint.""" # "Deprecated in 0.1.3, contract codes are country-specific" # " and can't be used to guess requirements." warn("This method is deprecated.", DeprecationWarning, stacklevel=2) return True # pragma: no cover async def request( websession: aiohttp.ClientSession, method: str, url: str, api_key: str, gigya_jwt: str, params: Dict[str, str], json: Optional[Dict[str, Any]] = None, schema: Optional[Schema] = None, *, wrap_array_in: Optional[str] = None, ) -> models.KamereonResponse: """Process Kamereon HTTP request.""" schema = schema or schemas.KamereonResponseSchema headers = { "Content-type": "application/vnd.api+json", "apikey": api_key, "x-gigya-id_token": gigya_jwt, } async with websession.request( method, url, headers=headers, params=params, json=json, ) as http_response: response_text = await http_response.text() if json: _LOGGER.debug( "Send Kamereon %s request to %s with body: %s", method, http_response.url, json_dumps(json), ) _LOGGER.debug( "Received Kamereon response %s on %s to %s: %s", http_response.status, method, http_response.url, response_text, ) # Some endpoints return arrays instead of objects. # These need to be wrapped in an object. if response_text.startswith("["): response_text = f'{{"{wrap_array_in or "data"}": {response_text}}}' # noqa: B907 if not response_text.startswith("{"): # Check for HTTP error http_response.raise_for_status() raise KamereonResponseException("Invalid JSON", response_text) kamereon_response: models.KamereonResponse = schema.loads(response_text) # Check for Kamereon error kamereon_response.raise_for_error_code() # Check for HTTP error http_response.raise_for_status() return kamereon_response async def get_person( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, person_id: str, ) -> models.KamereonPersonResponse: """GET to /persons/{person_id}.""" url = get_person_url(root_url, person_id) params = {"country": country} return cast( models.KamereonPersonResponse, await request( websession, "GET", url, api_key, gigya_jwt, params=params, schema=schemas.KamereonPersonResponseSchema, ), ) async def get_vehicle_contracts( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, locale: str, account_id: str, vin: str, ) -> models.KamereonVehicleContractsResponse: """GET to /accounts/{accountId}/vehicles/{vin}/contracts.""" url = get_contracts_url(root_url, account_id, vin) params = { "country": country, "locale": locale, "brand": "RENAULT", "connectedServicesContracts": "true", "warranty": "true", "warrantyMaintenanceContracts": "true", } return cast( models.KamereonVehicleContractsResponse, await request( websession, "GET", url, api_key, gigya_jwt, params=params, schema=schemas.KamereonVehicleContractsResponseSchema, wrap_array_in="contractList", ), ) async def get_account_vehicles( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, account_id: str, ) -> models.KamereonVehiclesResponse: """GET to /accounts/{account_id}/vehicles.""" url = f"{get_account_url(root_url, account_id)}/vehicles" params = {"country": country} return cast( models.KamereonVehiclesResponse, await request( websession, "GET", url, api_key, gigya_jwt, params=params, schema=schemas.KamereonVehiclesResponseSchema, ), ) async def get_vehicle_details( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, account_id: str, vin: str, ) -> models.KamereonVehicleDetailsResponse: """GET to /accounts/{account_id}/vehicles/{vin}/details.""" url = f"{get_account_url(root_url, account_id)}/vehicles/{vin}/details" params = {"country": country} return cast( models.KamereonVehicleDetailsResponse, await request( websession, "GET", url, api_key, gigya_jwt, params=params, schema=schemas.KamereonVehicleDetailsResponseSchema, ), ) async def get_vehicle_data( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, account_id: str, vin: str, endpoint: str, endpoint_version: Optional[int] = None, params: Optional[Dict[str, str]] = None, *, adapter_type: str = "kca", ) -> models.KamereonVehicleDataResponse: """GET to /v{endpoint_version}/cars/{vin}/{endpoint}.""" endpoint_details = _KCA_GET_ENDPOINTS[endpoint] car_adapter_url = get_car_adapter_url( root_url=root_url, account_id=account_id, version=endpoint_version or int(endpoint_details["version"]), vin=vin, adapter_type=adapter_type, ) url = f"{car_adapter_url}/{endpoint}" if endpoint else car_adapter_url params = params or {} params["country"] = country return cast( models.KamereonVehicleDataResponse, await request( websession, "GET", url, api_key, gigya_jwt, params=params, schema=schemas.KamereonVehicleDataResponseSchema, ), ) async def set_vehicle_action( websession: aiohttp.ClientSession, root_url: str, api_key: str, gigya_jwt: str, country: str, account_id: str, vin: str, endpoint: str, attributes: Dict[str, Any], endpoint_version: Optional[int] = None, data_type: Optional[Dict[str, Any]] = None, *, adapter_type: str = "kca", ) -> models.KamereonVehicleDataResponse: """POST to /v{endpoint_version}/cars/{vin}/{endpoint}.""" if "/" not in endpoint: # Deprecated in 0.1.8 warn( f"You should use the full endpoint: actions/{endpoint}.", DeprecationWarning, stacklevel=2, ) endpoint = f"actions/{endpoint}" if adapter_type == "kcm": endpoint_details = _KCM_POST_ENDPOINTS[endpoint] else: endpoint_details = _KCA_POST_ENDPOINTS[endpoint] car_adapter_url = get_car_adapter_url( root_url=root_url, account_id=account_id, version=endpoint_version or int(endpoint_details["version"]), vin=vin, adapter_type=adapter_type, ) url = f"{car_adapter_url}/{endpoint}" params = {"country": country} json = { "data": { "type": data_type or endpoint_details["type"], "attributes": attributes, } } return cast( models.KamereonVehicleDataResponse, await request( websession, "POST", url, api_key, gigya_jwt, params, json, schemas.KamereonVehicleDataResponseSchema, ), ) renault-api-0.2.9/src/renault_api/kamereon/enums.py000066400000000000000000000014421473671120200223430ustar00rootroot00000000000000"""Kamereon enums.""" from enum import Enum class ChargeState(Enum): """Enum for battery-status charge state.""" NOT_IN_CHARGE = 0.0 WAITING_FOR_A_PLANNED_CHARGE = 0.1 CHARGE_ENDED = 0.2 WAITING_FOR_CURRENT_CHARGE = 0.3 ENERGY_FLAP_OPENED = 0.4 CHARGE_IN_PROGRESS = 1.0 # This next is more accurately "not charging" (<= ZE40) or "error" (ZE50). CHARGE_ERROR = -1.0 UNAVAILABLE = -1.1 class PlugState(Enum): """Enum for battery-status plug state.""" UNPLUGGED = 0 PLUGGED = 1 # 3 is unconfirmed, as it is currently unsupported on the App (see #1262) PLUGGED_WAITING_FOR_CHARGE = 3 PLUG_ERROR = -1 PLUG_UNKNOWN = -2147483648 class AssetPictureSize(Enum): """Enum for the asset picture size.""" SMALL = 0 LARGE = 1 renault-api-0.2.9/src/renault_api/kamereon/exceptions.py000066400000000000000000000024711473671120200234000ustar00rootroot00000000000000"""Kamereon exceptions.""" from typing import Optional from renault_api.exceptions import RenaultException class KamereonException(RenaultException): """Base exception for Kamereon errors.""" pass class KamereonResponseException(KamereonException): """Kamereon returned a parsable errors.""" def __init__(self, error_code: Optional[str], error_details: Optional[str]): """Initialise KamereonResponseException.""" self.error_code = error_code self.error_details = error_details class AccessDeniedException(KamereonResponseException): """Access is denied for this resource.""" pass class NotSupportedException(KamereonResponseException): """This feature is not technically supported by this gateway.""" pass class InvalidUpstreamException(KamereonResponseException): """Invalid response from the upstream server.""" pass class QuotaLimitException(KamereonResponseException): """You have reached your quota limit.""" pass class InvalidInputException(KamereonResponseException): """The input is invalid.""" pass class ResourceNotFoundException(KamereonResponseException): """Resource not found.""" pass class FailedForwardException(KamereonResponseException): """Failed to forward request to remote service.""" pass renault-api-0.2.9/src/renault_api/kamereon/helpers.py000066400000000000000000000063731473671120200226660ustar00rootroot00000000000000"""Helpers for Kamereon models.""" from __future__ import annotations from typing import Any from warnings import warn from . import models DAYS_OF_WEEK = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ] def update_schedule( schedule: models.ChargeSchedule, settings: dict[str, Any] ) -> None: # pragma: no cover """Update charge schedule.""" warn( "This method is deprecated, please use update_charge_schedule.", DeprecationWarning, stacklevel=2, ) update_charge_schedule(schedule, settings) def update_charge_schedule( schedule: models.ChargeSchedule, settings: dict[str, Any] ) -> None: """Update charge schedule.""" if "activated" in settings: schedule.activated = settings["activated"] for day in DAYS_OF_WEEK: if day in settings: day_settings = settings[day] if day_settings is None: setattr(schedule, day, None) elif day_settings: # pragma: no branch start_time = day_settings["startTime"] duration = day_settings["duration"] setattr( schedule, day, models.ChargeDaySchedule(day_settings, start_time, duration), ) def update_hvac_schedule( schedule: models.HvacSchedule, settings: dict[str, Any] ) -> None: """Update HVAC schedule.""" if "activated" in settings: schedule.activated = settings["activated"] for day in DAYS_OF_WEEK: if day in settings: day_settings = settings[day] if day_settings is None: setattr(schedule, day, None) elif day_settings: # pragma: no branch ready_at_time = day_settings["readyAtTime"] setattr( schedule, day, models.HvacDaySchedule(day_settings, ready_at_time), ) def create_schedule( settings: dict[str, Any], ) -> models.ChargeSchedule: # pragma: no cover warn( "This method is deprecated, please use create_charge_schedule.", DeprecationWarning, stacklevel=2, ) return create_charge_schedule(settings) def create_charge_schedule( settings: dict[str, Any], ) -> models.ChargeSchedule: # pragma: no cover """Update schedule.""" raise NotImplementedError def create_hvac_schedule( settings: dict[str, Any], ) -> models.HvacSchedule: # pragma: no cover """Update schedule.""" raise NotImplementedError def get_end_time(start_time: str, duration: int | None = None) -> str: """Compute end time.""" total_minutes = get_total_minutes(start_time, duration) return format_time(total_minutes) def format_time(total_minutes: int) -> str: """Format time.""" end_hours, end_minutes = divmod(total_minutes, 60) end_hours = end_hours % 24 return f"T{end_hours:02g}:{end_minutes:02g}Z" def get_total_minutes(start_time: str | None, duration: int | None = None) -> int: """Get total minutes from a `Thh:mmZ` formatted time.""" if not start_time: # pragma: no cover return 0 return int(start_time[1:3]) * 60 + int(start_time[4:6]) + (duration or 0) renault-api-0.2.9/src/renault_api/kamereon/models.py000066400000000000000000000551101473671120200225000ustar00rootroot00000000000000"""Kamereon models.""" import json from dataclasses import dataclass from typing import Any from typing import Dict from typing import List from typing import Optional from typing import cast from marshmallow.schema import Schema from . import enums from . import exceptions from . import helpers from .enums import AssetPictureSize from renault_api.models import BaseModel COMMON_ERRRORS: List[Dict[str, Any]] = [ { "errorCode": "err.func.400", "error_type": exceptions.InvalidInputException, }, { "errorCode": "err.func.403", "error_type": exceptions.AccessDeniedException, }, { "errorCode": "err.tech.500", "error_type": exceptions.InvalidUpstreamException, }, { "errorCode": "err.tech.501", "error_type": exceptions.NotSupportedException, }, { "errorCode": "err.func.wired.notFound", "error_type": exceptions.ResourceNotFoundException, }, { "errorCode": "err.tech.wired.kamereon-proxy", "error_type": exceptions.FailedForwardException, }, { "errorCode": "err.func.wired.overloaded", "error_type": exceptions.QuotaLimitException, }, ] VEHICLE_SPECIFICATIONS: Dict[str, Dict[str, Any]] = { "X101VE": { # ZOE phase 1 "reports-charge-session-durations-in-minutes": True, "reports-in-watts": True, "support-endpoint-location": False, "support-endpoint-lock-status": False, }, "X102VE": { # ZOE phase 2 "warns-on-method-set_ac_stop": "Action `cancel` on endpoint `hvac-start` may not be supported on this model.", # noqa }, "XJA1VP": { # CLIO V "support-endpoint-hvac-status": False, }, "XJB1SU": { # CAPTUR II "support-endpoint-hvac-status": False, }, "XBG1VE": { # DACIA SPRING "control-charge-via-kcm": True, }, "XCB1VE": { # MEGANE E-TECH "support-endpoint-lock-status": False, }, "XCB1SE": { # SCENIC E-TECH "support-endpoint-lock-status": False, }, } GATEWAY_SPECIFICATIONS: Dict[str, Dict[str, Any]] = { "GDC": { # ZOE phase 1 "reports-charge-session-durations-in-minutes": True, "reports-in-watts": True, "support-endpoint-location": False, "support-endpoint-lock-status": False, }, } @dataclass class KamereonResponseError(BaseModel): """Kamereon response error.""" errorCode: Optional[str] errorMessage: Optional[str] def raise_for_error_code(self) -> None: """Raise exception from response error.""" error_details = self.get_error_details() for common_error in COMMON_ERRRORS: if self.errorCode == common_error["errorCode"]: error_type = common_error["error_type"] raise error_type(self.errorCode, error_details) raise exceptions.KamereonResponseException( self.errorCode, error_details ) # pragma: no cover def get_error_details(self) -> Optional[str]: """Extract the error details sometimes hidden inside nested JSON.""" try: error_details = json.loads(self.errorMessage or "{}") except json.JSONDecodeError: return self.errorMessage error_descriptions = [] for inner_error in error_details.get("errors", []): error_description = " ".join( filter( None, [ inner_error.get("title"), inner_error.get("source", {}).get("pointer"), inner_error.get("detail"), ], ) ) error_descriptions.append(error_description) return ", ".join(error_descriptions) or self.errorMessage @dataclass class KamereonResponse(BaseModel): """Kamereon response.""" errors: Optional[List[KamereonResponseError]] def raise_for_error_code(self) -> None: """Raise exception if errors found in the response.""" for error in self.errors or []: # Until we have a sample for multiple errors, just raise on first one error.raise_for_error_code() @dataclass class KamereonPersonAccount(BaseModel): """Kamereon person account data.""" accountId: Optional[str] accountType: Optional[str] accountStatus: Optional[str] @dataclass class KamereonPersonResponse(KamereonResponse): """Kamereon response to GET on /persons/{gigya_person_id}.""" accounts: Optional[List[KamereonPersonAccount]] @dataclass class KamereonVehicleDetailsGroup(BaseModel): """Kamereon vehicle details group data.""" code: Optional[str] label: Optional[str] group: Optional[str] @dataclass class KamereonVehicleDetails(BaseModel): """Kamereon vehicle details.""" vin: Optional[str] registrationNumber: Optional[str] radioCode: Optional[str] brand: Optional[KamereonVehicleDetailsGroup] model: Optional[KamereonVehicleDetailsGroup] energy: Optional[KamereonVehicleDetailsGroup] engineEnergyType: Optional[str] assets: Optional[List[Dict[str, Any]]] def get_energy_code(self) -> Optional[str]: """Return vehicle energy code.""" return self.energy.code if self.energy else None def get_brand_label(self) -> Optional[str]: """Return vehicle model label.""" return self.brand.label if self.brand else None def get_model_code(self) -> Optional[str]: """Return vehicle model code.""" return self.model.code if self.model else None def get_model_label(self) -> Optional[str]: """Return vehicle model label.""" return self.model.label if self.model else None def get_asset(self, asset_type: str) -> Optional[Dict[str, Any]]: """Return asset.""" return next( filter( lambda asset: asset.get("assetType") == asset_type, self.assets or [] ) ) def get_picture( self, size: AssetPictureSize = AssetPictureSize.LARGE ) -> Optional[str]: """Return vehicle picture.""" asset: Dict[str, Any] = self.get_asset("PICTURE") or {} rendition: Dict[str, str] = next( filter( lambda rendition: rendition.get("resolutionType") == f"ONE_MYRENAULT_{size.name}", asset.get("renditions", [{}]), ) ) return rendition.get("url") if rendition else None def uses_electricity(self) -> bool: """Return True if model uses electricity.""" energy_type = self.engineEnergyType or self.get_energy_code() if energy_type in [ "ELEC", "ELECX", "PHEV", ]: return True return False def uses_fuel(self) -> bool: """Return True if model uses fuel.""" energy_type = self.engineEnergyType or self.get_energy_code() if energy_type in [ "OTHER", "PHEV", "HEV", ]: return True return False def reports_charge_session_durations_in_minutes(self) -> bool: """Return True if model reports history durations in minutes.""" # Default to False (=seconds) for unknown vehicles if self.model and self.model.code: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.model.code, {} ).get("reports-charge-session-durations-in-minutes", False) return False # pragma: no cover def reports_charging_power_in_watts(self) -> bool: """Return True if model reports chargingInstantaneousPower in watts.""" # Default to False for unknown vehicles if self.model and self.model.code: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.model.code, {} ).get("reports-in-watts", False) return False # pragma: no cover def supports_endpoint(self, endpoint: str) -> bool: """Return True if model supports specified endpoint.""" # Default to True for unknown vehicles if self.model and self.model.code: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.model.code, {} ).get(f"support-endpoint-{endpoint}", True) return True # pragma: no cover def warns_on_method(self, method: str) -> Optional[str]: """Return warning message if model trigger a warning on the method call.""" # Default to None for unknown vehicles if self.model and self.model.code: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.model.code, {} ).get(f"warns-on-method-{method}", None) return None # pragma: no cover def controls_action_via_kcm(self, action: str) -> bool: """Return True if model uses endpoint via kcm.""" # Default to False for unknown vehicles if self.model and self.model.code: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.model.code, {} ).get(f"control-{action}-via-kcm", False) return False # pragma: no cover @dataclass class KamereonVehiclesLink(BaseModel): """Kamereon vehicles link data.""" vin: Optional[str] vehicleDetails: Optional[KamereonVehicleDetails] @dataclass class KamereonVehiclesResponse(KamereonResponse): """Kamereon response to GET on /accounts/{account_id}/vehicles.""" accountId: Optional[str] country: Optional[str] vehicleLinks: Optional[List[KamereonVehiclesLink]] @dataclass class KamereonVehicleDetailsResponse(KamereonResponse, KamereonVehicleDetails): """Kamereon response to GET on /accounts/{account_id}/vehicles/{vin}/details.""" @dataclass class KamereonVehicleDataAttributes(BaseModel): """Kamereon vehicle data attributes.""" @dataclass class KamereonVehicleContract(BaseModel): """Kamereon vehicle contract.""" type: Optional[str] contractId: Optional[str] code: Optional[str] group: Optional[str] durationMonths: Optional[int] startDate: Optional[str] endDate: Optional[str] status: Optional[str] statusLabel: Optional[str] description: Optional[str] @dataclass class KamereonVehicleContractsResponse(KamereonResponse): """Kamereon response to GET on /accounts/{accountId}/vehicles/{vin}/contracts.""" contractList: Optional[List[KamereonVehicleContract]] @dataclass class KamereonVehicleData(BaseModel): """Kamereon vehicle data.""" type: Optional[str] id: Optional[str] attributes: Optional[Dict[str, Any]] @dataclass class KamereonVehicleDataResponse(KamereonResponse): """Kamereon response to GET/POST on .../cars/{vin}/{type}.""" data: Optional[KamereonVehicleData] def get_attributes(self, schema: Schema) -> KamereonVehicleDataAttributes: """Return jwt token.""" attributes = {} if self.data and self.data.attributes is not None: attributes = self.data.attributes return cast(KamereonVehicleDataAttributes, schema.load(attributes)) @dataclass class KamereonVehicleBatteryStatusData(KamereonVehicleDataAttributes): """Kamereon vehicle battery-status data.""" timestamp: Optional[str] batteryLevel: Optional[int] batteryTemperature: Optional[int] batteryAutonomy: Optional[int] batteryCapacity: Optional[int] batteryAvailableEnergy: Optional[int] plugStatus: Optional[int] chargingStatus: Optional[float] chargingRemainingTime: Optional[int] chargingInstantaneousPower: Optional[float] def get_plug_status(self) -> Optional[enums.PlugState]: """Return plug status.""" try: return ( enums.PlugState(self.plugStatus) if self.plugStatus is not None else None ) except ValueError as err: # pragma: no cover # should we return PlugState.NOT_AVAILABLE? raise exceptions.KamereonException( f"Unable to convert `{self.plugStatus}` to PlugState." ) from err def get_charging_status(self) -> Optional[enums.ChargeState]: """Return charging status.""" try: return ( enums.ChargeState(self.chargingStatus) if self.chargingStatus is not None else None ) except ValueError as err: # pragma: no cover # should we return ChargeState.NOT_AVAILABLE? raise exceptions.KamereonException( f"Unable to convert `{self.chargingStatus}` to ChargeState." ) from err @dataclass class KamereonVehicleTyrePressureData(KamereonVehicleDataAttributes): """Kamereon vehicle tyre-pressure data.""" flPressure: Optional[int] frPressure: Optional[int] rlPressure: Optional[int] rrPressure: Optional[int] flStatus: Optional[int] frStatus: Optional[int] rlStatus: Optional[int] rrStatus: Optional[int] @dataclass class KamereonVehicleLocationData(KamereonVehicleDataAttributes): """Kamereon vehicle data location attributes.""" lastUpdateTime: Optional[str] gpsLatitude: Optional[float] gpsLongitude: Optional[float] @dataclass class KamereonVehicleHvacStatusData(KamereonVehicleDataAttributes): """Kamereon vehicle data hvac-status attributes.""" lastUpdateTime: Optional[str] externalTemperature: Optional[float] hvacStatus: Optional[str] nextHvacStartDate: Optional[str] socThreshold: Optional[float] @dataclass class KamereonVehicleChargeModeData(KamereonVehicleDataAttributes): """Kamereon vehicle data charge-mode attributes.""" chargeMode: Optional[str] @dataclass class KamereonVehicleCockpitData(KamereonVehicleDataAttributes): """Kamereon vehicle data cockpit attributes.""" fuelAutonomy: Optional[float] fuelQuantity: Optional[float] totalMileage: Optional[float] @dataclass class KamereonVehicleLockStatusData(KamereonVehicleDataAttributes): """Kamereon vehicle data lock-status attributes.""" lockStatus: Optional[str] doorStatusRearLeft: Optional[str] doorStatusRearRight: Optional[str] doorStatusDriver: Optional[str] doorStatusPassenger: Optional[str] hatchStatus: Optional[str] lastUpdateTime: Optional[str] @dataclass class KamereonVehicleResStateData(KamereonVehicleDataAttributes): """Kamereon vehicle data res-set attributes.""" details: Optional[str] code: Optional[str] @dataclass class KamereonVehicleCarAdapterData(KamereonVehicleDataAttributes): """Kamereon vehicle data hvac-status attributes.""" vin: Optional[str] vehicleId: Optional[int] batteryCode: Optional[str] brand: Optional[str] canGeneration: Optional[str] carGateway: Optional[str] deliveryCountry: Optional[str] deliveryDate: Optional[str] energy: Optional[str] engineType: Optional[str] familyCode: Optional[str] firstRegistrationDate: Optional[str] gearbox: Optional[str] modelCode: Optional[str] modelCodeDetail: Optional[str] modelName: Optional[str] radioType: Optional[str] region: Optional[str] registrationCountry: Optional[str] registrationNumber: Optional[str] tcuCode: Optional[str] versionCode: Optional[str] privacyMode: Optional[str] privacyModeUpdateDate: Optional[str] svtFlag: Optional[bool] svtBlockFlag: Optional[bool] def uses_electricity(self) -> bool: """Return True if model uses electricity.""" if self.energy in [ "electric", ]: return True return False def uses_fuel(self) -> bool: """Return True if model uses fuel.""" if self.energy in [ "gasoline", ]: return True return False def reports_charging_power_in_watts(self) -> bool: """Return True if model reports chargingInstantaneousPower in watts.""" # Default to False for unknown vehicles if self.carGateway: return GATEWAY_SPECIFICATIONS.get( # type:ignore[no-any-return] self.carGateway, {} ).get("reports-in-watts", False) return False # pragma: no cover def supports_endpoint(self, endpoint: str) -> bool: """Return True if model supports specified endpoint.""" # Default to True for unknown vehicles if self.carGateway: return GATEWAY_SPECIFICATIONS.get( # type:ignore[no-any-return] self.carGateway, {} ).get(f"support-endpoint-{endpoint}", True) return True # pragma: no cover def controls_action_via_kcm(self, action: str) -> bool: """Return True if model uses endpoint via kcm.""" # Default to False for unknown vehicles if self.modelCodeDetail: return VEHICLE_SPECIFICATIONS.get( # type:ignore[no-any-return] self.modelCodeDetail, {} ).get(f"control-{action}-via-kcm", False) return False # pragma: no cover @dataclass class ChargeDaySchedule(BaseModel): """Kamereon vehicle charge schedule for day.""" startTime: Optional[str] duration: Optional[int] def for_json(self) -> Dict[str, Any]: """Create dict for json.""" return { "startTime": self.startTime, "duration": self.duration, } def get_end_time(self) -> Optional[str]: """Get end time.""" if self.startTime is None: # pragma: no cover return None return helpers.get_end_time(self.startTime, self.duration) @dataclass class ChargeSchedule(BaseModel): """Kamereon vehicle charge schedule for week.""" id: Optional[int] activated: Optional[bool] monday: Optional[ChargeDaySchedule] tuesday: Optional[ChargeDaySchedule] wednesday: Optional[ChargeDaySchedule] thursday: Optional[ChargeDaySchedule] friday: Optional[ChargeDaySchedule] saturday: Optional[ChargeDaySchedule] sunday: Optional[ChargeDaySchedule] def for_json(self) -> Dict[str, Any]: """Create dict for json.""" result: Dict[str, Any] = { "id": self.id, "activated": self.activated, } for day in helpers.DAYS_OF_WEEK: day_spec: Optional[ChargeDaySchedule] = getattr(self, day, None) if day_spec is None: result[day] = day_spec else: result[day] = day_spec.for_json() return result @dataclass class HvacDaySchedule(BaseModel): """Kamereon vehicle hvac schedule for day.""" readyAtTime: Optional[str] def for_json(self) -> Dict[str, Optional[str]]: """Create dict for json.""" return { "readyAtTime": self.readyAtTime, } @dataclass class HvacSchedule(BaseModel): """Kamereon vehicle hvac schedule for week.""" id: Optional[int] activated: Optional[bool] monday: Optional[HvacDaySchedule] tuesday: Optional[HvacDaySchedule] wednesday: Optional[HvacDaySchedule] thursday: Optional[HvacDaySchedule] friday: Optional[HvacDaySchedule] saturday: Optional[HvacDaySchedule] sunday: Optional[HvacDaySchedule] def for_json(self) -> Dict[str, Any]: """Create dict for json.""" result: Dict[str, Any] = { "id": self.id, "activated": self.activated, } for day in helpers.DAYS_OF_WEEK: day_spec: Optional[HvacDaySchedule] = getattr(self, day, None) if day_spec is None: result[day] = day_spec else: result[day] = day_spec.for_json() return result @dataclass class KamereonVehicleChargingSettingsData(KamereonVehicleDataAttributes): """Kamereon vehicle data charging-settings attributes.""" mode: Optional[str] schedules: Optional[List[ChargeSchedule]] def update(self, args: Dict[str, Any]) -> None: """Update schedule.""" if "id" not in args: # pragma: no cover raise ValueError("id not provided for update.") if self.schedules is None: # pragma: no cover self.schedules = [] for schedule in self.schedules: if schedule.id == args["id"]: # pragma: no branch helpers.update_charge_schedule(schedule, args) return self.schedules.append(helpers.create_charge_schedule(args)) # pragma: no cover @dataclass class KamereonVehicleHvacSettingsData(KamereonVehicleDataAttributes): """Kamereon vehicle data hvac-settings (mode+schedules) attributes.""" mode: Optional[str] schedules: Optional[List[HvacSchedule]] def update(self, args: Dict[str, Any]) -> None: """Update schedule.""" if "id" not in args: # pragma: no cover raise ValueError("id not provided for update.") if self.schedules is None: # pragma: no cover self.schedules = [] for schedule in self.schedules: if schedule.id == args["id"]: # pragma: no branch helpers.update_hvac_schedule(schedule, args) return self.schedules.append(helpers.create_hvac_schedule(args)) # pragma: no cover @dataclass class KamereonVehicleNotificationSettingsData(KamereonVehicleDataAttributes): """Kamereon vehicle data notification-settings attributes.""" @dataclass class KamereonVehicleChargeHistoryData(KamereonVehicleDataAttributes): """Kamereon vehicle data charge-history attributes.""" @dataclass class KamereonVehicleChargesData(KamereonVehicleDataAttributes): """Kamereon vehicle data charges attributes.""" @dataclass class KamereonVehicleHvacHistoryData(KamereonVehicleDataAttributes): """Kamereon vehicle data hvac-history attributes.""" @dataclass class KamereonVehicleHvacSessionsData(KamereonVehicleDataAttributes): """Kamereon vehicle data hvac-sessions attributes.""" @dataclass class KamereonVehicleHvacStartActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data hvac-start attributes.""" @dataclass class KamereonVehicleHvacScheduleActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data hvac-schedule attributes.""" @dataclass class KamereonVehicleChargeScheduleActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data charge-schedule attributes.""" @dataclass class KamereonVehicleChargeModeActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data charge-mode attributes.""" @dataclass class KamereonVehicleHvacModeActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data hvac-mode attributes.""" @dataclass class KamereonVehicleChargingStartActionData(KamereonVehicleDataAttributes): """Kamereon vehicle action data charging-start attributes.""" renault-api-0.2.9/src/renault_api/kamereon/schemas.py000066400000000000000000000102121473671120200226320ustar00rootroot00000000000000"""Kamereon schemas.""" import marshmallow_dataclass from . import models from renault_api.models import BaseSchema KamereonResponseSchema = marshmallow_dataclass.class_schema( models.KamereonResponse, base_schema=BaseSchema )() KamereonPersonResponseSchema = marshmallow_dataclass.class_schema( models.KamereonPersonResponse, base_schema=BaseSchema )() KamereonVehicleContractsResponseSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleContractsResponse, base_schema=BaseSchema )() KamereonVehiclesResponseSchema = marshmallow_dataclass.class_schema( models.KamereonVehiclesResponse, base_schema=BaseSchema )() KamereonVehicleDetailsResponseSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleDetailsResponse, base_schema=BaseSchema )() KamereonVehicleDataResponseSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleDataResponse, base_schema=BaseSchema )() KamereonVehicleBatteryStatusDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleBatteryStatusData, base_schema=BaseSchema )() KamereonVehicleTyrePressureDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleTyrePressureData, base_schema=BaseSchema )() KamereonVehicleLocationDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleLocationData, base_schema=BaseSchema )() KamereonVehicleLockStatusDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleLockStatusData, base_schema=BaseSchema )() KamereonVehicleResStateDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleResStateData, base_schema=BaseSchema )() KamereonVehicleHvacStatusDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacStatusData, base_schema=BaseSchema )() KamereonVehicleChargeModeDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargeModeData, base_schema=BaseSchema )() KamereonVehicleCockpitDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleCockpitData, base_schema=BaseSchema )() KamereonVehicleLockStatusDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleLockStatusData, base_schema=BaseSchema )() KamereonVehicleCarAdapterDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleCarAdapterData, base_schema=BaseSchema )() KamereonVehicleChargingSettingsDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargingSettingsData, base_schema=BaseSchema )() KamereonVehicleHvacSettingsDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacSettingsData, base_schema=BaseSchema )() KamereonVehicleNotificationSettingsDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleNotificationSettingsData, base_schema=BaseSchema )() KamereonVehicleChargeHistoryDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargeHistoryData, base_schema=BaseSchema )() KamereonVehicleChargesDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargesData, base_schema=BaseSchema )() KamereonVehicleHvacHistoryDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacHistoryData, base_schema=BaseSchema )() KamereonVehicleHvacSessionsDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacSessionsData, base_schema=BaseSchema )() KamereonVehicleHvacStartActionDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacStartActionData, base_schema=BaseSchema )() KamereonVehicleHvacScheduleActionDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleHvacScheduleActionData, base_schema=BaseSchema )() KamereonVehicleChargeScheduleActionDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargeScheduleActionData, base_schema=BaseSchema )() KamereonVehicleChargeModeActionDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargeModeActionData, base_schema=BaseSchema )() KamereonVehicleChargingStartActionDataSchema = marshmallow_dataclass.class_schema( models.KamereonVehicleChargingStartActionData, base_schema=BaseSchema )() renault-api-0.2.9/src/renault_api/models.py000066400000000000000000000012411473671120200206730ustar00rootroot00000000000000"""Models for Renault API.""" from dataclasses import dataclass from typing import Any from typing import Dict import marshmallow @dataclass class BaseModel: """Base model for Gigya and Kamereon models to include raw_data.""" raw_data: Dict[str, Any] class BaseSchema(marshmallow.Schema): """Base schema for Gigya and Kamereon models to exclude unknown fields.""" class Meta: """Force unknown fields to 'exclude'.""" unknown = marshmallow.EXCLUDE @marshmallow.pre_load def get_raw_data(self, data, **kwargs): # type: ignore """Ensure raw_data is added to the data set.""" return {"raw_data": data, **data} renault-api-0.2.9/src/renault_api/py.typed000066400000000000000000000000001473671120200205250ustar00rootroot00000000000000renault-api-0.2.9/src/renault_api/renault_account.py000066400000000000000000000053671473671120200226130ustar00rootroot00000000000000"""Client for Renault API.""" import logging from typing import Dict from typing import List from typing import Optional import aiohttp from .credential_store import CredentialStore from .exceptions import RenaultException from .kamereon import models from .renault_session import RenaultSession from .renault_vehicle import RenaultVehicle _LOGGER = logging.getLogger(__name__) class RenaultAccount: """Proxy to a Renault account.""" def __init__( self, account_id: str, session: Optional[RenaultSession] = None, websession: Optional[aiohttp.ClientSession] = None, locale: Optional[str] = None, country: Optional[str] = None, locale_details: Optional[Dict[str, str]] = None, credential_store: Optional[CredentialStore] = None, ) -> None: """Initialise Renault account.""" self._account_id = account_id if session: self._session = session else: if websession is None: # pragma: no cover raise RenaultException( "`websession` is required if session is not provided." ) self._session = RenaultSession( websession=websession, locale=locale, country=country, locale_details=locale_details, credential_store=credential_store, ) @property def session(self) -> RenaultSession: """Get session provider.""" return self._session @property def account_id(self) -> str: """Get account id.""" return self._account_id async def get_vehicles(self) -> models.KamereonVehiclesResponse: """GET to /accounts/{account_id}/vehicles.""" return await self.session.get_account_vehicles( self.account_id, ) async def get_api_vehicles(self) -> List[RenaultVehicle]: """Get vehicle proxies.""" response = await self.get_vehicles() if response.vehicleLinks is None: # pragma: no cover raise ValueError("response.accounts is None") result: List[RenaultVehicle] = [] for vehicle in response.vehicleLinks: if vehicle.vin is None: # pragma: no cover continue result.append( RenaultVehicle( account_id=self.account_id, vin=vehicle.vin, session=self.session, vehicle_details=vehicle.vehicleDetails, ) ) return result async def get_api_vehicle(self, vin: str) -> RenaultVehicle: """Get vehicle proxy for specified vin.""" return RenaultVehicle(account_id=self.account_id, vin=vin, session=self.session) renault-api-0.2.9/src/renault_api/renault_client.py000066400000000000000000000045521473671120200224300ustar00rootroot00000000000000"""Client for Renault API.""" import logging from typing import Dict from typing import List from typing import Optional import aiohttp from .credential_store import CredentialStore from .exceptions import RenaultException from .kamereon import models from .renault_account import RenaultAccount from .renault_session import RenaultSession _LOGGER = logging.getLogger(__name__) class RenaultClient: """Proxy to a Renault profile.""" def __init__( self, session: Optional[RenaultSession] = None, websession: Optional[aiohttp.ClientSession] = None, locale: Optional[str] = None, country: Optional[str] = None, locale_details: Optional[Dict[str, str]] = None, credential_store: Optional[CredentialStore] = None, ) -> None: """Initialise Renault client.""" if session: self._session = session else: if websession is None: # pragma: no cover raise RenaultException( "`websession` is required if session is not provided." ) self._session = RenaultSession( websession=websession, locale=locale, country=country, locale_details=locale_details, credential_store=credential_store, ) @property def session(self) -> RenaultSession: """Get session provider.""" return self._session async def get_person(self) -> models.KamereonPersonResponse: """GET to /persons/{person_id}.""" return await self.session.get_person() async def get_api_accounts(self) -> List[RenaultAccount]: """Get account proxies.""" response = await self.get_person() if response.accounts is None: # pragma: no cover raise ValueError("response.accounts is None") result: List[RenaultAccount] = [] for account in response.accounts: if account.accountId is None: # pragma: no cover continue result.append( RenaultAccount(account_id=account.accountId, session=self.session) ) return result async def get_api_account(self, account_id: str) -> RenaultAccount: """Get account proxy for specified account id.""" return RenaultAccount(account_id=account_id, session=self.session) renault-api-0.2.9/src/renault_api/renault_session.py000066400000000000000000000240571473671120200226370ustar00rootroot00000000000000"""Session provider for interaction with Renault servers.""" import asyncio import logging from typing import Any from typing import Dict from typing import Optional import aiohttp from . import gigya from . import kamereon from .const import CONF_COUNTRY from .const import CONF_GIGYA_APIKEY from .const import CONF_GIGYA_URL from .const import CONF_KAMEREON_APIKEY from .const import CONF_KAMEREON_URL from .const import CONF_LOCALE from .credential import Credential from .credential import JWTCredential from .credential_store import CredentialStore from .exceptions import NotAuthenticatedException from .exceptions import RenaultException from .gigya.exceptions import GigyaResponseException from .kamereon import models from renault_api.helpers import get_api_keys _LOGGER = logging.getLogger(__name__) class RenaultSession: """Renault session for interaction with Renault servers.""" def __init__( self, websession: aiohttp.ClientSession, locale: Optional[str] = None, country: Optional[str] = None, locale_details: Optional[Dict[str, str]] = None, credential_store: Optional[CredentialStore] = None, ) -> None: """Initialise RenaultSession.""" self._gigya_lock = asyncio.Lock() self._websession = websession self._credentials: CredentialStore = credential_store or CredentialStore() if locale_details: for k, v in locale_details.items(): self._credentials[k] = Credential(v) if locale: self._credentials[CONF_LOCALE] = Credential(locale) if country: self._credentials[CONF_COUNTRY] = Credential(country) async def login(self, login_id: str, password: str) -> None: """Attempt login on Gigya.""" self._credentials.clear_keys(gigya.GIGYA_KEYS) response = await gigya.login( self._websession, await self._get_gigya_root_url(), await self._get_gigya_api_key(), login_id, password, ) credential = Credential(response.get_session_cookie()) self._credentials[gigya.GIGYA_LOGIN_TOKEN] = credential async def _get_credential(self, key: str) -> str: """Get specified credential, or raise RenaultException.""" if key not in self._credentials: if CONF_LOCALE in self._credentials: await self._update_from_locale() value = self._credentials.get_value(key) if value: return value if key == gigya.GIGYA_LOGIN_TOKEN: raise NotAuthenticatedException("Gigya login token not available.") raise RenaultException(f"Credential `{key}` not found in credential cache.") async def _update_from_locale(self) -> None: """Update all missing setting based on locale.""" locale = await self._get_credential(CONF_LOCALE) if CONF_COUNTRY not in self._credentials: self._credentials[CONF_COUNTRY] = Credential(locale[-2:]) locale_details = await get_api_keys(locale=locale, websession=self._websession) for k, v in locale_details.items(): if k not in self._credentials: self._credentials[k] = Credential(v) async def _get_country(self) -> str: """Get country from credential store.""" return await self._get_credential(CONF_COUNTRY) async def _get_kamereon_api_key(self) -> str: """Get Kamereon api-key from credential store.""" return await self._get_credential(CONF_KAMEREON_APIKEY) async def _get_kamereon_root_url(self) -> str: """Get Kamereon root url from credential store.""" return await self._get_credential(CONF_KAMEREON_URL) async def _get_gigya_api_key(self) -> str: """Get Gigya api-key from credential store.""" return await self._get_credential(CONF_GIGYA_APIKEY) async def _get_gigya_root_url(self) -> str: """Get Gigya root url from credential store.""" return await self._get_credential(CONF_GIGYA_URL) async def _get_login_token(self) -> str: """Get current login token from credential store.""" return await self._get_credential(gigya.GIGYA_LOGIN_TOKEN) async def _get_person_id(self) -> str: """Get person id from credential store or from Gigya.""" async with self._gigya_lock: person_id = self._credentials.get_value(gigya.GIGYA_PERSON_ID) if person_id: return person_id login_token = await self._get_login_token() response = await gigya.get_account_info( self._websession, await self._get_gigya_root_url(), await self._get_gigya_api_key(), login_token, ) person_id = response.get_person_id() self._credentials[gigya.GIGYA_PERSON_ID] = Credential(person_id) return person_id async def _get_jwt(self) -> str: """Get json web token from credential store or from Gigya..""" async with self._gigya_lock: jwt = self._credentials.get_value(gigya.GIGYA_JWT) if jwt: return jwt login_token = await self._get_login_token() try: response = await gigya.get_jwt( self._websession, await self._get_gigya_root_url(), await self._get_gigya_api_key(), login_token, ) except GigyaResponseException as exc: if exc.error_code in [403005, 403013]: # pragma: no branch self._credentials.clear_keys(gigya.GIGYA_KEYS) raise NotAuthenticatedException("Authentication expired.") from exc else: jwt = response.get_jwt() self._credentials[gigya.GIGYA_JWT] = JWTCredential(jwt) return jwt async def http_request( self, method: str, endpoint: str, json: Optional[Dict[str, Any]] = None ) -> models.KamereonResponse: """GET to specified endpoint.""" url = (await self._get_kamereon_root_url()) + endpoint params = {"country": await self._get_country()} return await kamereon.request( websession=self._websession, method=method, url=url, api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), params=params, json=json, ) async def get_person(self) -> models.KamereonPersonResponse: """GET to /persons/{person_id}.""" return await kamereon.get_person( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), person_id=await self._get_person_id(), ) async def get_account_vehicles( self, account_id: str ) -> models.KamereonVehiclesResponse: """GET to /accounts/{account_id}/vehicles.""" return await kamereon.get_account_vehicles( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), account_id=account_id, ) async def get_vehicle_details( self, account_id: str, vin: str ) -> models.KamereonVehicleDetailsResponse: """GET to /accounts/{account_id}/vehicles/{vin}/details.""" return await kamereon.get_vehicle_details( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), account_id=account_id, vin=vin, ) async def get_vehicle_data( self, account_id: str, vin: str, endpoint: str, params: Optional[Dict[str, str]] = None, *, adapter_type: str = "kca", ) -> models.KamereonVehicleDataResponse: """GET to /v{endpoint_version}/cars/{vin}/{endpoint}.""" return await kamereon.get_vehicle_data( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), account_id=account_id, vin=vin, endpoint=endpoint, params=params, adapter_type=adapter_type, ) async def get_vehicle_contracts( self, account_id: str, vin: str, ) -> models.KamereonVehicleContractsResponse: """GET to /v{endpoint_version}/cars/{vin}/contracts.""" return await kamereon.get_vehicle_contracts( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), account_id=account_id, vin=vin, locale=await self._get_credential(CONF_LOCALE), ) async def set_vehicle_action( self, account_id: str, vin: str, endpoint: str, attributes: Dict[str, Any], *, adapter_type: str = "kca", ) -> models.KamereonVehicleDataResponse: """POST to /v{endpoint_version}/cars/{vin}/{endpoint}.""" return await kamereon.set_vehicle_action( websession=self._websession, root_url=await self._get_kamereon_root_url(), api_key=await self._get_kamereon_api_key(), gigya_jwt=await self._get_jwt(), country=await self._get_country(), account_id=account_id, vin=vin, endpoint=endpoint, attributes=attributes, adapter_type=adapter_type, ) renault-api-0.2.9/src/renault_api/renault_vehicle.py000066400000000000000000000506521473671120200225730ustar00rootroot00000000000000"""Client for Renault API.""" import logging from datetime import datetime from datetime import timezone from typing import Dict from typing import List from typing import Optional from typing import cast from warnings import warn import aiohttp from .credential_store import CredentialStore from .exceptions import RenaultException from .kamereon import models from .kamereon import schemas from .renault_session import RenaultSession _LOGGER = logging.getLogger(__name__) PERIOD_DAY_FORMAT = "%Y%m%d" PERIOD_MONTH_FORMAT = "%Y%m" PERIOD_TZ_FORMAT = "%Y-%m-%dT%H:%M:%SZ" PERIOD_FORMATS = {"day": PERIOD_DAY_FORMAT, "month": PERIOD_MONTH_FORMAT} class RenaultVehicle: """Proxy to a Renault vehicle.""" def __init__( self, account_id: str, vin: str, *, session: Optional[RenaultSession] = None, websession: Optional[aiohttp.ClientSession] = None, locale: Optional[str] = None, country: Optional[str] = None, locale_details: Optional[Dict[str, str]] = None, credential_store: Optional[CredentialStore] = None, vehicle_details: Optional[models.KamereonVehicleDetails] = None, car_adapter: Optional[models.KamereonVehicleCarAdapterData] = None, ) -> None: """Initialise Renault vehicle.""" self._account_id = account_id self._vin = vin self._vehicle_details = vehicle_details self._car_adapter = car_adapter self._contracts: Optional[List[models.KamereonVehicleContract]] = None if session: self._session = session else: if websession is None: # pragma: no cover raise RenaultException( "`websession` is required if session is not provided." ) self._session = RenaultSession( websession=websession, locale=locale, country=country, locale_details=locale_details, credential_store=credential_store, ) @property def session(self) -> RenaultSession: """Get session.""" return self._session @property def account_id(self) -> str: """Get account id.""" return self._account_id @property def vin(self) -> str: """Get vin.""" return self._vin async def get_details(self) -> models.KamereonVehicleDetails: """Get vehicle details.""" if self._vehicle_details: return self._vehicle_details response = await self.session.get_vehicle_details( account_id=self.account_id, vin=self.vin, ) self._vehicle_details = cast( models.KamereonVehicleDetails, response, ) return self._vehicle_details async def get_car_adapter(self) -> models.KamereonVehicleCarAdapterData: """Get vehicle car adapter details.""" if self._car_adapter: return self._car_adapter response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="", ) self._car_adapter = cast( models.KamereonVehicleCarAdapterData, response.get_attributes(schemas.KamereonVehicleCarAdapterDataSchema), ) return self._car_adapter async def get_contracts(self) -> List[models.KamereonVehicleContract]: """Get vehicle contracts.""" if self._contracts: return self._contracts response = await self.session.get_vehicle_contracts( account_id=self.account_id, vin=self.vin, ) if response.contractList is None: # pragma: no cover raise ValueError("response.contractList is None") self._contracts = response.contractList return self._contracts async def get_battery_status(self) -> models.KamereonVehicleBatteryStatusData: """Get vehicle battery status.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="battery-status", ) return cast( models.KamereonVehicleBatteryStatusData, response.get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), ) async def get_tyre_pressure(self) -> models.KamereonVehicleTyrePressureData: """Get vehicle tyre pressure.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="pressure", ) return cast( models.KamereonVehicleTyrePressureData, response.get_attributes(schemas.KamereonVehicleTyrePressureDataSchema), ) async def get_location(self) -> models.KamereonVehicleLocationData: """Get vehicle location.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="location", ) return cast( models.KamereonVehicleLocationData, response.get_attributes(schemas.KamereonVehicleLocationDataSchema), ) async def get_hvac_status(self) -> models.KamereonVehicleHvacStatusData: """Get vehicle hvac status.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="hvac-status", ) return cast( models.KamereonVehicleHvacStatusData, response.get_attributes(schemas.KamereonVehicleHvacStatusDataSchema), ) async def get_hvac_settings(self) -> models.KamereonVehicleHvacSettingsData: """Get vehicle hvac settings (schedule+mode).""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="hvac-settings", ) return cast( models.KamereonVehicleHvacSettingsData, response.get_attributes(schemas.KamereonVehicleHvacSettingsDataSchema), ) async def get_charge_mode(self) -> models.KamereonVehicleChargeModeData: """Get vehicle charge mode.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="charge-mode", ) return cast( models.KamereonVehicleChargeModeData, response.get_attributes(schemas.KamereonVehicleChargeModeDataSchema), ) async def get_cockpit(self) -> models.KamereonVehicleCockpitData: """Get vehicle cockpit.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="cockpit", ) return cast( models.KamereonVehicleCockpitData, response.get_attributes(schemas.KamereonVehicleCockpitDataSchema), ) async def get_lock_status(self) -> models.KamereonVehicleLockStatusData: """Get vehicle lock status.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="lock-status", ) return cast( models.KamereonVehicleLockStatusData, response.get_attributes(schemas.KamereonVehicleLockStatusDataSchema), ) async def get_res_state(self) -> models.KamereonVehicleResStateData: """Get vehicle res state.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="res-state", ) return cast( models.KamereonVehicleResStateData, response.get_attributes(schemas.KamereonVehicleResStateDataSchema), ) async def get_charging_settings(self) -> models.KamereonVehicleChargingSettingsData: """Get vehicle charging settings.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="charging-settings", ) return cast( models.KamereonVehicleChargingSettingsData, response.get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ) async def get_notification_settings( self, ) -> models.KamereonVehicleNotificationSettingsData: """Get vehicle notification settings.""" response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="notification-settings", ) return cast( models.KamereonVehicleNotificationSettingsData, response.get_attributes( schemas.KamereonVehicleNotificationSettingsDataSchema ), ) async def get_charge_history( self, start: datetime, end: datetime, period: str ) -> models.KamereonVehicleChargeHistoryData: """Get vehicle charge history.""" if not isinstance(start, datetime): # pragma: no cover raise TypeError( "`start` should be an instance of datetime.datetime, " f"not {start.__class__}" ) if not isinstance(end, datetime): # pragma: no cover raise TypeError( "`end` should be an instance of datetime.datetime, " f"not {end.__class__}" ) if period not in PERIOD_FORMATS.keys(): # pragma: no cover raise TypeError("`period` should be one of `month`, `day`") params = { "type": period, "start": start.strftime(PERIOD_FORMATS[period]), "end": end.strftime(PERIOD_FORMATS[period]), } response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="charge-history", params=params, ) return cast( models.KamereonVehicleChargeHistoryData, response.get_attributes(schemas.KamereonVehicleChargeHistoryDataSchema), ) async def get_charges( self, start: datetime, end: datetime ) -> models.KamereonVehicleChargesData: """Get vehicle charges.""" if not isinstance(start, datetime): # pragma: no cover raise TypeError( "`start` should be an instance of datetime.datetime, " f"not {start.__class__}" ) if not isinstance(end, datetime): # pragma: no cover raise TypeError( "`end` should be an instance of datetime.datetime, " f"not {end.__class__}" ) params = { "start": start.strftime(PERIOD_DAY_FORMAT), "end": end.strftime(PERIOD_DAY_FORMAT), } response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="charges", params=params, ) return cast( models.KamereonVehicleChargesData, response.get_attributes(schemas.KamereonVehicleChargesDataSchema), ) async def get_hvac_history( self, start: datetime, end: datetime, period: str ) -> models.KamereonVehicleHvacHistoryData: """Get vehicle hvac history.""" if not isinstance(start, datetime): # pragma: no cover raise TypeError( "`start` should be an instance of datetime.datetime, " f"not {start.__class__}" ) if not isinstance(end, datetime): # pragma: no cover raise TypeError( "`end` should be an instance of datetime.datetime, " f"not {end.__class__}" ) if period not in PERIOD_FORMATS.keys(): # pragma: no cover raise TypeError("`period` should be one of `month`, `day`") params = { "type": period, "start": start.strftime(PERIOD_FORMATS[period]), "end": end.strftime(PERIOD_FORMATS[period]), } response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="hvac-history", params=params, ) return cast( models.KamereonVehicleHvacHistoryData, response.get_attributes(schemas.KamereonVehicleHvacHistoryDataSchema), ) async def get_hvac_sessions( self, start: datetime, end: datetime ) -> models.KamereonVehicleHvacSessionsData: """Get vehicle hvac sessions.""" if not isinstance(start, datetime): # pragma: no cover raise TypeError( "`start` should be an instance of datetime.datetime, " f"not {start.__class__}" ) if not isinstance(end, datetime): # pragma: no cover raise TypeError( "`end` should be an instance of datetime.datetime, " f"not {end.__class__}" ) params = { "start": start.strftime(PERIOD_DAY_FORMAT), "end": end.strftime(PERIOD_DAY_FORMAT), } response = await self.session.get_vehicle_data( account_id=self.account_id, vin=self.vin, endpoint="hvac-sessions", params=params, ) return cast( models.KamereonVehicleHvacSessionsData, response.get_attributes(schemas.KamereonVehicleHvacSessionsDataSchema), ) async def set_ac_start( self, temperature: float, when: Optional[datetime] = None ) -> models.KamereonVehicleHvacStartActionData: """Start vehicle ac.""" attributes = { "action": "start", "targetTemperature": temperature, } if when: if not isinstance(when, datetime): # pragma: no cover raise TypeError( "`when` should be an instance of datetime.datetime, " f"not {when.__class__}" ) start_date_time = when.astimezone(timezone.utc).strftime(PERIOD_TZ_FORMAT) attributes["startDateTime"] = start_date_time response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/hvac-start", attributes=attributes, ) return cast( models.KamereonVehicleHvacStartActionData, response.get_attributes(schemas.KamereonVehicleHvacStartActionDataSchema), ) async def set_ac_stop(self) -> models.KamereonVehicleHvacStartActionData: """Stop vehicle ac.""" await self.warn_on_method("set_ac_stop") attributes = {"action": "cancel"} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/hvac-start", attributes=attributes, ) return cast( models.KamereonVehicleHvacStartActionData, response.get_attributes(schemas.KamereonVehicleHvacStartActionDataSchema), ) async def set_hvac_schedules( self, schedules: List[models.HvacSchedule] ) -> models.KamereonVehicleHvacScheduleActionData: """Set vehicle charge schedules.""" for schedule in schedules: if not isinstance(schedule, models.HvacSchedule): # pragma: no cover raise TypeError( "`schedules` should be a list of HvacSchedule, " f"not {schedules.__class__}" ) attributes = {"schedules": [schedule.for_json() for schedule in schedules]} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/hvac-schedule", attributes=attributes, ) return cast( models.KamereonVehicleHvacScheduleActionData, response.get_attributes( schemas.KamereonVehicleHvacScheduleActionDataSchema ), ) async def set_charge_schedules( self, schedules: List[models.ChargeSchedule] ) -> models.KamereonVehicleChargeScheduleActionData: """Set vehicle charge schedules.""" for schedule in schedules: if not isinstance(schedule, models.ChargeSchedule): # pragma: no cover raise TypeError( "`schedules` should be a list of ChargeSchedule, " f"not {schedules.__class__}" ) attributes = {"schedules": [schedule.for_json() for schedule in schedules]} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/charge-schedule", attributes=attributes, ) return cast( models.KamereonVehicleChargeScheduleActionData, response.get_attributes( schemas.KamereonVehicleChargeScheduleActionDataSchema ), ) async def set_charge_mode( self, charge_mode: str ) -> models.KamereonVehicleChargeModeActionData: """Set vehicle charge mode.""" attributes = {"action": charge_mode} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/charge-mode", attributes=attributes, ) return cast( models.KamereonVehicleChargeModeActionData, response.get_attributes(schemas.KamereonVehicleChargeModeActionDataSchema), ) async def set_charge_start(self) -> models.KamereonVehicleChargingStartActionData: """Start vehicle charge.""" details = await self.get_details() if details.controls_action_via_kcm("charge"): attributes = {"action": "resume"} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="charge/pause-resume", attributes=attributes, adapter_type="kcm", ) else: attributes = {"action": "start"} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/charging-start", attributes=attributes, ) return cast( models.KamereonVehicleChargingStartActionData, response.get_attributes( schemas.KamereonVehicleChargingStartActionDataSchema ), ) async def set_charge_stop(self) -> models.KamereonVehicleChargingStartActionData: """Start vehicle charge.""" details = await self.get_details() if details.controls_action_via_kcm("charge"): attributes = {"action": "pause"} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="charge/pause-resume", attributes=attributes, adapter_type="kcm", ) else: attributes = {"action": "stop"} response = await self.session.set_vehicle_action( account_id=self.account_id, vin=self.vin, endpoint="actions/charging-start", attributes=attributes, ) return cast( models.KamereonVehicleChargingStartActionData, response.get_attributes( schemas.KamereonVehicleChargingStartActionDataSchema ), ) async def supports_endpoint(self, endpoint: str) -> bool: """Check if vehicle supports endpoint.""" details = await self.get_details() return details.supports_endpoint(endpoint) async def has_contract_for_endpoint(self, endpoint: str) -> bool: """Check if vehicle has contract for endpoint.""" # "Deprecated in 0.1.3, contract codes are country-specific" # " and can't be used to guess requirements." warn("This method is deprecated.", DeprecationWarning, stacklevel=2) return True # pragma: no cover async def warn_on_method(self, method: str) -> None: """Log a warning if the method requires it.""" details = await self.get_details() warning = details.warns_on_method(method) if warning: _LOGGER.warning(warning) renault-api-0.2.9/tests/000077500000000000000000000000001473671120200151105ustar00rootroot00000000000000renault-api-0.2.9/tests/__init__.py000066400000000000000000000000561473671120200172220ustar00rootroot00000000000000"""Test suite for the renault_api package.""" renault-api-0.2.9/tests/cli/000077500000000000000000000000001473671120200156575ustar00rootroot00000000000000renault-api-0.2.9/tests/cli/__init__.py000066400000000000000000000027411473671120200177740ustar00rootroot00000000000000"""Test suite for the renault_api CLI.""" import os from typing import Optional from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_LOCALE from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PERSON_ID from tests.const import TEST_VIN from tests.fixtures import get_jwt from renault_api.cli.renault_settings import CONF_ACCOUNT_ID from renault_api.cli.renault_settings import CONF_VIN from renault_api.cli.renault_settings import CREDENTIAL_PATH from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential import JWTCredential from renault_api.credential_store import FileCredentialStore from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.gigya import GIGYA_PERSON_ID def initialise_credential_store( include_account_id: Optional[bool] = None, include_vin: Optional[bool] = None, ) -> None: """Initialise CLI credential store.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(get_jwt()) if include_account_id: credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) if include_vin: credential_store[CONF_VIN] = Credential(TEST_VIN) renault-api-0.2.9/tests/cli/test_account.py000066400000000000000000000111331473671120200207230ustar00rootroot00000000000000"""Test cases for the __main__ module.""" import os from locale import getdefaultlocale from aioresponses import aioresponses from click.testing import CliRunner from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_LOCALE from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PASSWORD from tests.const import TEST_PERSON_ID from tests.const import TEST_USERNAME from renault_api.cli import __main__ from renault_api.cli.renault_settings import CONF_ACCOUNT_ID from renault_api.cli.renault_settings import CREDENTIAL_PATH from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential import JWTCredential from renault_api.credential_store import FileCredentialStore from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.gigya import GIGYA_PERSON_ID def test_list_vehicles_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" fixtures.inject_gigya_all(mocked_responses) fixtures.inject_get_person(mocked_responses) # Injected for account selection fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") vehicle2_urlpath = f"accounts/account-id-2/vehicles?{fixtures.DEFAULT_QUERY_STRING}" fixtures.inject_data( mocked_responses, vehicle2_urlpath, "vehicles/zoe_40.1.json", ) # Injected again for vehicle listing fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke( __main__.main, "vehicles", input=f"{TEST_LOCALE}\nN\n{TEST_USERNAME}\n{TEST_PASSWORD}\n1\ny\n", ) assert result.exit_code == 0, result.exception default_locale = getdefaultlocale()[0] prompt_default = f" [{default_locale}]" if default_locale else "" expected_output = ( f"Please select a locale{prompt_default}: {TEST_LOCALE}\n" "Do you want to save the locale to the credential store? [y/N]: N\n" "\n" f"User: {TEST_USERNAME}\n" "Password: \n" "\n" " ID Type Vehicles\n" "-- ------------ --------- ----------\n" " 1 account-id-1 MYRENAULT 1\n" " 2 account-id-2 SFDC 1\n" "\n" "Please select account [1]: 1\n" "Do you want to save the account ID to the credential store? [y/N]: y\n" "\n" "Registration Brand Model VIN\n" "-------------- ------- ------- -----------------\n" "REG-NUMBER RENAULT ZOE VF1AAAAA555777999\n" ) assert expected_output == result.output def test_list_vehicles_store( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke(__main__.main, "vehicles") assert result.exit_code == 0, result.exception expected_output = ( "Registration Brand Model VIN\n" "-------------- ------- ------- -----------------\n" "REG-NUMBER RENAULT ZOE VF1AAAAA555777999\n" ) assert expected_output == result.output def test_list_vehicles_no_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke(__main__.main, f"--account {TEST_ACCOUNT_ID} vehicles") assert result.exit_code == 0, result.exception expected_output = ( "Registration Brand Model VIN\n" "-------------- ------- ------- -----------------\n" "REG-NUMBER RENAULT ZOE VF1AAAAA555777999\n" ) assert expected_output == result.output renault-api-0.2.9/tests/cli/test_client.py000066400000000000000000000075001473671120200205500ustar00rootroot00000000000000"""Test cases for the __main__ module.""" import os from locale import getdefaultlocale from aioresponses import aioresponses from click.testing import CliRunner from tests import fixtures from tests.const import TEST_LOCALE from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PASSWORD from tests.const import TEST_PERSON_ID from tests.const import TEST_USERNAME from renault_api.cli import __main__ from renault_api.cli.renault_settings import CREDENTIAL_PATH from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential import JWTCredential from renault_api.credential_store import FileCredentialStore from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.gigya import GIGYA_PERSON_ID def test_login_prompt(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" fixtures.inject_gigya_login(mocked_responses) result = cli_runner.invoke( __main__.main, "login", input=f"{TEST_USERNAME}\n{TEST_PASSWORD}\n{TEST_LOCALE}\ny", ) assert result.exit_code == 0, result.exception default_locale = getdefaultlocale()[0] prompt_default = f" [{default_locale}]" if default_locale else "" expected_output = ( f"User: {TEST_USERNAME}\n" "Password: \n" f"Please select a locale{prompt_default}: {TEST_LOCALE}\n" "Do you want to save the locale to the credential store? [y/N]: y\n" "\n" ) assert expected_output == result.output def test_login_no_prompt(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" fixtures.inject_gigya_login(mocked_responses) result = cli_runner.invoke( __main__.main, f"--locale {TEST_LOCALE} " f"login --user {TEST_USERNAME} --password {TEST_PASSWORD}", ) assert result.exit_code == 0, result.exception assert "" == result.output def test_list_accounts_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" fixtures.inject_gigya_all(mocked_responses) fixtures.inject_get_person(mocked_responses) result = cli_runner.invoke( __main__.main, "accounts", input=f"{TEST_LOCALE}\nN\n{TEST_USERNAME}\n{TEST_PASSWORD}\n", ) assert result.exit_code == 0, result.exception default_locale = getdefaultlocale()[0] prompt_default = f" [{default_locale}]" if default_locale else "" expected_output = ( f"Please select a locale{prompt_default}: {TEST_LOCALE}\n" "Do you want to save the locale to the credential store? [y/N]: N\n" "\n" f"User: {TEST_USERNAME}\n" "Password: \n" "\n" "Type ID\n" "--------- ------------\n" "MYRENAULT account-id-1\n" "SFDC account-id-2\n" ) assert expected_output == result.output def test_list_accounts_no_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_person(mocked_responses) result = cli_runner.invoke(__main__.main, "accounts") assert result.exit_code == 0, result.exception expected_output = ( "Type ID\n" "--------- ------------\n" "MYRENAULT account-id-1\n" "SFDC account-id-2\n" ) assert expected_output == result.output renault-api-0.2.9/tests/cli/test_main.py000066400000000000000000000044101473671120200202130ustar00rootroot00000000000000"""Test cases for the __main__ module.""" import os import pathlib from datetime import datetime from typing import Any from typing import Generator from _pytest.fixtures import fixture from _pytest.monkeypatch import MonkeyPatch from click.testing import CliRunner from tests.const import TEST_LOCALE from renault_api.cli import __main__ PATCH_TODAY = datetime(2018, 12, 25) @fixture def patch_root(tmpdir: pathlib.Path) -> Generator[None, None, None]: """Update current directory to test log folder.""" root_dir = os.getcwd() os.chdir(tmpdir) yield os.chdir(root_dir) @fixture def patch_datetime(monkeypatch: MonkeyPatch) -> Generator[None, None, None]: """Allow override of __main__.datetime methods.""" class MyDatetime: @classmethod def today(cls) -> datetime: """Force today to return a static date.""" return PATCH_TODAY monkeypatch.setattr("renault_api.cli.__main__.datetime", MyDatetime) yield def test_main_succeeds(cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" result = cli_runner.invoke(__main__.main) assert result.exit_code == 0, result.exception def test_debug(cli_runner: CliRunner, caplog: Any) -> None: """Test enable debug.""" result = cli_runner.invoke(__main__.main, f"--debug set --locale {TEST_LOCALE}") assert result.exit_code == 0 assert __main__._WARNING_DEBUG_ENABLED in caplog.text def test_log_no_folder( cli_runner: CliRunner, patch_root: Any, patch_datetime: Any ) -> None: """Test enable log.""" assert not os.path.exists("logs") os.makedirs("logs") assert os.path.exists("logs") result = cli_runner.invoke(__main__.main, f"--log set --locale {TEST_LOCALE}") assert result.exit_code == 0 with open("logs/2018-12-25.log") as myfile: assert __main__._WARNING_DEBUG_ENABLED in myfile.read() def test_log_existing_folder( cli_runner: CliRunner, patch_root: Any, patch_datetime: Any ) -> None: """Test enable log.""" assert not os.path.exists("logs") result = cli_runner.invoke(__main__.main, f"--log set --locale {TEST_LOCALE}") assert result.exit_code == 0 with open("logs/2018-12-25.log") as myfile: assert __main__._WARNING_DEBUG_ENABLED in myfile.read() renault-api-0.2.9/tests/cli/test_session.py000066400000000000000000000101201473671120200207450ustar00rootroot00000000000000"""Test cases for the __main__ module.""" import os from click.testing import CliRunner from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_LOCALE from tests.const import TEST_LOCALE_DETAILS from tests.const import TEST_VIN from renault_api.cli import __main__ from renault_api.cli.renault_settings import CONF_ACCOUNT_ID from renault_api.cli.renault_settings import CONF_VIN from renault_api.cli.renault_settings import CREDENTIAL_PATH from renault_api.const import CONF_GIGYA_APIKEY from renault_api.const import CONF_GIGYA_URL from renault_api.const import CONF_KAMEREON_APIKEY from renault_api.const import CONF_KAMEREON_URL from renault_api.const import CONF_LOCALE from renault_api.credential_store import FileCredentialStore def test_set_locale(cli_runner: CliRunner) -> None: """Test set locale.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_LOCALE, CONF_GIGYA_APIKEY, CONF_GIGYA_URL, CONF_KAMEREON_APIKEY, CONF_KAMEREON_URL, ]: assert key not in credential_store result = cli_runner.invoke(__main__.main, f"set --locale {TEST_LOCALE}") assert result.exit_code == 0 credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_LOCALE, CONF_GIGYA_APIKEY, CONF_GIGYA_URL, CONF_KAMEREON_APIKEY, CONF_KAMEREON_URL, ]: assert key in credential_store assert "" == result.output def test_set_account(cli_runner: CliRunner) -> None: """Test set locale.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_ACCOUNT_ID, ]: assert key not in credential_store result = cli_runner.invoke(__main__.main, f"set --account {TEST_ACCOUNT_ID}") assert result.exit_code == 0 credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_ACCOUNT_ID, ]: assert key in credential_store assert "" == result.output def test_set_vin(cli_runner: CliRunner) -> None: """Test set vin.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_VIN, ]: assert key not in credential_store result = cli_runner.invoke(__main__.main, f"set --vin {TEST_VIN}") assert result.exit_code == 0 credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) for key in [ CONF_VIN, ]: assert key in credential_store assert "" == result.output def test_get_keys_succeeds(cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" result = cli_runner.invoke(__main__.main, f"set --locale {TEST_LOCALE}") assert result.exit_code == 0 result = cli_runner.invoke(__main__.main, "settings") assert result.exit_code == 0 expected_output = ( "Key Value\n" f"{'-'*17} {'-'*66}\n" f"locale {TEST_LOCALE}\n" f"{CONF_GIGYA_URL} {TEST_LOCALE_DETAILS[CONF_GIGYA_URL]}\n" f"{CONF_GIGYA_APIKEY} {TEST_LOCALE_DETAILS[CONF_GIGYA_APIKEY]}\n" f"{CONF_KAMEREON_URL} {TEST_LOCALE_DETAILS[CONF_KAMEREON_URL]}\n" f"{CONF_KAMEREON_APIKEY} {TEST_LOCALE_DETAILS[CONF_KAMEREON_APIKEY]}\n" ) assert expected_output == result.output def test_reset(cli_runner: CliRunner) -> None: """Test set vin.""" assert not os.path.exists(os.path.expanduser(CREDENTIAL_PATH)) result = cli_runner.invoke(__main__.main, f"set --locale {TEST_LOCALE}") assert result.exit_code == 0 assert os.path.exists(os.path.expanduser(CREDENTIAL_PATH)) # Reset a first time - file should get deleted result = cli_runner.invoke(__main__.main, "reset") assert result.exit_code == 0 assert not os.path.exists(os.path.expanduser(CREDENTIAL_PATH)) # Reset a second time - make sure it doesn't error result = cli_runner.invoke(__main__.main, "reset") assert result.exit_code == 0 assert not os.path.exists(os.path.expanduser(CREDENTIAL_PATH)) renault-api-0.2.9/tests/cli/test_vehicle.py000066400000000000000000000564421473671120200207220ustar00rootroot00000000000000"""Test cases for the __main__ module.""" import json import os from locale import getdefaultlocale from typing import Any import pytest from aioresponses import aioresponses from aioresponses.core import RequestCall from click.testing import CliRunner from typeguard import suppress_type_checks from yarl import URL from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_LOCALE from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PASSWORD from tests.const import TEST_PERSON_ID from tests.const import TEST_USERNAME from tests.const import TEST_VIN from renault_api.cli import __main__ from renault_api.cli.renault_settings import CONF_ACCOUNT_ID from renault_api.cli.renault_settings import CONF_VIN from renault_api.cli.renault_settings import CREDENTIAL_PATH from renault_api.const import CONF_LOCALE from renault_api.credential import Credential from renault_api.credential import JWTCredential from renault_api.credential_store import FileCredentialStore from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.gigya import GIGYA_PERSON_ID EXPECTED_STATUS = { "captur_ii.1.json": ( "-------------------- ----------------------\n" "Total mileage 5566.78 km\n" "Fuel autonomy 35.0 km\n" "Fuel quantity 3.0 L\n" "GPS Latitude 48.1234567\n" "GPS Longitude 11.1234567\n" "GPS last updated 2020-02-18 17:58:38\n" "Lock status locked\n" "Lock last updated 2022-02-02 14:51:13\n" "Engine state Stopped, ready for RES\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- ----------------------\n" ), "captur_ii.2.json": ( "-------------------- -------------------------\n" "Battery level 50 %\n" "Last updated 2020-11-17 09:06:48\n" "Range estimate 128 km\n" "Plug state PlugState.UNPLUGGED\n" "Charging state ChargeState.NOT_IN_CHARGE\n" "Charge mode always\n" "Total mileage 5566.78 km\n" "Fuel autonomy 35.0 km\n" "Fuel quantity 3.0 L\n" "GPS Latitude 48.1234567\n" "GPS Longitude 11.1234567\n" "GPS last updated 2020-02-18 17:58:38\n" "Lock status locked\n" "Lock last updated 2022-02-02 14:51:13\n" "Engine state Stopped, ready for RES\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "twingo_ze.1.json": ( "-------------------- -------------------------\n" "Battery level 50 %\n" "Last updated 2020-11-17 09:06:48\n" "Range estimate 128 km\n" "Plug state PlugState.UNPLUGGED\n" "Charging state ChargeState.NOT_IN_CHARGE\n" "Charge mode always\n" "Total mileage 49114.27 km\n" "GPS Latitude 48.1234567\n" "GPS Longitude 11.1234567\n" "GPS last updated 2020-02-18 17:58:38\n" "Lock status locked\n" "Lock last updated 2022-02-02 14:51:13\n" "Engine state Stopped, ready for RES\n" "HVAC status on\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_40.1.json": ( "-------------------- -------------------------\n" "Battery level 50 %\n" "Last updated 2020-11-17 09:06:48\n" "Range estimate 128 km\n" "Plug state PlugState.UNPLUGGED\n" "Charging state ChargeState.NOT_IN_CHARGE\n" "Charge mode always\n" "Total mileage 49114.27 km\n" "Engine state Stopped, ready for RES\n" "HVAC status off\n" "External temperature 8.0 °C\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_40.2.json": ( "-------------------- -------------------------\n" "Battery level 50 %\n" "Last updated 2020-11-17 09:06:48\n" "Range estimate 128 km\n" "Plug state PlugState.UNPLUGGED\n" "Charging state ChargeState.NOT_IN_CHARGE\n" "Charge mode always\n" "Total mileage 49114.27 km\n" "Engine state Stopped, ready for RES\n" "HVAC status off\n" "External temperature 8.0 °C\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_50.1.json": ( "-------------------- -------------------------\n" "Battery level 50 %\n" "Last updated 2020-11-17 09:06:48\n" "Range estimate 128 km\n" "Plug state PlugState.UNPLUGGED\n" "Charging state ChargeState.NOT_IN_CHARGE\n" "Charge mode always\n" "Total mileage 5785.75 km\n" "Fuel autonomy 0.0 km\n" "Fuel quantity 0.0 L\n" "GPS Latitude 48.1234567\n" "GPS Longitude 11.1234567\n" "GPS last updated 2020-02-18 17:58:38\n" "Lock status locked\n" "Lock last updated 2022-02-02 14:51:13\n" "Engine state Stopped, ready for RES\n" "HVAC status on\n" "Front left pressure 2460 bar\n" "Front right pressure 2730 bar\n" "Rear left pressure 2790 bar\n" "Rear right pressure 2790 bar\n" "-------------------- -------------------------\n" ), "zoe_40.1_json.json": ( '{"battery-status": {"timestamp": "2020-11-17T09:06:48+01:00", ' '"batteryLevel": 50, "batteryAutonomy": 128, "batteryCapacity": 0, ' '"batteryAvailableEnergy": 0, "plugStatus": 0, "chargingStatus": -1.0}, ' '"charge-mode": {"chargeMode": "always"}, ' '"cockpit": {"totalMileage": 49114.27}, ' '"res-state": {"details": "Stopped, ready for RES", "code": "10"}, ' '"hvac-status": {"externalTemperature": 8.0, "hvacStatus": "off"}, ' '"pressure": {"flPressure": 2460, "frPressure": 2730, ' '"rlPressure": 2790, "rrPressure": 2790, ' '"flStatus": 0, "frStatus": 0, ' '"rlStatus": 0, "rrStatus": 0}}\n' ), } def test_vehicle_details(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke(__main__.main, "vehicle") assert result.exit_code == 0, result.exception expected_output = ( "Registration Brand Model VIN\n" "-------------- ------- ------- -----------------\n" "REG-NUMBER RENAULT ZOE VF1AAAAA555777999\n" ) assert expected_output == result.output @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicles") ) def test_vehicle_status( mocked_responses: aioresponses, cli_runner: CliRunner, filename: str ) -> None: """It exits with a status code of zero.""" filename = os.path.basename(filename) credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_details(mocked_responses, filename) if filename in [ "captur_ii.1.json", "captur_ii.2.json", ]: fixtures.inject_vehicle_status(mocked_responses, "captur_ii") elif filename in [ "twingo_ze.1.json", ]: fixtures.inject_vehicle_status(mocked_responses, "twingo_ze") elif filename in [ "zoe_50.1.json", ]: fixtures.inject_vehicle_status(mocked_responses, "zoe_50") else: fixtures.inject_vehicle_status(mocked_responses, "zoe") result = cli_runner.invoke(__main__.main, "status") assert result.exit_code == 0, result.exception if filename in EXPECTED_STATUS: assert EXPECTED_STATUS[filename] == result.output def test_vehicle_status_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" fixtures.inject_gigya_all(mocked_responses) fixtures.inject_get_person(mocked_responses) # Injected for account selection fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") vehicle2_urlpath = f"accounts/account-id-2/vehicles?{fixtures.DEFAULT_QUERY_STRING}" fixtures.inject_data( mocked_responses, vehicle2_urlpath, "vehicles/zoe_40.1.json", ) # Injected again for vehicle selection fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.2.json") fixtures.inject_vehicle_status(mocked_responses, "zoe") result = cli_runner.invoke( __main__.main, "status", input=f"{TEST_LOCALE}\nN\n{TEST_USERNAME}\n{TEST_PASSWORD}\n1\ny\n1\ny\n", ) assert result.exit_code == 0, result.exception default_locale = getdefaultlocale()[0] prompt_default = f" [{default_locale}]" if default_locale else "" expected_output = ( f"Please select a locale{prompt_default}: {TEST_LOCALE}\n" "Do you want to save the locale to the credential store? [y/N]: N\n" "\n" f"User: {TEST_USERNAME}\n" "Password: \n" "\n" " ID Type Vehicles\n" "-- ------------ --------- ----------\n" " 1 account-id-1 MYRENAULT 1\n" " 2 account-id-2 SFDC 1\n" "\n" "Please select account [1]: 1\n" "Do you want to save the account ID to the credential store? [y/N]: y\n" "\n" " Vin Registration Brand Model\n" "-- ----------------- -------------- ------- -------\n" " 1 VF1AAAAA555777999 REG-NUMBER RENAULT ZOE\n" "\n" "Please select vehicle [1]: 1\n" "Do you want to save the VIN to the credential store? [y/N]: y\n" "\n" f"{EXPECTED_STATUS['zoe_40.1.json']}" ) assert expected_output == result.output def test_vehicle_status_no_prompt( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.2.json") fixtures.inject_vehicle_status(mocked_responses, "zoe") result = cli_runner.invoke( __main__.main, f"--account {TEST_ACCOUNT_ID} --vin {TEST_VIN} status" ) assert result.exit_code == 0, result.exception assert EXPECTED_STATUS["zoe_40.1.json"] == result.output def test_vehicle_status_json( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.2.json") fixtures.inject_vehicle_status(mocked_responses, "zoe") result = cli_runner.invoke( __main__.main, f"--account {TEST_ACCOUNT_ID} --vin {TEST_VIN} --json status" ) assert result.exit_code == 0, result.exception assert EXPECTED_STATUS["zoe_40.1_json.json"] == result.output def test_vehicle_contracts( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.1.json") result = cli_runner.invoke(__main__.main, "contracts") assert result.exit_code == 0, result.exception expected_output = ( "Type Code " "Description Start End Status\n" "------------------------------ -------------------- " "-------------------------------- ---------- ---------- " "------------------\n" "WARRANTY_MAINTENANCE_CONTRACTS 40 " "CONTRAT LOSANGE 2018-04-04 2022-04-03 Actif\n" "CONNECTED_SERVICES ZECONNECTP " "My Z.E. Connect en série 36 mois 2018-08-23 2021-08-23 Actif\n" "CONNECTED_SERVICES GBA " "Battery Services 2018-03-23 " "Echec d’activation\n" "WARRANTY ManufacturerWarranty " "Garantie fabricant 2020-04-03 Expiré\n" "WARRANTY PaintingWarranty " "Garantie peinture 2021-04-03 Actif\n" "WARRANTY CorrosionWarranty " "Garantie corrosion 2030-04-03 Actif\n" "WARRANTY GMPeWarranty " "Garantie GMPe 2020-04-03 Expiré\n" "WARRANTY AssistanceWarranty " "Garantie assistance 2020-04-03 Expiré\n" ) assert expected_output == result.output def test_http_get(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) fixtures.inject_get_charging_settings(mocked_responses, "single") endpoint = ( "/commerce/v1/accounts/{account_id}" "/kamereon/kca/car-adapter" "/v1/cars/{vin}/charging-settings" ) result = cli_runner.invoke( __main__.main, f"http get {endpoint}", ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': {'type': 'Car', 'id': 'VF1AAAAA555777999', 'attributes': {" "'mode': 'scheduled', 'schedules': [" "{'id': 1, 'activated': True, " "'monday': {'startTime': 'T12:00Z', 'duration': 15}, " "'tuesday': {'startTime': 'T04:30Z', 'duration': 420}, " "'wednesday': {'startTime': 'T22:30Z', 'duration': 420}, " "'thursday': {'startTime': 'T22:00Z', 'duration': 420}, " "'friday': {'startTime': 'T12:15Z', 'duration': 15}, " "'saturday': {'startTime': 'T12:30Z', 'duration': 30}, " "'sunday': {'startTime': 'T12:45Z', 'duration': 45}}" "]}}}\n" ) assert expected_output == result.output def test_http_get_list(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) print(fixtures.inject_get_notifications(mocked_responses)) endpoint = ( "/commerce/v1/persons/person-id-1" "/notifications/kmr?notificationId=ffcb0310-503f-4bc3-9056-e9d051a089c6" ) result = cli_runner.invoke( __main__.main, f"http get {endpoint}", ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': [{'notificationId': 'ffcb0310-503f-4bc3-9056-e9d051a089c6', " "'notifDate': '2022-02-01T19:01:51.622', 'vin': '*PRIVATE*', " "'personId': '*PRIVATE*', 'kmrUserId': '*PRIVATE*', " "'actionType': 'COMMAND_RESPONSE', 'commandResponse': {'status': 'CREATED'}, " "'commandType': 'SRP_SETS'}, " "{'notificationId': 'ffcb0310-503f-4bc3-9056-e9d051a089c6', " "'notifDate': '2022-02-01T19:01:51.623', 'vin': '*PRIVATE*', " "'personId': '*PRIVATE*', 'kmrUserId': '*PRIVATE*', " "'actionType': 'SRP_SALT_REQUEST', 'srpResponse': {'status': 'OK', 'loginB': " "'*PRIVATE*', 'loginSalt': '*PRIVATE*'}}]}\n" ) assert expected_output == result.output def test_http_post(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") endpoint = ( "/commerce/v1/accounts/{account_id}" "/kamereon/kca/car-adapter" "/v2/cars/{vin}/actions/charge-schedule" ) body = {"data": {"type": "ChargeSchedule", "attributes": {"schedules": []}}} json_body = json.dumps(body) result = cli_runner.invoke( __main__.main, f"http post {endpoint} '{json_body}'", # noqa: B907 ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': {'type': 'ChargeSchedule', 'id': 'guid', " "'attributes': {'schedules': [" "{'id': 1, 'activated': True, " "'tuesday': {'startTime': 'T04:30Z', 'duration': 420}, " "'wednesday': {'startTime': 'T22:30Z', 'duration': 420}, " "'thursday': {'startTime': 'T22:00Z', 'duration': 420}, " "'friday': {'startTime': 'T23:30Z', 'duration': 480}, " "'saturday': {'startTime': 'T18:30Z', 'duration': 120}, " "'sunday': {'startTime': 'T12:45Z', 'duration': 45}}]}}}\n" ) assert expected_output == result.output expected_json = { "data": {"type": "ChargeSchedule", "attributes": {"schedules": []}} } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] def test_http_post_file( tmpdir: Any, mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" credential_store = FileCredentialStore(os.path.expanduser(CREDENTIAL_PATH)) credential_store[CONF_LOCALE] = Credential(TEST_LOCALE) credential_store[CONF_ACCOUNT_ID] = Credential(TEST_ACCOUNT_ID) credential_store[CONF_VIN] = Credential(TEST_VIN) credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(fixtures.get_jwt()) url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") endpoint = ( "/commerce/v1/accounts/{account_id}" "/kamereon/kca/car-adapter" "/v2/cars/{vin}/actions/charge-schedule" ) body = {"data": {"type": "ChargeSchedule", "attributes": {"schedules": []}}} json_file = tmpdir.mkdir("json").join("sample.json") json_file.write(json.dumps(body)) with suppress_type_checks(): result = cli_runner.invoke( __main__.main, f"http post-file {endpoint} '{json_file}'", ) assert result.exit_code == 0, result.exception expected_output = ( "{'data': {'type': 'ChargeSchedule', 'id': 'guid', " "'attributes': {'schedules': [" "{'id': 1, 'activated': True, " "'tuesday': {'startTime': 'T04:30Z', 'duration': 420}, " "'wednesday': {'startTime': 'T22:30Z', 'duration': 420}, " "'thursday': {'startTime': 'T22:00Z', 'duration': 420}, " "'friday': {'startTime': 'T23:30Z', 'duration': 480}, " "'saturday': {'startTime': 'T18:30Z', 'duration': 120}, " "'sunday': {'startTime': 'T12:45Z', 'duration': 45}}]}}}\n" ) assert expected_output == result.output expected_json = { "data": {"type": "ChargeSchedule", "attributes": {"schedules": []}} } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] renault-api-0.2.9/tests/cli/test_vehicle_charge.py000066400000000000000000000632601473671120200222270ustar00rootroot00000000000000"""Test cases for the __main__ module.""" from aioresponses import aioresponses from aioresponses.core import RequestCall from click.testing import CliRunner from yarl import URL from tests import fixtures from . import initialise_credential_store from renault_api.cli import __main__ def test_charge_history_day( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_charge_history( mocked_responses, start="20201101", end="20201130", period="day" ) result = cli_runner.invoke( __main__.main, "charge history --from 2020-11-01 --to 2020-11-30 --period day" ) assert result.exit_code == 0, result.exception expected_output = ( " Day Number of charges Total time charging Errors\n" "-------- ------------------- --------------------- --------\n" "20201208 2 8:15:00 0\n" "20201205 1 10:57:00 0\n" ) assert expected_output == result.output def test_charge_history_month( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_charge_history( mocked_responses, start="202011", end="202011", period="month" ) result = cli_runner.invoke( __main__.main, "charge history --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = ( " Month Number of charges Total time charging Errors\n" "------- ------------------- --------------------- --------\n" " 202011 1 7:59:00 0\n" ) assert expected_output == result.output def test_charge_mode_get(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_charge_mode(mocked_responses) result = cli_runner.invoke(__main__.main, "charge mode") assert result.exit_code == 0, result.exception expected_output = "Charge mode: always\n" assert expected_output == result.output def test_charge_mode_set(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) url = fixtures.inject_set_charge_mode(mocked_responses, mode="schedule_mode") result = cli_runner.invoke(__main__.main, "charge mode --set schedule_mode") assert result.exit_code == 0, result.exception expected_json = { "data": {"attributes": {"action": "schedule_mode"}, "type": "ChargeMode"} } expected_output = "{'action': 'schedule_mode'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_sessions_40(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_charges(mocked_responses, start="20201101", end="20201130") result = cli_runner.invoke( __main__.main, "charge sessions --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = ( "Charge start Charge end Duration Power (kW) " " Started at Finished at Charge gained Energy gained " "Power level Status\n" "------------------- ------------------- ---------- ------------" " ------------ ------------- --------------- --------------- " "------------- --------\n" "2020-11-11 01:31:03 2020-11-11 09:30:17 7:59:00 3.10 kW " " 15 % 74 % 59 % slow " " ok\n" ) assert expected_output == result.output def test_sessions_45(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") fixtures.inject_get_charges( mocked_responses, start="20201101", end="20201130", filename="vehicle_data/charges-megane.json", ) result = cli_runner.invoke( __main__.main, "charge sessions --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = ( "Charge start Charge end Duration Power (kW) " " Started at Finished at Charge gained Energy gained " "Power level Status\n" "------------------- ------------------- ---------- ------------" " ------------ ------------- --------------- --------------- " "------------- --------\n" "2020-11-11 01:31:03 2020-11-11 09:30:17 7:59:00 3.10 kW " " 15 % 74 % 59 % slow " " ok\n" ) expected_output = ( "Charge start Charge end Duration Power (kW) " " Started at Finished at Charge gained Energy gained " "Power level Status\n" "------------------- ------------------- ---------- ------------ " "------------ ------------- --------------- --------------- " "------------- --------\n" "2023-04-24 12:12:44 2023-04-24 13:49:39 1:37:00 " " 43 % 50 % 4.10000038 kWh " " ok\n" "2023-04-24 15:19:40 2023-04-24 19:39:27 4:20:00 " " 49 % 64 % 9.200001 kWh " " ok\n" ) assert expected_output == result.output def test_sessions_50(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_50.1.json") fixtures.inject_get_charges( mocked_responses, start="20201101", end="20201130", filename="vehicle_data/charges-zoe_50.json", ) result = cli_runner.invoke( __main__.main, "charge sessions --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = ( "Charge start Charge end Duration Power (kW) " " Started at Finished at Charge gained Energy gained " "Power level Status\n" "------------------- ------------------- ---------- ------------" " ------------ ------------- --------------- --------------- " "------------- --------\n" "2022-03-16 03:02:16 2022-03-16 05:01:58 1:59:42 " " 62 % 62 % " " error\n" "2022-03-16 21:18:42 2022-03-16 22:50:08 1:31:26 " " 60 % " " error\n" "2022-03-17 02:01:10 2022-03-17 06:02:01 4:00:51 " " 74 % " " error\n" "2022-03-18 02:01:13 2022-03-18 06:02:04 4:00:51 " " 64 % " " error\n" "2022-03-18 18:34:28 2022-03-18 18:37:57 0:03:29 " " 55 % " " error\n" "2022-03-18 18:40:37 2022-03-18 19:17:22 0:36:45 " " 89 % " " error\n" "2022-03-19 16:18:18 2022-03-19 17:36:04 1:17:46 " " 99 % " " error\n" "2022-03-20 22:21:31 2022-03-21 07:32:50 9:11:19 " " 71 % " " error\n" ) assert expected_output == result.output def test_charge_schedule_show( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_charging_settings(mocked_responses, "multi") result = cli_runner.invoke(__main__.main, "charge schedule show") assert result.exit_code == 0, result.exception expected_output = ( "Mode: scheduled\n" "\n" "Schedule ID: 1 [Active]\n" "Day Start time End time Duration\n" "--------- ------------ ---------- ----------\n" "Monday 01:00 08:30 7:30:00\n" "Tuesday 01:00 08:30 7:30:00\n" "Wednesday 01:00 08:30 7:30:00\n" "Thursday 01:00 08:30 7:30:00\n" "Friday 01:00 08:30 7:30:00\n" "Saturday 01:00 08:30 7:30:00\n" "Sunday 01:00 08:30 7:30:00\n" "\n" "Schedule ID: 2 [Active]\n" "Day Start time End time Duration\n" "--------- ------------ ---------- ----------\n" "Monday 00:30 00:45 0:15:00\n" "Tuesday 00:30 00:45 0:15:00\n" "Wednesday 00:30 00:45 0:15:00\n" "Thursday 00:30 00:45 0:15:00\n" "Friday 00:30 00:45 0:15:00\n" "Saturday 00:30 00:45 0:15:00\n" "Sunday 00:30 00:45 0:15:00\n" "\n" "Schedule ID: 3\n" "Day Start time End time Duration\n" "--------- ------------ ---------- ----------\n" "Monday - - -\n" "Tuesday - - -\n" "Wednesday - - -\n" "Thursday - - -\n" "Friday - - -\n" "Saturday - - -\n" "Sunday - - -\n" "\n" "Schedule ID: 4\n" "Day Start time End time Duration\n" "--------- ------------ ---------- ----------\n" "Monday - - -\n" "Tuesday - - -\n" "Wednesday - - -\n" "Thursday - - -\n" "Friday - - -\n" "Saturday - - -\n" "Sunday - - -\n" "\n" "Schedule ID: 5\n" "Day Start time End time Duration\n" "--------- ------------ ---------- ----------\n" "Monday - - -\n" "Tuesday - - -\n" "Wednesday - - -\n" "Thursday - - -\n" "Friday - - -\n" "Saturday - - -\n" "Sunday - - -\n" ) assert expected_output == result.output def test_charging_settings_set( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_charging_settings(mocked_responses, "multi") url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") monday = "--monday clear" friday = "--friday T23:30Z,480" saturday = "--saturday 19:30,120" result = cli_runner.invoke( __main__.main, f"charge schedule set 1 {monday} {friday} {saturday}" ) assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": { "schedules": [ { "id": 1, "activated": True, "monday": None, "tuesday": {"startTime": "T00:00Z", "duration": 450}, "wednesday": {"startTime": "T00:00Z", "duration": 450}, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T23:30Z", "duration": 480}, "saturday": {"startTime": "T18:30Z", "duration": 120}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": True, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, { "id": 3, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 4, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 5, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, ], }, "type": "ChargeSchedule", } } expected_output = "{'schedules': [{'id': 1, 'activated': True, " request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert result.output.startswith(expected_output) def test_charging_settings_activate( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_charging_settings(mocked_responses, "multi") url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") result = cli_runner.invoke(__main__.main, "charge schedule activate 3") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": { "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T00:00Z", "duration": 450}, "tuesday": {"startTime": "T00:00Z", "duration": 450}, "wednesday": {"startTime": "T00:00Z", "duration": 450}, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T00:00Z", "duration": 450}, "saturday": {"startTime": "T00:00Z", "duration": 450}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": True, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, { "id": 3, "activated": True, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 4, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 5, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, ] }, "type": "ChargeSchedule", } } expected_output = "{'schedules': [{'id': 1, 'activated': True, " request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert result.output.startswith(expected_output) def test_charging_settings_deactivate( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_charging_settings(mocked_responses, "multi") url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") result = cli_runner.invoke(__main__.main, "charge schedule deactivate 1") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": { "schedules": [ { "id": 1, "activated": False, "monday": {"startTime": "T00:00Z", "duration": 450}, "tuesday": {"startTime": "T00:00Z", "duration": 450}, "wednesday": {"startTime": "T00:00Z", "duration": 450}, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T00:00Z", "duration": 450}, "saturday": {"startTime": "T00:00Z", "duration": 450}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": True, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, { "id": 3, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 4, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 5, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, ] }, "type": "ChargeSchedule", } } expected_output = "{'schedules': [{'id': 1, 'activated': True, " request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert result.output.startswith(expected_output) def test_charging_start(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") url = fixtures.inject_set_charging_start(mocked_responses, "start") result = cli_runner.invoke(__main__.main, "charge start") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": {"action": "start"}, "type": "ChargingStart", } } expected_output = "{'action': 'start'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_charging_stop(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") url = fixtures.inject_set_charging_start(mocked_responses, "stop") result = cli_runner.invoke(__main__.main, "charge stop") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": {"action": "stop"}, "type": "ChargingStart", } } expected_output = "{'action': 'stop'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_charging_dacia_start( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "spring.1.json") url = fixtures.inject_set_kcm_charge_pause_resume(mocked_responses, "resume") result = cli_runner.invoke(__main__.main, "charge start") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": {"action": "resume"}, "type": "ChargePauseResume", } } expected_output = "{'action': 'resume'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_charging_dacia_stop( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_vehicle_details(mocked_responses, "spring.1.json") url = fixtures.inject_set_kcm_charge_pause_resume(mocked_responses, "pause") result = cli_runner.invoke(__main__.main, "charge stop") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": {"action": "pause"}, "type": "ChargePauseResume", } } expected_output = "{'action': 'pause'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output renault-api-0.2.9/tests/cli/test_vehicle_hvac.py000066400000000000000000000107241473671120200217140ustar00rootroot00000000000000"""Test cases for the __main__ module.""" from aioresponses import aioresponses from aioresponses.core import RequestCall from click.testing import CliRunner from yarl import URL from tests import fixtures from . import initialise_credential_store from renault_api.cli import __main__ def test_hvac_history_day( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_hvac_history( mocked_responses, start="20201101", end="20201130", period="day" ) result = cli_runner.invoke( __main__.main, "hvac history --from 2020-11-01 --to 2020-11-30 --period day" ) assert result.exit_code == 0, result.exception expected_output = "{}\n" assert expected_output == result.output def test_hvac_history_month( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_hvac_history( mocked_responses, start="202011", end="202011", period="month" ) result = cli_runner.invoke( __main__.main, "hvac history --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = "{}\n" assert expected_output == result.output def test_hvac_cancel(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) url = fixtures.inject_set_hvac_start(mocked_responses, result="cancel") fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") result = cli_runner.invoke(__main__.main, "hvac cancel") assert result.exit_code == 0, result.exception expected_json = {"data": {"attributes": {"action": "cancel"}, "type": "HvacStart"}} expected_output = "{'action': 'cancel'}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_sessions(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) fixtures.inject_get_hvac_sessions( mocked_responses, start="20201101", end="20201130" ) result = cli_runner.invoke( __main__.main, "hvac sessions --from 2020-11-01 --to 2020-11-30" ) assert result.exit_code == 0, result.exception expected_output = "{}\n" assert expected_output == result.output def test_hvac_start_now(mocked_responses: aioresponses, cli_runner: CliRunner) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) url = fixtures.inject_set_hvac_start(mocked_responses, "start") result = cli_runner.invoke(__main__.main, "hvac start --temperature 25") assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": {"action": "start", "targetTemperature": 25}, "type": "HvacStart", } } expected_output = "{'action': 'start', 'targetTemperature': 21.0}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output def test_hvac_start_later( mocked_responses: aioresponses, cli_runner: CliRunner ) -> None: """It exits with a status code of zero.""" initialise_credential_store(include_account_id=True, include_vin=True) url = fixtures.inject_set_hvac_start(mocked_responses, "start") result = cli_runner.invoke( __main__.main, "hvac start --temperature 24 --at '2020-12-25T11:50:00+02:00'" ) assert result.exit_code == 0, result.exception expected_json = { "data": { "attributes": { "action": "start", "startDateTime": "2020-12-25T09:50:00Z", "targetTemperature": 24, }, "type": "HvacStart", } } expected_output = "{'action': 'start', 'targetTemperature': 21.0}\n" request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] assert expected_output == result.output renault-api-0.2.9/tests/conftest.py000066400000000000000000000070441473671120200173140ustar00rootroot00000000000000"""Test configuration.""" import asyncio import functools import pathlib from datetime import datetime from datetime import timedelta from datetime import tzinfo from typing import Any from typing import AsyncGenerator from typing import Generator from typing import Optional import pytest import pytest_asyncio from _pytest.monkeypatch import MonkeyPatch from aiohttp.client import ClientSession from aioresponses import aioresponses from click.testing import CliRunner @pytest_asyncio.fixture async def websession() -> AsyncGenerator[ClientSession, None]: """Fixture for generating ClientSession.""" async with ClientSession() as aiohttp_session: yield aiohttp_session closed_event = create_aiohttp_closed_event(aiohttp_session) await aiohttp_session.close() await closed_event.wait() @pytest.fixture(autouse=True) def mocked_responses() -> Generator[aioresponses, None, None]: """Fixture for mocking aiohttp responses.""" with aioresponses() as m: yield m @pytest.fixture def cli_runner(monkeypatch: MonkeyPatch, tmpdir: pathlib.Path) -> CliRunner: """Fixture for invoking command-line interfaces.""" runner = CliRunner() monkeypatch.setattr("os.path.expanduser", lambda x: x.replace("~", str(tmpdir))) class TZ1(tzinfo): def utcoffset(self, dt: Optional[datetime]) -> timedelta: return timedelta(hours=1) def dst(self, dt: Optional[datetime]) -> timedelta: return timedelta(0) def tzname(self, dt: Optional[datetime]) -> str: return "+01:00" def __repr__(self) -> str: return f"{self.__class__.__name__}()" def get_test_zone() -> Any: # Get a non UTC zone, avoiding DST on standard zones. return TZ1() monkeypatch.setattr("tzlocal.get_localzone", get_test_zone) return runner def create_aiohttp_closed_event( session: ClientSession, ) -> asyncio.Event: # pragma: no cover """Work around aiohttp issue that doesn't properly close transports on exit. See https://github.com/aio-libs/aiohttp/issues/1925#issuecomment-639080209 Args: session (ClientSession): session for which to generate the event. Returns: An event that will be set once all transports have been properly closed. """ transports = 0 all_is_lost = asyncio.Event() def connection_lost(exc, orig_lost): # type: ignore[no-untyped-def] nonlocal transports try: orig_lost(exc) finally: transports -= 1 if transports == 0: all_is_lost.set() def eof_received(orig_eof_received): # type: ignore[no-untyped-def] try: orig_eof_received() except AttributeError: # It may happen that eof_received() is called after # _app_protocol and _transport are set to None. pass for conn in session.connector._conns.values(): # type: ignore[union-attr] for handler, _ in conn: proto = getattr(handler.transport, "_ssl_protocol", None) if proto is None: continue transports += 1 orig_lost = proto.connection_lost orig_eof_received = proto.eof_received proto.connection_lost = functools.partial( connection_lost, orig_lost=orig_lost ) proto.eof_received = functools.partial( eof_received, orig_eof_received=orig_eof_received ) if transports == 0: all_is_lost.set() return all_is_lost renault-api-0.2.9/tests/const.py000066400000000000000000000027571473671120200166230ustar00rootroot00000000000000"""Constants for the test suite.""" from renault_api.const import AVAILABLE_LOCALES from renault_api.const import CONF_GIGYA_APIKEY from renault_api.const import CONF_GIGYA_URL from renault_api.const import CONF_KAMEREON_APIKEY from renault_api.const import CONF_KAMEREON_URL REDACTED = "*PRIVATE*" TO_REDACT = ["accountId", "id", "radioCode", "registrationNumber", "vin"] TEST_ACCOUNT_ID = "account-id-1" TEST_LOCALE = "fr_FR" TEST_LOGIN_TOKEN = "sample-cookie-value" # nosec TEST_PASSWORD = "test_password" # nosec TEST_PERSON_ID = "person-id-1" TEST_USERNAME = "test@example.com" TEST_VIN = "VF1AAAAA555777999" TEST_COUNTRY = TEST_LOCALE[-2:] TEST_LOCALE_DETAILS = AVAILABLE_LOCALES[TEST_LOCALE] TEST_GIGYA_APIKEY = TEST_LOCALE_DETAILS[CONF_GIGYA_APIKEY] TEST_GIGYA_URL = TEST_LOCALE_DETAILS[CONF_GIGYA_URL] TEST_KAMEREON_APIKEY = TEST_LOCALE_DETAILS[CONF_KAMEREON_APIKEY] TEST_KAMEREON_URL = TEST_LOCALE_DETAILS[CONF_KAMEREON_URL] TEST_SCHEDULES = { "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T12:00Z", "duration": 15}, "tuesday": {"startTime": "T04:30Z", "duration": 420}, "wednesday": {"startTime": "T22:30Z", "duration": 420}, "thursday": {"startTime": "T22:00Z", "duration": 420}, "friday": {"startTime": "T12:15Z", "duration": 15}, "saturday": {"startTime": "T12:30Z", "duration": 30}, "sunday": {"startTime": "T12:45Z", "duration": 45}, } ] } renault-api-0.2.9/tests/fixtures.py000066400000000000000000000374331473671120200173450ustar00rootroot00000000000000"""Test suite for the renault_api package.""" from __future__ import annotations import datetime import json from glob import glob from os import path from typing import Any from typing import Mapping import jwt from aioresponses import aioresponses from marshmallow.schema import Schema from tests.const import REDACTED from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_COUNTRY from tests.const import TEST_GIGYA_URL from tests.const import TEST_KAMEREON_URL from tests.const import TEST_PERSON_ID from tests.const import TEST_VIN from tests.const import TO_REDACT GIGYA_FIXTURE_PATH = "tests/fixtures/gigya" KAMEREON_FIXTURE_PATH = "tests/fixtures/kamereon" DEFAULT_QUERY_STRING = f"country={TEST_COUNTRY}" KAMEREON_BASE_URL = f"{TEST_KAMEREON_URL}/commerce/v1" ACCOUNT_PATH = f"accounts/{TEST_ACCOUNT_ID}" ADAPTER_PATH = f"{ACCOUNT_PATH}/kamereon/kca/car-adapter/v1/cars/{TEST_VIN}" KCM_ADAPTER_PATH = f"{ACCOUNT_PATH}/kamereon/kcm/v1/vehicles/{TEST_VIN}" ADAPTER2_PATH = f"{ACCOUNT_PATH}/kamereon/kca/car-adapter/v2/cars/{TEST_VIN}" def get_jwt(timedelta: datetime.timedelta | None = None) -> str: """Read fixture text file as string.""" if not timedelta: timedelta = datetime.timedelta(seconds=900) encoded_jwt = jwt.encode( payload={"exp": datetime.datetime.utcnow() + timedelta}, key="mock", algorithm="HS256", ) return _jwt_as_string(encoded_jwt) def _jwt_as_string(encoded_jwt: Any) -> str: """Ensure that JWT is returned as str.""" if isinstance(encoded_jwt, str): # pyjwt >= 2.0.0 return encoded_jwt if isinstance(encoded_jwt, bytes): # pyjwt < 2.0.0 return encoded_jwt.decode("utf-8") raise ValueError("Unable to read JWT token.") def get_json_files(parent_dir: str) -> list[str]: """Read fixture text file as string.""" return glob(f"{parent_dir}/*.json") def get_file_content(filename: str) -> str: """Read fixture text file as string.""" with open(filename, encoding="utf-8") as file: content = file.read() return content def get_file_content_as_schema(filename: str, schema: Schema) -> Any: """Read fixture text file as specified schema.""" with open(filename, encoding="utf-8") as file: content = file.read() return schema.loads(content) def get_file_content_as_wrapped_schema( filename: str, schema: Schema, wrap_in: str ) -> Any: """Read fixture text file as specified schema.""" with open(filename) as file: content = file.read() content = f'{{"{wrap_in}": {content}}}' # noqa: B907 return schema.loads(content) def inject_gigya( mocked_responses: aioresponses, urlpath: str, filename: str, ) -> str: """Inject Gigya data.""" url = f"{TEST_GIGYA_URL}/{urlpath}" body = get_file_content(f"{GIGYA_FIXTURE_PATH}/{filename}") if filename.endswith("get_jwt.json"): body = body.replace("sample-jwt-token", get_jwt()) mocked_responses.post( url, status=200, body=body, headers={"content-type": "text/javascript"}, ) return url def inject_gigya_login(mocked_responses: aioresponses) -> str: """Inject Gigya login data.""" return inject_gigya( mocked_responses, "accounts.login", "login.json", ) def inject_gigya_login_invalid(mocked_responses: aioresponses) -> str: """Inject Gigya login data.""" return inject_gigya( mocked_responses, "accounts.login", "login_invalid.txt", ) def inject_gigya_account_info(mocked_responses: aioresponses) -> str: """Inject Gigya getAccountInfo data.""" return inject_gigya( mocked_responses, "accounts.getAccountInfo", "get_account_info.json", ) def inject_gigya_jwt(mocked_responses: aioresponses) -> str: """Inject Gigya getJWT data.""" return inject_gigya( mocked_responses, "accounts.getJWT", "get_jwt.json", ) def inject_gigya_all(mocked_responses: aioresponses) -> None: """Inject Gigya login/getAccountInfo/getJWT data.""" inject_gigya_login(mocked_responses) inject_gigya_account_info(mocked_responses) inject_gigya_jwt(mocked_responses) def inject_data( mocked_responses: aioresponses, urlpath: str, filename: str | None = None, *, body: str | None = None, ) -> str: """Inject Kamereon data.""" url = f"{KAMEREON_BASE_URL}/{urlpath}" body = body or get_file_content(f"{KAMEREON_FIXTURE_PATH}/{filename}") mocked_responses.get( url, status=200, body=body, ) return url def inject_action( mocked_responses: aioresponses, urlpath: str, filename: str, ) -> str: """Inject Kamereon data.""" url = f"{KAMEREON_BASE_URL}/{urlpath}" body = get_file_content(f"{KAMEREON_FIXTURE_PATH}/{filename}") mocked_responses.post( url, status=200, body=body, ) return url def inject_get_person(mocked_responses: aioresponses) -> str: """Inject sample person.""" urlpath = f"persons/{TEST_PERSON_ID}?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "person.json", ) def inject_get_notifications(mocked_responses: aioresponses) -> str: """Inject sample charges.""" urlpath = ( f"persons/{TEST_PERSON_ID}/notifications/kmr?" "notificationId=ffcb0310-503f-4bc3-9056-e9d051a089c6&" f"{DEFAULT_QUERY_STRING}" ) return inject_data( mocked_responses, urlpath, "person/notifications.1.json", ) def inject_get_vehicles(mocked_responses: aioresponses, vehicle: str) -> str: """Inject sample vehicles.""" urlpath = f"accounts/{TEST_ACCOUNT_ID}/vehicles?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, f"vehicles/{vehicle}", ) def inject_get_vehicle_details(mocked_responses: aioresponses, vehicle: str) -> str: """Inject sample vehicles.""" urlpath = ( f"accounts/{TEST_ACCOUNT_ID}/vehicles/{TEST_VIN}/details?{DEFAULT_QUERY_STRING}" ) filename = f"vehicle_details/{vehicle}" if path.exists(f"{KAMEREON_FIXTURE_PATH}/{filename}"): return inject_data( mocked_responses, urlpath, filename, ) # If we do not have a specific fixture, extract it from the vehicle list result filename = f"vehicles/{vehicle}" body = json.loads(get_file_content(f"{KAMEREON_FIXTURE_PATH}/{filename}")) return inject_data( mocked_responses, urlpath, body=json.dumps(body["vehicleLinks"][0]["vehicleDetails"]), ) def inject_get_car_adapter(mocked_responses: aioresponses, vehicle: str) -> str: """Inject sample vehicles.""" urlpath = f"{ADAPTER2_PATH}?{DEFAULT_QUERY_STRING}" filename = f"vehicle_gateway/{vehicle}" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_vehicle_contracts(mocked_responses: aioresponses, filename: str) -> str: """Inject sample contracts.""" query_string = ( "brand=RENAULT&" "connectedServicesContracts=true&" "country=FR&" "locale=fr_FR&" "warranty=true&" "warrantyMaintenanceContracts=true" ) urlpath = f"accounts/{TEST_ACCOUNT_ID}/vehicles/{TEST_VIN}/contracts?{query_string}" return inject_data( mocked_responses, urlpath, f"vehicle_contract/{filename}", ) def inject_get_battery_status( mocked_responses: aioresponses, filename: str = "vehicle_data/battery-status.1.json" ) -> str: """Inject sample battery-status.""" urlpath = f"{ADAPTER2_PATH}/battery-status?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_tyre_pressure( mocked_responses: aioresponses, filename: str = "vehicle_data/tyre-pressure.json" ) -> str: """Inject sample tyre-pressure.""" urlpath = f"{ADAPTER_PATH}/pressure?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_location(mocked_responses: aioresponses) -> str: """Inject sample location.""" urlpath = f"{ADAPTER_PATH}/location?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/location.1.json", ) def inject_get_hvac_status(mocked_responses: aioresponses, vehicle: str) -> str: """Inject sample hvac-status.""" urlpath = f"{ADAPTER_PATH}/hvac-status?{DEFAULT_QUERY_STRING}" filename = f"vehicle_data/hvac-status.{vehicle}.json" if not path.exists(f"{KAMEREON_FIXTURE_PATH}/{filename}"): filename = "vehicle_data/hvac-status.zoe.json" if vehicle in ["twingo_ze"]: filename = "vehicle_data/hvac-status.zoe_50.json" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_hvac_settings(mocked_responses: aioresponses) -> str: """Inject sample hvac-settings.""" urlpath = f"{ADAPTER_PATH}/hvac-settings?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/hvac-settings.json", ) def inject_get_charge_mode(mocked_responses: aioresponses) -> str: """Inject sample charge-mode.""" urlpath = f"{ADAPTER_PATH}/charge-mode?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/charge-mode.json", ) def inject_get_charge_history( mocked_responses: aioresponses, start: str, end: str, period: str ) -> str: """Inject sample charge-history.""" query_string = f"{DEFAULT_QUERY_STRING}&end={end}&start={start}&type={period}" urlpath = f"{ADAPTER_PATH}/charge-history?{query_string}" return inject_data( mocked_responses, urlpath, f"vehicle_data/charge-history.{period}.json", ) def inject_get_hvac_history( mocked_responses: aioresponses, start: str, end: str, period: str ) -> str: """Inject sample hvac-history.""" query_string = f"{DEFAULT_QUERY_STRING}&end={end}&start={start}&type={period}" urlpath = f"{ADAPTER_PATH}/hvac-history?{query_string}" return inject_data( mocked_responses, urlpath, "vehicle_data/hvac-history.json", ) def inject_get_hvac_sessions( mocked_responses: aioresponses, start: str, end: str ) -> str: """Inject sample hvac-sessions.""" query_string = f"{DEFAULT_QUERY_STRING}&end={end}&start={start}" urlpath = f"{ADAPTER_PATH}/hvac-sessions?{query_string}" return inject_data( mocked_responses, urlpath, "vehicle_data/hvac-sessions.json", ) def inject_get_charges( mocked_responses: aioresponses, start: str, end: str, filename: str = "vehicle_data/charges.json", ) -> str: """Inject sample charges.""" query_string = f"{DEFAULT_QUERY_STRING}&end={end}&start={start}" urlpath = f"{ADAPTER_PATH}/charges?{query_string}" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_charging_settings(mocked_responses: aioresponses, type: str) -> str: """Inject sample charges.""" urlpath = f"{ADAPTER_PATH}/charging-settings?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, f"vehicle_data/charging-settings.{type}.json", ) def inject_get_cockpit(mocked_responses: aioresponses, vehicle: str) -> str: """Inject sample cockpit.""" urlpath = f"{ADAPTER_PATH}/cockpit?{DEFAULT_QUERY_STRING}" filename = f"vehicle_data/cockpit.{vehicle}.json" if not path.exists(f"{KAMEREON_FIXTURE_PATH}/{filename}"): filename = "vehicle_data/cockpit.zoe.json" return inject_data( mocked_responses, urlpath, filename, ) def inject_get_lock_status(mocked_responses: aioresponses) -> str: """Inject sample lock-status.""" urlpath = f"{ADAPTER_PATH}/lock-status?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/lock-status.1.json", ) def inject_get_res_state(mocked_responses: aioresponses) -> str: """Inject sample res-state.""" urlpath = f"{ADAPTER_PATH}/res-state?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/res-state.1.json", ) def inject_get_notification_settings(mocked_responses: aioresponses) -> str: """Inject sample notification-settings.""" urlpath = f"{ADAPTER_PATH}/notification-settings?{DEFAULT_QUERY_STRING}" return inject_data( mocked_responses, urlpath, "vehicle_data/notification-settings.json", ) def inject_set_charge_mode(mocked_responses: aioresponses, mode: str) -> str: """Inject sample charge-mode.""" urlpath = f"{ADAPTER_PATH}/actions/charge-mode?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, f"vehicle_action/charge-mode.{mode}.json", ) def inject_set_charge_schedule(mocked_responses: aioresponses, result: str) -> str: """Inject sample charge-schedule.""" urlpath = f"{ADAPTER2_PATH}/actions/charge-schedule?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, f"vehicle_action/charge-schedule.{result}.json", ) def inject_set_kcm_charge_pause_resume( mocked_responses: aioresponses, result: str ) -> str: """Inject sample charge-pause-resume.""" urlpath = f"{KCM_ADAPTER_PATH}/charge/pause-resume?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, f"vehicle_kcm_action/charge-pause-resume.{result}.json", ) def inject_set_charging_start(mocked_responses: aioresponses, result: str) -> str: """Inject sample charge-mode.""" urlpath = f"{ADAPTER_PATH}/actions/charging-start?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, f"vehicle_action/charging-start.{result}.json", ) def inject_set_hvac_start(mocked_responses: aioresponses, result: str) -> str: """Inject sample hvac-start.""" urlpath = f"{ADAPTER_PATH}/actions/hvac-start?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, f"vehicle_action/hvac-start.{result}.json", ) def inject_set_hvac_schedules(mocked_responses: aioresponses) -> str: """Inject sample hvac-schedules.""" urlpath = f"{ADAPTER2_PATH}/actions/hvac-schedule?{DEFAULT_QUERY_STRING}" return inject_action( mocked_responses, urlpath, "vehicle_action/hvac-schedule.schedules.json", ) def inject_vehicle_status(mocked_responses: aioresponses, vehicle: str) -> None: """Inject Kamereon vehicle status data.""" inject_get_battery_status(mocked_responses) inject_get_location(mocked_responses) inject_get_lock_status(mocked_responses) inject_get_res_state(mocked_responses) inject_get_hvac_status(mocked_responses, vehicle) inject_get_charge_mode(mocked_responses) inject_get_cockpit(mocked_responses, vehicle) inject_get_tyre_pressure(mocked_responses) def ensure_redacted(data: Mapping[str, Any], to_redact: list[str] = TO_REDACT) -> None: """Ensure all PII keys are redacted.""" for key in to_redact: value = data.get(key) if value: assert isinstance(value, str) assert _is_redacted(key, value), f"Ensure {key} is redacted." def _is_redacted(key: str, value: str) -> bool: """Ensure all PII keys are redacted.""" if value == REDACTED: return True if key == "accountId": return value.startswith("account-id") if key == "vin": return value.startswith(("VF1AAAA", "UU1AAAA")) if key == "registrationNumber": return value == "REG-NUMBER" if key == "radioCode": return value == "1234" return False renault-api-0.2.9/tests/fixtures/000077500000000000000000000000001473671120200167615ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/config_sample.txt000066400000000000000000000206251473671120200223350ustar00rootroot00000000000000{ "instabug": { "enabled": false }, "visibility": { "sections": { "smart_route_planner": true, "pairing_ivi": true, "pairing_ze": true, "ots": true }, "subsections": {}, "views": { "profile_menu_alertes": true, "car_maintenance_tab_title": true, "car_offers_tab_title": true, "car_menu_status_pressure": false, "car_status_quotation": true, "help_menu_contact": true, "help_FAQ_tab_title": true, "help_ADVICES_tab_title": true, "help_notice_title": true, "profile_menu_infos": true, "profile_menu_infos_modify_email": false, "profile_menu_settings": true, "profile_menu_settings_delete_cta": true, "profile_menu_edition_region_layout": false, "carpage_tab_title_status": true, "navigation_myr_off": true, "navigation_myr": true, "navigation_menu_map_parking_btn": true, "navigation_menu_map_more_btn": true, "navigation_menu_poi_bottom_ctas": true, "navigation_menu_poi_details_sendto_cta": true, "help_myr": true, "profile_myr": true, "signup_firstname": true, "add_car_type_layout": false, "input_layout_add_car_reg": false, "profile": { "input_layout_civility": true, "input_layout_firstname": true, "input_layout_lastname": true, "input_layout_birthday": false, "input_layout_national_id": false, "input_layout_mobile_phone": true, "input_layout_landline_phone": false, "input_layout_address": true, "input_layout_postal_code": true, "input_layout_city": true, "profile_menu_infos_region_layout": false, "input_layout_marital_status": false, "input_layout_children_number": false } } }, "pages": { "car": { "cotation": "https://cote.renault.fr/", "general_advise": [ { "picture": "", "maxAdviseNb": 2 } ], "acceleration_advise": [ { "picture": "", "maxAdviseNb": 2 } ], "switch_advise": [ { "picture": "", "maxAdviseNb": 2 } ], "anticipation_advise": [ { "picture": "", "maxAdviseNb": 2 } ], "eco_advice_elec_conduct": [ { "picture": "", "maxAdviseNb": 3 } ], "eco_advice_elec_tyre": [ { "picture": "", "maxAdviseNb": 1 } ], "eco_advice_elec_conso": [ { "picture": "", "maxAdviseNb": 4 } ] }, "offers": { "user": true, "car": true }, "rating_application": { "mail": "contact.client@renault.com", "url": "" }, "faq": { "maxSectionNb": 3, "maxQuestionNb": 3, "picture": "", "response": [ { "idSection": "1", "idQuestion": "1", "urlType": "video", "url": "Eni138mjSpw" }, { "idSection": "1", "idQuestion": "2", "urlType": "video", "url": "KRkqPA0_5rk" } ], "contact": { "display": true, "urlType": "mail", "url": "contact.client@renault.com" } }, "advices": { "maxSectionNb": 3, "maxQuestionNb": 3, "picture": "", "response": [ { "idSection": "1", "idQuestion": "1", "urlType": "video", "url": "Eni138mjSpw" }, { "idSection": "1", "idQuestion": "2", "urlType": "video", "url": "KRkqPA0_5rk" }, { "idSection": "1", "idQuestion": "3", "urlType": "video", "url": "ReUP5ErzceA" }, { "idSection": "2", "idQuestion": "1", "urlType": "video", "url": "Ur-JvJd8M90" }, { "idSection": "2", "idQuestion": "2", "urlType": "video", "url": "aK9j5urvogw" }, { "idSection": "2", "idQuestion": "3", "urlType": "video", "url": "-YSbVnkdaEI" } ], "contact": { "display": true, "urlType": "mail", "url": "contact.client@renault.com" } } }, "regex": { "name": "^[\\p{L}\\s\\’'-]{2,255}$", "email": "^[[:alnum:]]([-_.]?[[:alnum:]])*@[[:alnum:]]([-.]?[[:alnum:]])*\\.([a-z]{2,4})$", "password": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?!.*\\s)[A-Za-z\\d\\W]{8,}", "phone": "^0[1,2,3,4,5,9](([\\.\\s]?[0-9]{2}){4})\\s?$", "mobile_phone": "^0[6,7](([\\.\\s]?[0-9]{2}){4})\\s?$", "vin": "^[a-zA-Z0-9]{17}$", "registration_number": "", "postal_code": "^((0[1-9])|([1-8][0-9])|(9[0-8])|(2A)|(2B))[0-9]{3}$", "city": "^[a-zA-ZÀ-ÿ\\s-]{1,40}$" }, "contact": { "call_center": { "channels": [ { "id": 1, "type": "mail", "value": "assistance.multimedia@renault.com" }, { "id": 2, "type": "url", "value": "https://easyconnect.renault.fr" } ] }, "renault_assistance": { "phone_number": "0800051515" } }, "servers": { "kamareon": { "target": "https://alliance-platform-serviceadapter-staging.apps.prod.eu.kamereon.org", "apikey": "Z7P4xyNrTITzNZh52oObSOVfKns9XGLE" }, "wired": { "target": "https://api-wired-dev-1-euw1.wrd-aws.com", "apikey": "AHdOWFASWEPUVQVlhJWshsios0FqTG2E" }, "wiredValid": { "target": "https://api-wired-valid-1-euw1.wrd-aws.com", "apikey": "AHdOWFASWEPUVQVlhJWshsios0FqTG2E" }, "wiredProd": { "target": "https://api-wired-prod-1-euw1.wrd-aws.com", "apikey": "oF09WnKqvBDcrQzcW1rJNpjIuy7KdGaB" }, "gigya": { "target": "https://accounts.eu1.gigya.com", "apikey": "3_S0OWIrqeJ6mxOkXFT8i3TTDwW1IGKk2rIypZjGXi4Hh8vce2ohuERio1Ka5DBbUr" }, "gigyaValid": { "target": "https://accounts.eu1.gigya.com", "apikey": "3_2YjuX_CyODLN3o4wK70yHKUJFHxKFiePSD65DlCS89AmsZ0Va77k_g7HUmubA4pj" }, "gigyaProd": { "target": "https://accounts.eu1.gigya.com", "apikey": "3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a" } }, "booking": { "url": "https://www.renault.fr/contact/rdv-entretien.html" }, "settings": { "defaultUnits": { "system": "Metric", "mileage": { "value": "km" }, "volume": { "value": "l" }, "pressure": { "value": "bar" }, "speed": { "value": "km/h" }, "consumption": { "value": "l100" }, "temperature": { "value": "c" } }, "links": { "notice": "https://www.renault.fr/legal.html", "legal_info_url": "https://www.renault.fr/donnees-personnelles.html" } }, "additional_legal_page": { "display": false, "picture": "", "url1": "https://www.renault.de/datenschutz-und-rechtliche-hinweise.html", "url2": "https://www.renault.de/cookies.html", "url3": "https://www.renault.de/impressum.html" }, "globalCommunication": false } renault-api-0.2.9/tests/fixtures/gigya/000077500000000000000000000000001473671120200200615ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/gigya/error/000077500000000000000000000000001473671120200212125ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/gigya/error/get_jwt.403005.json000066400000000000000000000004051473671120200243010ustar00rootroot00000000000000{ "callId": "782cef59173646f1840134cdaec798f0", "errorCode": 403005, "errorDetails": "Unauthorized user", "errorMessage": "Unauthorized user", "apiVersion": 2, "statusCode": 403, "statusReason": "Forbidden", "time": "2020-11-26T08:47:46.600Z" } renault-api-0.2.9/tests/fixtures/gigya/error/get_jwt.403013.json000066400000000000000000000004011473671120200242740ustar00rootroot00000000000000{ "callId": "0f34ac6540fa4da9adbb67e1a22f3fd4", "errorCode": 403013, "errorDetails": "Unverified user", "errorMessage": "Unverified user", "apiVersion": 2, "statusCode": 403, "statusReason": "Forbidden", "time": "2020-11-26T08:28:56.248Z" } renault-api-0.2.9/tests/fixtures/gigya/error/login.403042.json000066400000000000000000000003631473671120200237520ustar00rootroot00000000000000{ "callId": "callId", "errorCode": 403042, "errorDetails": "invalid loginID or password", "errorMessage": "Invalid LoginID", "apiVersion": 2, "statusCode": 403, "statusReason": "Forbidden", "time": "2020-11-17T08:22:36.561Z" } renault-api-0.2.9/tests/fixtures/gigya/get_account_info.json000066400000000000000000000021161473671120200242620ustar00rootroot00000000000000{ "callId": "callId", "errorCode": 0, "apiVersion": 2, "statusCode": 200, "statusReason": "OK", "time": "2020-11-10T08:31:02.748Z", "registeredTimestamp": 1556570377000, "UID": "UID", "UIDSignature": "UIDSignature=", "signatureTimestamp": "1604997062", "created": "2019-04-29T20:39:36.880Z", "createdTimestamp": 1556570376000, "data": { "personId": "person-id-1", "gigyaDataCenter": "gigyaDataCenter" }, "preferences": {}, "emails": { "verified": ["email@email.com"], "unverified": [] }, "isActive": true, "isRegistered": true, "isVerified": true, "lastLogin": "2020-11-10T08:31:02.595Z", "lastLoginTimestamp": 1604997062000, "lastUpdated": "2019-04-29T20:40:39.441Z", "lastUpdatedTimestamp": 1556570439441, "loginProvider": "site", "oldestDataUpdated": "2019-04-29T20:39:36.880Z", "oldestDataUpdatedTimestamp": 1556570376880, "profile": { "email": "email@email.com" }, "registered": "2019-04-29T20:39:37.022Z", "socialProviders": "site", "verified": "2019-04-29T20:40:39.441Z", "verifiedTimestamp": 1556570439441 } renault-api-0.2.9/tests/fixtures/gigya/get_jwt.json000066400000000000000000000003121473671120200224130ustar00rootroot00000000000000{ "callId": "callId", "errorCode": 0, "apiVersion": 2, "statusCode": 200, "statusReason": "OK", "time": "2020-11-10T08:31:02.872Z", "ignoredFields": "", "id_token": "sample-jwt-token" } renault-api-0.2.9/tests/fixtures/gigya/login.json000066400000000000000000000017771473671120200221000ustar00rootroot00000000000000{ "callId": "callId", "errorCode": 0, "apiVersion": 2, "statusCode": 200, "statusReason": "OK", "time": "2020-11-10T08:31:02.637Z", "registeredTimestamp": 1556570377, "UID": "UID", "UIDSignature": "UIDSignature=", "signatureTimestamp": "1604997062", "created": "2019-04-29T20:39:36.880Z", "createdTimestamp": 1556570376, "isActive": true, "isRegistered": true, "isVerified": true, "lastLogin": "2020-11-10T08:31:02.595Z", "lastLoginTimestamp": 1604997062, "lastUpdated": "2019-04-29T20:40:39.441Z", "lastUpdatedTimestamp": 1556570439441, "loginProvider": "site", "oldestDataUpdated": "2019-04-29T20:39:36.880Z", "oldestDataUpdatedTimestamp": 1556570376880, "profile": { "email": "email@email.com" }, "registered": "2019-04-29T20:39:37.022Z", "socialProviders": "site", "verified": "2019-04-29T20:40:39.441Z", "verifiedTimestamp": 1556570439441, "newUser": false, "sessionInfo": { "cookieName": "cookieName", "cookieValue": "sample-cookie-value" } } renault-api-0.2.9/tests/fixtures/gigya/login_invalid.txt000066400000000000000000000000001473671120200234260ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/000077500000000000000000000000001473671120200205625ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/Contributions.rst000066400000000000000000000006321473671120200241570ustar00rootroot00000000000000Sample vehicles =============== captur_ii.1.json ---------------- First fuel vehicle. Cockpit data has extra attributes fuelAutonomy and fuelQuantity that are not available on electric vehicles. zoe_40.1.json ------------- Navigation is available in the vehicle, but the location is not available through the API (NotSupported error). zoe_40.2.json ------------- Navigation is not available in the vehicle. renault-api-0.2.9/tests/fixtures/kamereon/error/000077500000000000000000000000001473671120200217135ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/error/access_denied.json000066400000000000000000000007201473671120200253560ustar00rootroot00000000000000{ "type": "FUNCTIONAL", "messages": [ { "code": "err.func.403", "message": "{\"errors\":[{\"status\":\"403\",\"code\":\"security.access\",\"detail\":\"Access is denied for this resource\"}]}" } ], "errors": [ { "errorCode": "err.func.403", "errorMessage": "{\"errors\":[{\"status\":\"403\",\"code\":\"security.access\",\"detail\":\"Access is denied for this resource\"}]}" } ], "error_reference": "FUNCTIONAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/bad_gateway.html000066400000000000000000000002221473671120200250440ustar00rootroot00000000000000 502 Bad Gateway

502 Bad Gateway

renault-api-0.2.9/tests/fixtures/kamereon/error/failed_forward.json000066400000000000000000000005441473671120200255610ustar00rootroot00000000000000{ "type": "TECHNICAL", "messages": [ { "code": "err.tech.wired.kamereon-proxy", "message": "Failed to forward request to remote service." } ], "errors": [ { "errorCode": "err.tech.wired.kamereon-proxy", "errorMessage": "Failed to forward request to remote service." } ], "error_reference": "TECHNICAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/hvac-status.403.403011.json000066400000000000000000000010321473671120200261000ustar00rootroot00000000000000{ "type": "FUNCTIONAL", "messages": [ { "code": "err.func.403", "message": "{\"errors\":[{\"status\":\"403\",\"code\":\"403011\",\"title\":\"Operation not supported\",\"detail\":\"Operation not supported for this can (C1A)\"}]}" } ], "errors": [ { "errorCode": "err.func.403", "errorMessage": "{\"errors\":[{\"status\":\"403\",\"code\":\"403011\",\"title\":\"Operation not supported\",\"detail\":\"Operation not supported for this can (C1A)\"}]}" } ], "error_reference": "FUNCTIONAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/invalid_date.json000066400000000000000000000010341473671120200252270ustar00rootroot00000000000000{ "type": "FUNCTIONAL", "messages": [ { "code": "err.func.400", "message": "{\"errors\":[{\"status\":\"400\",\"code\":\"Future\",\"detail\":\"must be a future date\",\"source\":{\"pointer\":\"/data/attributes/startDateTime\"}}]}" } ], "errors": [ { "errorCode": "err.func.400", "errorMessage": "{\"errors\":[{\"status\":\"400\",\"code\":\"Future\",\"detail\":\"must be a future date\",\"source\":{\"pointer\":\"/data/attributes/startDateTime\"}}]}" } ], "error_reference": "FUNCTIONAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/invalid_upstream.json000066400000000000000000000011401473671120200261500ustar00rootroot00000000000000{ "type": "TECHNICAL", "messages": [ { "code": "err.tech.500", "message": "{\"errors\":[{\"status\":\"Internal Server Error\",\"code\":\"500\",\"title\":\"Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway\"}]}" } ], "errors": [ { "errorCode": "err.tech.500", "errorMessage": "{\"errors\":[{\"status\":\"Internal Server Error\",\"code\":\"500\",\"title\":\"Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway\"}]}" } ], "error_reference": "TECHNICAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/not_supported.json000066400000000000000000000007721473671120200255210ustar00rootroot00000000000000{ "type": "TECHNICAL", "messages": [ { "code": "err.tech.501", "message": "{\"errors\":[{\"status\":\"501\",\"code\":\"error.internal\",\"detail\":\"This feature is not technically supported by this gateway\"}]}" } ], "errors": [ { "errorCode": "err.tech.501", "errorMessage": "{\"errors\":[{\"status\":\"501\",\"code\":\"error.internal\",\"detail\":\"This feature is not technically supported by this gateway\"}]}" } ], "error_reference": "TECHNICAL" } renault-api-0.2.9/tests/fixtures/kamereon/error/quota_limit.json000066400000000000000000000005061473671120200251360ustar00rootroot00000000000000{ "type": "FUNCTIONAL", "messages": [ { "code": "err.func.wired.overloaded", "message": "You have reached your quota limit" } ], "errors": [ { "errorCode": "err.func.wired.overloaded", "errorMessage": "You have reached your quota limit", "errorType": "functional" } ] } renault-api-0.2.9/tests/fixtures/kamereon/error/resource_not_found.json000066400000000000000000000004261473671120200265120ustar00rootroot00000000000000{ "type": "FUNCTIONAL", "messages": [ { "code": "err.func.wired.notFound", "message": "Resource not found" } ], "error_reference": "FUNCTIONAL", "errors": [ { "errorCode": "err.func.wired.notFound", "errorMessage": "Resource not found" } ] } renault-api-0.2.9/tests/fixtures/kamereon/expected_specs.json000066400000000000000000000404771473671120200244670ustar00rootroot00000000000000{ "captur_ii.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ESS", "get_model_code": "XJB1SU", "get_model_label": "CAPTUR II", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=ADR00%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FCDVIT1%2FSSCCPC%2FRCALL%2FMET04%2FDANGMO%2FSSRCAR%2FAVGSI%2FITPK7%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLCHA%2FSREACT%2FDWGE01%2FAVCAM%2FHJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRVDIST%2FSBARTO%2FCA02%2FTOPAN%2FPBNCH%2FVSTLAR%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIXPE%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETIN2%2FREPNTC%2FLVAVIP%2FLVAREI%2FSGACHA%2FALOUC5%2FNA406%2FBVA7%2FECLHB4%2FRDIF10%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB01%2FH5H&databaseId=3e814da7-766d-4039-ac69-f001a1f738c8&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=ADR00%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FCDVIT1%2FSSCCPC%2FRCALL%2FMET04%2FDANGMO%2FSSRCAR%2FAVGSI%2FITPK7%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLCHA%2FSREACT%2FDWGE01%2FAVCAM%2FHJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRVDIST%2FSBARTO%2FCA02%2FTOPAN%2FPBNCH%2FVSTLAR%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIXPE%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETIN2%2FREPNTC%2FLVAVIP%2FLVAREI%2FSGACHA%2FALOUC5%2FNA406%2FBVA7%2FECLHB4%2FRDIF10%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB01%2FH5H&databaseId=3e814da7-766d-4039-ac69-f001a1f738c8&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": false, "uses_fuel": true, "supports-hvac-status": false, "supports-location": true, "charge-uses-kcm": false }, "captur_ii.2.json": { "get_brand_label": "RENAULT", "get_energy_code": "ESS", "get_model_code": "XJB1SU", "get_model_label": "CAPTUR II", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=HJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRV%2FSBARTO%2FCA02%2FTN%2FPBNCH%2FVT%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIYPC%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETRCR%2FREPNTC%2FLVAVIP%2FLVAREI%2FALOUC5%2FNA418%2FBVH4%2FECLHB4%2FRDIF10%2FCSRFLY%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB06%2FH4M%2FNOADR%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FSSCCPC%2FRCALL%2FMET05%2FSDANGM%2FSSRCAR%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLNCH%2FSREACT%2FDWGE01%2FRRCAM&databaseId=b2b4fefb-d131-4f8f-9a24-4223c38bc710&bookmarkSet=CARPICKER&bookmark=EXT_34_RIGHT_FRONT&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=HJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRV%2FSBARTO%2FCA02%2FTN%2FPBNCH%2FVT%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIYPC%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETRCR%2FREPNTC%2FLVAVIP%2FLVAREI%2FALOUC5%2FNA418%2FBVH4%2FECLHB4%2FRDIF10%2FCSRFLY%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB06%2FH4M%2FNOADR%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FSSCCPC%2FRCALL%2FMET05%2FSDANGM%2FSSRCAR%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLNCH%2FSREACT%2FDWGE01%2FRRCAM&databaseId=b2b4fefb-d131-4f8f-9a24-4223c38bc710&bookmarkSet=CARPICKER&bookmark=EXT_34_RIGHT_FRONT&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": true, "supports-hvac-status": false, "supports-location": true, "charge-uses-kcm": false }, "clio_v.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ESS", "get_model_code": "XJA1VP", "get_model_label": "CLIO V", "get_picture_small": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FBJA%2FEA3%2FESS%2FDG%2FRV%2FCA02%2FVT%2FCPE%2FRET04%2FSPROJA%2FRALU17%2FDRA%2FDRAP06%2FHARM01%2FATAR03%2FSGAV03%2FSGAR02%2FTECNM%2FKM%2FMCSOL2%2FABLAVI%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLMOU1%2FRETRCR%2FRETC%2FLVAREI%2FRSGALT%2FPANP02%2FNA406%2FBVH4%2FECLHB3%2FRDIF20%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHBASE%2FM2018%2FSSAPLC%2FTHABT1%2FHYB05%2FNOADR%2FSSCAEC%2FDLIGM2%2FPGPRT2%2FSRANCF%2FFEUAR3%2FSKTPOU%2FRCALL%2FMET04%2FDANGMO%2FECOMOD%2FSSRCAR%2FAIVCT%2FPRCHR1%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FPERB01%2FPERC01%2FSSPERG%2FSPERTP%2FSPERTS%2FREACTI%2FEV1GA1%2FAVTSR1%2FDWGE01%2FNOLIE%2FNOLII%2FAEBS07%2FAVCAM&databaseId=f457977c-94e9-4746-aba4-b465dcccc955&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FBJA%2FEA3%2FESS%2FDG%2FRV%2FCA02%2FVT%2FCPE%2FRET04%2FSPROJA%2FRALU17%2FDRA%2FDRAP06%2FHARM01%2FATAR03%2FSGAV03%2FSGAR02%2FTECNM%2FKM%2FMCSOL2%2FABLAVI%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLMOU1%2FRETRCR%2FRETC%2FLVAREI%2FRSGALT%2FPANP02%2FNA406%2FBVH4%2FECLHB3%2FRDIF20%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHBASE%2FM2018%2FSSAPLC%2FTHABT1%2FHYB05%2FNOADR%2FSSCAEC%2FDLIGM2%2FPGPRT2%2FSRANCF%2FFEUAR3%2FSKTPOU%2FRCALL%2FMET04%2FDANGMO%2FECOMOD%2FSSRCAR%2FAIVCT%2FPRCHR1%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FPERB01%2FPERC01%2FSSPERG%2FSPERTP%2FSPERTS%2FREACTI%2FEV1GA1%2FAVTSR1%2FDWGE01%2FNOLIE%2FNOLII%2FAEBS07%2FAVCAM&databaseId=f457977c-94e9-4746-aba4-b465dcccc955&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": false, "uses_fuel": true, "supports-hvac-status": false, "supports-location": true, "charge-uses-kcm": false }, "duster.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ESS", "get_model_code": "XJD1SU", "get_model_label": "NEW DUSTER", "get_picture_small": "https://3dv.renault.com/ImageFromBookmark?configuration=HJD%2FEA2%2FDG%2FTR4X4%2FRV%2FABS%2FBARLO2%2FCA02%2FPBCH%2FRET03%2FPROJAB%2FRALU16%2FDRA%2FDRAP07%2FHARM02%2FATAR02%2FSGAV05%2FTECNW%2FKM%2FABLAV%2FESPDES%2FALEVA%2FSOPC2C%2FVLCUIR%2FLVCIPE%2FLVAREL%2FSGACHA%2FAPL03%2FVOLRHP%2FCMAR3P%2FRA41A%2FBVM6%2FRDIF03%2FENPB01%2FCSRBA2%2FSANACF%2FSAN913%2FH5H%2FRCALL%2FSDANGM%2FECOMOD%2FITPK1%2FPERN02%2FVOLCHA%2FRRCAM&databaseId=42b2c477-c47a-40c9-8ed2-a6966c244777&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv.renault.com/ImageFromBookmark?configuration=HJD%2FEA2%2FDG%2FTR4X4%2FRV%2FABS%2FBARLO2%2FCA02%2FPBCH%2FRET03%2FPROJAB%2FRALU16%2FDRA%2FDRAP07%2FHARM02%2FATAR02%2FSGAV05%2FTECNW%2FKM%2FABLAV%2FESPDES%2FALEVA%2FSOPC2C%2FVLCUIR%2FLVCIPE%2FLVAREL%2FSGACHA%2FAPL03%2FVOLRHP%2FCMAR3P%2FRA41A%2FBVM6%2FRDIF03%2FENPB01%2FCSRBA2%2FSANACF%2FSAN913%2FH5H%2FRCALL%2FSDANGM%2FECOMOD%2FITPK1%2FPERN02%2FVOLCHA%2FRRCAM&databaseId=42b2c477-c47a-40c9-8ed2-a6966c244777&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": false, "uses_fuel": true, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": false }, "twingo_ze.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELEC", "get_model_code": "X071VE", "get_model_label": "TWINGO III", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGMA%2FAVSVEL%2FRAGAC2%2FCOIN02%2FSKTPOU%2FSRGTLU%2FSSPREM%2FELCTRI%2FSSTOST%2FSECAMH%2FSRGPDB%2FPERB16%2FSPERTN%2FSPERTP%2FSDCOLS%2FX07%2FB07%2FEA3%2FELEC%2FDG%2FRV%2FCAREG1%2FTN%2FVSTLAR%2FRET01%2FPROJAB%2FRALU15%2FTICUI6%2FATAR%2FSGAV02%2FOVRPP%2FKM%2FVERCAP%2FSSDECA%2FRDAR02%2FALEVA%2FVLCUIR%2FRETC%2FLVCIPE%2FSGACHA%2FAPL03%2FBECQA1%2FPLAT02%2FVOLRH%2FALOUCP%2FSSNAV%2FNA437%2FBVEL%2FSPREST%2FRANPAR%2FRDIF24%2FENPH02%2FSACSRA%2FSANACF%2FCHC03%2FPRUPT1%2FSAN913&databaseId=b43ab41f-7f61-4f96-94d7-2a58c2dd7d44&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGMA%2FAVSVEL%2FRAGAC2%2FCOIN02%2FSKTPOU%2FSRGTLU%2FSSPREM%2FELCTRI%2FSSTOST%2FSECAMH%2FSRGPDB%2FPERB16%2FSPERTN%2FSPERTP%2FSDCOLS%2FX07%2FB07%2FEA3%2FELEC%2FDG%2FRV%2FCAREG1%2FTN%2FVSTLAR%2FRET01%2FPROJAB%2FRALU15%2FTICUI6%2FATAR%2FSGAV02%2FOVRPP%2FKM%2FVERCAP%2FSSDECA%2FRDAR02%2FALEVA%2FVLCUIR%2FRETC%2FLVCIPE%2FSGACHA%2FAPL03%2FBECQA1%2FPLAT02%2FVOLRH%2FALOUCP%2FSSNAV%2FNA437%2FBVEL%2FSPREST%2FRANPAR%2FRDIF24%2FENPH02%2FSACSRA%2FSANACF%2FCHC03%2FPRUPT1%2FSAN913&databaseId=b43ab41f-7f61-4f96-94d7-2a58c2dd7d44&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": false }, "zoe_40.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELEC", "get_model_code": "X101VE", "get_model_label": "ZOE", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": true, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": false, "charge-uses-kcm": false }, "zoe_40.2.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELEC", "get_model_code": "X101VE", "get_model_label": "ZOE", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FSPRSEP%2FSTANDA%2FB10%2FEA1%2FDG%2FVT001%2FRET02%2FRTOL15%2FDRAP07%2FHARM01%2FOV369%2FSRDPRO%2FSALEVA%2FSOP01C%2FTRNOR%2FLVAVEL%2FLVARMA%2FRAD33A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FSPRSEP%2FSTANDA%2FB10%2FEA1%2FDG%2FVT001%2FRET02%2FRTOL15%2FDRAP07%2FHARM01%2FOV369%2FSRDPRO%2FSALEVA%2FSOP01C%2FTRNOR%2FLVAVEL%2FLVARMA%2FRAD33A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": true, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": false, "charge-uses-kcm": false }, "zoe_50.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELEC", "get_model_code": "X102VE", "get_model_label": "ZOE", "get_picture_small": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGM2%2FKITPOU%2FDANGMO%2FITPK4%2FVOLNCH%2FREACTI%2FSSAEBS%2FPRAHL%2FRRCAM%2FX10%2FB10%2FEA3%2FDG%2FCAREG%2FVSTLAR%2FRET03%2FPROJAB%2FRALU16%2FDRAP13%2F3ATRPH%2FTELNJ%2FALEVA%2FVLCUIR%2FRETRCR%2FRETC%2FLVAREL%2FSGSCHA%2FNA418%2FRDIF01%2FTL01A%2FNBT022&databaseId=a864e752-b1b9-405e-9c3e-880073e36cc9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGM2%2FKITPOU%2FDANGMO%2FITPK4%2FVOLNCH%2FREACTI%2FSSAEBS%2FPRAHL%2FRRCAM%2FX10%2FB10%2FEA3%2FDG%2FCAREG%2FVSTLAR%2FRET03%2FPROJAB%2FRALU16%2FDRAP13%2F3ATRPH%2FTELNJ%2FALEVA%2FVLCUIR%2FRETRCR%2FRETC%2FLVAREL%2FSGSCHA%2FNA418%2FRDIF01%2FTL01A%2FNBT022&databaseId=a864e752-b1b9-405e-9c3e-880073e36cc9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": false }, "spring.1.json": { "get_brand_label": "DACIA", "get_energy_code": "ELEC", "get_model_code": "XBG1VE", "get_model_label": "SPRING", "get_picture_small": "https://3dv.renault.com/ImageFromBookmark?configuration=BBG%2FE2%2FLIMVIT%2FCA01%2FRET02%2FDRAP05%2FSGAR02%2FOVQPA%2FDECA01%2FESPHSA%2FPRAMBI%2FRETP01%2FLVAREL%2FBRA05%2FRA43D%2FRDIF04%2FENPH02%2FM2019%2FSAN913%2FRCALL%2FECOMOD%2FITPK2%2FAEB00&databaseId=8afd6a52-b67b-407d-8469-fddf3d6a68ab&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv.renault.com/ImageFromBookmark?configuration=BBG%2FE2%2FLIMVIT%2FCA01%2FRET02%2FDRAP05%2FSGAR02%2FOVQPA%2FDECA01%2FESPHSA%2FPRAMBI%2FRETP01%2FLVAREL%2FBRA05%2FRA43D%2FRDIF04%2FENPH02%2FM2019%2FSAN913%2FRCALL%2FECOMOD%2FITPK2%2FAEB00&databaseId=8afd6a52-b67b-407d-8469-fddf3d6a68ab&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": true }, "megane_e-tech.1.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELECX", "get_model_code": "XCB1VE", "get_model_label": "MEGANE E-TECH", "get_picture_large": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FEA4%2FLHDG%2FACD03%2FSTDRF%2FWFURP%2FRVX07%2FRAL20%2FLRS02%2FFSE06%2FBIXUF%2FLEDCO%2FPALAW%2FSPBDA%2FLRSW0%2FRVIAT%2FTRSV0%2FAJSW2%2FNA448%2FASOC2%2FRIM09%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FSFANT%2FADR00%2FNODUP%2FNOWAP%2FRCALL%2FBSD02%2F2BCOL%2FITP15%2FMDMOD%2FPXA00%2FPXB00%2FPIG02%2FHTSW0%2FNOLII%2FRRCAM%2FDTRNI&databaseId=08eb195d-c2b1-42e8-910c-6c350668b3b9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "get_picture_small": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FEA4%2FLHDG%2FACD03%2FSTDRF%2FWFURP%2FRVX07%2FRAL20%2FLRS02%2FFSE06%2FBIXUF%2FLEDCO%2FPALAW%2FSPBDA%2FLRSW0%2FRVIAT%2FTRSV0%2FAJSW2%2FNA448%2FASOC2%2FRIM09%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FSFANT%2FADR00%2FNODUP%2FNOWAP%2FRCALL%2FBSD02%2F2BCOL%2FITP15%2FMDMOD%2FPXA00%2FPXB00%2FPIG02%2FHTSW0%2FNOLII%2FRRCAM%2FDTRNI&databaseId=08eb195d-c2b1-42e8-910c-6c350668b3b9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": false }, "megane_e-tech.2.json": { "get_brand_label": "RENAULT", "get_energy_code": "ELECX", "get_model_code": "XCB1VE", "get_model_label": "MEGANE E-TECH", "get_picture_small": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", "get_picture_large": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", "reports_charging_power_in_watts": false, "uses_electricity": true, "uses_fuel": false, "supports-hvac-status": true, "supports-location": true, "charge-uses-kcm": false } } renault-api-0.2.9/tests/fixtures/kamereon/person.json000066400000000000000000000036241473671120200227700ustar00rootroot00000000000000{ "personId": "person-id-1", "type": "I", "country": "FR", "civility": "1", "firstName": "firstName", "lastName": "lastName", "idp": { "idpId": "idpId", "idpType": "GIGYA", "idpStatus": "ACTIVE", "login": "email@email.com", "loginType": "EMAIL", "lastLoginDate": "2020-11-16T11:43:52Z", "termsConditionAcceptance": true, "termsConditionLastAcceptanceDate": "2019-04-29T20:39:37.153608Z" }, "emails": [ { "emailType": "MAIN", "emailValue": "email@email.com", "validityFlag": true, "createdDate": "2019-04-29T20:39:37.162651Z", "lastModifiedDate": "2019-04-29T20:39:37.162654Z", "functionalCreationDate": "2019-04-29T20:39:37.162651Z", "functionalModificationDate": "2019-04-29T20:39:37.162654Z" } ], "phones": [ { "phoneType": "MOBILE", "phoneValue": "phoneValue", "areaCode": "33", "createdDate": "2019-06-17T09:47:14.745555Z", "lastModifiedDate": "2019-06-17T09:47:14.745730Z", "functionalCreationDate": "2019-06-17T09:47:14.745555Z", "functionalModificationDate": "2019-06-17T09:47:14.745730Z" } ], "identities": [], "myrRequest": false, "accounts": [ { "accountId": "account-id-1", "accountType": "MYRENAULT", "accountStatus": "ACTIVE", "country": "FR", "personId": "person-id-1", "relationType": "OWNER" }, { "accountId": "account-id-2", "externalId": "externalId", "accountType": "SFDC", "accountStatus": "ACTIVE", "country": "FR", "personId": "person-id-1", "relationType": "OWNER" } ], "partyId": "partyId", "mdmId": "mdmId", "createdDate": "2019-04-29T20:39:37.163635Z", "lastModifiedDate": "2020-11-16T11:43:57.658639Z", "functionalCreationDate": "2019-04-29T20:39:37.162578Z", "functionalModificationDate": "2020-11-16T11:29:48.631544Z", "locale": "fr-FR" } renault-api-0.2.9/tests/fixtures/kamereon/person/000077500000000000000000000000001473671120200220705ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/person/notifications.1.json000066400000000000000000000012261473671120200257740ustar00rootroot00000000000000[ { "notificationId": "ffcb0310-503f-4bc3-9056-e9d051a089c6", "notifDate": "2022-02-01T19:01:51.622", "vin": "*PRIVATE*", "personId": "*PRIVATE*", "kmrUserId": "*PRIVATE*", "actionType": "COMMAND_RESPONSE", "commandResponse": { "status": "CREATED" }, "commandType": "SRP_SETS" }, { "notificationId": "ffcb0310-503f-4bc3-9056-e9d051a089c6", "notifDate": "2022-02-01T19:01:51.623", "vin": "*PRIVATE*", "personId": "*PRIVATE*", "kmrUserId": "*PRIVATE*", "actionType": "SRP_SALT_REQUEST", "srpResponse": { "status": "OK", "loginB": "*PRIVATE*", "loginSalt": "*PRIVATE*" } } ] renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/000077500000000000000000000000001473671120200235365ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/charge-mode.schedule_mode.json000066400000000000000000000001601473671120200314000ustar00rootroot00000000000000{ "data": { "type": "ChargeMode", "id": "guid", "attributes": { "action": "schedule_mode" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/charge-schedule.schedules.json000066400000000000000000000014271473671120200314360ustar00rootroot00000000000000{ "data": { "type": "ChargeSchedule", "id": "guid", "attributes": { "schedules": [ { "id": 1, "activated": true, "tuesday": { "startTime": "T04:30Z", "duration": 420 }, "wednesday": { "startTime": "T22:30Z", "duration": 420 }, "thursday": { "startTime": "T22:00Z", "duration": 420 }, "friday": { "startTime": "T23:30Z", "duration": 480 }, "saturday": { "startTime": "T18:30Z", "duration": 120 }, "sunday": { "startTime": "T12:45Z", "duration": 45 } } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/charging-start.start.json000066400000000000000000000001531473671120200305010ustar00rootroot00000000000000{ "data": { "type": "ChargingStart", "id": "guid", "attributes": { "action": "start" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/charging-start.stop.json000066400000000000000000000001521473671120200303300ustar00rootroot00000000000000{ "data": { "type": "ChargingStart", "id": "guid", "attributes": { "action": "stop" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/hvac-schedule.schedules.json000066400000000000000000000010641473671120200311230ustar00rootroot00000000000000{ "data": { "type": "HvacSchedule", "id": "guid", "attributes": { "schedules": [ { "id": 1, "activated": false }, { "id": 2, "activated": true, "monday": { "readyAtTime": "T20:45Z" }, "sunday": { "readyAtTime": "T20:45Z" } }, { "id": 3, "activated": false }, { "id": 4, "activated": false }, { "id": 5, "activated": false } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/hvac-start.cancel.json000066400000000000000000000001501473671120200277250ustar00rootroot00000000000000{ "data": { "type": "HvacStart", "id": "guid", "attributes": { "action": "cancel" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_action/hvac-start.start.json000066400000000000000000000002021473671120200276330ustar00rootroot00000000000000{ "data": { "type": "HvacStart", "id": "guid", "attributes": { "action": "start", "targetTemperature": 21.0 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_contract/000077500000000000000000000000001473671120200240765ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_contract/fr_FR.1.json000066400000000000000000000060011473671120200261230ustar00rootroot00000000000000[ { "type": "WARRANTY_MAINTENANCE_CONTRACTS", "contractId": "AB12345", "code": "40", "subCode": "34", "group": "EXT", "durationMonths": 47, "startDate": "2018-04-04", "endDate": "2022-04-03", "status": "ACTIVE", "statusLabel": "Actif", "description": "CONTRAT LOSANGE", "conditions": [ { "startDate": "2018-04-04", "endDate": "2022-04-03", "unlimitedMileage": false, "maximumMileage": 80000, "mileageUnit": "Km" } ], "extendable": false }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZECONNECTP", "group": "Z", "durationMonths": 36, "startDate": "2018-08-23", "endDate": "2021-08-23", "status": "ACTIVE", "statusLabel": "Actif", "description": "My Z.E. Connect en série 36 mois", "conditions": [ { "startDate": "2018-08-23", "endDate": "2021-08-23" } ] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "GBA", "group": "T", "durationMonths": 0, "startDate": "2018-03-23", "status": "REJECTED", "statusLabel": "Echec d’activation", "description": "Battery Services", "conditions": [ { "startDate": "2018-03-23" } ] }, { "type": "WARRANTY", "code": "ManufacturerWarranty", "durationMonths": 24, "endDate": "2020-04-03", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie fabricant", "conditions": [ { "endDate": "2020-04-03", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "PaintingWarranty", "durationMonths": 36, "endDate": "2021-04-03", "status": "ACTIVE", "statusLabel": "Actif", "description": "Garantie peinture", "conditions": [ { "endDate": "2021-04-03", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "CorrosionWarranty", "durationMonths": 144, "endDate": "2030-04-03", "status": "ACTIVE", "statusLabel": "Actif", "description": "Garantie corrosion", "conditions": [ { "endDate": "2030-04-03", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "GMPeWarranty", "durationMonths": 24, "endDate": "2020-04-03", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie GMPe", "conditions": [ { "endDate": "2020-04-03", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "AssistanceWarranty", "durationMonths": 24, "endDate": "2020-04-03", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie assistance", "conditions": [ { "endDate": "2020-04-03", "unlimitedMileage": true, "mileageUnit": "km" } ] } ] renault-api-0.2.9/tests/fixtures/kamereon/vehicle_contract/fr_FR.2.json000066400000000000000000000115611473671120200261330ustar00rootroot00000000000000[ { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZEINTER", "group": "Z", "durationMonths": 12, "startDate": "2020-09-13", "endDate": "2021-09-13", "status": "ACTIVE", "statusLabel": "Actif", "description": "My Z.E Inter@ctive", "conditions": [{ "startDate": "2020-09-13", "endDate": "2021-09-13" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "MMCOYOTE", "group": "N", "durationMonths": 3, "startDate": "2017-08-07", "endDate": "2017-11-07", "status": "ENDED", "statusLabel": "Expiré", "description": "Coyote Series 3 mois en série", "conditions": [{ "startDate": "2017-08-07", "endDate": "2017-11-07" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "MMLIVE", "group": "N", "durationMonths": 36, "startDate": "2023-08-07", "endDate": "2026-08-07", "status": "ACTIVE", "statusLabel": "Actif", "description": "TomTom Traffic & Connectivité 36 Mois en série", "conditions": [{ "startDate": "2023-08-07", "endDate": "2026-08-07" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "MUSEUMM12", "group": "L", "durationMonths": 36, "startDate": "2017-08-05", "endDate": "2020-08-05", "status": "ENDED", "statusLabel": "Expiré", "description": "TomTom - Map update Europe RL 1 Inclusion 36 mois", "conditions": [{ "startDate": "2017-08-05", "endDate": "2020-08-05" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZECONNECTP", "group": "Z", "durationMonths": 12, "startDate": "2020-08-16", "endDate": "2021-08-16", "status": "ACTIVE", "statusLabel": "Actif", "description": "My Z.E. Connect with Navigation", "conditions": [{ "startDate": "2020-08-16", "endDate": "2021-08-16" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZECONNECTP", "group": "Z", "durationMonths": 36, "startDate": "2017-08-05", "endDate": "2020-08-05", "status": "ENDED", "statusLabel": "Expiré", "description": "My Z.E. Connect en série 36 mois", "conditions": [{ "startDate": "2017-08-05", "endDate": "2020-08-05" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZETRIP", "group": "L", "durationMonths": 36, "startDate": "2017-08-07", "endDate": "2020-08-07", "status": "ENDED", "statusLabel": "Expiré", "description": "My ZE Trip 36 Mois", "conditions": [{ "startDate": "2017-08-07", "endDate": "2020-08-07" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "ZEINTER", "group": "Z", "durationMonths": 36, "startDate": "2017-08-08", "endDate": "2020-08-08", "status": "ENDED", "statusLabel": "Expiré", "description": "My ZE Interactive en série 36 Mois", "conditions": [{ "startDate": "2017-08-08", "endDate": "2020-08-08" }] }, { "type": "CONNECTED_SERVICES", "contractId": "AB12345", "code": "GBA", "group": "T", "durationMonths": 0, "startDate": "2017-07-06", "status": "ACTIVE", "statusLabel": "Actif", "description": "Battery Services", "conditions": [{ "startDate": "2017-07-06" }] }, { "type": "WARRANTY", "code": "GMPeWarranty", "durationMonths": 24, "endDate": "2019-08-10", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie GMPe", "conditions": [ { "endDate": "2019-08-10", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "AssistanceWarranty", "durationMonths": 24, "endDate": "2019-08-10", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie assistance", "conditions": [ { "endDate": "2019-08-10", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "ManufacturerWarranty", "durationMonths": 24, "endDate": "2019-08-10", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie fabricant", "conditions": [ { "endDate": "2019-08-10", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "PaintingWarranty", "durationMonths": 36, "endDate": "2020-08-10", "status": "ENDED", "statusLabel": "Expiré", "description": "Garantie peinture", "conditions": [ { "endDate": "2020-08-10", "unlimitedMileage": true, "mileageUnit": "km" } ] }, { "type": "WARRANTY", "code": "CorrosionWarranty", "durationMonths": 144, "endDate": "2029-08-10", "status": "ACTIVE", "statusLabel": "Actif", "description": "Garantie corrosion", "conditions": [ { "endDate": "2029-08-10", "unlimitedMileage": true, "mileageUnit": "km" } ] } ] renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/000077500000000000000000000000001473671120200231725ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/battery-status.1.json000066400000000000000000000004731473671120200272230ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "timestamp": "2020-11-17T09:06:48+01:00", "batteryLevel": 50, "batteryAutonomy": 128, "batteryCapacity": 0, "batteryAvailableEnergy": 0, "plugStatus": 0, "chargingStatus": -1.0 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/battery-status.2.json000066400000000000000000000006441473671120200272240ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "timestamp": "2020-01-12T21:40:16Z", "batteryLevel": 60, "batteryTemperature": 20, "batteryAutonomy": 141, "batteryCapacity": 0, "batteryAvailableEnergy": 31, "plugStatus": 1, "chargingStatus": 1.0, "chargingRemainingTime": 145, "chargingInstantaneousPower": 27.0 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charge-history.day.json000066400000000000000000000006761473671120200276020ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "chargeSummaries": [ { "day": "20201208", "totalChargesNumber": 2, "totalChargesDuration": 495, "totalChargesErrors": 0 }, { "day": "20201205", "totalChargesNumber": 1, "totalChargesDuration": 657, "totalChargesErrors": 0 } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charge-history.month.json000066400000000000000000000004401473671120200301370ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "chargeSummaries": [ { "month": "202011", "totalChargesNumber": 1, "totalChargesDuration": 479, "totalChargesErrors": 0 } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charge-mode.json000066400000000000000000000001631473671120200262400ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "chargeMode": "always" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charges-megane.json000066400000000000000000000013651473671120200267400ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "charges": [ { "chargeStartDate": "2023-04-24T11:12:44Z", "chargeEndDate": "2023-04-24T12:49:39Z", "chargeDuration": 97, "chargeStartBatteryLevel": 43, "chargeEndBatteryLevel": 50, "chargeEnergyRecovered": 4.10000038, "chargeEndStatus": "ok" }, { "chargeStartDate": "2023-04-24T14:19:40Z", "chargeEndDate": "2023-04-24T18:39:27Z", "chargeDuration": 260, "chargeStartBatteryLevel": 49, "chargeEndBatteryLevel": 64, "chargeEnergyRecovered": 9.200001, "chargeEndStatus": "ok" } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charges-zoe_50.json000066400000000000000000000037751473671120200266140ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "charges": [ { "chargeStartDate": "2022-03-16T02:02:16Z", "chargeEndDate": "2022-03-16T04:01:58Z", "chargeDuration": 7182, "chargeStartBatteryLevel": 62, "chargeEndBatteryLevel": 62, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-16T20:18:42Z", "chargeEndDate": "2022-03-16T21:50:08Z", "chargeDuration": 5486, "chargeEndBatteryLevel": 60, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-17T01:01:10Z", "chargeEndDate": "2022-03-17T05:02:01Z", "chargeDuration": 14451, "chargeEndBatteryLevel": 74, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-18T01:01:13Z", "chargeEndDate": "2022-03-18T05:02:04Z", "chargeDuration": 14451, "chargeEndBatteryLevel": 64, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-18T17:34:28Z", "chargeEndDate": "2022-03-18T17:37:57Z", "chargeDuration": 209, "chargeEndBatteryLevel": 55, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-18T17:40:37Z", "chargeEndDate": "2022-03-18T18:17:22Z", "chargeDuration": 2205, "chargeEndBatteryLevel": 89, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-19T15:18:18Z", "chargeEndDate": "2022-03-19T16:36:04Z", "chargeDuration": 4666, "chargeEndBatteryLevel": 99, "chargeEndStatus": "error" }, { "chargeStartDate": "2022-03-20T21:21:31Z", "chargeEndDate": "2022-03-21T06:32:50Z", "chargeDuration": 33079, "chargeEndBatteryLevel": 71, "chargeEndStatus": "error" } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charges.json000066400000000000000000000010111473671120200254720ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "charges": [ { "chargeStartDate": "2020-11-11T00:31:03Z", "chargeEndDate": "2020-11-11T08:30:17Z", "chargeDuration": 479, "chargeStartBatteryLevel": 15, "chargeEndBatteryLevel": 74, "chargeBatteryLevelRecovered": 59, "chargePower": "slow", "chargeStartInstantaneousPower": 3100, "chargeEndStatus": "ok" } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charging-settings.multi.json000066400000000000000000000035411473671120200306410ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "mode": "scheduled", "schedules": [ { "id": 1, "activated": true, "monday": { "startTime": "T00:00Z", "duration": 450 }, "tuesday": { "startTime": "T00:00Z", "duration": 450 }, "wednesday": { "startTime": "T00:00Z", "duration": 450 }, "thursday": { "startTime": "T00:00Z", "duration": 450 }, "friday": { "startTime": "T00:00Z", "duration": 450 }, "saturday": { "startTime": "T00:00Z", "duration": 450 }, "sunday": { "startTime": "T00:00Z", "duration": 450 } }, { "id": 2, "activated": true, "monday": { "startTime": "T23:30Z", "duration": 15 }, "tuesday": { "startTime": "T23:30Z", "duration": 15 }, "wednesday": { "startTime": "T23:30Z", "duration": 15 }, "thursday": { "startTime": "T23:30Z", "duration": 15 }, "friday": { "startTime": "T23:30Z", "duration": 15 }, "saturday": { "startTime": "T23:30Z", "duration": 15 }, "sunday": { "startTime": "T23:30Z", "duration": 15 } }, { "id": 3, "activated": false }, { "id": 4, "activated": false }, { "id": 5, "activated": false } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/charging-settings.single.json000066400000000000000000000016241473671120200307700ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "mode": "scheduled", "schedules": [ { "id": 1, "activated": true, "monday": { "startTime": "T12:00Z", "duration": 15 }, "tuesday": { "startTime": "T04:30Z", "duration": 420 }, "wednesday": { "startTime": "T22:30Z", "duration": 420 }, "thursday": { "startTime": "T22:00Z", "duration": 420 }, "friday": { "startTime": "T12:15Z", "duration": 15 }, "saturday": { "startTime": "T12:30Z", "duration": 30 }, "sunday": { "startTime": "T12:45Z", "duration": 45 } } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/cockpit.captur_ii.json000066400000000000000000000002651473671120200275020ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777123", "attributes": { "fuelAutonomy": 35.0, "fuelQuantity": 3.0, "totalMileage": 5566.78 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/cockpit.spring.json000066400000000000000000000001771473671120200270270ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "totalMileage": 49114.27 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/cockpit.zoe.json000066400000000000000000000001651473671120200263170ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "totalMileage": 49114.27 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/cockpit.zoe_50.json000066400000000000000000000002601473671120200266170ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "fuelAutonomy": 0, "fuelQuantity": 0, "totalMileage": 5785.75 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-history.json000066400000000000000000000001331473671120200265020ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": {} } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-sessions.json000066400000000000000000000001331473671120200266470ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": {} } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-settings.json000066400000000000000000000012041473671120200266410ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "dateTime": "2020-12-24T20:00:00.000Z", "mode": "scheduled", "schedules": [ { "id": 1, "activated": false }, { "id": 2, "activated": true, "wednesday": { "readyAtTime": "T15:15Z" }, "friday": { "readyAtTime": "T15:15Z" } }, { "id": 3, "activated": false }, { "id": 4, "activated": false }, { "id": 5, "activated": false } ] } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-status.spring.json000066400000000000000000000003061473671120200276270ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "UU1AAAAA555777123", "attributes": { "socThreshold": 30.0, "hvacStatus": "off", "lastUpdateTime": "2020-12-03T00:00:00Z" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-status.zoe.json000066400000000000000000000002141473671120200271200ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "externalTemperature": 8.0, "hvacStatus": "off" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/hvac-status.zoe_50.json000066400000000000000000000002031473671120200274220ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "socThreshold": 40, "hvacStatus": "on" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/location.1.json000066400000000000000000000003221473671120200260310ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "gpsLatitude": 48.1234567, "gpsLongitude": 11.1234567, "lastUpdateTime": "2020-02-18T16:58:38Z" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/location.2.json000066400000000000000000000003561473671120200260410ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "gpsDirection": null, "gpsLatitude": 48.1234567, "gpsLongitude": 11.1234567, "lastUpdateTime": "2020-02-18T16:58:38Z" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/lock-status.1.json000066400000000000000000000005441473671120200265000ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "lockStatus": "locked", "doorStatusRearLeft": "closed", "doorStatusRearRight": "closed", "doorStatusDriver": "closed", "doorStatusPassenger": "closed", "hatchStatus": "closed", "lastUpdateTime": "2022-02-02T13:51:13Z" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/lock-status.2.json000066400000000000000000000005461473671120200265030ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "lockStatus": "unlocked", "doorStatusRearLeft": "closed", "doorStatusRearRight": "closed", "doorStatusDriver": "closed", "doorStatusPassenger": "closed", "hatchStatus": "closed", "lastUpdateTime": "2022-02-02T13:51:13Z" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/no_data.json000066400000000000000000000001021473671120200254630ustar00rootroot00000000000000{ "data": { "type": "ChargeMode", "id": "VF1AAAA" } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/notification-settings.json000066400000000000000000000001331473671120200304060ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": {} } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/res-state.1.json000066400000000000000000000002431473671120200261320ustar00rootroot00000000000000{ "data": { "type": "ResState", "id": "VF1AAAAA555777999", "attributes": { "details": "Stopped, ready for RES", "code": "10" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/res-state.2.json000066400000000000000000000002241473671120200261320ustar00rootroot00000000000000{ "data": { "type": "ResState", "id": "VF1AAAAA555777999", "attributes": { "details": "Running", "code": "42" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_data/tyre-pressure.json000066400000000000000000000004331473671120200267160ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "VF1AAAAA555777999", "attributes": { "flPressure": 2460, "frPressure": 2730, "rlPressure": 2790, "rrPressure": 2790, "flStatus": 0, "frStatus": 0, "rlStatus": 0, "rrStatus": 0 } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_details/000077500000000000000000000000001473671120200237065ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_details/megane_e-tech.2.json000066400000000000000000000112671473671120200274310ustar00rootroot00000000000000{ "vin": "VF1AAAAA123987456", "registrationDate": "2022-12-26", "firstRegistrationDate": "2022-12-26", "engineType": "6AM", "engineRatio": "402", "modelSCR": "ZO1", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "XCB", "label": "XCB FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BTBAE", "label": "BTBAE BATTERY", "group": "968" }, "radioType": { "code": "RA466", "label": "IVI2 DA CLASS 4HP DAB", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "XCB1VE", "label": "MEGANE E-TECH", "group": "971" }, "gearbox": { "code": "1EVGB", "label": "REDUCER FOR ELECTRIC MOTOR", "group": "427" }, "version": { "code": "2ZEN M J1 L" }, "energy": { "code": "ELECX", "label": "ELECTRICITY", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "PAVEH/XCB/BCB/E2S/J1/ELECX/LHDG/TEMP/2WDRV/BEMBK/ACCLM/00ABS/ACD03/STDRF/HTRWI/WFTRT/CLK00/RVX03/1RVLG/FFGL2/SDLGT/RAL18/FSBAJ/NOSPA/FAB02/NOCNV/CLSCO/CLS02/HAR00/RHR03/FSE04/RSE00/OV369/NTIBC/KMETR/TPRM2/SDSGL/NOSTK/SABG5/LEDH2/ESCHS/PALAW/SPBDA/M3CA3/PRROP/DB4C0/NOAGR/SLSW0/CDSLI/RVIMA/TRSV0/BCRVX/FWL1T/RWL1T/NOOSW/REPKT/NHTS0/00FRA/BRA03/AJSW2/HSTPL/SBR05/RMSB3/RA466/1EVGB/ASOC2/EVAU0/RIM06/TYSUM/ISOFI/EPER0/HR11M/SLCCA/NOATD/CPTC0/CHGS4/TL01A/BDPRO/NOADT/ABCXL/ELC1/NOETC/NOLSV/NOFEX/M2021/PHAS1/NOLTD/NOATY/NOHYB/60K0B/BTBAE/VEC091/XCB1VE/NB003/6AM/WRANT/NOADR/LKA05/PSFT0/BIHT0/NODUP/NOWAP/NOCCH/AMLT0/DRL02/RCALL/PURFR/TBI00/MET05/NOBSD/ECMD0/NRCAR/M2CA0/AIVCT/GSI00/TP369/TS369/1BCOL/ITPK0/NOMDM/PXA00/NOPXB/PIG00/NHTSW/DAALT/NOWIC/EV1GA1/SMTSR/NOWMC/FCOWA/C1AHS/NOPRA/VSPTA/1234Y/NOLIE/NOLII/NOWFI/AEB09/WOSRE/NOAHL/SPMIR/RRCAM/NORCT/NODTR", "assets": [ { "assetType": "PICTURE", "viewpoint": "mybrand_2", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "assetType": "PICTURE", "viewpoint": "mybrand_5", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_AV&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_AV&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "deliveryDate": "2023-04-06", "retrievedFromDhs": false, "radioCode": "1234" } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_details/zoe_40.1.json000066400000000000000000000064271473671120200260510ustar00rootroot00000000000000{ "vin": "VF1AAAAA555777999", "registrationDate": "2017-08-01", "firstRegistrationDate": "2017-08-01", "engineType": "5AQ", "engineRatio": "601", "modelSCR": "ZOE", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "X10", "label": "", "group": "007" }, "tcu": { "code": "TCU0G2", "label": "TCU VER 0 GEN 2", "group": "E70" }, "navigationAssistanceLevel": { "code": "NAV3G5", "label": "LEVEL 3 TYPE 5 NAVIGATION", "group": "408" }, "battery": { "code": "BT4AR1", "label": "BT4AR1 BATTERY", "group": "968" }, "radioType": { "code": "RAD37A", "label": "RADIO 37A", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "X101VE", "label": "ZOE", "group": "971" }, "gearbox": { "code": "BVEL", "label": "ELECTRIC VARIATOR GEARBOX", "group": "427" }, "version": { "code": "INT MB 10R" }, "energy": { "code": "ELEC", "label": "ELECTRIC", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "STANDA/X10/B10/EA2/MB/ELEC/DG/TEMP/TR4X2/RV/ABS/CAREG/LAC/VT003/CPE/RET03/SPROJA/RALU16/CEAVRH/AIRBA1/SERIE/DRA/DRAP08/HARM02/ATAR/TERQG/SFBANA/KM/DPRPN/AVREPL/SSDECA/ASRESP/RDAR02/ALEVA/CACBL2/SOP02C/CTHAB2/TRNOR/LVAVIP/LVAREL/SASURV/KTGREP/SGSCHA/APL03/ALOUCC/CMAR3P/NAV3G5/RAD37A/BVEL/AUTAUG/RNORM/ISOFIX/EQPEUR/HRGM01/SDPCLV/TLFRAN/SPRODI/SAN613/SSAPEX/GENEV1/ELC1/SANCML/PE2012/PHAS1/SAN913/045KWH/BT4AR1/VEC153/X101VE/NBT017/5AQ/SYTINC/SKTPOU/SAND41/FDIU1/SSESM/MAPSUP/SSCALL/SAND88/SAND90/SQKDRO/SDIFPA/FACBA2/PRLEX1/SSRCAR/CABDO2/TCU0G2/SWALBO/EVTEC1", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017%2FSKTPOU%2FPRLEX1&databaseId=b4572adc-6c81-48ef-b4b1-2aff24ed7550&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017%2FSKTPOU%2FPRLEX1&databaseId=b4572adc-6c81-48ef-b4b1-2aff24ed7550&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "assetType": "URL", "assetRole": "GUIDE", "title": "e-guide", "description": "", "renditions": [{ "url": "https://www.tutos-video.renault.fr/?id=zoe" }] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Video 1", "description": "", "renditions": [{ "url": "dEjqEJeqTO8" }] }, { "assetType": "URL", "assetRole": "CAR", "title": "More videos", "description": "", "renditions": [{ "url": "https://www.tutos-video.renault.fr/?id=zoe" }] } ], "yearsOfMaintenance": 12, "deliveryDate": "2017-08-11", "retrievedFromDhs": false, "radioCode": "*PRIVATE*" } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/000077500000000000000000000000001473671120200237225ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/duster.1.json000066400000000000000000000027731473671120200262730ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "*PRIVATE*", "attributes": { "vin": "*PRIVATE*", "vehicleId": 12345, "batteryCode": "SANBAT", "bin": null, "brand": "RENAULT", "canGeneration": "C1A", "carGateway": "AVN", "color": null, "dealerId": null, "deliveryCountry": "RU ", "deliveryDate": null, "deviceSerialNumber": null, "electricityUnitCost": null, "energy": "gasoline", "engineType": "H5H", "familyCode": "XJD", "firstRegistrationDate": null, "fuelUnitCost": null, "gearbox": "MANUAL", "modelCode": "HJD", "modelCodeDetail": "XJD1SU", "modelName": "NEW DUSTER", "modelYear": null, "navigAssistCode": null, "nickname": null, "packId": null, "packManufacturingDate": null, "packNominalCapacity": null, "packType": null, "pictureUrl": null, "radioType": "RA41A", "region": "RU", "registrationCountry": "RU ", "registrationNumber": "*PRIVATE*", "sourceReferenceId": null, "tcuCode": "AIVCT", "temperatureUnit": null, "upholstery": null, "vehicleSourceReference": null, "vehicleSourceType": null, "vehicleType": null, "versionCode": "B2 4 M3M 5C", "versionName": null, "privacyMode": "off", "privacyModeUpdateDate": null, "svtFlag": false, "svtLastUpdateTime": null, "svtBlockFlag": false, "svtBlockLastUpdateTime": null } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/spring.1.json000066400000000000000000000030021473671120200262510ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "*PRIVATE*", "attributes": { "vin": "*PRIVATE*", "vehicleId": null, "batteryCode": "SANBAT", "bin": null, "brand": "DACIA", "canGeneration": "T4VS", "carGateway": "AVN", "color": null, "dealerId": null, "deliveryCountry": "FR", "deliveryDate": null, "deviceSerialNumber": null, "electricityUnitCost": null, "energy": "electric", "engineType": "4DB", "familyCode": "XBG", "firstRegistrationDate": null, "fuelUnitCost": null, "gearbox": "AUTOMATIC", "modelCode": "BBG", "modelCodeDetail": "XBG1VE", "modelName": "SPRING", "modelYear": null, "navigAssistCode": "", "nickname": null, "pictureUrl": null, "radioType": "RA43D", "region": "EU", "registrationCountry": "FR", "registrationNumber": "", "sourceReferenceId": null, "tcuCode": "AIVCT", "upholstery": null, "vehicleSourceReference": "BVM", "versionCode": "E2PM1 B4E2R", "versionName": null, "privacyMode": "off", "privacyModeUpdateDate": "2022-01-22T12:22:46.617Z", "svtFlag": false, "svtLastUpdateTime": null, "svtBlockFlag": false, "svtBlockLastUpdateTime": null, "packId": null, "packManufacturingDate": null, "packNominalCapacity": null, "packType": null, "temperatureUnit": null, "vehicleSourceType": null, "vehicleType": null } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/twingo_ze.1.json000066400000000000000000000030011473671120200267530ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "*PRIVATE*", "attributes": { "vin": "*PRIVATE*", "vehicleId": 1053757, "batteryCode": "SANBAT", "bin": null, "brand": "RENAULT", "canGeneration": "T4VS", "carGateway": "AVN", "color": null, "dealerId": null, "deliveryCountry": "IT ", "deliveryDate": null, "deviceSerialNumber": null, "electricityUnitCost": null, "energy": "electric", "engineType": "5AL", "familyCode": "X07", "firstRegistrationDate": null, "fuelUnitCost": null, "gearbox": "AUTOMATIC", "modelCode": "B07", "modelCodeDetail": "X071VE", "modelName": "TWINGO III", "modelYear": null, "navigAssistCode": null, "nickname": null, "packId": null, "packManufacturingDate": null, "packNominalCapacity": null, "packType": null, "pictureUrl": null, "radioType": "NA437", "region": "EU", "registrationCountry": "IT ", "registrationNumber": "*PRIVATE*", "sourceReferenceId": null, "tcuCode": "AIVCT", "temperatureUnit": null, "upholstery": null, "vehicleSourceReference": null, "vehicleSourceType": null, "vehicleType": null, "versionCode": "INTEA1E C1P", "versionName": null, "privacyMode": "off", "privacyModeUpdateDate": null, "svtFlag": false, "svtLastUpdateTime": null, "svtBlockFlag": false, "svtBlockLastUpdateTime": null } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/zoe_40.1.json000066400000000000000000000030301473671120200260500ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "*PRIVATE*", "attributes": { "vin": "*PRIVATE*", "vehicleId": 55722, "batteryCode": "BT4AR1", "bin": null, "brand": "RENAULT", "canGeneration": "C1A", "carGateway": "GDC", "color": null, "dealerId": null, "deliveryCountry": "FR ", "deliveryDate": "2017-08-11T00:00:00.000+0000", "deviceSerialNumber": null, "electricityUnitCost": null, "energy": "electric", "engineType": "5AQ", "familyCode": "X10", "firstRegistrationDate": "20170801", "fuelUnitCost": null, "gearbox": "AUTOMATIC", "modelCode": "B10", "modelCodeDetail": "X101VE", "modelName": "ZOE", "modelYear": null, "navigAssistCode": null, "nickname": null, "packId": null, "packManufacturingDate": null, "packNominalCapacity": null, "packType": null, "pictureUrl": null, "radioType": "RAD37A", "region": "EU", "registrationCountry": "FR ", "registrationNumber": "*PRIVATE*", "sourceReferenceId": null, "tcuCode": "TCU0G2", "temperatureUnit": null, "upholstery": null, "vehicleSourceReference": null, "vehicleSourceType": null, "vehicleType": null, "versionCode": "INT MB 10R", "versionName": null, "privacyMode": "off", "privacyModeUpdateDate": null, "svtFlag": false, "svtLastUpdateTime": null, "svtBlockFlag": false, "svtBlockLastUpdateTime": null } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_gateway/zoe_50.1.json000066400000000000000000000030151473671120200260540ustar00rootroot00000000000000{ "data": { "type": "Car", "id": "*PRIVATE*", "attributes": { "vin": "*PRIVATE*", "vehicleId": 271893, "batteryCode": "BT4AR1", "bin": null, "brand": "RENAULT", "canGeneration": "C1A", "carGateway": "AVN", "color": null, "dealerId": null, "deliveryCountry": "FR ", "deliveryDate": null, "deviceSerialNumber": null, "electricityUnitCost": null, "energy": "electric", "engineType": "5AQ", "familyCode": "X10", "firstRegistrationDate": null, "fuelUnitCost": null, "gearbox": "AUTOMATIC", "modelCode": "B10", "modelCodeDetail": "X102VE", "modelName": "ZOE", "modelYear": null, "navigAssistCode": null, "nickname": null, "packId": null, "packManufacturingDate": null, "packNominalCapacity": null, "packType": null, "pictureUrl": null, "radioType": "NA418", "region": "EU", "registrationCountry": "FR ", "registrationNumber": "*PRIVATE*", "sourceReferenceId": null, "tcuCode": "AIVCT", "temperatureUnit": null, "upholstery": null, "vehicleSourceReference": null, "vehicleSourceType": null, "vehicleType": null, "versionCode": "INT A MD 1L", "versionName": null, "privacyMode": "off", "privacyModeUpdateDate": "2020-02-29T12:03:44.13Z", "svtFlag": false, "svtLastUpdateTime": null, "svtBlockFlag": false, "svtBlockLastUpdateTime": null } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_kcm_action/000077500000000000000000000000001473671120200243705ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicle_kcm_action/charge-pause-resume.pause.json000066400000000000000000000001571473671120200322440ustar00rootroot00000000000000{ "data": { "type": "ChargePauseResume", "id": "guid", "attributes": { "action": "pause" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicle_kcm_action/charge-pause-resume.resume.json000066400000000000000000000001601473671120200324210ustar00rootroot00000000000000{ "data": { "type": "ChargePauseResume", "id": "guid", "attributes": { "action": "resume" } } } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/000077500000000000000000000000001473671120200223645ustar00rootroot00000000000000renault-api-0.2.9/tests/fixtures/kamereon/vehicles/captur_ii.1.json000066400000000000000000000120231473671120200253730ustar00rootroot00000000000000{ "accountId": "account-id-1", "country": "LU", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777123", "status": "ACTIVE", "linkType": "USER", "garageBrand": "RENAULT", "mileage": 346, "startDate": "2020-06-12", "createdDate": "2020-06-12T15:02:00.555432Z", "lastModifiedDate": "2020-06-15T06:21:43.762467Z", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2020-06-15T06:20:39.107794Z", "lastModifiedDate": "2020-06-15T06:20:39.107794Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777123", "engineType": "H5H", "engineRatio": "470", "modelSCR": "CP1", "deliveryCountry": { "code": "BE", "label": "BELGIQUE" }, "family": { "code": "XJB", "label": "FAMILLE B+X OVER", "group": "007" }, "tcu": { "code": "AIVCT", "label": "AVEC BOITIER CONNECT AIVC", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "SANBAT", "label": "SANS BATTERIE", "group": "968" }, "radioType": { "code": "NA406", "label": "A-IVIMINDL, 2BO + 2BI + 2T, MICRO-DOUBLE, FM1/DAB+FM2", "group": "425" }, "registrationCountry": { "code": "BE" }, "brand": { "label": "RENAULT" }, "model": { "code": "XJB1SU", "label": "CAPTUR II", "group": "971" }, "gearbox": { "code": "BVA7", "label": "BOITE DE VITESSE AUTOMATIQUE 7 RAPPORTS", "group": "427" }, "version": { "code": "ITAMFHA 6TH" }, "energy": { "code": "ESS", "label": "ESSENCE", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "ADR00/DLIGM2/PGPRT2/FEUAR3/CDVIT1/SKTPOU/SKTPGR/SSCCPC/SSPREM/FDIU2/MAPSTD/RCALL/MET04/DANGMO/ECOMOD/SSRCAR/AIVCT/AVGSI/TPRPE/TSGNE/2TON/ITPK7/MLEXP1/SPERTA/SSPERG/SPERTP/VOLCHA/SREACT/AVOSP1/SWALBO/DWGE01/AVC1A/1234Y/AEBS07/PRAHL/AVCAM/STANDA/XJB/HJB/EA3/MF/ESS/DG/TEMP/TR4X2/AFURGE/RVDIST/ABS/SBARTO/CA02/TOPAN/PBNCH/LAC/VSTLAR/CPE/RET04/2RVLG/RALU17/CEAVRH/AIRBA2/SERIE/DRA/DRAP05/HARM01/ATAR03/SGAV02/SGAR02/BIXPE/BANAL/KM/TPRM3/AVREPL/SSDECA/SFIRBA/ABLAVI/ESPHSA/FPAS2/ALEVA/SCACBA/SOP03C/SSADPC/STHPLG/SKTGRV/VLCUIR/RETIN2/TRSEV1/REPNTC/LVAVIP/LVAREI/SASURV/KTGREP/SGACHA/BEL01/APL03/FSTPO/ALOUC5/CMAR3P/FIPOU2/NA406/BVA7/ECLHB4/RDIF10/PNSTRD/ISOFIX/ENPH01/HRGM01/SANFLT/CSRGAC/SANACF/SDPCLV/TLRP00/SPRODI/SAN613/AVFAP/AIRBDE/CHC03/E06T/SAN806/SSPTLP/SANCML/SSFLEX/SDRQAR/SEXTIN/M2019/PHAS1/SPRTQT/SAN913/STHABT/SSTYAD/HYB01/SSCABA/SANBAT/VEC012/XJB1SU/SSNBT/H5H", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=ADR00%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FCDVIT1%2FSSCCPC%2FRCALL%2FMET04%2FDANGMO%2FSSRCAR%2FAVGSI%2FITPK7%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLCHA%2FSREACT%2FDWGE01%2FAVCAM%2FHJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRVDIST%2FSBARTO%2FCA02%2FTOPAN%2FPBNCH%2FVSTLAR%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIXPE%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETIN2%2FREPNTC%2FLVAVIP%2FLVAREI%2FSGACHA%2FALOUC5%2FNA406%2FBVA7%2FECLHB4%2FRDIF10%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB01%2FH5H&databaseId=3e814da7-766d-4039-ac69-f001a1f738c8&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=ADR00%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FCDVIT1%2FSSCCPC%2FRCALL%2FMET04%2FDANGMO%2FSSRCAR%2FAVGSI%2FITPK7%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLCHA%2FSREACT%2FDWGE01%2FAVCAM%2FHJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRVDIST%2FSBARTO%2FCA02%2FTOPAN%2FPBNCH%2FVSTLAR%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIXPE%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETIN2%2FREPNTC%2FLVAVIP%2FLVAREI%2FSGACHA%2FALOUC5%2FNA406%2FBVA7%2FECLHB4%2FRDIF10%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB01%2FH5H&databaseId=3e814da7-766d-4039-ac69-f001a1f738c8&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": false, "rlinkStore": false, "deliveryDate": "2020-06-17", "retrievedFromDhs": false, "engineEnergyType": "OTHER", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/captur_ii.2.json000066400000000000000000000120431473671120200253760ustar00rootroot00000000000000{ "accountId": "account-id-2", "country": "IT", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777123", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "RENAULT", "startDate": "2020-10-07", "createdDate": "2020-10-07T09:17:44.692802Z", "lastModifiedDate": "2021-03-28T10:44:01.139649Z", "ownershipStartDate": "2020-09-30", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2020-10-08T17:36:39.445523Z", "lastModifiedDate": "2020-10-08T17:36:39.445523Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777123", "registrationDate": "2020-09-30", "firstRegistrationDate": "2020-09-30", "engineType": "H4M", "engineRatio": "630", "modelSCR": "", "deliveryCountry": { "code": "IT", "label": "ITALY" }, "family": { "code": "XJB", "label": "B+X OVER FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BT9AE1", "label": "BATTERY BT9AE1", "group": "968" }, "radioType": { "code": "NA418", "label": "FULL NAV DAB ETH - AUDI", "group": "425" }, "registrationCountry": { "code": "IT" }, "brand": { "label": "RENAULT" }, "model": { "code": "XJB1SU", "label": "CAPTUR II", "group": "971" }, "gearbox": { "code": "BVH4", "label": "HYBRID 4 SPEED GEARBOX", "group": "427" }, "version": { "code": "ITAMMHH 6UP" }, "energy": { "code": "ESS", "label": "PETROL", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "STANDA/XJB/HJB/EA3/MM/ESS/DG/TEMP/TR4X2/AFURGE/RV/ABS/SBARTO/CA02/TN/PBNCH/LAC/VT/CPE/RET04/2RVLG/RALU17/CEAVRH/AIRBA2/SERIE/DRA/DRAP05/HARM01/ATAR03/SGAV01/SGAR02/BIYPC/BANAL/KM/TPRM3/AVREPL/SSDECA/SFIRBA/ABLAVI/ESPHSA/FPAS2/ALEVA/CACBL3/SOP03C/SSADPC/STHPLG/SKTGRV/VLCUIR/RETRCR/TRSEV1/REPNTC/LVAVIP/LVAREI/SASURV/KTGREP/SGSCHA/ITA01/APL03/FSTPO/ALOUC5/PART01/CMAR3P/FIPOU2/NA418/BVH4/ECLHB4/RDIF10/PNSTRD/ISOFIX/ENPH01/HRGM01/SANFLT/CSRFLY/SANACF/SDPCLV/TLRP00/SPRODI/SAN613/AVFAP/AIRBDE/CHC03/E06U/SAN806/SSPTLP/SANCML/SSFLEX/SDRQAR/SEXTIN/M2019/PHAS1/SPRTQT/SAN913/STHABT/5DHS/HYB06/010KWH/BT9AE1/VEC237/XJB1SU/NBT018/H4M/NOADR/DLIGM2/PGPRT2/FEUAR3/SCDVIT/SKTPOU/SKTPGR/SSCCPC/SSPREM/FDIU2/MAPSTD/RCALL/MET05/SDANGM/ECOMOD/SSRCAR/AIVCT/AVGSI/TPQNW/TSGNE/2TON/ITPK4/MLEXP1/SPERTA/SSPERG/SPERTP/VOLNCH/SREACT/AVTSR1/SWALBO/DWGE01/AVC1A/VSPTA/1234Y/AEBS07/PRAHL/RRCAM", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=HJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRV%2FSBARTO%2FCA02%2FTN%2FPBNCH%2FVT%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIYPC%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETRCR%2FREPNTC%2FLVAVIP%2FLVAREI%2FALOUC5%2FNA418%2FBVH4%2FECLHB4%2FRDIF10%2FCSRFLY%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB06%2FH4M%2FNOADR%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FSSCCPC%2FRCALL%2FMET05%2FSDANGM%2FSSRCAR%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLNCH%2FSREACT%2FDWGE01%2FRRCAM&databaseId=b2b4fefb-d131-4f8f-9a24-4223c38bc710&bookmarkSet=CARPICKER&bookmark=EXT_34_RIGHT_FRONT&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=HJB%2FEA3%2FESS%2FDG%2FTEMP%2FTR4X2%2FRV%2FSBARTO%2FCA02%2FTN%2FPBNCH%2FVT%2FCPE%2FRET04%2FRALU17%2FDRA%2FDRAP05%2FHARM01%2FBIYPC%2FKM%2FSSDECA%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLCUIR%2FRETRCR%2FREPNTC%2FLVAVIP%2FLVAREI%2FALOUC5%2FNA418%2FBVH4%2FECLHB4%2FRDIF10%2FCSRFLY%2FSANACF%2FTLRP00%2FAIRBDE%2FCHC03%2FSSPTLP%2FSPRTQT%2FSAN913%2FHYB06%2FH4M%2FNOADR%2FDLIGM2%2FPGPRT2%2FFEUAR3%2FSSCCPC%2FRCALL%2FMET05%2FSDANGM%2FSSRCAR%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FSSPERG%2FSPERTP%2FVOLNCH%2FSREACT%2FDWGE01%2FRRCAM&databaseId=b2b4fefb-d131-4f8f-9a24-4223c38bc710&bookmarkSet=CARPICKER&bookmark=EXT_34_RIGHT_FRONT&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": false, "rlinkStore": false, "deliveryDate": "2020-09-30", "retrievedFromDhs": false, "engineEnergyType": "PHEV", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/clio_v.1.json000066400000000000000000000127141473671120200246760ustar00rootroot00000000000000{ "accountId": "account-id-clio", "country": "IT", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777123", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "RENAULT", "mileage": 20096, "mileageUnit": "km", "mileageDate": "2021-08-18", "startDate": "2020-10-26", "createdDate": "2020-10-26T06:00:43.742183Z", "lastModifiedDate": "2022-04-11T06:45:39.027640Z", "ownershipStartDate": "2020-12-17", "cancellationReason": {}, "preferredDealer": { "dealerId": "38013362_001", "dealerName": "AUTONORD FIORETTO SPA", "brand": "RENAULT", "createdDate": "2020-12-20T14:57:02.939854Z", "lastModifiedDate": "2020-12-20T14:57:02.939854Z" }, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2020-12-20T14:13:57.932714Z", "lastModifiedDate": "2020-12-20T14:13:57.932714Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777123", "registrationDate": "2020-12-17", "firstRegistrationDate": "2020-12-17", "engineType": "H4M", "engineRatio": "632", "modelSCR": "CL5", "deliveryCountry": { "code": "IT", "label": "ITALY" }, "family": { "code": "XJA", "label": "XJA FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BTAAR1", "label": "BTAAR1 BATTERY", "group": "968" }, "radioType": { "code": "NA406", "label": "AIVI FULL NAV DAB 6 SPEAKERS", "group": "425" }, "registrationCountry": { "code": "IT" }, "brand": { "label": "RENAULT" }, "model": { "code": "XJA1VP", "label": "CLIO V", "group": "971" }, "gearbox": { "code": "BVH4", "label": "HYBRID 4 SPEED GEARBOX", "group": "427" }, "version": { "code": "INT MUH6UY" }, "energy": { "code": "ESS", "label": "PETROL", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "STANDA/XJA/BJA/EA3/MU/ESS/DG/TEMP/TR4X2/AFURGE/RV/ABS/CA02/TN/PBNCH/LAC/VT/CPE/RET04/SPROJA/RALU17/CEAVRH/SANCL/AIRBA2/SERIE/DRA/DRAP06/HARM01/ATAR03/SGAV03/SGAR02/TECNM/BANAL/KM/MCSOL2/TPRM3/AVREPL/SSDECA/ABLAVI/ESPHSA/FPAS2/ALEVA/SOP03C/SSADPC/STHPLG/SKTGRV/VLMOU1/RETRCR/TRSEV1/RETC/LVAVIP/LVAREI/SASURV/RSGALT/SGSCHA/ITA01/APL03/SAN346/PANP02/FSTPO/ALOUC5/PART01/CMAR3P/SAN417/NA406/BVH4/ECLHB3/RDIF20/PNSTRD/ISOFIX/ENPH01/HRGM01/SANFLT/CSRGAC/SANACF/SDPCLV/TLRP00/FRDIS1/SPRODI/SAN613/AVFAP/AIRBDE/CHC03/PSMREC/E06U/SSPTLP/CHBASE/SANCML/SSFLEX/SDRQAR/SEXTIN/M2018/PHAS1/SPRTQT/SAN913/SSAPLC/THABT1/5DHS/HYB05/1KWH29/BTAAR1/VEC374/XJA1VP/NB004/SUSNPI/H4M/NOADR/SSCAEC/DLIGM2/PGPRT2/SRANCF/FEUAR3/SCDVIT/SANC09/SKTPOU/SKTPGR/SSCCPC/SSPREM/FDIU2/MAPSTD/RCALL/MET04/DANGMO/ECOMOD/SDCOSP/SSRCAR/AIVCT/PRCHR1/AVGSI/ITPK4/MLEXP1/SPERTA/PERB01/PERC01/SSPERG/SPERTP/SPERTS/VOLNCH/SSACTE/NODAA/REACTI/EV1GA1/AVTSR1/DWGE01/AVC1A/1234Y/NOLIE/NOLII/NOLIS/LIECHS/AEBS07/WOSRE/PRAHL/AVCAM", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FBJA%2FEA3%2FESS%2FDG%2FRV%2FCA02%2FVT%2FCPE%2FRET04%2FSPROJA%2FRALU17%2FDRA%2FDRAP06%2FHARM01%2FATAR03%2FSGAV03%2FSGAR02%2FTECNM%2FKM%2FMCSOL2%2FABLAVI%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLMOU1%2FRETRCR%2FRETC%2FLVAREI%2FRSGALT%2FPANP02%2FNA406%2FBVH4%2FECLHB3%2FRDIF20%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHBASE%2FM2018%2FSSAPLC%2FTHABT1%2FHYB05%2FNOADR%2FSSCAEC%2FDLIGM2%2FPGPRT2%2FSRANCF%2FFEUAR3%2FSKTPOU%2FRCALL%2FMET04%2FDANGMO%2FECOMOD%2FSSRCAR%2FAIVCT%2FPRCHR1%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FPERB01%2FPERC01%2FSSPERG%2FSPERTP%2FSPERTS%2FREACTI%2FEV1GA1%2FAVTSR1%2FDWGE01%2FNOLIE%2FNOLII%2FAEBS07%2FAVCAM&databaseId=f457977c-94e9-4746-aba4-b465dcccc955&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=STANDA%2FBJA%2FEA3%2FESS%2FDG%2FRV%2FCA02%2FVT%2FCPE%2FRET04%2FSPROJA%2FRALU17%2FDRA%2FDRAP06%2FHARM01%2FATAR03%2FSGAV03%2FSGAR02%2FTECNM%2FKM%2FMCSOL2%2FABLAVI%2FESPHSA%2FFPAS2%2FALEVA%2FSOP03C%2FSSADPC%2FVLMOU1%2FRETRCR%2FRETC%2FLVAREI%2FRSGALT%2FPANP02%2FNA406%2FBVH4%2FECLHB3%2FRDIF20%2FCSRGAC%2FSANACF%2FTLRP00%2FAIRBDE%2FCHBASE%2FM2018%2FSSAPLC%2FTHABT1%2FHYB05%2FNOADR%2FSSCAEC%2FDLIGM2%2FPGPRT2%2FSRANCF%2FFEUAR3%2FSKTPOU%2FRCALL%2FMET04%2FDANGMO%2FECOMOD%2FSSRCAR%2FAIVCT%2FPRCHR1%2FAVGSI%2FITPK4%2FMLEXP1%2FSPERTA%2FPERB01%2FPERC01%2FSSPERG%2FSPERTP%2FSPERTS%2FREACTI%2FEV1GA1%2FAVTSR1%2FDWGE01%2FNOLIE%2FNOLII%2FAEBS07%2FAVCAM&databaseId=f457977c-94e9-4746-aba4-b465dcccc955&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": false, "rlinkStore": false, "deliveryDate": "2020-12-17", "retrievedFromDhs": false, "engineEnergyType": "HEV", "radioCode": "" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/duster.1.json000066400000000000000000000114211473671120200247230ustar00rootroot00000000000000{ "accountId": "account-id-7", "country": "RU", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAA1234567876", "status": "ACTIVE", "linkType": "USER", "garageBrand": "renault", "mileage": 500, "mileageUnit": "km", "mileageDate": "2021-10-29", "startDate": "2021-03-01", "createdDate": "2021-04-01T11:00:00.648452Z", "lastModifiedDate": "2021-10-29T08:26:03.648452Z", "cancellationReason": {}, "preferredDealer": { "dealerId": "51214321_001", "brand": "RENAULT", "createdDate": "2018-01-01T19:27:14.798452", "lastModifiedDate": "8-01-1:27:14.798452" }, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2021-03-03T00:01:02.648452Z", "lastModifiedDate": "2021-03-03T00:01:02.648452Z" }, "vehicleDetails": { "vin": "VF1AAAA1234567876", "engineType": "H5H", "engineRatio": "460", "modelSCR": "JD1", "deliveryCountry": { "code": "RU", "label": "RUSSIA" }, "family": { "code": "XJD", "label": "XJD FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "SANBAT", "label": "", "group": "968" }, "radioType": { "code": "RA41A", "label": "ENDAFFM (SCREEN ATTACH), AUDIO 7 (2 BO + 2 BI + 2 TWEETER, DOUBLE-MICRO", "group": "425" }, "registrationCountry": { "code": "RU" }, "brand": { "label": "RENAULT" }, "model": { "code": "XJD1SU", "label": "NEW DUSTER", "group": "971" }, "gearbox": { "code": "BVM6", "label": "6-SPEED MANUAL GEARBOX", "group": "427" }, "version": { "code": "B2 4 M3M 5C" }, "energy": { "code": "ESS", "label": "PETROL", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "STANDA/XJD/HJD/EA2/M3/ESS/DG/GDFROI/TR4X4/AFURGE/RV/ABS/BARLO2/CA02/PBCH/LAC/VT/CPE/RET03/1RVLG/PROJAB/RALU16/CEAVFX/ADAC/AIRBA2/SERIE/DRA/DRAP07/HARM02/ATAR02/SGAV05/SGAR02/TECNW/BANAL/KM/PTCAV2/TPRM3/AVREPL/SSDECA/ABLAV/ESPDES/ALEVA/SOPC2C/SPRGAZ/STHPLG/KITGRV/VLCUIR/TRGAS/REPNTC/LVCIPE/LVAREL/SASURV/RSEC01/SGACHA/RUS01/APL03/AIRAR/VOLRHP/FSTPO/ALOUCC/CMAR3P/RA41A/BVM6/RDIF03/ISOFIX/ENPB01/HRGM01/SANFLT/CSRBA2/SANACF/SCHSTA/TLRP21/PRODIS/SAN613/SSFAP/AIRBDE/CHC03/E05C/ADFR/SSPTLP/SANCML/SSFLEX/SEXTIN/PE2017/PHAS1/SAN913/THABT1/SSTYAD/SSHYB/SSCABA/SANBAT/VEC654/XJD1SU/SSNBT/H5H/SDLIGM/SOUVCF/RMEGS/KITPOU/KITPGR/DPSEC/SSPREM/FDIU1/SSMAP/RCALL/PROCBO/SDANGM/ECOMOD/AIVCT/AVGSI/ITPK1/PERN02/VOLCHA/R134A/SFSSAC/SSVOLP/TCHA0/SPMIR/RRCAM/TCHC0/HTNZA", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=HJD%2FEA2%2FDG%2FTR4X4%2FRV%2FABS%2FBARLO2%2FCA02%2FPBCH%2FRET03%2FPROJAB%2FRALU16%2FDRA%2FDRAP07%2FHARM02%2FATAR02%2FSGAV05%2FTECNW%2FKM%2FABLAV%2FESPDES%2FALEVA%2FSOPC2C%2FVLCUIR%2FLVCIPE%2FLVAREL%2FSGACHA%2FAPL03%2FVOLRHP%2FCMAR3P%2FRA41A%2FBVM6%2FRDIF03%2FENPB01%2FCSRBA2%2FSANACF%2FSAN913%2FH5H%2FRCALL%2FSDANGM%2FECOMOD%2FITPK1%2FPERN02%2FVOLCHA%2FRRCAM&databaseId=42b2c477-c47a-40c9-8ed2-a6966c244777&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=HJD%2FEA2%2FDG%2FTR4X4%2FRV%2FABS%2FBARLO2%2FCA02%2FPBCH%2FRET03%2FPROJAB%2FRALU16%2FDRA%2FDRAP07%2FHARM02%2FATAR02%2FSGAV05%2FTECNW%2FKM%2FABLAV%2FESPDES%2FALEVA%2FSOPC2C%2FVLCUIR%2FLVCIPE%2FLVAREL%2FSGACHA%2FAPL03%2FVOLRHP%2FCMAR3P%2FRA41A%2FBVM6%2FRDIF03%2FENPB01%2FCSRBA2%2FSANACF%2FSAN913%2FH5H%2FRCALL%2FSDANGM%2FECOMOD%2FITPK1%2FPERN02%2FVOLCHA%2FRRCAM&databaseId=42b2c477-c47a-40c9-8ed2-a6966c244777&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": false, "rlinkStore": false, "deliveryDate": "2021-03-01", "retrievedFromDhs": false, "engineEnergyType": "OTHER", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/megane_e-tech.1.json000066400000000000000000000116561473671120200261100ustar00rootroot00000000000000{ "accountId": "account-id-megane", "country": "FR", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA123987456", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "renault", "startDate": "2022-08-31", "createdDate": "2022-08-31T08:06:05.425594Z", "lastModifiedDate": "2022-11-01T21:12:28.863554Z", "ownershipStartDate": "2022-08-26", "cancellationReason": {}, "preferredDealer": { "dealerId": "00000701_001", "dealerName": "RENAULT NANTES - GROUPE JEAN ROUYER", "brand": "RENAULT", "createdDate": "2022-09-11T18:52:15.089690388Z", "lastModifiedDate": "2022-09-11T18:52:15.089690388Z" }, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2022-09-02T18:09:47.429134080Z", "lastModifiedDate": "2022-09-02T18:09:47.429134080Z" }, "vehicleDetails": { "vin": "VF1AAAAA123987456", "registrationDate": "2022-08-26", "firstRegistrationDate": "2022-08-26", "engineType": "6AM", "engineRatio": "402", "modelSCR": "", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "XCB", "label": "XCB FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BTBAE", "label": "BTBAE BATTERY", "group": "968" }, "radioType": { "code": "NA448", "label": "IVI2 FULL PREM DAB", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "XCB1VE", "label": "MEGANE E-TECH", "group": "971" }, "gearbox": { "code": "1EVGB", "label": "REDUCER FOR ELECTRIC MOTOR", "group": "427" }, "version": { "code": "2PRE M J1 L" }, "energy": { "code": "ELECX", "label": "ELECTRICITY", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "PAVEH/XCB/BCB/EA4/J1/ELECX/LHDG/TEMP/2WDRV/BEMBK/ACC02/00ABS/ACD03/STDRF/HTRWI/WFURP/CLK00/RVX07/1RVLG/FFGL2/SDLGT/RAL20/FSBAJ/SPADP/FAB02/NOCNV/LRSCO/LRS02/HAR04/RHR03/FSE06/RSE00/BIXUF/NTIBC/KMETR/TPRM2/SDSGL/NOSTK/SABG5/LEDCO/ESCHS/PALAW/SPBDA/M3CA3/NOADS/DB4C0/NOAGR/LRSW0/OSEWA/RVIAT/TRSV0/NBRVX/FWL1T/RWL1T/NOOSW/REPKT/HTS02/00FRA/BRA03/AJSW2/HSTPL/SBR05/RMSB3/NA448/1EVGB/ASOC2/EVAU2/RIM09/TYSUM/ISOFI/EPER0/HR11M/SLCCA/NOATD/CPTC0/CHGS4/TL01A/BDPRO/NOADT/ABCXL/ELC1/NOETC/NOLSV/NOFEX/M2021/PHAS1/NOLTD/NOATY/NOHYB/60K0B/BTBAE/VEC088/XCB1VE/NB003/6AM/SFANT/ADR00/LKA05/PSFT0/BIHT0/NODUP/NOWAP/NOCCH/AMLT0/DRL02/RCALL/PURFR/TBI00/MET05/BSD02/ECMD0/NRCAR/M2CA0/AIVCT/GSI00/TP369/TSGNE/2BCOL/ITP15/MDMOD/PXA00/PXB00/PIG02/HTSW0/DAALT/WICH0/EV1GA1/SMOSP/NOWMC/FCOWA/C1AHS/NOPRA/VSPTA/1234Y/NOLIE/NOLII/NOWFI/AEB09/WOSRE/PRAHL/SPMIR/RRCAM/NORCT/DTRNI", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FEA4%2FLHDG%2FACD03%2FSTDRF%2FWFURP%2FRVX07%2FRAL20%2FLRS02%2FFSE06%2FBIXUF%2FLEDCO%2FPALAW%2FSPBDA%2FLRSW0%2FRVIAT%2FTRSV0%2FAJSW2%2FNA448%2FASOC2%2FRIM09%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FSFANT%2FADR00%2FNODUP%2FNOWAP%2FRCALL%2FBSD02%2F2BCOL%2FITP15%2FMDMOD%2FPXA00%2FPXB00%2FPIG02%2FHTSW0%2FNOLII%2FRRCAM%2FDTRNI&databaseId=08eb195d-c2b1-42e8-910c-6c350668b3b9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FEA4%2FLHDG%2FACD03%2FSTDRF%2FWFURP%2FRVX07%2FRAL20%2FLRS02%2FFSE06%2FBIXUF%2FLEDCO%2FPALAW%2FSPBDA%2FLRSW0%2FRVIAT%2FTRSV0%2FAJSW2%2FNA448%2FASOC2%2FRIM09%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FSFANT%2FADR00%2FNODUP%2FNOWAP%2FRCALL%2FBSD02%2F2BCOL%2FITP15%2FMDMOD%2FPXA00%2FPXB00%2FPIG02%2FHTSW0%2FNOLII%2FRRCAM%2FDTRNI&databaseId=08eb195d-c2b1-42e8-910c-6c350668b3b9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2022-09-07", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/megane_e-tech.2.json000066400000000000000000000142141473671120200261020ustar00rootroot00000000000000{ "accountId": "account-id-megane", "country": "FR", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA123987456", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "renault", "mileage": 20, "mileageUnit": "km", "mileageDate": "2023-04-06", "startDate": "2023-04-06", "createdDate": "2023-04-06T14:59:50.515315Z", "lastModifiedDate": "2023-04-06T15:04:46.930466Z", "ownershipStartDate": "2022-12-26", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2023-04-06T15:04:46.930306081Z", "lastModifiedDate": "2023-04-06T15:04:46.930306081Z" }, "vehicleDetails": { "vin": "VF1AAAAA123987456", "registrationDate": "2022-12-26", "firstRegistrationDate": "2022-12-26", "engineType": "6AM", "engineRatio": "402", "modelSCR": "ZO1", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "XCB", "label": "XCB FAMILY", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BTBAE", "label": "BTBAE BATTERY", "group": "968" }, "radioType": { "code": "1234", "label": "IVI2 DA CLASS 4HP DAB", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "XCB1VE", "label": "MEGANE E-TECH", "group": "971" }, "gearbox": { "code": "1EVGB", "label": "REDUCER FOR ELECTRIC MOTOR", "group": "427" }, "version": { "code": "2ZEN M J1 L" }, "energy": { "code": "ELECX", "label": "ELECTRICITY", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "PAVEH/XCB/BCB/E2S/J1/ELECX/LHDG/TEMP/2WDRV/BEMBK/ACCLM/00ABS/ACD03/STDRF/HTRWI/WFTRT/CLK00/RVX03/1RVLG/FFGL2/SDLGT/RAL18/FSBAJ/NOSPA/FAB02/NOCNV/CLSCO/CLS02/HAR00/RHR03/FSE04/RSE00/OV369/NTIBC/KMETR/TPRM2/SDSGL/NOSTK/SABG5/LEDH2/ESCHS/PALAW/SPBDA/M3CA3/PRROP/DB4C0/NOAGR/SLSW0/CDSLI/RVIMA/TRSV0/BCRVX/FWL1T/RWL1T/NOOSW/REPKT/NHTS0/00FRA/BRA03/AJSW2/HSTPL/SBR05/RMSB3/RA466/1EVGB/ASOC2/EVAU0/RIM06/TYSUM/ISOFI/EPER0/HR11M/SLCCA/NOATD/CPTC0/CHGS4/TL01A/BDPRO/NOADT/ABCXL/ELC1/NOETC/NOLSV/NOFEX/M2021/PHAS1/NOLTD/NOATY/NOHYB/60K0B/BTBAE/VEC091/XCB1VE/NB003/6AM/WRANT/NOADR/LKA05/PSFT0/BIHT0/NODUP/NOWAP/NOCCH/AMLT0/DRL02/RCALL/PURFR/TBI00/MET05/NOBSD/ECMD0/NRCAR/M2CA0/AIVCT/GSI00/TP369/TS369/1BCOL/ITPK0/NOMDM/PXA00/NOPXB/PIG00/NHTSW/DAALT/NOWIC/EV1GA1/SMTSR/NOWMC/FCOWA/C1AHS/NOPRA/VSPTA/1234Y/NOLIE/NOLII/NOWFI/AEB09/WOSRE/NOAHL/SPMIR/RRCAM/NORCT/NODTR", "assets": [ { "assetType": "PICTURE", "viewpoint": "mybrand_2", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "assetType": "PICTURE", "viewpoint": "mybrand_5", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_AV&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BCB%2FE2S%2FLHDG%2FACD03%2FSTDRF%2FWFTRT%2FRVX03%2FRAL18%2FCLS02%2FFSE04%2FOV369%2FLEDH2%2FPALAW%2FSPBDA%2FSLSW0%2FRVIMA%2FTRSV0%2FAJSW2%2FRA466%2FASOC2%2FRIM06%2FSLCCA%2FNOATD%2FBDPRO%2FNOETC%2FM2021%2FNB003%2FWRANT%2FNOADR%2FNODUP%2FNOWAP%2FRCALL%2FNOBSD%2F1BCOL%2FITPK0%2FNOMDM%2FPXA00%2FNOPXB%2FPIG00%2FNHTSW%2FNOLII%2FRRCAM%2FNODTR&databaseId=7669019f-69c7-43a7-8b1a-a4a26ab21b0d&bookmarkSet=RSITE&bookmark=EXT_34_AV&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2023-04-06", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/spring.1.json000066400000000000000000000100261473671120200247170ustar00rootroot00000000000000{ "accountId": "account-id-8", "country": "FR", "vehicleLinks": [ { "brand": "DACIA", "vin": "UU1AAAA555777999", "status": "ACTIVE", "linkType": "USER", "garageBrand": "DACIA", "startDate": "2021-03-01", "createdDate": "2021-04-01T11:00:00.648452Z", "lastModifiedDate": "2021-10-29T08:26:03.648452Z", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2021-03-03T00:01:02.648452Z", "lastModifiedDate": "2021-03-03T00:01:02.648452Z" }, "vehicleDetails": { "vin": "UU1AAAA555777999", "registrationDate": "2021-11-29", "firstRegistrationDate": "2021-11-29", "engineType": "4DB", "engineRatio": "401", "modelSCR": "", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "XBG", "label": "KWID EV", "group": "007" }, "tcu": { "code": "AIVCT", "label": "WITH AIVC CONNECTION UNIT", "group": "E70" }, "navigationAssistanceLevel": { "code": "", "label": "", "group": "" }, "battery": { "code": "BTDAN", "label": "BTDAN BATTERY", "group": "968" }, "radioType": { "code": "RA43D", "label": "RA43D", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "DACIA" }, "model": { "code": "XBG1VE", "label": "SPRING", "group": "971" }, "gearbox": { "code": "BVEL", "label": "ELECTRIC VARIATOR GEARBOX", "group": "427" }, "version": { "code": "E2PM1 B4E2R" }, "energy": { "code": "ELEC", "label": "ELECTRIC", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "STANDA/XBG/BBG/E2/M1/ELEC/DG/TEMP/TR4X2/DA/AFURGE/LIMVIT/ABS/CA01/LAC/VT/ELA/CPE/RET02/2RVLG/SPROJA/NOSDL/RTOL14/CEAVRH/ADAC/AIRBA2/SERIE/DRA/DRAP05/HARM03/ATAR02/SGAV02/SGAR02/OVQPA/BANAL/KM/TPRM2/AVREPL/DECA01/ABLAVI/ESPHSA/SRDPRO/ALAEVM/M3CA0/PRAMBI/SOPC2C/SKTGRV/COPOR/TRSEV1/RETP01/LVAVEL/LVAREL/RSNORT/FRA01/BRA05/HSTPL/SBR05/RA43D/BVEL/RDIF04/ISOFIX/ENPH02/HRGM01/SANFLT/CHARAP/TL01A/SPRODI/SAN613/AIRBDE/ELC1/SSFLEX/SSLVIT/M2019/PHAS1/SAN913/SSTYAD/SSHYB/26K0B/BTDAN/VEC016/XBG1VE/NB024/4DB/NOLK0/SKTPOU/SKTPGR/NOVTS/FDIU1/MAPOST/RCALL/FACBA1/ECOMOD/M2CA0/AIVCT/NOGSI/ITPK2/AVARC2/1234Y/AEB00", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BBG%2FE2%2FLIMVIT%2FCA01%2FRET02%2FDRAP05%2FSGAR02%2FOVQPA%2FDECA01%2FESPHSA%2FPRAMBI%2FRETP01%2FLVAREL%2FBRA05%2FRA43D%2FRDIF04%2FENPH02%2FM2019%2FSAN913%2FRCALL%2FECOMOD%2FITPK2%2FAEB00&databaseId=8afd6a52-b67b-407d-8469-fddf3d6a68ab&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv.renault.com/ImageFromBookmark?configuration=BBG%2FE2%2FLIMVIT%2FCA01%2FRET02%2FDRAP05%2FSGAR02%2FOVQPA%2FDECA01%2FESPHSA%2FPRAMBI%2FRETP01%2FLVAREL%2FBRA05%2FRA43D%2FRDIF04%2FENPH02%2FM2019%2FSAN913%2FRCALL%2FECOMOD%2FITPK2%2FAEB00&databaseId=8afd6a52-b67b-407d-8469-fddf3d6a68ab&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "NONE", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2021-03-01", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/twingo_ze.1.json000066400000000000000000000117101473671120200254230ustar00rootroot00000000000000{ "accountId": "account-id-1", "country": "IT", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777999", "status": "ACTIVE", "linkType": "USER", "garageBrand": "RENAULT", "startDate": "2020-12-11", "createdDate": "2020-12-11T22:10:56.010230Z", "lastModifiedDate": "2020-12-21T16:34:24.952880Z", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2020-12-21T16:34:24.951990Z", "lastModifiedDate": "2020-12-21T16:34:24.951990Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777999", "registrationDate": "2020-12-03", "firstRegistrationDate": "2020-12-03", "engineType": "5AL", "engineRatio": "605", "modelSCR": "", "deliveryCountry": { "code": "IT", "label": "ITALIE" }, "family": { "code": "X07", "label": "FAMILLE X07", "group": "007" }, "tcu": { "code": "AIVCT", "label": "AVEC BOITIER CONNECT AIVC", "group": "E70" }, "navigationAssistanceLevel": { "code": "SSNAV", "label": "SANS AIDE A LA NAVIGATION", "group": "408" }, "battery": { "code": "BT6AE", "label": "BATTERIE BT6AE", "group": "968" }, "radioType": { "code": "NA437", "label": "CORE NAV DAB - CLASS", "group": "425" }, "registrationCountry": { "code": "IT" }, "brand": { "label": "RENAULT" }, "model": { "code": "X071VE", "label": "TWINGO III", "group": "971" }, "gearbox": { "code": "BVEL", "label": "BOITE A VARIATEUR ELECTRIQUE", "group": "427" }, "version": { "code": "INTEA1E C1P" }, "energy": { "code": "ELEC", "label": "ELECTRIQUE", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "DLIGMA/AVSVEL/RAGAC2/CDVOL1/SCDVIT/COIN02/SKTPOU/SKTPGR/SSCCPC/SRGTLU/SSPREM/ELCTRI/SSTOST/SECAMH/FDIU1/SSESM/SRGPDB/SSCALL/FACBA1/SPRCIN/TABANA/AIVCT/PREVSE/TPRPP/TSRPP/1TON/SPERTA/PERB16/SPERTN/SPERTP/VOLNCH/SDCOLS/CHOBAT/SSCORA/SAFDEP/EV1GA1/AVARC2/1234YF/SAACC1/COFMOF/SPMIR/STANDA/X07/B07/EA3/A1/ELEC/DG/TEMP/TR4X2/DA/AFURGE/RV/ABS/CAREG1/TN/LAC/VSTLAR/CPE/RET01/PROJAB/RALU15/CEAVFX/ADAC/CCHBAM/AIRBA1/SERIE/DRA/TICUI6/HARM01/ATAR/SGAV02/FBANAR/OVRPP/BANAL/KM/PTCAV/TPRM3/VERCAP/AVREPL/SSDECA/ABLAV1/ASRESP/RDAR02/ALEVA/CACBA2/PRENFA/SOP02C/CTHAB2/VLCUIR/RETC/LVCIPE/SASURV/KTGREP/SGACHA/ITA01/APL03/BECQA1/PLAT02/VOLRH/FSTPO/ALOUCP/CMAR3P/PROJ1/SSNAV/ABPA01/NA437/BVEL/SSCAPO/SPREST/RANPAR/RDIF24/PRLOO1/PNSTRD/ISOFIA/ENPH02/HRGM01/SANFLT/SACSRA/SANACF/PREALA/CHARAP/TLRP02/SRGAR/SPRODI/SAN613/SSFAP/SAN713/CHC03/ELC1/SANCML/PRUPT1/SSRESE/SSFLEX/M2018/PHAS1/SAN913/024KWH/BT6AE/VEC009/X071VE/NB005/5AL", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGMA%2FAVSVEL%2FRAGAC2%2FCOIN02%2FSKTPOU%2FSRGTLU%2FSSPREM%2FELCTRI%2FSSTOST%2FSECAMH%2FSRGPDB%2FPERB16%2FSPERTN%2FSPERTP%2FSDCOLS%2FX07%2FB07%2FEA3%2FELEC%2FDG%2FRV%2FCAREG1%2FTN%2FVSTLAR%2FRET01%2FPROJAB%2FRALU15%2FTICUI6%2FATAR%2FSGAV02%2FOVRPP%2FKM%2FVERCAP%2FSSDECA%2FRDAR02%2FALEVA%2FVLCUIR%2FRETC%2FLVCIPE%2FSGACHA%2FAPL03%2FBECQA1%2FPLAT02%2FVOLRH%2FALOUCP%2FSSNAV%2FNA437%2FBVEL%2FSPREST%2FRANPAR%2FRDIF24%2FENPH02%2FSACSRA%2FSANACF%2FCHC03%2FPRUPT1%2FSAN913&databaseId=b43ab41f-7f61-4f96-94d7-2a58c2dd7d44&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGMA%2FAVSVEL%2FRAGAC2%2FCOIN02%2FSKTPOU%2FSRGTLU%2FSSPREM%2FELCTRI%2FSSTOST%2FSECAMH%2FSRGPDB%2FPERB16%2FSPERTN%2FSPERTP%2FSDCOLS%2FX07%2FB07%2FEA3%2FELEC%2FDG%2FRV%2FCAREG1%2FTN%2FVSTLAR%2FRET01%2FPROJAB%2FRALU15%2FTICUI6%2FATAR%2FSGAV02%2FOVRPP%2FKM%2FVERCAP%2FSSDECA%2FRDAR02%2FALEVA%2FVLCUIR%2FRETC%2FLVCIPE%2FSGACHA%2FAPL03%2FBECQA1%2FPLAT02%2FVOLRH%2FALOUCP%2FSSNAV%2FNA437%2FBVEL%2FSPREST%2FRANPAR%2FRDIF24%2FENPH02%2FSACSRA%2FSANACF%2FCHC03%2FPRUPT1%2FSAN913&databaseId=b43ab41f-7f61-4f96-94d7-2a58c2dd7d44&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "RLINK1", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2020-12-04", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/zoe_40.1.json000066400000000000000000000132741473671120200245250ustar00rootroot00000000000000{ "accountId": "account-id-1", "country": "FR", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777999", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "RENAULT", "annualMileage": 16000, "mileage": 26464, "startDate": "2017-08-07", "createdDate": "2019-05-23T21:38:16.409008Z", "lastModifiedDate": "2020-11-17T08:41:40.497400Z", "ownershipStartDate": "2017-08-01", "cancellationReason": {}, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2019-06-17T09:49:06.880627Z", "lastModifiedDate": "2019-06-17T09:49:06.880627Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777999", "registrationDate": "2017-08-01", "firstRegistrationDate": "2017-08-01", "engineType": "5AQ", "engineRatio": "601", "modelSCR": "ZOE", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "X10", "label": "FAMILLE X10", "group": "007" }, "tcu": { "code": "TCU0G2", "label": "TCU VER 0 GEN 2", "group": "E70" }, "navigationAssistanceLevel": { "code": "NAV3G5", "label": "LEVEL 3 TYPE 5 NAVIGATION", "group": "408" }, "battery": { "code": "BT4AR1", "label": "BATTERIE BT4AR1", "group": "968" }, "radioType": { "code": "RAD37A", "label": "RADIO 37A", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "X101VE", "label": "ZOE", "group": "971" }, "gearbox": { "code": "BVEL", "label": "BOITE A VARIATEUR ELECTRIQUE", "group": "427" }, "version": { "code": "INT MB 10R" }, "energy": { "code": "ELEC", "label": "ELECTRIQUE", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "SYTINC/SKTPOU/SAND41/FDIU1/SSESM/MAPSUP/SSCALL/SAND88/SAND90/SQKDRO/SDIFPA/FACBA2/PRLEX1/SSRCAR/CABDO2/TCU0G2/SWALBO/EVTEC1/STANDA/X10/B10/EA2/MB/ELEC/DG/TEMP/TR4X2/RV/ABS/CAREG/LAC/VT003/CPE/RET03/SPROJA/RALU16/CEAVRH/AIRBA1/SERIE/DRA/DRAP08/HARM02/ATAR/TERQG/SFBANA/KM/DPRPN/AVREPL/SSDECA/ASRESP/RDAR02/ALEVA/CACBL2/SOP02C/CTHAB2/TRNOR/LVAVIP/LVAREL/SASURV/KTGREP/SGSCHA/APL03/ALOUCC/CMAR3P/NAV3G5/RAD37A/BVEL/AUTAUG/RNORM/ISOFIX/EQPEUR/HRGM01/SDPCLV/TLFRAN/SPRODI/SAN613/SSAPEX/GENEV1/ELC1/SANCML/PE2012/PHAS1/SAN913/045KWH/BT4AR1/VEC153/X101VE/NBT017/5AQ", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "assetType": "PDF", "assetRole": "GUIDE", "title": "PDF Guide", "description": "", "renditions": [ { "url": "https://cdn.group.renault.com/ren/gb/myr/assets/x101ve/manual.pdf.asset.pdf/1558704861676.pdf" } ] }, { "assetType": "URL", "assetRole": "GUIDE", "title": "e-guide", "description": "", "renditions": [{ "url": "http://gb.e-guide.renault.com/eng/Zoe" }] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "10 Fundamentals about getting the best out of your electric vehicle", "description": "", "renditions": [{ "url": "39r6QEKcOM4" }] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Automatic Climate Control", "description": "", "renditions": [{ "url": "Va2FnZFo_GE" }] }, { "assetType": "URL", "assetRole": "CAR", "title": "More videos", "description": "", "renditions": [ { "url": "https://www.youtube.com/watch?v=wfpCMkK1rKI" } ] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Charging the battery", "description": "", "renditions": [{ "url": "RaEad8DjUJs" }] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Charging the battery at a station with a flap", "description": "", "renditions": [{ "url": "zJfd7fJWtr0" }] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "RLINK1", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2017-08-11", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/zoe_40.2.json000066400000000000000000000146401473671120200245240ustar00rootroot00000000000000{ "accountId": "account-id-1", "country": "GB", "vehicleLinks": [ { "brand": "RENAULT", "vin": "VF1AAAAA555777999", "status": "ACTIVE", "linkType": "OWNER", "garageBrand": "RENAULT", "startDate": "2018-08-24", "createdDate": "2019-05-22T07: 54: 16.354711Z", "lastModifiedDate": "2020-11-15T22: 35: 52.878198Z", "ownershipStartDate": "2018-08-24", "cancellationReason": {}, "preferredDealer": { "dealerId": "dealer-id-1", "brand": "RENAULT", "createdDate": "2019-05-22T07: 54: 15.962530Z", "lastModifiedDate": "2019-05-22T07: 54: 15.962530Z" }, "connectedDriver": { "role": "MAIN_DRIVER", "createdDate": "2019-06-06T10: 18: 09.801144Z", "lastModifiedDate": "2019-06-06T10: 18: 09.801144Z" }, "vehicleDetails": { "vin": "VF1AAAAA555777999", "registrationDate": "2018-03-29", "firstRegistrationDate": "2018-03-29", "engineType": "5AQ", "engineRatio": "601", "modelSCR": "ZOE", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "family": { "code": "X10", "label": "FAMILLE X10", "group": "007" }, "tcu": { "code": "TCU0G2", "label": "TCU VER 0 GEN 2", "group": "E70" }, "navigationAssistanceLevel": { "code": "SSNAV", "label": "SANS AIDE A LA NAVIGATION", "group": "408" }, "battery": { "code": "BT4AR1", "label": "BATTERIE BT4AR1", "group": "968" }, "radioType": { "code": "RAD33A", "label": "RADIO 33A", "group": "425" }, "registrationCountry": { "code": "FR" }, "brand": { "label": "RENAULT" }, "model": { "code": "X101VE", "label": "ZOE", "group": "971" }, "gearbox": { "code": "BVEL", "label": "BOITE A VARIATEUR ELECTRIQUE", "group": "427" }, "version": { "code": "LIF ME 10R" }, "energy": { "code": "ELEC", "label": "ELECTRIQUE", "group": "019" }, "registrationNumber": "REG-NUMBER", "vcd": "SYTINC/SKTPOU/SAND41/FDIU1/SSESM/SSMAP/SSCALL/SAND88/SAND90/SQKDRO/SDIFPA/FACBA2/SPRSEP/SSRCAR/CABDO2/TCU0G2/SWALBO/EVTEC1/STANDA/X10/B10/EA1/ME/ELEC/DG/TEMP/TR4X2/RV/ABS/CA/LAC/VT001/CPE/RET02/SPROJA/RTOL15/CEAVRH/AIRBA1/SERIE/DRA/DRAP07/HARM01/ATAR/OV369/SFBANA/KM/DPRPN/AVREPL/SSDECA/ASRESP/SRDPRO/SALEVA/CACBL2/SOP01C/CTHAB2/TRNOR/LVAVEL/LVARMA/SASURV/KTGREP/SGSCHA/APL03/ALOUCC/CMAR3P/SSNAV/RAD33A/BVEL/AUTAUG/RNORM/ISOFIX/EQPEUR/HRGM01/SDPCLV/TLFRAN/SPRODI/SAN613/SSAPEX/GENEV1/ELC1/SANCML/PE2012/PHAS1/SAN913/045KWH/BT4AR1/VEC174/X101VE/NBT017/5AQ", "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FSPRSEP%2FSTANDA%2FB10%2FEA1%2FDG%2FVT001%2FRET02%2FRTOL15%2FDRAP07%2FHARM01%2FOV369%2FSRDPRO%2FSALEVA%2FSOP01C%2FTRNOR%2FLVAVEL%2FLVARMA%2FRAD33A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FSPRSEP%2FSTANDA%2FB10%2FEA1%2FDG%2FVT001%2FRET02%2FRTOL15%2FDRAP07%2FHARM01%2FOV369%2FSRDPRO%2FSALEVA%2FSOP01C%2FTRNOR%2FLVAVEL%2FLVARMA%2FRAD33A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "assetType": "PDF", "assetRole": "GUIDE", "title": "PDF Guide", "description": "", "renditions": [ { "url": "https://cdn.group.renault.com/ren/gb/myr/assets/x101ve/manual.pdf.asset.pdf/1558704861735.pdf" } ] }, { "assetType": "URL", "assetRole": "GUIDE", "title": "e-guide", "description": "", "renditions": [ { "url": "http://gb.e-guide.renault.com/eng/Zoe" } ] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "10 Fundamentals about getting the best out of your electric vehicle", "description": "", "renditions": [ { "url": "39r6QEKcOM4" } ] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Automatic Climate Control", "description": "", "renditions": [ { "url": "Va2FnZFo_GE" } ] }, { "assetType": "URL", "assetRole": "CAR", "title": "More videos", "description": "", "renditions": [ { "url": "https://www.youtube.com/watch?v=wfpCMkK1rKI" } ] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Charging the battery", "description": "", "renditions": [ { "url": "RaEad8DjUJs" } ] }, { "assetType": "VIDEO", "assetRole": "CAR", "title": "Charging the battery at a station with a flap", "description": "", "renditions": [ { "url": "zJfd7fJWtr0" } ] } ], "yearsOfMaintenance": 12, "connectivityTechnology": "RLINK1", "easyConnectStore": false, "electrical": true, "rlinkStore": false, "deliveryDate": "2018-04-04", "retrievedFromDhs": false, "engineEnergyType": "ELEC", "radioCode": "1234" } } ] } renault-api-0.2.9/tests/fixtures/kamereon/vehicles/zoe_50.1.json000066400000000000000000000135211473671120200245210ustar00rootroot00000000000000{ "country": "GB", "vehicleLinks": [ { "preferredDealer": { "brand": "RENAULT", "createdDate": "2019-05-23T20:42:01.086661Z", "lastModifiedDate": "2019-05-23T20:42:01.086662Z", "dealerId": "dealer-id-1" }, "garageBrand": "RENAULT", "vehicleDetails": { "assets": [ { "assetType": "PICTURE", "renditions": [ { "resolutionType": "ONE_MYRENAULT_LARGE", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGM2%2FKITPOU%2FDANGMO%2FITPK4%2FVOLNCH%2FREACTI%2FSSAEBS%2FPRAHL%2FRRCAM%2FX10%2FB10%2FEA3%2FDG%2FCAREG%2FVSTLAR%2FRET03%2FPROJAB%2FRALU16%2FDRAP13%2F3ATRPH%2FTELNJ%2FALEVA%2FVLCUIR%2FRETRCR%2FRETC%2FLVAREL%2FSGSCHA%2FNA418%2FRDIF01%2FTL01A%2FNBT022&databaseId=a864e752-b1b9-405e-9c3e-880073e36cc9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE" }, { "resolutionType": "ONE_MYRENAULT_SMALL", "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=DLIGM2%2FKITPOU%2FDANGMO%2FITPK4%2FVOLNCH%2FREACTI%2FSSAEBS%2FPRAHL%2FRRCAM%2FX10%2FB10%2FEA3%2FDG%2FCAREG%2FVSTLAR%2FRET03%2FPROJAB%2FRALU16%2FDRAP13%2F3ATRPH%2FTELNJ%2FALEVA%2FVLCUIR%2FRETRCR%2FRETC%2FLVAREL%2FSGSCHA%2FNA418%2FRDIF01%2FTL01A%2FNBT022&databaseId=a864e752-b1b9-405e-9c3e-880073e36cc9&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2" } ] }, { "title": "PDF Guide", "description": "", "assetType": "PDF", "assetRole": "GUIDE", "renditions": [ { "url": "https://cdn.group.renault.com/ren/gb/myr/assets/x102ve/manual.pdf.asset.pdf/1558696740707.pdf" } ] }, { "title": "e-guide", "description": "", "assetType": "URL", "assetRole": "GUIDE", "renditions": [ { "url": "https://gb.e-guide.renault.com/eng/Zoe-ph2" } ] }, { "title": "All-New ZOE: Welcome to your new car", "description": "", "assetType": "VIDEO", "assetRole": "CAR", "renditions": [ { "url": "1OGwwmWHB6o" } ] }, { "title": "Renault ZOE: All you need to know", "description": "", "assetType": "VIDEO", "assetRole": "CAR", "renditions": [ { "url": "_BVH-Rd6e5I" } ] } ], "engineType": "5AQ", "registrationCountry": { "code": "FR" }, "radioType": { "group": "425", "code": "NA418", "label": " FULL NAV DAB ETH - AUDI" }, "tcu": { "group": "E70", "code": "AIVCT", "label": "AVEC BOITIER CONNECT AIVC" }, "brand": { "label": "RENAULT" }, "deliveryDate": "2020-01-22", "engineEnergyType": "ELEC", "registrationDate": "2020-01-13", "gearbox": { "group": "427", "code": "BVEL", "label": "BOITE A VARIATEUR ELECTRIQUE" }, "model": { "group": "971", "code": "X102VE", "label": "ZOE" }, "electrical": true, "energy": { "group": "019", "code": "ELEC", "label": "ELECTRIQUE" }, "navigationAssistanceLevel": { "group": "408", "code": "SAN408", "label": "CRITERE DE CONTEXTE" }, "yearsOfMaintenance": 12, "rlinkStore": false, "radioCode": "1234", "registrationNumber": "REG-NUMBER", "modelSCR": "ZOE", "easyConnectStore": false, "engineRatio": "605", "battery": { "group": "968", "code": "BT4AR1", "label": "BATTERIE BT4AR1" }, "vin": "VF1AAAAA555777999", "retrievedFromDhs": false, "vcd": "ASCOD0/DLIGM2/SSTINC/KITPOU/SKTPGR/SSCCPC/SDPSEC/FDIU2/SSMAP/SSCALL/FACBA1/DANGMO/SSRCAR/SSCABD/AIVCT/AVGSI/ITPK4/VOLNCH/REACTI/AVOSP1/SWALBO/SSDWGE/1234Y/SSAEBS/PRAHL/RRCAM/STANDA/X10/B10/EA3/MD/ELEC/DG/TEMP/TR4X2/AFURGE/RV/ABS/CAREG/LAC/VSTLAR/CPETIR/RET03/PROJAB/RALU16/CEAVRH/ADAC/AIRBA2/SERIE/DRA/DRAP13/HARM02/3ATRPH/SGAV01/BARRAB/TELNJ/SFBANA/KM/DPRPN/AVREPL/SSDECA/ABLAV/ASRESP/ALEVA/SCACBA/SOP02C/STHPLG/SKTGRV/VLCUIR/RETRCR/TRSEV1/RETC/LVAVIP/LVAREL/SASURV/KTGREP/SGSCHA/FRA01/APL03/FSTPO/ALOUC5/CMAR3P/SAN408/NA418/BVEL/AUTAUG/SPREST/RDIF01/ISOFIX/EQPEUR/HRGM01/SDPCLV/CHASTD/TL01A/SPRODI/SAN613/AIRBDE/PSMREC/ELC1/SSPTLP/SANCML/SEXTIN/PE2019/PHAS2/SAN913/THABT2/SSTYAD/SSHYB/052KWH/BT4AR1/VEC018/X102VE/NBT022/5AQ", "firstRegistrationDate": "2020-01-13", "deliveryCountry": { "code": "FR", "label": "FRANCE" }, "connectivityTechnology": "RLINK1", "family": { "group": "007", "code": "X10", "label": "FAMILLE X10" }, "version": { "code": "INT A MD 1L" } }, "status": "ACTIVE", "createdDate": "2020-08-21T16:48:00.243967Z", "cancellationReason": {}, "linkType": "OWNER", "connectedDriver": { "role": "MAIN_DRIVER", "lastModifiedDate": "2020-08-22T09:41:53.477398Z", "createdDate": "2020-08-22T09:41:53.477398Z" }, "vin": "VF1AAAAA555777999", "lastModifiedDate": "2020-11-29T22:01:21.162572Z", "brand": "RENAULT", "startDate": "2020-08-21", "ownershipStartDate": "2020-01-13", "ownershipEndDate": "2020-08-21" } ], "accountId": "account-id-1" } renault-api-0.2.9/tests/gigya/000077500000000000000000000000001473671120200162105ustar00rootroot00000000000000renault-api-0.2.9/tests/gigya/test_gigya.py000066400000000000000000000040471473671120200207260ustar00rootroot00000000000000"""Tests for Gigya API.""" import aiohttp import pytest from aioresponses import aioresponses from tests import fixtures from tests.const import TEST_GIGYA_APIKEY from tests.const import TEST_GIGYA_URL from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PASSWORD from tests.const import TEST_PERSON_ID from tests.const import TEST_USERNAME from renault_api import gigya @pytest.mark.asyncio async def test_login( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test login response.""" fixtures.inject_gigya_login(mocked_responses) response = await gigya.login( websession, TEST_GIGYA_URL, TEST_GIGYA_APIKEY, TEST_USERNAME, TEST_PASSWORD, ) assert response.get_session_cookie() == TEST_LOGIN_TOKEN @pytest.mark.asyncio async def test_login_error( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test login response.""" fixtures.inject_gigya_login_invalid(mocked_responses) with pytest.raises(gigya.exceptions.GigyaException): await gigya.login( websession, TEST_GIGYA_URL, TEST_GIGYA_APIKEY, TEST_USERNAME, TEST_PASSWORD, ) @pytest.mark.asyncio async def test_person_id( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_account_info response.""" fixtures.inject_gigya_account_info(mocked_responses) response = await gigya.get_account_info( websession, TEST_GIGYA_URL, TEST_GIGYA_APIKEY, TEST_LOGIN_TOKEN, ) assert response.get_person_id() == TEST_PERSON_ID @pytest.mark.asyncio async def test_get_jwt_token( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_jwt response.""" fixtures.inject_gigya_jwt(mocked_responses) response = await gigya.get_jwt( websession, TEST_GIGYA_URL, TEST_GIGYA_APIKEY, TEST_LOGIN_TOKEN, ) assert response.get_jwt() renault-api-0.2.9/tests/gigya/test_gigya_error.py000066400000000000000000000041051473671120200221320ustar00rootroot00000000000000"""Tests for Gigya errors.""" import pytest from tests import fixtures from renault_api.gigya import exceptions from renault_api.gigya import models from renault_api.gigya import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.GIGYA_FIXTURE_PATH}/error") ) def test_error_response(filename: str) -> None: """Test all error responses.""" response: models.GigyaResponse = fixtures.get_file_content_as_schema( filename, schemas.GigyaResponseSchema ) with pytest.raises(exceptions.GigyaResponseException): response.raise_for_error_code() def test_get_jwt_403005_response() -> None: """Test get_jwt.403005 response.""" response: models.GigyaGetJWTResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/error/get_jwt.403005.json", schemas.GigyaGetJWTResponseSchema, ) with pytest.raises(exceptions.GigyaResponseException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == 403005 assert excinfo.value.error_details == "Unauthorized user" def test_get_jwt_403013_response() -> None: """Test get_jwt.403013 response.""" response: models.GigyaGetJWTResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/error/get_jwt.403013.json", schemas.GigyaGetJWTResponseSchema, ) with pytest.raises(exceptions.GigyaResponseException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == 403013 assert excinfo.value.error_details == "Unverified user" def test_login_403042_response() -> None: """Test login.403042 response.""" response: models.GigyaLoginResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/error/login.403042.json", schemas.GigyaLoginResponseSchema, ) with pytest.raises(exceptions.InvalidCredentialsException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == 403042 assert excinfo.value.error_details == "invalid loginID or password" renault-api-0.2.9/tests/gigya/test_gigya_models.py000066400000000000000000000030261473671120200222650ustar00rootroot00000000000000"""Tests for Gigya models.""" import pytest from tests import fixtures from renault_api.gigya import models from renault_api.gigya import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(fixtures.GIGYA_FIXTURE_PATH) ) def test_valid_response(filename: str) -> None: """Test all valid responses.""" response: models.GigyaResponse = fixtures.get_file_content_as_schema( filename, schemas.GigyaResponseSchema ) response.raise_for_error_code() def test_login_response() -> None: """Test login response.""" response: models.GigyaLoginResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/login.json", schemas.GigyaLoginResponseSchema ) response.raise_for_error_code() assert response.get_session_cookie() == "sample-cookie-value" def test_get_account_info_response() -> None: """Test get_account_info response.""" response: models.GigyaGetAccountInfoResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/get_account_info.json", schemas.GigyaGetAccountInfoResponseSchema, ) response.raise_for_error_code() assert response.get_person_id() == "person-id-1" def test_get_jwt_response() -> None: """Test get_jwt response.""" response: models.GigyaGetJWTResponse = fixtures.get_file_content_as_schema( f"{fixtures.GIGYA_FIXTURE_PATH}/get_jwt.json", schemas.GigyaGetJWTResponseSchema ) response.raise_for_error_code() assert response.get_jwt() == "sample-jwt-token" renault-api-0.2.9/tests/kamereon/000077500000000000000000000000001473671120200167115ustar00rootroot00000000000000renault-api-0.2.9/tests/kamereon/__init__.py000066400000000000000000000000671473671120200210250ustar00rootroot00000000000000"""Test suite for the renault_api kamereon package.""" renault-api-0.2.9/tests/kamereon/test_kamereon.py000066400000000000000000000073651473671120200221360ustar00rootroot00000000000000"""Tests for Kamereon API.""" import aiohttp import pytest from aioresponses import aioresponses from aioresponses.core import RequestCall from yarl import URL from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_COUNTRY from tests.const import TEST_KAMEREON_APIKEY from tests.const import TEST_KAMEREON_URL from tests.const import TEST_PERSON_ID from tests.const import TEST_VIN from renault_api import kamereon from renault_api.kamereon import exceptions @pytest.mark.asyncio async def test_get_person( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_person.""" fixtures.inject_get_person(mocked_responses) person = await kamereon.get_person( websession=websession, root_url=TEST_KAMEREON_URL, api_key=TEST_KAMEREON_APIKEY, gigya_jwt=fixtures.get_jwt(), country=TEST_COUNTRY, person_id=TEST_PERSON_ID, ) assert person.accounts is not None assert len(person.accounts) == 2 @pytest.mark.asyncio async def test_get_account_vehicles( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_account_vehicles.""" fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") await kamereon.get_account_vehicles( websession=websession, root_url=TEST_KAMEREON_URL, api_key=TEST_KAMEREON_APIKEY, gigya_jwt=fixtures.get_jwt(), country=TEST_COUNTRY, account_id=TEST_ACCOUNT_ID, ) @pytest.mark.asyncio async def test_get_vehicle_data( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_vehicle_data.""" fixtures.inject_get_battery_status(mocked_responses) assert await kamereon.get_vehicle_data( websession=websession, root_url=TEST_KAMEREON_URL, api_key=TEST_KAMEREON_APIKEY, gigya_jwt=fixtures.get_jwt(), country=TEST_COUNTRY, account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, endpoint="battery-status", ) @pytest.mark.asyncio async def test_get_vehicle_data_xml_bad_gateway( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test get_vehicle_data with invalid xml data.""" fixtures.inject_get_battery_status(mocked_responses, "error/bad_gateway.html") with pytest.raises(exceptions.KamereonResponseException) as excinfo: await kamereon.get_vehicle_data( websession=websession, root_url=TEST_KAMEREON_URL, api_key=TEST_KAMEREON_APIKEY, gigya_jwt=fixtures.get_jwt(), country=TEST_COUNTRY, account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, endpoint="battery-status", ) assert excinfo.value.error_code == "Invalid JSON" assert excinfo.value.error_details assert excinfo.value.error_details.startswith( "\n \n 502 Bad Gateway" ) @pytest.mark.asyncio async def test_set_vehicle_action( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test set_vehicle_action.""" url = fixtures.inject_set_hvac_start(mocked_responses, "cancel") assert await kamereon.set_vehicle_action( websession=websession, root_url=TEST_KAMEREON_URL, api_key=TEST_KAMEREON_APIKEY, gigya_jwt=fixtures.get_jwt(), country=TEST_COUNTRY, account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, endpoint="hvac-start", attributes={"action": "cancel"}, ) expected_json = {"data": {"type": "HvacStart", "attributes": {"action": "cancel"}}} request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] renault-api-0.2.9/tests/kamereon/test_kamereon_error.py000066400000000000000000000131641473671120200233410ustar00rootroot00000000000000"""Tests for Kamereon models.""" import pytest from marshmallow.schema import Schema from tests import fixtures from renault_api.kamereon import exceptions from renault_api.kamereon import models from renault_api.kamereon import schemas RESPONSE_SCHEMAS = [ schemas.KamereonResponseSchema, schemas.KamereonPersonResponseSchema, schemas.KamereonVehiclesResponseSchema, schemas.KamereonVehicleContractsResponseSchema, schemas.KamereonVehicleDetailsResponseSchema, schemas.KamereonVehicleDataResponseSchema, ] @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/error") ) def test_vehicle_error_response(filename: str) -> None: """Test vehicle error response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( filename, schemas.KamereonVehicleDataResponseSchema ) with pytest.raises(exceptions.KamereonResponseException): response.raise_for_error_code() assert response.errors is not None def test_vehicle_error_quota_limit() -> None: """Test vehicle quota_limit response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/quota_limit.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.QuotaLimitException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.func.wired.overloaded" assert excinfo.value.error_details == "You have reached your quota limit" def test_vehicle_error_invalid_date() -> None: """Test vehicle invalid_date response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/invalid_date.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.InvalidInputException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.func.400" assert ( excinfo.value.error_details == "/data/attributes/startDateTime must be a future date" ) def test_vehicle_error_invalid_upstream() -> None: """Test vehicle invalid_upstream response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/invalid_upstream.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.InvalidUpstreamException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.tech.500" assert ( excinfo.value.error_details == "Invalid response from the upstream server (The request sent to the GDC" " is erroneous) ; 502 Bad Gateway" ) def test_vehicle_error_not_supported() -> None: """Test vehicle not_supported response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/not_supported.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.NotSupportedException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.tech.501" assert ( excinfo.value.error_details == "This feature is not technically supported by this gateway" ) def test_vehicle_error_resource_not_found() -> None: """Test vehicle resource_not_found response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/resource_not_found.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.ResourceNotFoundException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.func.wired.notFound" assert excinfo.value.error_details == "Resource not found" def test_vehicle_error_access_denied() -> None: """Test vehicle access_denied response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/access_denied.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.AccessDeniedException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.func.403" assert excinfo.value.error_details == "Access is denied for this resource" def test_vehicle_error_failed_foward() -> None: """Test vehicle access_denied response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/failed_forward.json", schemas.KamereonVehicleDataResponseSchema, ) with pytest.raises(exceptions.FailedForwardException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.tech.wired.kamereon-proxy" assert excinfo.value.error_details == "Failed to forward request to remote service." @pytest.mark.parametrize("target_schema", RESPONSE_SCHEMAS) def test_error_on_schema(target_schema: Schema) -> None: """Test vehicle access_denied response.""" response: models.KamereonResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/error/access_denied.json", target_schema, ) with pytest.raises(exceptions.AccessDeniedException) as excinfo: response.raise_for_error_code() assert excinfo.value.error_code == "err.func.403" assert excinfo.value.error_details == "Access is denied for this resource" renault-api-0.2.9/tests/kamereon/test_kamereon_person.py000066400000000000000000000017121473671120200235120ustar00rootroot00000000000000"""Tests for Kamereon models.""" from tests import fixtures from renault_api.kamereon import models from renault_api.kamereon import schemas def test_person_response() -> None: """Test person details response.""" response: models.KamereonPersonResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/person.json", schemas.KamereonPersonResponseSchema, ) response.raise_for_error_code() assert response.accounts is not None assert response.accounts[0].accountId == "account-id-1" assert response.accounts[0].accountType == "MYRENAULT" assert response.accounts[0].accountStatus == "ACTIVE" assert response.accounts[1].accountId == "account-id-2" assert response.accounts[1].accountType == "SFDC" assert response.accounts[1].accountStatus == "ACTIVE" for account in response.accounts: assert account.accountId assert account.accountId.startswith("account-id") renault-api-0.2.9/tests/kamereon/test_kamereon_vehicle_action.py000066400000000000000000000302321473671120200251570ustar00rootroot00000000000000"""Tests for Kamereon models.""" from typing import cast import pytest from tests import fixtures from renault_api.kamereon import models from renault_api.kamereon import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_action"), ) def test_vehicle_action_response(filename: str) -> None: """Test vehicle action response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( filename, schemas.KamereonVehicleDataResponseSchema ) response.raise_for_error_code() # Ensure the guid is hidden assert response.data is not None assert response.data.id == "guid" def test_vehicle_action_response_attributes() -> None: """Test vehicle action response attributes.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_action/hvac-start.start.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "action": "start", "targetTemperature": 21.0, } def test_charge_schedule_for_json() -> None: """Test for updating charge settings.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/charging-settings.multi.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() vehicle_data = cast( models.KamereonVehicleChargingSettingsData, response.get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ) # Check that for_json returns the same as the original data assert vehicle_data.schedules is not None for_json = { "schedules": [schedule.for_json() for schedule in vehicle_data.schedules] } assert for_json == { "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T00:00Z", "duration": 450}, "tuesday": {"startTime": "T00:00Z", "duration": 450}, "wednesday": {"startTime": "T00:00Z", "duration": 450}, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T00:00Z", "duration": 450}, "saturday": {"startTime": "T00:00Z", "duration": 450}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": True, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, { "id": 3, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 4, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 5, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, ] } # Test update specific day vehicle_data.update( { "id": 1, "tuesday": {"startTime": "T12:00Z", "duration": 15}, "wednesday": None, } ) # Monday kept initial values assert vehicle_data.schedules[0].monday is not None assert vehicle_data.schedules[0].monday.startTime == "T00:00Z" assert vehicle_data.schedules[0].monday.duration == 450 # Tuesday has updated values assert vehicle_data.schedules[0].tuesday is not None assert vehicle_data.schedules[0].tuesday.startTime == "T12:00Z" assert vehicle_data.schedules[0].tuesday.duration == 15 # Wednesday has values cleared assert vehicle_data.schedules[0].wednesday is None # Test update activated state assert vehicle_data.schedules[1].activated vehicle_data.update( { "id": 2, "activated": False, } ) assert not vehicle_data.schedules[1].activated # Activated flag has been updated in 'vehicle_data.update' # Refresh for_json with the updated data for_json = { # type: ignore[unreachable] "schedules": [schedule.for_json() for schedule in vehicle_data.schedules] } assert for_json == { "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T00:00Z", "duration": 450}, "tuesday": {"startTime": "T12:00Z", "duration": 15}, "wednesday": None, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T00:00Z", "duration": 450}, "saturday": {"startTime": "T00:00Z", "duration": 450}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": False, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, { "id": 3, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 4, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 5, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, ] } def test_hvac_schedule_for_json() -> None: """Test for parsing and serializing settings.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/hvac-settings.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() vehicle_data = cast( models.KamereonVehicleHvacSettingsData, response.get_attributes(schemas.KamereonVehicleHvacSettingsDataSchema), ) # verify for_json returns proper original data assert vehicle_data.schedules is not None for_json = { "schedules": [schedule.for_json() for schedule in vehicle_data.schedules] } expected_json = { "schedules": [ { "id": 1, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, }, { "id": 2, "activated": True, "monday": None, "tuesday": None, "wednesday": {"readyAtTime": "T15:15Z"}, "thursday": None, "friday": {"readyAtTime": "T15:15Z"}, "saturday": None, "sunday": None, }, ] } for i in [3, 4, 5]: expected_json["schedules"].append( { "id": i, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, } ) assert for_json == expected_json # Update days only vehicle_data.update( { "id": 1, "sunday": {"readyAtTime": "T20:30Z"}, "tuesday": {"readyAtTime": "T20:30Z"}, "thursday": None, } ) for_json = { "schedules": [schedule.for_json() for schedule in vehicle_data.schedules] } expected_json = { "schedules": [ { "id": 1, "activated": False, "monday": None, "tuesday": {"readyAtTime": "T20:30Z"}, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": {"readyAtTime": "T20:30Z"}, }, { "id": 2, "activated": True, "monday": None, "tuesday": None, "wednesday": {"readyAtTime": "T15:15Z"}, "thursday": None, "friday": {"readyAtTime": "T15:15Z"}, "saturday": None, "sunday": None, }, ] } for i in [3, 4, 5]: expected_json["schedules"].append( { "id": i, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, } ) assert for_json == expected_json # Update 'activated' only vehicle_data.update( { "id": 2, "activated": False, } ) for_json = { "schedules": [schedule.for_json() for schedule in vehicle_data.schedules] } expected_json = { "schedules": [ { "id": 1, "activated": False, "monday": None, "tuesday": {"readyAtTime": "T20:30Z"}, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": {"readyAtTime": "T20:30Z"}, }, { "id": 2, "activated": False, "monday": None, "tuesday": None, "wednesday": {"readyAtTime": "T15:15Z"}, "thursday": None, "friday": {"readyAtTime": "T15:15Z"}, "saturday": None, "sunday": None, }, ] } for i in [3, 4, 5]: expected_json["schedules"].append( { "id": i, "activated": False, "monday": None, "tuesday": None, "wednesday": None, "thursday": None, "friday": None, "saturday": None, "sunday": None, } ) assert for_json == expected_json renault-api-0.2.9/tests/kamereon/test_kamereon_vehicle_contract.py000066400000000000000000000045111473671120200255200ustar00rootroot00000000000000"""Tests for Kamereon models.""" import pytest from tests import fixtures from renault_api.kamereon import has_required_contracts from renault_api.kamereon import models from renault_api.kamereon import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_contract"), ) def test_vehicle_contract_response(filename: str) -> None: """Test vehicle contract response.""" response: models.KamereonVehicleContractsResponse = ( fixtures.get_file_content_as_wrapped_schema( filename, schemas.KamereonVehicleContractsResponseSchema, "contractList" ) ) response.raise_for_error_code() assert response.contractList is not None for contract in response.contractList: if contract.contractId: assert contract.contractId.startswith( "AB1234" ), "Ensure contractId is obfuscated." def test_has_required_contract_1() -> None: """Test has_required_contract.""" response: models.KamereonVehicleContractsResponse = ( fixtures.get_file_content_as_wrapped_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_contract/fr_FR.1.json", schemas.KamereonVehicleContractsResponseSchema, "contractList", ) ) response.raise_for_error_code() assert response.contractList is not None assert has_required_contracts(response.contractList, "battery-status") def test_has_required_contract_2() -> None: """Test has_required_contract.""" response: models.KamereonVehicleContractsResponse = ( fixtures.get_file_content_as_wrapped_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_contract/fr_FR.2.json", schemas.KamereonVehicleContractsResponseSchema, "contractList", ) ) response.raise_for_error_code() assert response.contractList is not None assert has_required_contracts(response.contractList, "battery-status") assert has_required_contracts(response.contractList, "charge-mode") assert has_required_contracts(response.contractList, "charging-settings") assert has_required_contracts(response.contractList, "hvac-history") assert has_required_contracts(response.contractList, "hvac-sessions") assert has_required_contracts(response.contractList, "hvac-status") renault-api-0.2.9/tests/kamereon/test_kamereon_vehicle_data.py000066400000000000000000000507011473671120200246160ustar00rootroot00000000000000"""Tests for Kamereon models.""" from typing import cast import pytest from tests import fixtures from renault_api.kamereon import enums from renault_api.kamereon import models from renault_api.kamereon import schemas from renault_api.kamereon.helpers import DAYS_OF_WEEK @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data"), ) def test_vehicle_data_response(filename: str) -> None: """Test vehicle data response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( filename, schemas.KamereonVehicleDataResponseSchema ) response.raise_for_error_code() # Ensure the VIN is hidden assert response.data is not None assert response.data.id is not None assert response.data.id.startswith(("VF1AAAA", "UU1AAAA")) def test_battery_status_1() -> None: """Test vehicle data for battery-status.1.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/battery-status.1.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "timestamp": "2020-11-17T09:06:48+01:00", "batteryLevel": 50, "batteryAutonomy": 128, "batteryCapacity": 0, "batteryAvailableEnergy": 0, "plugStatus": 0, "chargingStatus": -1.0, } vehicle_data = cast( models.KamereonVehicleBatteryStatusData, response.get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), ) assert vehicle_data.timestamp == "2020-11-17T09:06:48+01:00" assert vehicle_data.batteryLevel == 50 assert vehicle_data.batteryTemperature is None assert vehicle_data.batteryAutonomy == 128 assert vehicle_data.batteryCapacity == 0 assert vehicle_data.batteryAvailableEnergy == 0 assert vehicle_data.plugStatus == 0 assert vehicle_data.chargingStatus == -1.0 assert vehicle_data.chargingRemainingTime is None assert vehicle_data.chargingInstantaneousPower is None assert vehicle_data.get_plug_status() == enums.PlugState.UNPLUGGED assert vehicle_data.get_charging_status() == enums.ChargeState.CHARGE_ERROR def test_battery_status_2() -> None: """Test vehicle data for battery-status.2.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/battery-status.2.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "timestamp": "2020-01-12T21:40:16Z", "batteryLevel": 60, "batteryTemperature": 20, "batteryAutonomy": 141, "batteryCapacity": 0, "batteryAvailableEnergy": 31, "plugStatus": 1, "chargingStatus": 1.0, "chargingRemainingTime": 145, "chargingInstantaneousPower": 27.0, } vehicle_data = cast( models.KamereonVehicleBatteryStatusData, response.get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), ) assert vehicle_data.timestamp == "2020-01-12T21:40:16Z" assert vehicle_data.batteryLevel == 60 assert vehicle_data.batteryTemperature == 20 assert vehicle_data.batteryAutonomy == 141 assert vehicle_data.batteryCapacity == 0 assert vehicle_data.batteryAvailableEnergy == 31 assert vehicle_data.plugStatus == 1 assert vehicle_data.chargingStatus == 1.0 assert vehicle_data.chargingRemainingTime == 145 assert vehicle_data.chargingInstantaneousPower == 27.0 assert vehicle_data.get_plug_status() == enums.PlugState.PLUGGED assert vehicle_data.get_charging_status() == enums.ChargeState.CHARGE_IN_PROGRESS def test_tyre_pressure() -> None: """Test vehicle data for tyre-pressure.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/tyre-pressure.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "flPressure": 2460, "frPressure": 2730, "rlPressure": 2790, "rrPressure": 2790, "flStatus": 0, "frStatus": 0, "rlStatus": 0, "rrStatus": 0, } def test_cockpit_zoe() -> None: """Test vehicle data for cockpit.zoe.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/cockpit.zoe.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == {"totalMileage": 49114.27} vehicle_data = cast( models.KamereonVehicleCockpitData, response.get_attributes(schemas.KamereonVehicleCockpitDataSchema), ) assert vehicle_data.totalMileage == 49114.27 assert vehicle_data.fuelAutonomy is None assert vehicle_data.fuelQuantity is None def test_cockpit_captur_ii() -> None: """Test vehicle data for cockpit.captur_ii.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/cockpit.captur_ii.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "fuelAutonomy": 35.0, "fuelQuantity": 3.0, "totalMileage": 5566.78, } vehicle_data = cast( models.KamereonVehicleCockpitData, response.get_attributes(schemas.KamereonVehicleCockpitDataSchema), ) assert vehicle_data.totalMileage == 5566.78 assert vehicle_data.fuelAutonomy == 35.0 assert vehicle_data.fuelQuantity == 3.0 def test_charging_settings_single() -> None: """Test vehicle data for charging-settings.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/charging-settings.single.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "mode": "scheduled", "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T12:00Z", "duration": 15}, "tuesday": {"startTime": "T04:30Z", "duration": 420}, "wednesday": {"startTime": "T22:30Z", "duration": 420}, "thursday": {"startTime": "T22:00Z", "duration": 420}, "friday": {"startTime": "T12:15Z", "duration": 15}, "saturday": {"startTime": "T12:30Z", "duration": 30}, "sunday": {"startTime": "T12:45Z", "duration": 45}, } ], } vehicle_data = cast( models.KamereonVehicleChargingSettingsData, response.get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ) assert vehicle_data.mode == "scheduled" assert vehicle_data.schedules is not None assert len(vehicle_data.schedules) == 1 schedule_data = vehicle_data.schedules[0] assert schedule_data.id == 1 assert schedule_data.activated is True assert schedule_data.monday is not None assert schedule_data.monday.startTime == "T12:00Z" assert schedule_data.monday.duration == 15 assert schedule_data.tuesday is not None assert schedule_data.tuesday.startTime == "T04:30Z" assert schedule_data.tuesday.duration == 420 assert schedule_data.wednesday is not None assert schedule_data.wednesday.startTime == "T22:30Z" assert schedule_data.wednesday.duration == 420 assert schedule_data.thursday is not None assert schedule_data.thursday.startTime == "T22:00Z" assert schedule_data.thursday.duration == 420 assert schedule_data.friday is not None assert schedule_data.friday.startTime == "T12:15Z" assert schedule_data.friday.duration == 15 assert schedule_data.saturday is not None assert schedule_data.saturday.startTime == "T12:30Z" assert schedule_data.saturday.duration == 30 assert schedule_data.sunday is not None assert schedule_data.sunday.startTime == "T12:45Z" assert schedule_data.sunday.duration == 45 def test_charging_settings_multi() -> None: """Test vehicle data for charging-settings.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/charging-settings.multi.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "mode": "scheduled", "schedules": [ { "id": 1, "activated": True, "monday": {"startTime": "T00:00Z", "duration": 450}, "tuesday": {"startTime": "T00:00Z", "duration": 450}, "wednesday": {"startTime": "T00:00Z", "duration": 450}, "thursday": {"startTime": "T00:00Z", "duration": 450}, "friday": {"startTime": "T00:00Z", "duration": 450}, "saturday": {"startTime": "T00:00Z", "duration": 450}, "sunday": {"startTime": "T00:00Z", "duration": 450}, }, { "id": 2, "activated": True, "monday": {"startTime": "T23:30Z", "duration": 15}, "tuesday": {"startTime": "T23:30Z", "duration": 15}, "wednesday": {"startTime": "T23:30Z", "duration": 15}, "thursday": {"startTime": "T23:30Z", "duration": 15}, "friday": {"startTime": "T23:30Z", "duration": 15}, "saturday": {"startTime": "T23:30Z", "duration": 15}, "sunday": {"startTime": "T23:30Z", "duration": 15}, }, {"id": 3, "activated": False}, {"id": 4, "activated": False}, {"id": 5, "activated": False}, ], } vehicle_data = cast( models.KamereonVehicleChargingSettingsData, response.get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ) assert vehicle_data.mode == "scheduled" assert vehicle_data.schedules is not None assert len(vehicle_data.schedules) == 5 schedule_data = vehicle_data.schedules[0] assert schedule_data.id == 1 assert schedule_data.activated is True assert schedule_data.monday is not None assert schedule_data.monday.startTime == "T00:00Z" assert schedule_data.monday.duration == 450 assert schedule_data.tuesday is not None assert schedule_data.tuesday.startTime == "T00:00Z" assert schedule_data.tuesday.duration == 450 assert schedule_data.wednesday is not None assert schedule_data.wednesday.startTime == "T00:00Z" assert schedule_data.wednesday.duration == 450 assert schedule_data.thursday is not None assert schedule_data.thursday.startTime == "T00:00Z" assert schedule_data.thursday.duration == 450 assert schedule_data.friday is not None assert schedule_data.friday.startTime == "T00:00Z" assert schedule_data.friday.duration == 450 assert schedule_data.saturday is not None assert schedule_data.saturday.startTime == "T00:00Z" assert schedule_data.saturday.duration == 450 assert schedule_data.sunday is not None assert schedule_data.sunday.startTime == "T00:00Z" assert schedule_data.sunday.duration == 450 def test_location_v1() -> None: """Test vehicle data for location.1.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/location.1.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "gpsLatitude": 48.1234567, "gpsLongitude": 11.1234567, "lastUpdateTime": "2020-02-18T16:58:38Z", } vehicle_data = cast( models.KamereonVehicleLocationData, response.get_attributes(schemas.KamereonVehicleLocationDataSchema), ) assert vehicle_data.gpsLatitude == 48.1234567 assert vehicle_data.gpsLongitude == 11.1234567 assert vehicle_data.lastUpdateTime == "2020-02-18T16:58:38Z" def test_location_v2() -> None: """Test vehicle data for location.2.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/location.2.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "gpsDirection": None, "gpsLatitude": 48.1234567, "gpsLongitude": 11.1234567, "lastUpdateTime": "2020-02-18T16:58:38Z", } vehicle_data = cast( models.KamereonVehicleLocationData, response.get_attributes(schemas.KamereonVehicleLocationDataSchema), ) assert vehicle_data.gpsLatitude == 48.1234567 assert vehicle_data.gpsLongitude == 11.1234567 assert vehicle_data.lastUpdateTime == "2020-02-18T16:58:38Z" def test_lock_status_locked() -> None: """Test lock-status for lock-status.1.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/lock-status.1.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "lockStatus": "locked", "doorStatusRearLeft": "closed", "doorStatusRearRight": "closed", "doorStatusDriver": "closed", "doorStatusPassenger": "closed", "hatchStatus": "closed", "lastUpdateTime": "2022-02-02T13:51:13Z", } vehicle_data = cast( models.KamereonVehicleLockStatusData, response.get_attributes(schemas.KamereonVehicleLockStatusDataSchema), ) assert vehicle_data.lockStatus == "locked" assert vehicle_data.doorStatusRearLeft == "closed" assert vehicle_data.doorStatusRearRight == "closed" assert vehicle_data.doorStatusDriver == "closed" assert vehicle_data.doorStatusPassenger == "closed" assert vehicle_data.hatchStatus == "closed" assert vehicle_data.lastUpdateTime == "2022-02-02T13:51:13Z" def test_lock_status_unlocked() -> None: """Test lock-status for lock-status.2.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/lock-status.2.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "lockStatus": "unlocked", "doorStatusRearLeft": "closed", "doorStatusRearRight": "closed", "doorStatusDriver": "closed", "doorStatusPassenger": "closed", "hatchStatus": "closed", "lastUpdateTime": "2022-02-02T13:51:13Z", } vehicle_data = cast( models.KamereonVehicleLockStatusData, response.get_attributes(schemas.KamereonVehicleLockStatusDataSchema), ) assert vehicle_data.lockStatus == "unlocked" assert vehicle_data.doorStatusRearLeft == "closed" assert vehicle_data.doorStatusRearRight == "closed" assert vehicle_data.doorStatusDriver == "closed" assert vehicle_data.doorStatusPassenger == "closed" assert vehicle_data.hatchStatus == "closed" assert vehicle_data.lastUpdateTime == "2022-02-02T13:51:13Z" def test_res_state_stopped() -> None: """Test res-state for res-state.1.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/res-state.1.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "details": "Stopped, ready for RES", "code": "10", } vehicle_data = cast( models.KamereonVehicleResStateData, response.get_attributes(schemas.KamereonVehicleResStateDataSchema), ) assert vehicle_data.details == "Stopped, ready for RES" assert vehicle_data.code == "10" def test_res_state_running() -> None: """Test res-state for res-state.2.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/res-state.2.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == { "details": "Running", "code": "42", } vehicle_data = cast( models.KamereonVehicleResStateData, response.get_attributes(schemas.KamereonVehicleResStateDataSchema), ) assert vehicle_data.details == "Running" assert vehicle_data.code == "42" def test_charge_mode() -> None: """Test vehicle data for charge-mode.json.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/charge-mode.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data["attributes"] == {"chargeMode": "always"} vehicle_data = cast( models.KamereonVehicleChargeModeData, response.get_attributes(schemas.KamereonVehicleChargeModeDataSchema), ) assert vehicle_data.chargeMode == "always" def test_hvac_settings_mode() -> None: """Test vehicle data with hvac settings for mode.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/hvac-settings.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() vehicle_data = cast( models.KamereonVehicleHvacSettingsData, response.get_attributes(schemas.KamereonVehicleHvacSettingsDataSchema), ) assert vehicle_data.mode == "scheduled" def test_hvac_settings_schedule() -> None: """Test vehicle data with hvac schedule entries.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/hvac-settings.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() vehicle_data = cast( models.KamereonVehicleHvacSettingsData, response.get_attributes(schemas.KamereonVehicleHvacSettingsDataSchema), ) assert vehicle_data.mode == "scheduled" assert vehicle_data.schedules is not None assert vehicle_data.schedules[1].id == 2 assert vehicle_data.schedules[1].wednesday is not None assert vehicle_data.schedules[1].wednesday.readyAtTime == "T15:15Z" assert vehicle_data.schedules[1].friday is not None assert vehicle_data.schedules[1].friday.readyAtTime == "T15:15Z" assert vehicle_data.schedules[1].monday is None for i in [0, 2, 3, 4]: assert vehicle_data.schedules[i].id == i + 1 for day in DAYS_OF_WEEK: assert vehicle_data.schedules[i].__dict__.get(day) is None def test_no_data() -> None: """Test missing vehicle data.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_data/no_data.json", schemas.KamereonVehicleDataResponseSchema, ) response.raise_for_error_code() assert response.data is not None assert response.data.raw_data == {"id": "VF1AAAA", "type": "ChargeMode"} vehicle_data = cast( models.KamereonVehicleCockpitData, response.get_attributes(schemas.KamereonVehicleCockpitDataSchema), ) assert vehicle_data.totalMileage is None assert vehicle_data.fuelAutonomy is None assert vehicle_data.fuelQuantity is None renault-api-0.2.9/tests/kamereon/test_kamereon_vehicle_details.py000066400000000000000000000037401473671120200253330ustar00rootroot00000000000000"""Tests for Kamereon models.""" from copy import deepcopy from os import path import pytest from tests import fixtures from tests.const import TO_REDACT from .test_kamereon_vehicles import EXPECTED_SPECS from renault_api.kamereon import models from renault_api.kamereon import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_details"), ) def test_vehicle_details_response(filename: str) -> None: """Test vehicle_details response.""" vehicle_details: models.KamereonVehicleDetailsResponse = ( fixtures.get_file_content_as_schema( filename, schemas.KamereonVehicleDetailsResponseSchema ) ) vehicle_details.raise_for_error_code() fixtures.ensure_redacted(vehicle_details.raw_data, [*TO_REDACT, "id"]) if path.basename(filename) in EXPECTED_SPECS: expected_specs = deepcopy(EXPECTED_SPECS[path.basename(filename)]) # It seems that at least on zoe_40.1.json the images # don't match in the details fixture file del expected_specs["get_picture_large"] del expected_specs["get_picture_small"] power_in_watts = vehicle_details.reports_charging_power_in_watts() generated_specs = { "reports_charging_power_in_watts": power_in_watts, "uses_electricity": vehicle_details.uses_electricity(), "uses_fuel": vehicle_details.uses_fuel(), "supports-hvac-status": vehicle_details.supports_endpoint("hvac-status"), "supports-location": vehicle_details.supports_endpoint("location"), "charge-uses-kcm": vehicle_details.controls_action_via_kcm("charge"), "get_brand_label": vehicle_details.get_brand_label(), "get_energy_code": vehicle_details.get_energy_code(), "get_model_code": vehicle_details.get_model_code(), "get_model_label": vehicle_details.get_model_label(), } assert expected_specs == generated_specs renault-api-0.2.9/tests/kamereon/test_kamereon_vehicle_gateway.py000066400000000000000000000037701473671120200253520ustar00rootroot00000000000000"""Tests for Kamereon models.""" import os from copy import deepcopy from typing import cast import pytest from tests import fixtures from tests.const import TO_REDACT from .test_kamereon_vehicles import EXPECTED_SPECS from renault_api.kamereon import models from renault_api.kamereon import schemas @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicle_gateway"), ) def test_vehicles_response(filename: str) -> None: """Test vehicles list response.""" response: models.KamereonVehicleDataResponse = fixtures.get_file_content_as_schema( filename, schemas.KamereonVehicleDataResponseSchema ) response.raise_for_error_code() fixtures.ensure_redacted(response.raw_data, [*TO_REDACT, "id"]) assert response.data assert response.data.attributes fixtures.ensure_redacted(response.data.attributes) vehicle_data = cast( models.KamereonVehicleCarAdapterData, response.get_attributes(schemas.KamereonVehicleCarAdapterDataSchema), ) if os.path.basename(filename) in EXPECTED_SPECS: expected_specs = deepcopy(EXPECTED_SPECS[os.path.basename(filename)]) del expected_specs["get_brand_label"] del expected_specs["get_energy_code"] del expected_specs["get_model_code"] del expected_specs["get_model_label"] del expected_specs["get_picture_large"] del expected_specs["get_picture_small"] power_in_watts = vehicle_data.reports_charging_power_in_watts() generated_specs = { "reports_charging_power_in_watts": power_in_watts, "uses_electricity": vehicle_data.uses_electricity(), "uses_fuel": vehicle_data.uses_fuel(), "supports-hvac-status": vehicle_data.supports_endpoint("hvac-status"), "supports-location": vehicle_data.supports_endpoint("location"), "charge-uses-kcm": vehicle_data.controls_action_via_kcm("charge"), } assert expected_specs == generated_specs renault-api-0.2.9/tests/kamereon/test_kamereon_vehicles.py000066400000000000000000000045151473671120200240120ustar00rootroot00000000000000"""Tests for Kamereon models.""" import json import os import pytest from tests import fixtures from renault_api.kamereon import models from renault_api.kamereon import schemas from renault_api.kamereon.enums import AssetPictureSize EXPECTED_SPECS = json.loads( fixtures.get_file_content(f"{fixtures.KAMEREON_FIXTURE_PATH}/expected_specs.json") ) @pytest.mark.parametrize( "filename", fixtures.get_json_files(f"{fixtures.KAMEREON_FIXTURE_PATH}/vehicles") ) def test_vehicles_response(filename: str) -> None: """Test vehicles list response.""" response: models.KamereonVehiclesResponse = fixtures.get_file_content_as_schema( filename, schemas.KamereonVehiclesResponseSchema ) response.raise_for_error_code() fixtures.ensure_redacted(response.raw_data) assert response.vehicleLinks is not None for vehicle_link in response.vehicleLinks: fixtures.ensure_redacted(vehicle_link.raw_data) vehicle_details = vehicle_link.vehicleDetails assert vehicle_details fixtures.ensure_redacted(vehicle_details.raw_data) if os.path.basename(filename) in EXPECTED_SPECS: power_in_watts = vehicle_details.reports_charging_power_in_watts() generated_specs = { "get_brand_label": vehicle_details.get_brand_label(), "get_energy_code": vehicle_details.get_energy_code(), "get_model_code": vehicle_details.get_model_code(), "get_model_label": vehicle_details.get_model_label(), "get_picture_small": vehicle_details.get_picture( AssetPictureSize.SMALL ), "get_picture_large": vehicle_details.get_picture( AssetPictureSize.LARGE ), "reports_charging_power_in_watts": power_in_watts, "uses_electricity": vehicle_details.uses_electricity(), "uses_fuel": vehicle_details.uses_fuel(), "supports-hvac-status": vehicle_details.supports_endpoint( "hvac-status" ), "supports-location": vehicle_details.supports_endpoint("location"), "charge-uses-kcm": vehicle_details.controls_action_via_kcm("charge"), } assert EXPECTED_SPECS[os.path.basename(filename)] == generated_specs renault-api-0.2.9/tests/ruff.toml000066400000000000000000000003771473671120200167560ustar00rootroot00000000000000# This extend our general Ruff rules specifically for tests extend = "../pyproject.toml" [lint] extend-ignore = [ "PLR2004", # Magic value used in comparison, consider replacing ] [lint.isort] known-first-party = [ "renault-api", "tests", ] renault-api-0.2.9/tests/test_api_keys.py000066400000000000000000000056451473671120200203370ustar00rootroot00000000000000"""Test cases for the Renault client API keys.""" import pytest from aiohttp import ClientSession from aioresponses import aioresponses from renault_api.const import AVAILABLE_LOCALES from renault_api.const import CONF_GIGYA_APIKEY from renault_api.const import CONF_GIGYA_URL from renault_api.const import CONF_KAMEREON_APIKEY from renault_api.const import CONF_KAMEREON_URL from renault_api.const import LOCALE_BASE_URL from renault_api.exceptions import RenaultException from renault_api.helpers import get_api_keys @pytest.mark.asyncio @pytest.mark.parametrize("locale", AVAILABLE_LOCALES.keys()) async def test_available_locales(locale: str) -> None: """Ensure all items AVAILABLE_LOCALES have correct data.""" expected_api_keys = AVAILABLE_LOCALES[locale] api_keys = await get_api_keys(locale) assert api_keys == expected_api_keys for key in [ CONF_GIGYA_APIKEY, CONF_GIGYA_URL, CONF_KAMEREON_APIKEY, CONF_KAMEREON_URL, ]: assert api_keys[key] @pytest.mark.asyncio async def test_missing_aiohttp_session() -> None: """Ensure failure to unknown locale if aiohttp_session is not set.""" locale = "invalid" with pytest.raises(RenaultException) as excinfo: await get_api_keys(locale) assert "aiohttp_session is not set." in str(excinfo) @pytest.mark.asyncio @pytest.mark.parametrize("locale", AVAILABLE_LOCALES.keys()) @pytest.mark.skip(reason="Makes real calls to Renault servers") async def test_preload_force_api_keys(websession: ClientSession, locale: str) -> None: """Ensure is able to parse a valid locale from Renault servers.""" expected_api_keys = AVAILABLE_LOCALES[locale] api_keys = await get_api_keys(locale, True, websession) assert api_keys == expected_api_keys @pytest.mark.asyncio @pytest.mark.skip("API keys are out of date.") async def test_preload_unknown_api_keys( websession: ClientSession, mocked_responses: aioresponses ) -> None: """Ensure is able to parse a known known.""" expected_api_keys = AVAILABLE_LOCALES["fr_FR"] fake_locale = "invalid" fake_url = f"{LOCALE_BASE_URL}/configuration/android/config_{fake_locale}.json" with open("tests/fixtures/config_sample.txt") as f: fake_body = f.read() mocked_responses.get(fake_url, status=200, body=fake_body) api_keys = await get_api_keys(fake_locale, websession=websession) assert api_keys == expected_api_keys @pytest.mark.asyncio async def test_preload_invalid_api_keys( websession: ClientSession, mocked_responses: aioresponses ) -> None: """Ensure is able to parse an invalid locale.""" fake_locale = "fake" fake_url = f"{LOCALE_BASE_URL}/configuration/android/config_{fake_locale}.json" mocked_responses.get(fake_url, status=404) with pytest.raises(RenaultException) as excinfo: await get_api_keys(fake_locale, websession=websession) assert "Locale not found on Renault server" in str(excinfo) renault-api-0.2.9/tests/test_credential.py000066400000000000000000000014431473671120200206350ustar00rootroot00000000000000"""Tests for Credential models.""" import time from unittest import mock from tests.fixtures import get_jwt from renault_api.credential import Credential from renault_api.credential import JWTCredential TEST_VALUE = "test-value" def test_simple_credential() -> None: """Test for Credential class.""" credential = Credential(TEST_VALUE) assert credential.value == TEST_VALUE assert not credential.has_expired() def test_jwt() -> None: """Test for Credential class.""" jwt_token = get_jwt() credential = JWTCredential(jwt_token) assert credential.value == jwt_token assert not credential.has_expired() expired_time = time.time() + 3600 with mock.patch("time.time", mock.MagicMock(return_value=expired_time)): assert credential.has_expired() renault-api-0.2.9/tests/test_credential_store.py000066400000000000000000000207731473671120200220600ustar00rootroot00000000000000"""Test cases for the Gigya client.""" import tempfile import time from datetime import timedelta from shutil import copyfile from unittest import mock import pytest from typeguard import suppress_type_checks from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PERSON_ID from tests.fixtures import get_jwt from renault_api.credential import Credential from renault_api.credential import JWTCredential from renault_api.credential_store import CredentialStore from renault_api.credential_store import FileCredentialStore from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.gigya import GIGYA_PERSON_ID def get_logged_in_credential_store() -> CredentialStore: """Get credential store initialised with Gigya credentials.""" credential_store = CredentialStore() credential_store[GIGYA_LOGIN_TOKEN] = Credential(TEST_LOGIN_TOKEN) credential_store[GIGYA_PERSON_ID] = Credential(TEST_PERSON_ID) credential_store[GIGYA_JWT] = JWTCredential(get_jwt()) return credential_store def test_invalid_credential() -> None: """Test set with invalid types.""" credential_store = CredentialStore() test_key = "test" with suppress_type_checks(), pytest.raises(TypeError): credential_store[test_key] = test_key # type:ignore test_value = Credential("test_value") with suppress_type_checks(), pytest.raises(TypeError): credential_store[test_value] = test_value # type:ignore def test_simple_credential() -> None: """Test get/set with simple credential.""" credential_store = CredentialStore() test_key = "test" # Try to get value from empty store assert test_key not in credential_store assert not credential_store.get(test_key) with pytest.raises(KeyError): credential_store[test_key] # Set value test_value = Credential("test_value") credential_store[test_key] = test_value # Try to get values from filled store assert test_key in credential_store assert credential_store.get(test_key) == test_value assert credential_store[test_key] == test_value # Delete value del credential_store[test_key] # Try to get values from filled store assert test_key not in credential_store assert credential_store.get(test_key) is None assert credential_store.get_value(test_key) is None def test_jwt_credential() -> None: """Test get/set with jwt credential.""" credential_store = CredentialStore() test_key = "test" # Try to get value from empty store assert test_key not in credential_store assert not credential_store.get(test_key) with pytest.raises(KeyError): credential_store[test_key] # Set value test_value = JWTCredential(get_jwt()) credential_store[test_key] = test_value # Try to get values from filled store assert test_key in credential_store assert credential_store.get(test_key) == test_value assert credential_store.get_value(test_key) == test_value.value assert credential_store[test_key] == test_value # Try again with expired expired_time = time.time() + 3600 with mock.patch("time.time", mock.MagicMock(return_value=expired_time)): assert test_key not in credential_store assert credential_store.get_value(test_key) is None assert not credential_store.get(test_key) with pytest.raises(KeyError): credential_store[test_key] def test_clear() -> None: """Test clearance of credential store.""" credential_store = CredentialStore() test_key = "test" test_permanent_key = "locale" # Try to get value from empty store assert test_key not in credential_store assert test_permanent_key not in credential_store # Set value test_value = Credential("test_value") test_pemanent_value = Credential("test_locale") credential_store[test_key] = test_value credential_store[test_permanent_key] = test_pemanent_value # Try to get values from filled store assert test_key in credential_store assert credential_store[test_key] == test_value assert test_permanent_key in credential_store assert credential_store[test_permanent_key] == test_pemanent_value # Clear the store credential_store.clear() # Try to get values from filled store assert test_key not in credential_store assert test_permanent_key in credential_store assert credential_store[test_permanent_key] == test_pemanent_value def test_clear_keys() -> None: """Test clearance of specified keys from credential store.""" credential_store = CredentialStore() test_key = "test" test_permanent_key = "locale" # Try to get value from empty store assert test_key not in credential_store assert test_permanent_key not in credential_store # Set value test_value = Credential("test_value") test_pemanent_value = Credential("test_locale") credential_store[test_key] = test_value credential_store[test_permanent_key] = test_pemanent_value # Try to get values from filled store assert test_key in credential_store assert credential_store[test_key] == test_value assert test_permanent_key in credential_store assert credential_store[test_permanent_key] == test_pemanent_value # Clear the store credential_store.clear_keys([test_permanent_key]) # Try to get values from filled store assert test_key in credential_store assert test_permanent_key not in credential_store def test_file_store() -> None: """Test file credential store.""" with tempfile.TemporaryDirectory() as tmpdirname: # Prepare initial store old_filename = f"{tmpdirname}/.credentials/renault-api.json" old_credential_store = FileCredentialStore(old_filename) test_key = "key" test_value = Credential("value") test_jwt_key = "gigya_jwt" test_jwt_value = JWTCredential(get_jwt()) old_credential_store[test_key] = test_value old_credential_store[test_jwt_key] = test_jwt_value assert test_key in old_credential_store assert old_credential_store.get(test_key) == test_value assert old_credential_store[test_key] == test_value assert test_jwt_key in old_credential_store assert old_credential_store.get(test_jwt_key) == test_jwt_value assert old_credential_store[test_jwt_key] == test_jwt_value # Copy the data into new file new_filename = f"{tmpdirname}/.credentials/renault-api-copy.json" copyfile(old_filename, new_filename) new_credential_store = FileCredentialStore(new_filename) # Check that the data is in the new store assert test_key in new_credential_store assert new_credential_store.get(test_key) == test_value assert new_credential_store[test_key] == test_value assert test_jwt_key in new_credential_store assert new_credential_store.get(test_jwt_key) == test_jwt_value assert new_credential_store[test_jwt_key] == test_jwt_value def test_file_store_expired_token() -> None: """Test loading expired token from credential store.""" with tempfile.TemporaryDirectory() as tmpdirname: # Prepare initial store expired_token = get_jwt(timedelta(seconds=-900)) old_filename = f"{tmpdirname}/.credentials/renault-api.json" old_credential_store = FileCredentialStore(old_filename) test_key = "key" test_value = Credential("value") test_jwt_key = "gigya_jwt" test_jwt_value = Credential(expired_token) # bypass JWTCredential old_credential_store[test_key] = test_value old_credential_store[test_jwt_key] = test_jwt_value assert test_key in old_credential_store assert old_credential_store.get(test_key) == test_value assert old_credential_store[test_key] == test_value assert test_jwt_key in old_credential_store assert old_credential_store.get(test_jwt_key) == test_jwt_value assert old_credential_store[test_jwt_key] == test_jwt_value # Copy the data into new file new_filename = f"{tmpdirname}/.credentials/renault-api-copy.json" copyfile(old_filename, new_filename) new_credential_store = FileCredentialStore(new_filename) # Check that the data is in the new store assert test_key in new_credential_store assert new_credential_store.get(test_key) == test_value assert new_credential_store[test_key] == test_value # Except the JWT token which was rejected on load assert test_jwt_key not in new_credential_store renault-api-0.2.9/tests/test_renault_account.py000066400000000000000000000036071473671120200217150ustar00rootroot00000000000000"""Test cases for the Renault client API keys.""" import aiohttp import pytest from aioresponses import aioresponses from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_COUNTRY from tests.const import TEST_LOCALE_DETAILS from tests.const import TEST_VIN from tests.test_credential_store import get_logged_in_credential_store from tests.test_renault_session import get_logged_in_session from renault_api.renault_account import RenaultAccount @pytest.fixture def account(websession: aiohttp.ClientSession) -> RenaultAccount: """Fixture for testing RenaultAccount.""" return RenaultAccount( account_id=TEST_ACCOUNT_ID, session=get_logged_in_session(websession), ) def tests_init(websession: aiohttp.ClientSession) -> None: """Test RenaultAccount initialisation.""" assert RenaultAccount( account_id=TEST_ACCOUNT_ID, session=get_logged_in_session(websession), ) assert RenaultAccount( account_id=TEST_ACCOUNT_ID, websession=websession, country=TEST_COUNTRY, locale_details=TEST_LOCALE_DETAILS, credential_store=get_logged_in_credential_store(), ) @pytest.mark.asyncio async def test_get_vehicles( account: RenaultAccount, mocked_responses: aioresponses ) -> None: """Test get_vehicles.""" fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") await account.get_vehicles() @pytest.mark.asyncio async def test_get_api_vehicles( account: RenaultAccount, mocked_responses: aioresponses ) -> None: """Test get_api_vehicles.""" fixtures.inject_get_vehicles(mocked_responses, "zoe_40.1.json") await account.get_api_vehicles() @pytest.mark.asyncio async def test_get_api_vehicle(account: RenaultAccount) -> None: """Test get_api_vehicle.""" vehicle = await account.get_api_vehicle(TEST_VIN) assert vehicle._vin == TEST_VIN renault-api-0.2.9/tests/test_renault_client.py000066400000000000000000000035161473671120200215360ustar00rootroot00000000000000"""Test cases for the Renault client API keys.""" import aiohttp import pytest from aioresponses import aioresponses from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_COUNTRY from tests.const import TEST_LOCALE_DETAILS from tests.test_credential_store import get_logged_in_credential_store from tests.test_renault_session import get_logged_in_session from renault_api.renault_client import RenaultClient @pytest.fixture def client(websession: aiohttp.ClientSession) -> RenaultClient: """Fixture for testing RenaultClient.""" return RenaultClient( session=get_logged_in_session(websession), ) def test_init(websession: aiohttp.ClientSession) -> None: """Test RenaultClient initialisation.""" assert RenaultClient( session=get_logged_in_session(websession), ) assert RenaultClient( websession=websession, country=TEST_COUNTRY, locale_details=TEST_LOCALE_DETAILS, credential_store=get_logged_in_credential_store(), ) @pytest.mark.asyncio async def test_get_person( client: RenaultClient, mocked_responses: aioresponses ) -> None: """Test get_person.""" fixtures.inject_get_person(mocked_responses) person = await client.get_person() assert person.accounts is not None assert len(person.accounts) == 2 @pytest.mark.asyncio async def test_get_api_accounts( client: RenaultClient, mocked_responses: aioresponses ) -> None: """Test get_api_accounts.""" fixtures.inject_get_person(mocked_responses) accounts = await client.get_api_accounts() assert len(accounts) == 2 @pytest.mark.asyncio async def test_get_api_account(client: RenaultClient) -> None: """Test get_api_account.""" account = await client.get_api_account(TEST_ACCOUNT_ID) assert account._account_id == TEST_ACCOUNT_ID renault-api-0.2.9/tests/test_renault_session.py000066400000000000000000000162531473671120200217450ustar00rootroot00000000000000"""Test cases for initialisation of the Kamereon client.""" from typing import cast import aiohttp import pytest from aioresponses import aioresponses from tests import fixtures from tests.const import TEST_COUNTRY from tests.const import TEST_LOCALE from tests.const import TEST_LOCALE_DETAILS from tests.const import TEST_LOGIN_TOKEN from tests.const import TEST_PASSWORD from tests.const import TEST_PERSON_ID from tests.const import TEST_USERNAME from tests.test_credential_store import get_logged_in_credential_store from renault_api.credential import JWTCredential from renault_api.exceptions import NotAuthenticatedException from renault_api.exceptions import RenaultException from renault_api.gigya import GIGYA_JWT from renault_api.gigya import GIGYA_LOGIN_TOKEN from renault_api.renault_session import RenaultSession def get_logged_in_session(websession: aiohttp.ClientSession) -> RenaultSession: """Get initialised RenaultSession.""" return RenaultSession( websession=websession, country=TEST_COUNTRY, locale=TEST_LOCALE, locale_details=TEST_LOCALE_DETAILS, credential_store=get_logged_in_credential_store(), ) @pytest.fixture def session(websession: aiohttp.ClientSession) -> RenaultSession: """Fixture for testing RenaultSession.""" return RenaultSession( websession=websession, country=TEST_COUNTRY, locale_details=TEST_LOCALE_DETAILS, ) @pytest.mark.asyncio async def test_init_locale_only(websession: aiohttp.ClientSession) -> None: """Test initialisation with locale only.""" session = RenaultSession( websession=websession, locale=TEST_LOCALE, ) assert await session._get_country() assert await session._get_gigya_api_key() assert await session._get_gigya_root_url() assert await session._get_kamereon_api_key() assert await session._get_kamereon_root_url() @pytest.mark.asyncio async def test_init_country_only(websession: aiohttp.ClientSession) -> None: """Test initialisation with country only.""" session = RenaultSession( websession=websession, country=TEST_COUNTRY, ) assert await session._get_country() with pytest.raises( RenaultException, match="Credential `gigya-api-key` not found in credential cache.", ): assert await session._get_gigya_api_key() with pytest.raises( RenaultException, match="Credential `gigya-root-url` not found in credential cache.", ): assert await session._get_gigya_root_url() with pytest.raises( RenaultException, match="Credential `kamereon-api-key` not found in credential cache.", ): assert await session._get_kamereon_api_key() with pytest.raises( RenaultException, match="Credential `kamereon-root-url` not found in credential cache.", ): assert await session._get_kamereon_root_url() @pytest.mark.asyncio async def test_init_locale_details_only(websession: aiohttp.ClientSession) -> None: """Test initialisation with locale_details only.""" session = RenaultSession( websession=websession, locale_details=TEST_LOCALE_DETAILS, ) with pytest.raises( RenaultException, match="Credential `country` not found in credential cache.", ): assert await session._get_country() assert await session._get_gigya_api_key() assert await session._get_gigya_root_url() assert await session._get_kamereon_api_key() assert await session._get_kamereon_root_url() @pytest.mark.asyncio async def test_init_locale_and_details(websession: aiohttp.ClientSession) -> None: """Test initialisation with locale and locale_details.""" session = RenaultSession( websession=websession, locale=TEST_LOCALE, locale_details=TEST_LOCALE_DETAILS, ) assert await session._get_country() assert await session._get_gigya_api_key() assert await session._get_gigya_root_url() assert await session._get_kamereon_api_key() assert await session._get_kamereon_root_url() @pytest.mark.asyncio async def test_init_locale_country(websession: aiohttp.ClientSession) -> None: """Test initialisation with locale and country.""" session = RenaultSession( websession=websession, locale=TEST_LOCALE, country=TEST_COUNTRY, ) assert await session._get_country() assert await session._get_gigya_api_key() assert await session._get_gigya_root_url() assert await session._get_kamereon_api_key() assert await session._get_kamereon_root_url() @pytest.mark.asyncio async def test_not_logged_in(session: RenaultSession) -> None: """Test errors when not logged in.""" with pytest.raises( NotAuthenticatedException, match="Gigya login token not available.", ): await session._get_login_token() with pytest.raises( NotAuthenticatedException, match="Gigya login token not available.", ): await session._get_person_id() with pytest.raises( NotAuthenticatedException, match="Gigya login token not available.", ): await session._get_jwt() @pytest.mark.asyncio async def test_login(session: RenaultSession, mocked_responses: aioresponses) -> None: """Test login/person/jwt response.""" fixtures.inject_gigya_all(mocked_responses) await session.login(TEST_USERNAME, TEST_PASSWORD) assert await session._get_login_token() == TEST_LOGIN_TOKEN assert len(mocked_responses.requests) == 1 assert await session._get_person_id() == TEST_PERSON_ID assert len(mocked_responses.requests) == 2 assert await session._get_jwt() assert len(mocked_responses.requests) == 3 # Ensure further requests use cache assert await session._get_person_id() == TEST_PERSON_ID assert await session._get_jwt() assert len(mocked_responses.requests) == 3 @pytest.mark.asyncio async def test_expired_login_token( websession: aiohttp.ClientSession, mocked_responses: aioresponses ) -> None: """Test _get_jwt response on expired login token.""" session = get_logged_in_session(websession=websession) fixtures.inject_gigya( mocked_responses, urlpath="accounts.getJWT", filename="error/get_jwt.403005.json", ) # First attempt uses cached values assert await session._get_jwt() assert len(mocked_responses.requests) == 0 assert GIGYA_JWT in session._credentials assert GIGYA_LOGIN_TOKEN in session._credentials # mark JWT as expired jwt_credential = cast(JWTCredential, session._credentials.get(GIGYA_JWT)) jwt_credential.expiry = 1 # first attempt show authentication as expired with pytest.raises( NotAuthenticatedException, match="Authentication expired.", ): assert await session._get_jwt() assert len(mocked_responses.requests) == 1 assert GIGYA_JWT not in session._credentials assert GIGYA_LOGIN_TOKEN not in session._credentials # subsequent attempts just show not authenticated with pytest.raises( NotAuthenticatedException, match="Gigya login token not available.", ): assert await session._get_jwt() assert len(mocked_responses.requests) == 1 renault-api-0.2.9/tests/test_renault_vehicle.py000066400000000000000000000274611473671120200217040ustar00rootroot00000000000000"""Test cases for the Renault client API keys.""" from datetime import datetime from datetime import timezone from typing import List import aiohttp import pytest from aioresponses import aioresponses from aioresponses.core import RequestCall from yarl import URL from tests import fixtures from tests.const import TEST_ACCOUNT_ID from tests.const import TEST_COUNTRY from tests.const import TEST_LOCALE_DETAILS from tests.const import TEST_VIN from tests.test_credential_store import get_logged_in_credential_store from tests.test_renault_session import get_logged_in_session from renault_api.kamereon.helpers import DAYS_OF_WEEK from renault_api.kamereon.models import ChargeSchedule from renault_api.kamereon.models import HvacSchedule from renault_api.renault_vehicle import RenaultVehicle @pytest.fixture def vehicle(websession: aiohttp.ClientSession) -> RenaultVehicle: """Fixture for testing RenaultVehicle.""" return RenaultVehicle( account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, session=get_logged_in_session(websession), ) def test_init(websession: aiohttp.ClientSession) -> None: """Test RenaultVehicle initialisation.""" assert RenaultVehicle( account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, session=get_logged_in_session(websession), ) assert RenaultVehicle( account_id=TEST_ACCOUNT_ID, vin=TEST_VIN, websession=websession, country=TEST_COUNTRY, locale_details=TEST_LOCALE_DETAILS, credential_store=get_logged_in_credential_store(), ) @pytest.mark.asyncio async def test_get_details( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_details.""" fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") assert await vehicle.get_details() # Ensure second call still works (ie. use cached value) assert await vehicle.get_details() @pytest.mark.asyncio async def test_get_car_adapter( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_details.""" fixtures.inject_get_car_adapter(mocked_responses, "zoe_40.1.json") assert await vehicle.get_car_adapter() # Ensure second call still works (ie. use cached value) assert await vehicle.get_car_adapter() @pytest.mark.asyncio async def test_get_contracts( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_contracts.""" fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.1.json") assert await vehicle.get_contracts() # Ensure second call still works (ie. use cached value) assert await vehicle.get_contracts() @pytest.mark.asyncio async def test_has_contract_for_endpoint_1( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test has_contract_for_endpoint.""" fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.2.json") assert await vehicle.has_contract_for_endpoint("battery-status") assert await vehicle.has_contract_for_endpoint("hvac-status") assert await vehicle.has_contract_for_endpoint("charge-mode") @pytest.mark.asyncio async def test_has_contract_for_endpoint_2( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test has_contract_for_endpoint.""" fixtures.inject_get_vehicle_contracts(mocked_responses, "fr_FR.1.json") assert await vehicle.has_contract_for_endpoint("battery-status") @pytest.mark.asyncio async def test_get_battery_status( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_battery_status.""" fixtures.inject_get_battery_status(mocked_responses) assert await vehicle.get_battery_status() @pytest.mark.asyncio async def test_get_tyre_pressure( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_tyre_pressure.""" fixtures.inject_get_tyre_pressure(mocked_responses) assert await vehicle.get_tyre_pressure() @pytest.mark.asyncio async def test_get_location( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_location.""" fixtures.inject_get_location(mocked_responses) assert await vehicle.get_location() @pytest.mark.asyncio async def test_get_hvac_status( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_hvac_status.""" fixtures.inject_get_hvac_status(mocked_responses, "zoe") assert await vehicle.get_hvac_status() @pytest.mark.asyncio async def test_get_hvac_settings( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_hvac_settings.""" fixtures.inject_get_hvac_settings(mocked_responses) data = await vehicle.get_hvac_settings() assert data.mode == "scheduled" assert data.schedules schedules: List[HvacSchedule] = data.schedules assert schedules assert schedules[1].id == 2 assert schedules[1].activated is True assert schedules[1].wednesday assert schedules[1].wednesday.readyAtTime == "T15:15Z" assert schedules[1].friday assert schedules[1].friday.readyAtTime == "T15:15Z" for i in (0, 2, 3, 4): assert schedules[i].id == i + 1 assert schedules[i].activated is False for day in DAYS_OF_WEEK: assert schedules[i].__dict__[day] is None @pytest.mark.asyncio async def test_get_charge_mode( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_charge_mode.""" fixtures.inject_get_charge_mode(mocked_responses) assert await vehicle.get_charge_mode() @pytest.mark.asyncio async def test_get_cockpit( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_cockpit.""" fixtures.inject_get_cockpit(mocked_responses, "zoe") assert await vehicle.get_cockpit() @pytest.mark.asyncio async def test_get_lock_status( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_lock_status.""" fixtures.inject_get_lock_status(mocked_responses) assert await vehicle.get_lock_status() @pytest.mark.asyncio async def test_get_charging_settings( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_charging_settings.""" fixtures.inject_get_charging_settings(mocked_responses, "multi") assert await vehicle.get_charging_settings() @pytest.mark.asyncio async def test_get_notification_settings( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_notification_settings.""" fixtures.inject_get_notification_settings(mocked_responses) assert await vehicle.get_notification_settings() @pytest.mark.asyncio async def test_get_charge_history_month( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_charge_history.""" fixtures.inject_get_charge_history(mocked_responses, "202010", "202011", "month") assert await vehicle.get_charge_history( start=datetime(2020, 10, 1), end=datetime(2020, 11, 15), period="month", ) @pytest.mark.asyncio async def test_get_charge_history_day( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_charge_history.""" fixtures.inject_get_charge_history(mocked_responses, "20201001", "20201115", "day") assert await vehicle.get_charge_history( start=datetime(2020, 10, 1), end=datetime(2020, 11, 15), period="day", ) @pytest.mark.asyncio async def test_get_charges( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_charges.""" fixtures.inject_get_charges(mocked_responses, "20201001", "20201115") assert await vehicle.get_charges( start=datetime(2020, 10, 1), end=datetime(2020, 11, 15), ) @pytest.mark.asyncio async def test_get_hvac_history( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_hvac_history.""" fixtures.inject_get_hvac_history(mocked_responses, "202010", "202011", "month") assert await vehicle.get_hvac_history( start=datetime(2020, 10, 1), end=datetime(2020, 11, 15), period="month", ) @pytest.mark.asyncio async def test_get_hvac_sessions( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test get_hvac_sessions.""" fixtures.inject_get_hvac_sessions(mocked_responses, "20201001", "20201115") assert await vehicle.get_hvac_sessions( start=datetime(2020, 10, 1), end=datetime(2020, 11, 15), ) @pytest.mark.asyncio async def test_set_ac_start( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_ac_start.""" url = fixtures.inject_set_hvac_start(mocked_responses, "start") assert await vehicle.set_ac_start( 21, datetime(2020, 11, 24, 6, 30, tzinfo=timezone.utc) ) expected_json = { "data": { "type": "HvacStart", "attributes": { "action": "start", "targetTemperature": 21, "startDateTime": "2020-11-24T06:30:00Z", }, } } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] @pytest.mark.asyncio async def test_set_ac_stop( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_ac_stop.""" url = fixtures.inject_set_hvac_start(mocked_responses, "cancel") fixtures.inject_get_vehicle_details(mocked_responses, "zoe_50.1.json") assert await vehicle.set_ac_stop() expected_json = {"data": {"type": "HvacStart", "attributes": {"action": "cancel"}}} request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] @pytest.mark.asyncio async def test_set_charge_mode( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_charge_mode.""" url = fixtures.inject_set_charge_mode(mocked_responses, "schedule_mode") assert await vehicle.set_charge_mode("schedule_mode") expected_json = { "data": {"type": "ChargeMode", "attributes": {"action": "schedule_mode"}} } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] @pytest.mark.asyncio async def test_set_charge_schedules( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_charge_schedules.""" url = fixtures.inject_set_charge_schedule(mocked_responses, "schedules") schedules: List[ChargeSchedule] = [] assert await vehicle.set_charge_schedules(schedules) expected_json = { "data": {"type": "ChargeSchedule", "attributes": {"schedules": []}} } request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] @pytest.mark.asyncio async def test_set_charge_start( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_charge_start.""" fixtures.inject_get_vehicle_details(mocked_responses, "zoe_40.1.json") url = fixtures.inject_set_charging_start(mocked_responses, "start") expected_json = { "data": {"type": "ChargingStart", "attributes": {"action": "start"}} } assert await vehicle.set_charge_start() request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert expected_json == request.kwargs["json"] @pytest.mark.asyncio async def test_set_hvac_schedules( vehicle: RenaultVehicle, mocked_responses: aioresponses ) -> None: """Test set_hvac_schedules.""" schedules: List[HvacSchedule] = [] url = fixtures.inject_set_hvac_schedules(mocked_responses) assert await vehicle.set_hvac_schedules(schedules) request: RequestCall = mocked_responses.requests[("POST", URL(url))][0] assert request.kwargs["json"] == { "data": {"type": "HvacSchedule", "attributes": {"schedules": []}} }