diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..836a14c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +github: sr-murthy +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..290f9ac --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ${{ github.event.number || github.run_id }} + cancel-in-progress: true + +jobs: + test: + env: + PYTHONDEVMODE: 1 + API_USERNAME: ${{ secrets.API_USERNAME }} + API_KEY: ${{ secrets.API_KEY }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13"] + os: [ubuntu-latest, macos-latest, windows-latest] + install-via: [pip] + arch: [x64] + include: + - python-version: "3.10" + os: ubuntu-latest + install-via: pip + arch: x64 + - python-version: "3.10" + os: macos-13 + install-via: pip + arch: x64 + - python-version: "3.10" + os: windows-latest + install-via: pip + arch: x64 + - python-version: "3.11" + os: macos-latest + install-via: pip + arch: arm64 + - python-version: "3.12" + os: macos-latest + install-via: pip + arch: arm64 + - python-version: "3.13" + os: macos-latest + install-via: pip + arch: arm64 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.arch }} + allow-prereleases: false + + - name: Set Variables + id: set_variables + shell: bash + run: | + echo "PY=$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_OUTPUT + echo "PIP_CACHE=$(pip cache dir)" >> $GITHUB_OUTPUT + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ${{ steps.set_variables.outputs.PIP_CACHE }} + key: ${{ runner.os }}-pip-${{ steps.set_variables.outputs.PY }} + + - name: Cache venv + uses: actions/cache@v4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.python-version }}-${{ hashFiles('pdm.lock') }} + restore-keys: | + venv-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.python-version }}- + + - name: Install current PDM via pip + if: matrix.install-via == 'pip' + run: | + python -m pip install pdm + pdm info -v + pdm venv list -v + pdm use -f .venv + echo "$VIRTUAL_ENV/bin" >> $GITHUB_PATH + echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $GITHUB_ENV + echo "GITHUB_PATH=$GITHUB_PATH" + echo "GITHUB_ENV=$GITHUB_ENV" + + - name: Install current PDM via install script + if: matrix.install-via == 'script' + run: | + curl -sSLO https://pdm-project.org/install-pdm.py + curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py.sha256 | shasum -a 256 -c - + python3 install-pdm.py + pdm info -v + pdm venv list -v + pdm use -f .venv + echo "$VIRTUAL_ENV/bin" >> $GITHUB_PATH + echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $GITHUB_ENV + echo "GITHUB_PATH=$GITHUB_PATH" + echo "GITHUB_ENV=$GITHUB_ENV" + + - name: Lint + Run unit & acceptance tests + Measure code coverage + run: | + pdm use -f .venv + pdm install -v -dGlint --no-self --no-isolation + pdm run -v lint + pdm install -v -dGtest --no-self --no-isolation + pdm run -v pytest + pdm run -v doctests + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: sr-murthy/fsrapiclient + file: ./coverage.xml + flags: unit diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..707b69a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,82 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'python' && ('macos-13' || 'macos-latest' || 'ubuntu-latest' || 'window-latest')) }} + timeout-minutes: ${{ (matrix.language == 'python' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82f9275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ab5269e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,210 @@ +google-site-verification +3F2Jbz15v4TUv5j0vDJAA-mSyHmYIJq0okBoro3-WMY + +# Contributing + +Contributors and contributions are welcome. Please read these guidelines +first. + +## Git + +The project homepage is on +[GitHub](https://github.com/sr-murthy/fsrapiclient). + +Contributors can open pull requests from a fork targeting the parent +`main` +[branch](https://github.com/sr-murthy/fsrapiclient/tree/main). But +it may be a good first step to create an +[issue](https://github.com/sr-murthy/fsrapiclient/issues) or open +a [discussion +topic](https://github.com/sr-murthy/fsrapiclient/discussions). + +A simple Git workflow, using a feature and/or fix branch created off the +`main` branch of your fork, is recommended. + +## Repo + +If you wish to contribute please first ensure you have [SSH access to +GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh). +This basically involves creating a project-specific SSH keypair - if you +don't already have one - and adding it to GitHub. If you have done this +successfully then this verification step should work: + +``` shell +ssh -vT git@github.com +``` + +Some SSH configuration may be required: on MacOS or Linux your +user-defined SSH configuration file (`~/.ssh/config`) should look +something like this: + +``` shell +Host github.com + AddKeysToAgent yes + UseKeychain yes + ForwardAgent yes + Preferredauthentications publickey + IdentityFile ~/.ssh/ + PasswordAuthentication no +``` + +For Windows please consult the [Windows OpenSSH +documentation](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration). + +Once you’ve forked the repository, you can clone your fork, e.g. over +SSH: + +``` python +git clone git+ssh://git@github.com//fsrapiclient +``` + +You can create additional remotes for the parent project to enable +easier syncing, or you can simply create PRs directly against the parent +project. + +## Dependencies & PDM + +The package has only [`requests`](https://requests.readthedocs.io/en/latest/) +as a (production) dependency - this and its sub-dependencies are automatically +installed on installing the package. + +Development dependencies are specified in the `[tool.pdm.dev-dependencies]` section +of the [project +TOML](https://github.com/sr-murthy/fsrapiclient/blob/main/pyproject.toml), +but they are not mandatory. Of these, the most important are probably +the `'test'` dependencies, including +[pytest](https://docs.pytest.org/en/8.0.x/) and +[pytest-cov](https://pytest-cov.readthedocs.io/): + +``` toml +test = [ + "coverage[toml]", + "pytest", + "pytest-cov", + "pytest-xdist", +] +``` + +[PDM](https://pdm-project.org/latest) is used (by myself, currently, the +sole maintainer) to manage all dependencies and publish packages to +PyPI. It is also used to automate certain tasks, such as running tests, +as described in the section. + +There are no root-level `requirements*.txt` files - but only a single +(default, version-controlled, cross-platform) +[pdm.lock](https://github.com/sr-murthy/fsrapiclient/blob/main/pdm.lock) +lockfile, which defines metadata for all TOML-defined development +dependencies, including the currently empty set of production +dependencies, and their sub-dependencies etc. This can be used to +install all development dependencies, including the project itself, in +editable mode where available: + +``` shell +pdm install -v --dev +``` + +> [!NOTE] +> It is important to note that `pdm install` uses either the default +> lockfile (`pdm.lock`), or one specified with `-L `. Multiple +> lockfiles can be generated and maintained. Refer to the [PDM install +> documentation](https://pdm-project.org/latest/reference/cli/#install) +> for more information. + +If you don't wish to install any editable dependencies, including the +project itself, you can use: + +``` shell +pdm install -v --dev --no-editable --no-self +``` + +The default lockfile can be updated with any and all upstream changes in +the TOML-defined dependencies, but excluding any editable dependencies +including the project itself, using: + +``` shell +pdm update -v --dev --no-editable --no-self --update-all +``` + +This will usually modify `pdm.lock`, in which case the file should be +staged and included in a commit. + +The lockfile can be exported in its entirety to another format, such as +the auto-generated `requirements.txt` using: + +``` shell +pdm export -v -f requirements --dev -o requirements.txt +``` + +For more information on PDM lockfiles and installing requirements see +the [PDM documentation](https://pdm-project.org/latest/). + +## Tests `microscope` + +Tests are defined in the `tests` folder, and should be run with +[pytest](https://pytest-cov.readthedocs.io/en/latest/). + +For convenience different types of test targets are defined in the +[Makefile](https://github.com/sr-murthy/fsrapiclient/blob/main/Makefile): +`lint` for Ruff linting, `doctests` for running +[doctests](https://docs.python.org/3/library/doctest.html) and +`unittests` for running unittests and measuring coverage, using `pytest` +and the `pytest-cov` plugin: + +``` shell +make lint +make unittests +make doctests +``` + +Linting warnings should be addressed first, and any changes staged and +committed. + +Unit tests can be run all at once using `make unittests` or individually +using `pytest`, e.g. running the test class for the +`~fsrapiclient.api.FsrApiClient` class: + +``` shell +python -m pytest -sv tests/units/test_api.py::TestFsrApiClient +``` + +> [!NOTE] +> The `-s` option in the `pytest` command is to allow interactive +> environments to be entered on errors, e.g. debugger breakpoints. The +> default behaviour of [capturing console +> input/output](https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html#default-stdout-stderr-stdin-capturing-behaviour) +> would otherwise prevent debuggers from being triggered. + +The doctests serve as acceptance tests, and are best run after the unit +tests. They can be run all at once using `make doctests`, or +individually by library using `python -m doctest`, e.g. running all the +doctests in `fsrapiclient.api`: + +``` shell +python -m doctest -v src/fsrapiclient/api.py +``` + +## Documentation + +Documentation is currently the README. A separate documentation site will +be deployed to Read The Docs in the future. + +## CI + +The CI pipelines are defined in the [CI +YML](https://github.com/sr-murthy/fsrapiclient/blob/main/.github/workflows/ci.yml) +and the [CodeQL Analysis +YML](https://github.com/sr-murthy/fsrapiclient/blob/main/.github/workflows/codeql-analysis.yml). +Currently, pipelines for all branches include a tests stage that +includes Ruff linting, unit tests, Python doctests, and in that order. + +## Versioning and Releases + +The [PyPI package](https://pypi.org/project/fsrapiclient/) is +currently at version `0.1.0. + +There is currently no dedicated pipeline for releases - both [GitHub +releases](https://github.com/sr-murthy/fsrapiclient/releases) and +[PyPI packages](https://pypi.org/project/fsrapiclient) are +published manually, but both have the same version tag. + +Pipelines for releases will be added as part of a future release. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0099489 --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +SHELL := /bin/bash + +REPO := https://github.com/sr-murthy/fsrapiclient + +PACKAGE_NAME := fsrapiclient +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +HEAD := $(shell git rev-parse --short=8 HEAD) +PACKAGE_VERSION := $(shell grep __version__ src/__version__.py | cut -d '=' -f 2 | xargs) + +PROJECT_ROOT := $(PWD) + +TESTS_ROOT := $(PROJECT_ROOT)/tests + +#DOCS_ROOT := $(PROJECT_ROOT)/docs +#DOCS_BUILD := $(DOCS_ROOT)/_build +#DOCS_BUILD_HTML := $(DOCS_ROOT)/_build/html + +# Make everything (possible) +all: + +# Git +git_stage: + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Staging new, modified, deleted and/or renamed files in Git\n" + git status -uno | grep modified | tr -s ' ' | cut -d ' ' -f 2 | xargs git add && \ + git status -uno | grep deleted | tr -s ' ' | cut -d ' ' -f 2 | xargs git add -A && \ + git status -uno + +# Housekeeping +clean: + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Deleting all temporary files\n" + rm -fr docs/_build/* .pytest_cache *.pyc *__pycache__* ./dist/* ./build/* *.egg-info* + +# A simple version check for the installed package (local, sdist or wheel) +version_check: + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Checking installed package version (if it is installed)\n" + python3 -c "import os; os.chdir('src'); from __version__ import __version__; print(__version__); os.chdir('../')" + +version_extract: + echo "$(PACKAGE_VERSION)" + +# Dependency management +update_deps: + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Update all development dependencies, including documentation and production dependencies\n" + pdm update -v --dev --no-editable --no-self --update-all + +# Linting +lint: clean + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Linting source code with Ruff\n" + cd "$(PROJECT_ROOT)" && ruff check src + +# Running tests +doctests: clean + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Running doctests in all core libraries\n" + cd "$(PROJECT_ROOT)" && \ + python -m doctest -v src/fsrapiclient/*.py + +unittests: clean + @echo "\n$(PACKAGE_NAME)[$(BRANCH)@$(HEAD)]: Running package unit tests + measuring coverage\n" + cd "$(PROJECT_ROOT)" && \ + python3 -m pytest \ + --cache-clear \ + --capture=no \ + --code-highlight=yes \ + --color=yes \ + --cov=src \ + --cov-report=term-missing:skip-covered \ + --dist worksteal \ + --numprocesses=auto \ + --tb=native \ + --verbosity=3 \ + tests/units diff --git a/README.md b/README.md index 4fc7b8b..273ded4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@
[![CI](https://github.com/sr-murthy/fsrapiclient/actions/workflows/ci.yml/badge.svg)](https://github.com/sr-murthy/fsrapiclient/actions/workflows/ci.yml) +[![CodeQL](https://github.com/sr-murthy/fsrapiclient/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/sr-murthy/fsrapiclient/actions/workflows/codeql-analysis.yml) [![codecov](https://codecov.io/github/sr-murthy/fsrapiclient/graph/badge.svg?token=F41VZIHT2K)](https://codecov.io/github/sr-murthy/fsrapiclient) [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm-project.org) [![License: MPL @@ -10,4 +11,506 @@ # fsrapiclient -A lightweight Python client library for the UK Financial Services Register (FSR) RESTful API. +A lightweight Python client library for the [UK Financial Services Register (FS Register)](https://register.fca.org.uk/s/) [RESTful API](https://register.fca.org.uk/Developer/s/). + +The FS Register is a **public** database of all firms, individuals and other entities that are currently, or have been previously, authorised by the [UK Financial Conduct Authority (FCA)](https://www.fca.org.uk) and/or the [Prudential Regulatory Authority (PRA)](http://bankofengland.co.uk/pra). + +**NOTE #1:** The FS Register API is free to use but accessing it, including via this library, requires [registration](https://register.fca.org.uk/Developer/ShAPI_LoginPage?ec=302&startURL=%2FDeveloper%2Fs%2F#). Registration involves signing up with an email, which is used as the API username in requests, and some personal information. Once registered an API key is available from your registration profile - the API key can be used in request headers to programmatically make requests via any suitable language and library of choice. + +**NOTE #2:** The author has no current or previous affiliation with either the FCA or PRA. + +# The FS Register API + +The `fsrapiclient` functionality reflects the structure and properties of the current FS Register API (V0.1). There are three main categories of resource about which information can be requested from the register via API endpoints: + +* **firms** - authorised firms (either currently or previously authorised) +* **individuals** - individuals associated or affiliated with authorised firms, either currently or previously +* **funds** - funds or collective investment schemes (CIS), including subfunds of funds + +There is also a **common search** API endpoint that allows a search for any of these resources by name or namelike-substring and a corresponding type specification (firm, individual, or fund). + +These are described in a bit more detail below. + +## Firms + +Firms are identified by unique firm reference numbers (FRN). The following is a table summarising firm-specific API endpoints: + +| API endpoint | Parameters | +|----------------------------------------------------------|----------------------------------------| +| `/V0.1/Firm/{FRN}` | FRN (str) | +| `/V0.1/Firm/{FRN}/Address` | FRN (str) | +| `/V0.1/Firm/{FRN}/AR` | FRN (str) | +| `/V0.1/Firm/{FRN}/CF` | FRN (str) | +| `/V0.1/Firm/{FRN}/DisciplinaryHistory` | FRN (str) | +| `/V0.1/Firm/{FRN}/Exclusions` | FRN (str) | +| `/V0.1/Firm/{FRN}/Individuals` | FRN (str) | +| `/V0.1/Firm/{FRN}/Names` | FRN (str) | +| `/V0.1/Firm/{FRN}/Passports` | FRN (str) | +| `/V0.1/Firm/{FRN}/Passports/{Country}/Permission` | FRN (str), Country (str) | +| `/V0.1/Firm/{FRN}/Permissions` | FRN (str) | +| `/V0.1/Firm/{FRN}/Regulators` | FRN (str) | +| `/V0.1/Firm/{FRN}/Requirements` | FRN (str) | +| `/V0.1/Firm/{FRN}/Requirements/{ReqRef}/InvestmentTypes` | FRN (str), Requirement Reference (str) | +| `/V0.1/Firm/{FRN}/Waiver` | FRN (str) | + +**NOTE:** The abbreviations "CF" and "AR" refer to "controlled functions" and "appointed representatives" respectively. + +## Individuals + +Individuals associated with firms are identified by unique individual reference numbers (IRN). and the following is a table summarising individual-specific API endpoints. + +| API endpoint | Parameters | +|-----------------------------------------------|------------| +| `/V0.1/Individuals/{IRN}` | IRN (str) | +| `/V0.1/Individuals/{IRN}/CF` | IRN (str) | +| `/V0.1/Individuals/{IRN}/DisciplinaryHistory` | IRN (str) | + +**NOTE:** The abbreviation "CF" refers to "controlled functions". + +## Funds + +Funds are identified by unique product reference numbers (PRN). The following is a table summarising fund-specific API endpoints: + +| API endpoint | Parameters | +|---------------------------|------------| +| `/V0.1/CIS/{PRN}` | PRN (str) | +| `/V0.1/CIS/{PRN}/Names` | PRN (str) | +| `/V0.1/CIS/{PRN}/Subfund` | PRN (str) | + +## Common Search + +The common search API endpoint has the following structure: +```http +/V0.1/CommonSearch?q=&type= +``` +where `` is a substring of the name of a firm, individual or fund, of interest, and `` should indicate whether the query refers to a firm (`"firm"`), individual (`"individual"`), or fund (`"fund"`). For example, here are a few valid common search requests: +```http +/V0.1/CommonSearch?q=Barclays+Bank+plc&type=firm +/V0.1/CommonSearch?q=Hastings+Direct&type=firm +/V0.1/CommonSearch?q=Natwest&type=firm +/V0.1/CommonSearch?q=Mark+Carney&type=individual +/V0.1/CommonSearch?q=John+Smith&type=individual +/V0.1/CommonSearch?q=Jupiter+Asia+Pacific+Income&type=fund +/V0.1/CommonSearch?q=abrdn+multi-asset+fund&type=fund +``` + +## Getting Started + +Currently, `fsrapiclient` only uses the [`requests`](https://requests.readthedocs.io/en/latest/) library (and its dependencies). All you need to get started is a simple `pip install`: +```bash +pip install fsrapiclient +``` +which will install all the dependencies. + +Note that you first need to [register](https://register.fca.org.uk/Developer/ShAPI_LoginPage?ec=302&startURL=%2FDeveloper%2Fs%2F#) with the FS Register, and get your API key. + +If you're interested in contributing see the [contributions guidelines](CONTRIBUTIONS.md). + +## Usage + +Import the FS Register client `fsrapiclient.api.FsrApiClient`, and create an instance using the signup email, which is the API username, and the API key: +```python +>>> from fsrapiclient.api import FsrApiClient +>>> client = FsrApiClient(, ) +>>> client + +``` + +Each client instance maintains its own API session (`fsrapiclient.api.FsrApiSession`) state: +```python +>>> client.api_session + +``` +storing the API username (signup email) and API key. These, and also the API version, are available as properties: +```python +>>> client.api_session.api_username + +>>> client.api_session.api_key + +>>> client.api_version +V0.1 +``` + +Almost client methods return an `fsrapiclient.api.FsrApiResponse` object, which has four properties specific to the FS Register API: + +* `fsr_status` - an FS Register-specific status indicator for the request +* `fsr_message` - an FS Register-specific status message for the request +* `fsr_data` - the response data +* `fsr_resultinfo` - pagination information for the response data + +### Common Search + +The common search endpoint can be used via the `FsrApiClient.common_search` method to make generic queries for firms, individuals, or funds. It requires an URL-encoded string of the form: +```bash +q=&type= +``` +Use [`urlencode.parse.urlencode`](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode) to do the URL-encoding. Some examples are given below. +```python +from urllib.parse import urlencode +>>> client.common_search(urlencode({'q': 'barclays bank', 'type': 'firm'})).fsr_data +[{'URL': 'https://register.fca.org.uk/services/V0.1/Firm/759676', + 'Status': 'Authorised', + 'Reference Number': '759676', + 'Type of business or Individual': 'Firm', + 'Name': 'Barclays Bank UK PLC (Postcode: E14 5HP)'}, + ... +{'URL': 'https://register.fca.org.uk/services/V0.1/Firm/122702', + 'Status': 'Authorised', + 'Reference Number': '122702', + 'Type of business or Individual': 'Firm', + 'Name': 'Barclays Bank Plc (Postcode: E14 5HP)'}] +>>> client.common_search(urlencode({'q': 'mark carney', 'type': 'individual'})).fsr_data +[{'URL': 'https://register.fca.org.uk/services/V0.1/Individuals/MXC29012', + 'Status': 'Active', + 'Reference Number': 'MXC29012', + 'Type of business or Individual': 'Individual', + 'Name': 'Mark Carney'}] +>>> client.common_search(urlencode({'q': 'jupiter asia pacific income', 'type': 'fund'})) +[{'URL': 'https://register.fca.org.uk/services/V0.1/CIS/635641', + 'Status': 'Recognised', + 'Reference Number': '635641', + 'Type of business or Individual': 'Collective investment scheme', + 'Name': 'Jupiter Asia Pacific Income Fund (IRL)'}] +``` +The response data as stored in the `fsr_data` property might be non-empty or empty depending on whether the combination of query and entity type is valid, e.g.: +```python +>>> client.common_search(urlencode({'q': 'natwest', 'type': 'individual'})).fsr_data +# Null +``` + +### Searching for FRNs, IRNs and PRNs + +Generally, firm reference numbers (FRN), individual reference numbers (IRN), and product reference numbers (PRN), may not be known in advance. These can be found via the following client search methods: + +* `search_frn` +* `search_irn` +* `search_prn` + +FRNs, IRNs, and PRNs uniquely identify firms, individuals, and funds, respectively, in the FS Register, whether current or past. +The searches can be performed with the relevant method by name - the search is case-insensitive in name, and the more precise the name the more likely is an exact, unique result. Some examples are given below for each type of search, starting with FRNs: +```python +>>> client.search_frn('hiscox insurance company limited') +'113849' +``` +Imprecise names in the search can produce multiple records, and will trigger an `fsrapiclient.api.FsrResponseException` indicating the problem, e.g.: +```python +>>> client.search_frn('hiscox') +FsrApiResponseException: Multiple firms returned. Firm name needs to be more precise. If you are unsure of the results please use the common search endpoint +``` +In this case the exception was generated because a common search for `"hiscox"` shows that there are multiple firm entries featuring the name `"Hiscox"``: +```python +>>> client.common_search(urlencode({'q': 'hiscox', 'type': 'firm'})).fsr_data +[{'URL': 'https://register.fca.org.uk/services/V0.1/Firm/812274', + 'Status': 'No longer authorised', + 'Reference Number': '812274', + 'Type of business or Individual': 'Firm', + 'Name': 'HISCOX ASSURE'}, +... +... + {'URL': 'https://register.fca.org.uk/services/V0.1/Firm/732312', + 'Status': 'Authorised', + 'Reference Number': '732312', + 'Type of business or Individual': 'Firm', + 'Name': 'Hiscox MGA Ltd (Postcode: EC2N 4BQ)'}] +``` +Searches for non-existent firms will trigger an `FsrApiResponseException` indicating that no data found in the FS Register for the given name: +```python +>>> client.search_frn('a nonexistent firm') +FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. +``` + +A few examples are given below of IRN searches. +```python +>>> client.search_irn('mark carney') +'MXC29012' +>>> client.search_irn('mark c') +FsrApiResponseException: Multiple individuals returned. The individual name needs to be more precise. If you are unsure of the results please use the common search endpoint +>>> client.search_irn('a nonexistent individual') +FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. +``` + +A few examples are given below of PRN searches. +```python +>>> client.search_prn('jupiter asia pacific income') +'635641' +>>> client.search('jupiter asia') +FsrApiResponseException: Multiple funds returned. The fund name needs to be more precise. If you are unsure of the results please use the common search endpoint +>>> client.search_frn('a nonexistent fund') +FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. +``` +## Firms + +Firm-specific client methods, the associated API endpoints, and parameters and returns are summarised in the table below. + +| Method | API endpoint | Parameters | Return | +|-----------------------------------------|----------------------------------------------------------|----------------------------------------|------------------| +| `get_firm` | `/V0.1/Firm/{FRN}` | FRN (str) | `FsrApiResponse` | +| `get_firm_addresses` | `/V0.1/Firm/{FRN}/Address` | FRN (str) | `FsrApiResponse` | +| `get_firm_appointed_representatives` | `/V0.1/Firm/{FRN}/AR` | FRN (str) | `FsrApiResponse` | +| `get_firm_controlled_functions` | `/V0.1/Firm/{FRN}/CF` | FRN (str) | `FsrApiResponse` | +| `get_firm_disciplinary_history` | `/V0.1/Firm/{FRN}/DisciplinaryHistory` | FRN (str) | `FsrApiResponse` | +| `get_firm_exclusions` | `/V0.1/Firm/{FRN}/Exclusions` | FRN (str) | `FsrApiResponse` | +| `get_firm_individuals` | `/V0.1/Firm/{FRN}/Individuals` | FRN (str) | `FsrApiResponse` | +| `get_firm_names` | `/V0.1/Firm/{FRN}/Names` | FRN (str) | `FsrApiResponse` | +| `get_firm_passports` | `/V0.1/Firm/{FRN}/Passports` | FRN (str) | `FsrApiResponse` | +| `get_firm_passport_permissions` | `/V0.1/Firm/{FRN}/Passports/{Country}/Permission` | FRN (str), Country (str) | `FsrApiResponse` | +| `get_firm_permissions` | `/V0.1/Firm/{FRN}/Permissions` | FRN (str) | `FsrApiResponse` | +| `get_firm_regulators` | `/V0.1/Firm/{FRN}/Regulators` | FRN (str) | `FsrApiResponse` | +| `get_firm_requirements` | `/V0.1/Firm/{FRN}/Requirements` | FRN (str) | `FsrApiResponse` | +| `get_firm_requirement_investment_types` | `/V0.1/Firm/{FRN}/Requirements/{ReqRef}/InvestmentTypes` | FRN (str), Requirement Reference (str) | `FsrApiResponse` | +| `get_firm_waivers` | `/V0.1/Firm/{FRN}/Waiver` | FRN (str) | `FsrApiResponse` | + +Examples are given below for Barclays Bank Plc. +```python +>>> client.get_firm('122702').fsr_data +[{'Name': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Names', + 'Individuals': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Individuals', + 'Requirements': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Requirements', + 'Permission': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Permissions', + 'Passport': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Passports', + 'Regulators': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Regulators', + 'Appointed Representative': 'https://register.fca.org.uk/services/V0.1/Firm/122702/AR', + 'Address': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Address', + 'Waivers': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Waivers', + 'Exclusions': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Exclusions', + 'DisciplinaryHistory': 'https://register.fca.org.uk/services/V0.1/Firm/122702/DisciplinaryHistory', + 'System Timestamp': '30/11/2024 20:34', + 'Exceptional Info Details': [], + 'Status Effective Date': '01/12/2001', + 'E-Money Agent Status': '', + 'PSD / EMD Effective Date': '', + 'Client Money Permission': 'Control but not hold client money', + 'Sub Status Effective from': '', + 'Sub-Status': '', + 'Mutual Society Number': '', + 'Companies House Number': '01026167', + 'MLRs Status Effective Date': '', + 'MLRs Status': '', + 'E-Money Agent Effective Date': '', + 'PSD Agent Effective date': '', + 'PSD Agent Status': '', + 'PSD / EMD Status': '', + 'Status': 'Authorised', + 'Business Type': 'Regulated', + 'Organisation Name': 'Barclays Bank Plc', + 'FRN': '122702'}] +>>> client.get_firm_addresses('122702') +[{'URL': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Address?Type=PPOB', + 'Website Address': 'www.barclays.com', + 'Phone Number': '+442071161000', + 'Country': 'UNITED KINGDOM', + 'Postcode': 'E14 5HP', + 'County': '', + 'Town': 'London', + 'Address Line 4': '', + 'Address LIne 3': '', + 'Address Line 2': '', + 'Address Line 1': 'One Churchill Place', + 'Address Type': 'Principal Place of Business'}, + {'URL': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Address?Type=Complaint', + 'Website Address': '', + 'Phone Number': '+4403301595858', + 'Country': 'UNITED KINGDOM', + 'Postcode': 'NN4 7SG', + 'County': 'Northamptonshire', + 'Town': 'Northampton', + 'Address Line 4': '', + 'Address LIne 3': '', + 'Address Line 2': '', + 'Address Line 1': '1234 Pavilion Drive', + 'Individual': '', + 'Address Type': 'Complaints Contact'}] +>>> client.get_firm_controlled_functions('122702').fsr_data +[{'Current': {'(6707)SMF4 Chief Risk': {'Suspension / Restriction End Date': '', + 'Suspension / Restriction Start Date': '', + 'Restriction': '', + 'Effective Date': '16/02/2023', + 'Individual Name': 'Bevan Cowie', + 'Name': 'SMF4 Chief Risk', + 'URL': 'https://register.fca.org.uk/services/V0.1/Individuals/BXC00280'}, +... + '(22338)[PRA CF] Significant risk taker or Material risk taker': {'End Date': '30/06/2020', + 'Suspension / Restriction End Date': '', + 'Suspension / Restriction Start Date': '', + 'Restriction': '', + 'Effective Date': '07/03/2016', + 'Individual Name': 'Lynne Atkin', + 'Name': '[PRA CF] Significant risk taker or Material risk taker', + 'URL': 'https://register.fca.org.uk/services/V0.1/Individuals/LAA01049'}}}] +>>> client.get_firm_disciplinary_history('122702').fsr_data +[{'TypeofDescription': "On 19 August 2009, the FSA imposed a penalty on Barclays Bank plc and Barclays Capital Securities Limited (Barclays) of £2,450,000 (discounted from £3,500,000 for early settlement) in respect of breaches of SUP 17 of the FSA Handbook and breaches of Principles 2 and 3 of the FSA's Principles for Businesses which occurred between 1 October 2006 and 31 October 2008. The breach of SUP 17 related to Barclays failure to submit accurate transaction reports as required in respect of an estimated 57.5 million transactions. Barclays breached Principle 2 by failing to conduct its business with due skill, care and diligence in failing to respond sufficiently to opportunities to review the adequacy of its transaction reporting systems. Barclays breached Principle 3 by failing to take reasonable care to organise and control its affairs responsibly and effectively, with adequate risk management systems, to meet the requirements to submit accurate transaction reports to the FSA", + 'TypeofAction': 'Fines', + 'EnforcementType': 'FSMA', + 'ActionEffectiveFrom': '08/09/2009'}, + ... + {'TypeofDescription': "On 23 September 2022, the FCA decided to impose a financial penalty on Barclays Bank Plc. The reason for this action is because Barclays Bank Plc failed to comply with Listing Rule 1.3.3 in October 2008. This matter has been referred by Barclays Bank Plc to the Upper Tribunal. The FCA’s findings and proposed action are therefore provisional and will not take effect pending determination of this matter by the Upper Tribunal. The FCA’s decision was issued on 23 September 2022 and a copy of the Decision Notice is displayed on the FCA's web site here: https://www.fca.org.uk/publication/decision-notices/barclays-bank-plc-dn-2022.pdf \xa0", + 'TypeofAction': 'Fines', + 'EnforcementType': 'FSMA', + 'ActionEffectiveFrom': '23/09/2022'}] +>>> client.get_firm_exclusions('122702').fsr_data +[{'PSD2_Exclusion_Type': 'Limited Network Exclusion', + 'Particular_Exclusion_relied_upon': '2(k)(iii) – may be used only to acquire a very limited range of goods or services', + 'Description_of_services': 'Precision pay Virtual Prepaid - DVLA Service'}] +>>> client.get_firm_individuals('122702').fsr_data +[{'Status': 'Approved by regulator', + 'URL': 'https://register.fca.org.uk/services/V0.1/Individuals/BXC00280', + 'IRN': 'BXC00280', + 'Name': 'Bevan Cowie'}, +... + {'Status': 'Approved by regulator', + 'URL': 'https://register.fca.org.uk/services/V0.1/Individuals/TXW00011', + 'IRN': 'TXW00011', + 'Name': 'Herbert Wright'}] +>>> client.get_firm_names('122702').fsr_data +[{'Current Names': [{'Effective From': '17/05/2013', + 'Status': 'Trading', + 'Name': 'Barclays Bank'}, +... + {'Effective To': '25/01/2010', + 'Effective From': '08/03/2004', + 'Status': 'Trading', + 'Name': 'Banca Woolwich'}]}] +>>> client.get_firm_passports('122702').fsr_data +[{'Passports': [{'PassportDirection': 'Passporting Out', + 'Permissions': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Passports/GIBRALTAR/Permission', + 'Country': 'GIBRALTAR'}]}] +>>> client.get_firm_passport_permissions('122702', 'Gibraltar') +[{'Permissions': [{'Name': '* - additional MiFID services and activities subject to mutual recognition under the BCD', + 'InvestmentTypes': []}, +... + {'Permissions': [{'Name': 'Insurance Distribution or Reinsurance Distribution', + 'InvestmentTypes': []}], + 'PassportType': 'Service', + 'PassportDirection': 'Passporting Out', + 'Directive': 'Insurance Distribution', + 'Country': 'GIBRALTAR'}] +>>> client.get_firm_permissions('122702').fsr_data +{'Debt Adjusting': [{'Limitation': ['This permission is limited to debt adjusting with no debt management activity']}], + 'Credit Broking': [{'Limitation Not Found': ['Valid limitation not present']}], + ... + 'Accepting Deposits': [{'Customer Type': ['All']}, + {'Investment Type': ['Deposit']}]} +>>> client.get_firm_regulators('122702').fsr_data +[{'Termination Date': '', + 'Effective Date': '01/04/2013', + 'Regulator Name': 'Financial Conduct Authority'}, +... + {'Termination Date': '30/11/2001', + 'Effective Date': '25/11/1993', + 'Regulator Name': 'Securities and Futures Authority'}] +>>> client.get_firm_requirements('122702').fsr_data +[{'Effective Date': '23/03/2020', + 'Written Notice - Market Risk Consolidation': 'REQUIREMENTS RELEVANT TO THE MARKET RISK CONSOLIDATION PERMISSION THAT THE FIRM HAS SOUGHT AND THE PRA IMPOSES UNDER SECTION 55M (5) OF THE ACT 1.This Market Risk Consolidation Permission applies to an institution or undertaking listed in Table 1 only for as long as it remains part of the Barclays Group. The firm must notify the PRA promptly if any of those institutions or undertakings ceases to be part of the Barclays Group. 2.The firm must, no later than 23 business days after the end of each quarter, ending March, June, September and December submit, in respect of that quarter, a report to the PRA highlighting the capital impact of market risk consolidation for each of the institutions listed in Table 1. 3.The firm must: 1.ensure that any existing legal agreements or arrangements necessary for fulfilment of the conditions of Article 325(2) of the CRR as between any of the institutions in Table 1 are maintained; and 2.notify the PRA of any variation in the terms of such agreements, or of any change in the relevant legal or regulatory framework of which it becomes aware and which may have an impact on the ability of any of the institutions listed in Table 1 to meet the conditions of Article 325(2) of the CRR. THE MARKET RISK CONSOLIDATION PERMISSION Legal Entities 1.The Market Risk Consolidation Permission means that the firm may use positions in an institution or undertaking listed in Table 1 to offset positions in another institution or undertaking listed therein only for the purposes of calculating net positions and own funds requirements in accordance with Title IV of the CRR on a consolidated basis. Table 1 Institutions and Location of undertaking: Barclays Bank PLC (BBPLC) - UK Barclays Capital Securities Limited (BCSL) UK Barclays Bank Ireland - Ireland', + 'Requirement Reference': 'OR-0170047', + 'Financial Promotions Requirement': 'FALSE'}, + ... + {'Effective Date': '01/10/2024', + 'Financial Promotion for other unauthorised clients': 'This firm can: (1) approve its own financial promotions as well as those of members of its wider group and, in certain circumstances, those of its appointed representatives; and (2) approve financial promotions for other unauthorised persons for the following types of investment:', + 'Requirement Reference': 'OR-0262545', + 'Financial Promotions Requirement': 'TRUE', + 'Financial Promotions Investment Types': 'https://register.fca.org.uk/services/V0.1/Firm/122702/Requirements/OR-0262545/InvestmentTypes'}] +>>> client.get_firm_requirement_investment_types('122702').fsr_data +[{'Investment Type Name': 'Certificates representing certain securities'}, + {'Investment Type Name': 'Debentures'}, + {'Investment Type Name': 'Government and public security'}, + {'Investment Type Name': 'Listed shares'}, + {'Investment Type Name': 'Warrants'}] +>>> client.get_firm_waivers('122702').fsr_data +[{'Waivers_Discretions_URL': 'https://register.fca.org.uk/servlet/servlet.FileDownload?file=00P0X00001YXBw1UAH', + 'Waivers_Discretions': 'A4823494P.pdf', + 'Rule_ArticleNo': ['CRR Ar.313']}, +... + {'Waivers_Discretions_URL': 'https://register.fca.org.uk/servlet/servlet.FileDownload?file=00P4G00002oJPciUAG', + 'Waivers_Discretions': 'A00003642P.pdf', + 'Rule_ArticleNo': ['Perm & Wav - CRR Ru 2.2']}] +``` + +## Individuals + +Individual-specific client methods, the associated API endpoints, and parameters and returns are summarised in the table below. + +| Method | API endpoint | Parameters | Return | +|-------------------------------------- |-----------------------------------------------|------------|------------------| +| `get_individual` | `/V0.1/Individuals/{IRN}` | IRN (str) | `FsrApiResponse` | +| `get_individual_controlled_functions` | `/V0.1/Individuals/{IRN}/CF` | IRN (str) | `FsrApiResponse` | +| `get_individual_disciplinary_history` | `/V0.1/Individuals/{IRN}/DisciplinaryHistory` | IRN (str) | `FsrApiResponse` | + +Some examples are given below. +```python +>>> client.get_individual('MXC29012').fsr_data +[{'Details': {'Disciplinary History': 'https://register.fca.org.uk/services/V0.1/Individuals/MXC29012/DisciplinaryHistory', + 'Current roles & activities': 'https://register.fca.org.uk/services/V0.1/Individuals/MXC29012/CF', + 'IRN': 'MXC29012', + 'Commonly Used Name': 'Mark', + 'Status': 'Certified / assessed by firm', + 'Full Name': 'Mark Carney'}, + 'Workplace Location 1': {'Firm Name': 'TSB Bank plc', + 'Location 1': 'Liverpool'}}] +>>> client.get_individual_controlled_functions('MXC29012') +[{'Previous': {'(5)Appointed representative dealing with clients for which they require qualification': {'Customer Engagement Method': 'Face To Face; Telephone; Online', + 'End Date': '05/04/2022', + 'Suspension / Restriction End Date': '', + 'Suspension / Restriction Start Date': '', + 'Restriction': '', + 'Effective Date': '23/10/2020', + 'Firm Name': 'HL Partnership Limited', + 'Name': 'Appointed representative dealing with clients for which they require qualification', + 'URL': 'https://register.fca.org.uk/services/V0.1/Firm/303397'}, +... + '(1)The London Institute of Banking and Finance (LIBF) - formerly known as IFS': {'Customer Engagement Method': '', + 'Suspension / Restriction End Date': '', + 'Suspension / Restriction Start Date': '', + 'Restriction': '', + 'Effective Date': '', + 'Firm Name': 'Echo Finance Limited', + 'Name': 'The London Institute of Banking and Finance (LIBF) - formerly known as IFS', + 'URL': 'https://register.fca.org.uk/services/V0.1/Firm/570073'}}}] +>>> client.get_individual_disciplinary_history('MXC29012') +# None +``` + +## Funds + +Fund-specific client methods, the associated API endpoints, and parameters and returns are summarised in the table below. + +| Method | API endpoint | Parameters | Return | +|---------------------|---------------------------|------------|------------------| +| `get_fund` | `/V0.1/CIS/{PRN}` | PRN (str) | `FsrApiResponse` | +| `get_fund_names` | `/V0.1/CIS/{PRN}/Names` | PRN (str) | `FsrApiResponse` | +| `get_fund_subfunds` | `/V0.1/CIS/{PRN}/Subfund` | PRN (str) | `FsrApiResponse` | + +Some examples are given below. + +```python +>>> client.get_fund('185045').fsr_data +[{'Sub-funds': 'https://register.fca.org.uk/services/V0.1/CIS/185045/Subfund', + 'Other Name': 'https://register.fca.org.uk/services/V0.1/CIS/185045/Names', + 'CIS Depositary': 'https://register.fca.org.uk/services/V0.1/Firm/805574', + 'CIS Depositary Name': 'Citibank UK Limited', + 'Operator Name': 'abrdn Fund Managers Limited', + 'Operator': 'https://register.fca.org.uk/services/V0.1/Firm/121803', + 'MMF Term Type': '', + 'MMF NAV Type': '', + 'Effective Date': '23/12/1997', + 'Scheme Type': 'UCITS (COLL)', + 'Product Type': 'ICVC', + 'ICVC Registration No': 'SI000001', + 'Status': 'Authorised'}] +>>> client.get_fund_names('185045').fsr_data +[{'Effective To': '22/08/2019', + 'Effective From': '23/12/1997', + 'Product Other Name': 'ABERDEEN INVESTMENT FUNDS ICVC'}, + {'Effective To': '01/08/2022', + 'Effective From': '23/12/1997', + 'Product Other Name': 'Aberdeen Standard OEIC I'}] +>>> client.get_fund_subfunds('185045').fsr_data +[{'URL': 'https://register.fca.org.uk/services/apexrest/V0.1/CIS/185045', + 'Sub-Fund Type': 'Other', + 'Name': 'abrdn (AAM) UK Smaller Companies Fund'}, +... + {'URL': 'https://register.fca.org.uk/services/apexrest/V0.1/CIS/185045', + 'Sub-Fund Type': 'Other', + 'Name': 'abrdn Strategic Bond Fund'}] +``` + + diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..81823a7 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,15 @@ +coverage: + status: + project: + default: + target: 90% + threshold: 1% + flags: + - unit + paths: + - "src" + branches: + - main + if_ci_failed: error + informational: false + only_pulls: false diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..3db6965 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,2170 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "build", "dev", "docs", "lint", "test", "user"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:eb0d1f907c5dccea1ba035c5dd814179abc3491c192eb1de4ff08fa2fafec2c1" + +[[metadata.targets]] +requires_python = ">=3.10" + +[[package]] +name = "anyio" +version = "4.6.2.post1" +requires_python = ">=3.9" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +groups = ["user"] +dependencies = [ + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", + "typing-extensions>=4.1; python_version < \"3.11\"", +] +files = [ + {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, + {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, +] + +[[package]] +name = "appnope" +version = "0.1.4" +requires_python = ">=3.6" +summary = "Disable App Nap on macOS >= 10.9" +groups = ["user"] +marker = "platform_system == \"Darwin\"" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +requires_python = ">=3.7" +summary = "Argon2 for Python" +groups = ["user"] +dependencies = [ + "argon2-cffi-bindings", + "typing-extensions; python_version < \"3.8\"", +] +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +requires_python = ">=3.6" +summary = "Low-level CFFI bindings for Argon2" +groups = ["user"] +dependencies = [ + "cffi>=1.0.1", +] +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, +] + +[[package]] +name = "arrow" +version = "1.3.0" +requires_python = ">=3.8" +summary = "Better dates & times for Python" +groups = ["user"] +dependencies = [ + "python-dateutil>=2.7.0", + "types-python-dateutil>=2.8.10", +] +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +requires_python = ">=3.8" +summary = "Annotate AST trees with source code positions" +groups = ["dev", "user"] +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[[package]] +name = "async-lru" +version = "2.0.4" +requires_python = ">=3.8" +summary = "Simple LRU cache for asyncio" +groups = ["user"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.11\"", +] +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +requires_python = ">=3.7" +summary = "Classes Without Boilerplate" +groups = ["user"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[[package]] +name = "babel" +version = "2.16.0" +requires_python = ">=3.8" +summary = "Internationalization utilities" +groups = ["user"] +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +requires_python = ">=3.6.0" +summary = "Screen-scraping library" +groups = ["user"] +dependencies = [ + "soupsieve>1.2", +] +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[[package]] +name = "bleach" +version = "6.2.0" +requires_python = ">=3.9" +summary = "An easy safelist-based HTML-sanitizing tool." +groups = ["user"] +dependencies = [ + "webencodings", +] +files = [ + {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, + {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default", "user"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +groups = ["user"] +dependencies = [ + "pycparser", +] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default", "user"] +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["dev", "test", "user"] +marker = "sys_platform == \"win32\"" +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 = "comm" +version = "0.2.2" +requires_python = ">=3.8" +summary = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +groups = ["user"] +dependencies = [ + "traitlets>=4", +] +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[[package]] +name = "coverage" +version = "7.6.8" +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["dev", "test"] +files = [ + {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, + {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, + {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, + {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, + {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, + {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, + {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, + {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, + {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, + {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, + {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, + {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, + {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, +] + +[[package]] +name = "coverage" +version = "7.6.8" +extras = ["toml"] +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["dev", "test"] +dependencies = [ + "coverage==7.6.8", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, + {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, + {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, + {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, + {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, + {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, + {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, + {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, + {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, + {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, + {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, + {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, + {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, +] + +[[package]] +name = "debugpy" +version = "1.8.9" +requires_python = ">=3.8" +summary = "An implementation of the Debug Adapter Protocol for Python" +groups = ["user"] +files = [ + {file = "debugpy-1.8.9-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e"}, + {file = "debugpy-1.8.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f"}, + {file = "debugpy-1.8.9-cp310-cp310-win32.whl", hash = "sha256:c36856343cbaa448171cba62a721531e10e7ffb0abff838004701454149bc037"}, + {file = "debugpy-1.8.9-cp310-cp310-win_amd64.whl", hash = "sha256:17c5e0297678442511cf00a745c9709e928ea4ca263d764e90d233208889a19e"}, + {file = "debugpy-1.8.9-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:b74a49753e21e33e7cf030883a92fa607bddc4ede1aa4145172debc637780040"}, + {file = "debugpy-1.8.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d22dacdb0e296966d7d74a7141aaab4bec123fa43d1a35ddcb39bf9fd29d70"}, + {file = "debugpy-1.8.9-cp311-cp311-win32.whl", hash = "sha256:8138efff315cd09b8dcd14226a21afda4ca582284bf4215126d87342bba1cc66"}, + {file = "debugpy-1.8.9-cp311-cp311-win_amd64.whl", hash = "sha256:ff54ef77ad9f5c425398efb150239f6fe8e20c53ae2f68367eba7ece1e96226d"}, + {file = "debugpy-1.8.9-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:957363d9a7a6612a37458d9a15e72d03a635047f946e5fceee74b50d52a9c8e2"}, + {file = "debugpy-1.8.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e565fc54b680292b418bb809f1386f17081d1346dca9a871bf69a8ac4071afe"}, + {file = "debugpy-1.8.9-cp312-cp312-win32.whl", hash = "sha256:3e59842d6c4569c65ceb3751075ff8d7e6a6ada209ceca6308c9bde932bcef11"}, + {file = "debugpy-1.8.9-cp312-cp312-win_amd64.whl", hash = "sha256:66eeae42f3137eb428ea3a86d4a55f28da9bd5a4a3d369ba95ecc3a92c1bba53"}, + {file = "debugpy-1.8.9-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd"}, + {file = "debugpy-1.8.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee"}, + {file = "debugpy-1.8.9-cp313-cp313-win32.whl", hash = "sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee"}, + {file = "debugpy-1.8.9-cp313-cp313-win_amd64.whl", hash = "sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a"}, + {file = "debugpy-1.8.9-py2.py3-none-any.whl", hash = "sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899"}, + {file = "debugpy-1.8.9.zip", hash = "sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +requires_python = ">=3.5" +summary = "Decorators for Humans" +groups = ["dev", "user"] +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "XML bomb protection for Python stdlib modules" +groups = ["user"] +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "distlib" +version = "0.3.9" +summary = "Distribution utilities" +groups = ["dev"] +files = [ + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, +] + +[[package]] +name = "dotenv" +version = "0.0.5" +summary = "Handle .env files" +groups = ["dev"] +files = [ + {file = "dotenv-0.0.5.tar.gz", hash = "sha256:b58d2ab3f83dbd4f8a362b21158a606bee87317a9444485566b3c8f0af847091"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["dev", "test", "user"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "execnet" +version = "2.1.1" +requires_python = ">=3.8" +summary = "execnet: rapid multi-Python deployment" +groups = ["test"] +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[[package]] +name = "executing" +version = "2.1.0" +requires_python = ">=3.8" +summary = "Get the currently executing AST node of a frame, and other information" +groups = ["dev", "user"] +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.0" +summary = "Fastest Python implementation of JSON schema" +groups = ["user"] +files = [ + {file = "fastjsonschema-2.21.0-py3-none-any.whl", hash = "sha256:5b23b8e7c9c6adc0ecb91c03a0768cb48cd154d9159378a69c8318532e0b5cbf"}, + {file = "fastjsonschema-2.21.0.tar.gz", hash = "sha256:a02026bbbedc83729da3bfff215564b71902757f33f60089f1abae193daa4771"}, +] + +[[package]] +name = "filelock" +version = "3.16.1" +requires_python = ">=3.8" +summary = "A platform independent file lock." +groups = ["dev"] +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +requires_python = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +summary = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +groups = ["user"] +dependencies = [ + "cached-property>=1.3.0; python_version < \"3.8\"", +] +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +groups = ["user"] +dependencies = [ + "typing-extensions; python_version < \"3.8\"", +] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +requires_python = ">=3.8" +summary = "A minimal low-level HTTP client." +groups = ["user"] +dependencies = [ + "certifi", + "h11<0.15,>=0.13", +] +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[[package]] +name = "httpx" +version = "0.28.0" +requires_python = ">=3.8" +summary = "The next generation HTTP client." +groups = ["user"] +dependencies = [ + "anyio", + "certifi", + "httpcore==1.*", + "idna", +] +files = [ + {file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"}, + {file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default", "user"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["dev", "test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipdb" +version = "0.13.13" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "IPython-enabled pdb" +groups = ["dev"] +dependencies = [ + "decorator; python_version == \"3.5\"", + "decorator; python_version == \"3.6\"", + "decorator; python_version > \"3.6\" and python_version < \"3.11\"", + "decorator; python_version >= \"3.11\"", + "decorator<5.0.0; python_version == \"2.7\"", + "decorator<5.0.0; python_version == \"3.4\"", + "ipython<6.0.0,>=5.1.0; python_version == \"2.7\"", + "ipython<7.0.0,>=6.0.0; python_version == \"3.4\"", + "ipython<7.10.0,>=7.0.0; python_version == \"3.5\"", + "ipython<7.17.0,>=7.16.3; python_version == \"3.6\"", + "ipython>=7.31.1; python_version > \"3.6\" and python_version < \"3.11\"", + "ipython>=7.31.1; python_version >= \"3.11\"", + "pathlib; python_version == \"2.7\"", + "toml>=0.10.2; python_version == \"2.7\"", + "toml>=0.10.2; python_version == \"3.4\"", + "toml>=0.10.2; python_version == \"3.5\"", + "tomli; python_version == \"3.6\"", + "tomli; python_version > \"3.6\" and python_version < \"3.11\"", +] +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +requires_python = ">=3.8" +summary = "IPython Kernel for Jupyter" +groups = ["user"] +dependencies = [ + "appnope; platform_system == \"Darwin\"", + "comm>=0.1.1", + "debugpy>=1.6.5", + "ipython>=7.23.1", + "jupyter-client>=6.1.12", + "jupyter-core!=5.0.*,>=4.12", + "matplotlib-inline>=0.1", + "nest-asyncio", + "packaging", + "psutil", + "pyzmq>=24", + "tornado>=6.1", + "traitlets>=5.4.0", +] +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[[package]] +name = "ipython" +version = "8.30.0" +requires_python = ">=3.10" +summary = "IPython: Productive Interactive Computing" +groups = ["dev", "user"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "decorator", + "exceptiongroup; python_version < \"3.11\"", + "jedi>=0.16", + "matplotlib-inline", + "pexpect>4.3; sys_platform != \"win32\" and sys_platform != \"emscripten\"", + "prompt-toolkit<3.1.0,>=3.0.41", + "pygments>=2.4.0", + "stack-data", + "traitlets>=5.13.0", + "typing-extensions>=4.6; python_version < \"3.12\"", +] +files = [ + {file = "ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321"}, + {file = "ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e"}, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +requires_python = ">=3.7" +summary = "Jupyter interactive widgets" +groups = ["user"] +dependencies = [ + "comm>=0.1.3", + "ipython>=6.1.0", + "jupyterlab-widgets~=3.0.12", + "traitlets>=4.3.1", + "widgetsnbextension~=4.0.12", +] +files = [ + {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, + {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +requires_python = ">=3.7" +summary = "Operations with ISO 8601 durations" +groups = ["user"] +dependencies = [ + "arrow>=0.15.0", +] +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[[package]] +name = "jedi" +version = "0.19.2" +requires_python = ">=3.6" +summary = "An autocompletion tool for Python that can be used for text editors." +groups = ["dev", "user"] +dependencies = [ + "parso<0.9.0,>=0.8.4", +] +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["user"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[[package]] +name = "json5" +version = "0.10.0" +requires_python = ">=3.8.0" +summary = "A Python implementation of the JSON5 data format." +groups = ["user"] +files = [ + {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, + {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +requires_python = ">=3.7" +summary = "Identify specific nodes in a JSON document (RFC 6901) " +groups = ["user"] +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +requires_python = ">=3.8" +summary = "An implementation of JSON Schema validation for Python" +groups = ["user"] +dependencies = [ + "attrs>=22.2.0", + "importlib-resources>=1.4.0; python_version < \"3.9\"", + "jsonschema-specifications>=2023.03.6", + "pkgutil-resolve-name>=1.3.10; python_version < \"3.9\"", + "referencing>=0.28.4", + "rpds-py>=0.7.1", +] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +requires_python = ">=3.9" +summary = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +groups = ["user"] +dependencies = [ + "referencing>=0.31.0", +] +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +extras = ["format-nongpl"] +requires_python = ">=3.8" +summary = "An implementation of JSON Schema validation for Python" +groups = ["user"] +dependencies = [ + "fqdn", + "idna", + "isoduration", + "jsonpointer>1.13", + "jsonschema==4.23.0", + "rfc3339-validator", + "rfc3986-validator>0.1.0", + "uri-template", + "webcolors>=24.6.0", +] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[[package]] +name = "jupyter" +version = "1.1.1" +summary = "Jupyter metapackage. Install all the Jupyter components in one go." +groups = ["user"] +dependencies = [ + "ipykernel", + "ipywidgets", + "jupyter-console", + "jupyterlab", + "nbconvert", + "notebook", +] +files = [ + {file = "jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83"}, + {file = "jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a"}, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +requires_python = ">=3.8" +summary = "Jupyter protocol implementation and client libraries" +groups = ["user"] +dependencies = [ + "importlib-metadata>=4.8.3; python_version < \"3.10\"", + "jupyter-core!=5.0.*,>=4.12", + "python-dateutil>=2.8.2", + "pyzmq>=23.0", + "tornado>=6.2", + "traitlets>=5.3", +] +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +requires_python = ">=3.7" +summary = "Jupyter terminal console" +groups = ["user"] +dependencies = [ + "ipykernel>=6.14", + "ipython", + "jupyter-client>=7.0.0", + "jupyter-core!=5.0.*,>=4.12", + "prompt-toolkit>=3.0.30", + "pygments", + "pyzmq>=17", + "traitlets>=5.4", +] +files = [ + {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, + {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +requires_python = ">=3.8" +summary = "Jupyter core package. A base package on which Jupyter projects rely." +groups = ["user"] +dependencies = [ + "platformdirs>=2.5", + "pywin32>=300; sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"", + "traitlets>=5.3", +] +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[[package]] +name = "jupyter-events" +version = "0.10.0" +requires_python = ">=3.8" +summary = "Jupyter Event System library" +groups = ["user"] +dependencies = [ + "jsonschema[format-nongpl]>=4.18.0", + "python-json-logger>=2.0.4", + "pyyaml>=5.3", + "referencing", + "rfc3339-validator", + "rfc3986-validator>=0.1.1", + "traitlets>=5.3", +] +files = [ + {file = "jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960"}, + {file = "jupyter_events-0.10.0.tar.gz", hash = "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22"}, +] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +requires_python = ">=3.8" +summary = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +groups = ["user"] +dependencies = [ + "importlib-metadata>=4.8.3; python_version < \"3.10\"", + "jupyter-server>=1.1.2", +] +files = [ + {file = "jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001"}, + {file = "jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da"}, +] + +[[package]] +name = "jupyter-server" +version = "2.14.2" +requires_python = ">=3.8" +summary = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +groups = ["user"] +dependencies = [ + "anyio>=3.1.0", + "argon2-cffi>=21.1", + "jinja2>=3.0.3", + "jupyter-client>=7.4.4", + "jupyter-core!=5.0.*,>=4.12", + "jupyter-events>=0.9.0", + "jupyter-server-terminals>=0.4.4", + "nbconvert>=6.4.4", + "nbformat>=5.3.0", + "overrides>=5.0", + "packaging>=22.0", + "prometheus-client>=0.9", + "pywinpty>=2.0.1; os_name == \"nt\"", + "pyzmq>=24", + "send2trash>=1.8.2", + "terminado>=0.8.3", + "tornado>=6.2.0", + "traitlets>=5.6.0", + "websocket-client>=1.7", +] +files = [ + {file = "jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd"}, + {file = "jupyter_server-2.14.2.tar.gz", hash = "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b"}, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +requires_python = ">=3.8" +summary = "A Jupyter Server Extension Providing Terminals." +groups = ["user"] +dependencies = [ + "pywinpty>=2.0.3; os_name == \"nt\"", + "terminado>=0.8.3", +] +files = [ + {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, + {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, +] + +[[package]] +name = "jupyterlab" +version = "4.2.6" +requires_python = ">=3.8" +summary = "JupyterLab computational environment" +groups = ["user"] +dependencies = [ + "async-lru>=1.0.0", + "httpx>=0.25.0", + "importlib-metadata>=4.8.3; python_version < \"3.10\"", + "importlib-resources>=1.4; python_version < \"3.9\"", + "ipykernel>=6.5.0", + "jinja2>=3.0.3", + "jupyter-core", + "jupyter-lsp>=2.0.0", + "jupyter-server<3,>=2.4.0", + "jupyterlab-server<3,>=2.27.1", + "notebook-shim>=0.2", + "packaging", + "setuptools>=40.1.0", + "tomli>=1.2.2; python_version < \"3.11\"", + "tornado>=6.2.0", + "traitlets", +] +files = [ + {file = "jupyterlab-4.2.6-py3-none-any.whl", hash = "sha256:78dd42cae5b460f377624b03966a8730e3b0692102ddf5933a2a3730c1bc0a20"}, + {file = "jupyterlab-4.2.6.tar.gz", hash = "sha256:625f3ac19da91f9706baf66df25723b2f1307c1159fc7293035b066786d62a4a"}, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +requires_python = ">=3.8" +summary = "Pygments theme using JupyterLab CSS variables" +groups = ["user"] +files = [ + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +requires_python = ">=3.8" +summary = "A set of server components for JupyterLab and JupyterLab like applications." +groups = ["user"] +dependencies = [ + "babel>=2.10", + "importlib-metadata>=4.8.3; python_version < \"3.10\"", + "jinja2>=3.0.3", + "json5>=0.9.0", + "jsonschema>=4.18.0", + "jupyter-server<3,>=1.21", + "packaging>=21.3", + "requests>=2.31", +] +files = [ + {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, + {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +requires_python = ">=3.7" +summary = "Jupyter interactive widgets for JupyterLab" +groups = ["user"] +files = [ + {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, + {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +requires_python = ">=3.9" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["user"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +requires_python = ">=3.8" +summary = "Inline Matplotlib backend for Jupyter" +groups = ["dev", "user"] +dependencies = [ + "traitlets", +] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[[package]] +name = "mistune" +version = "3.0.2" +requires_python = ">=3.7" +summary = "A sane and fast Markdown parser with useful plugins and renderers" +groups = ["user"] +files = [ + {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, + {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, +] + +[[package]] +name = "nbclient" +version = "0.10.1" +requires_python = ">=3.8.0" +summary = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +groups = ["user"] +dependencies = [ + "jupyter-client>=6.1.12", + "jupyter-core!=5.0.*,>=4.12", + "nbformat>=5.1", + "traitlets>=5.4", +] +files = [ + {file = "nbclient-0.10.1-py3-none-any.whl", hash = "sha256:949019b9240d66897e442888cfb618f69ef23dc71c01cb5fced8499c2cfc084d"}, + {file = "nbclient-0.10.1.tar.gz", hash = "sha256:3e93e348ab27e712acd46fccd809139e356eb9a31aab641d1a7991a6eb4e6f68"}, +] + +[[package]] +name = "nbconvert" +version = "7.16.4" +requires_python = ">=3.8" +summary = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." +groups = ["user"] +dependencies = [ + "beautifulsoup4", + "bleach!=5.0.0", + "defusedxml", + "importlib-metadata>=3.6; python_version < \"3.10\"", + "jinja2>=3.0", + "jupyter-core>=4.7", + "jupyterlab-pygments", + "markupsafe>=2.0", + "mistune<4,>=2.0.3", + "nbclient>=0.5.0", + "nbformat>=5.7", + "packaging", + "pandocfilters>=1.4.1", + "pygments>=2.4.1", + "tinycss2", + "traitlets>=5.1", +] +files = [ + {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, + {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +requires_python = ">=3.8" +summary = "The Jupyter Notebook format" +groups = ["user"] +dependencies = [ + "fastjsonschema>=2.15", + "jsonschema>=2.6", + "jupyter-core!=5.0.*,>=4.12", + "traitlets>=5.1", +] +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +requires_python = ">=3.5" +summary = "Patch asyncio to allow nested event loops" +groups = ["user"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "notebook" +version = "7.2.2" +requires_python = ">=3.8" +summary = "Jupyter Notebook - A web-based notebook environment for interactive computing" +groups = ["user"] +dependencies = [ + "jupyter-server<3,>=2.4.0", + "jupyterlab-server<3,>=2.27.1", + "jupyterlab<4.3,>=4.2.0", + "notebook-shim<0.3,>=0.2", + "tornado>=6.2.0", +] +files = [ + {file = "notebook-7.2.2-py3-none-any.whl", hash = "sha256:c89264081f671bc02eec0ed470a627ed791b9156cad9285226b31611d3e9fe1c"}, + {file = "notebook-7.2.2.tar.gz", hash = "sha256:2ef07d4220421623ad3fe88118d687bc0450055570cdd160814a59cf3a1c516e"}, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +requires_python = ">=3.7" +summary = "A shim layer for notebook traits and config" +groups = ["user"] +dependencies = [ + "jupyter-server<3,>=1.8", +] +files = [ + {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, + {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, +] + +[[package]] +name = "overrides" +version = "7.7.0" +requires_python = ">=3.6" +summary = "A decorator to automatically detect mismatch when overriding a method." +groups = ["user"] +dependencies = [ + "typing; python_version < \"3.5\"", +] +files = [ + {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, + {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, +] + +[[package]] +name = "packaging" +version = "24.2" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["dev", "test", "user"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Utilities for writing pandoc filters in python" +groups = ["user"] +files = [ + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +requires_python = ">=3.6" +summary = "A Python Parser" +groups = ["dev", "user"] +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +summary = "Pexpect allows easy control of interactive console applications." +groups = ["dev", "user"] +marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +dependencies = [ + "ptyprocess>=0.5", +] +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[[package]] +name = "pip" +version = "24.3.1" +requires_python = ">=3.8" +summary = "The PyPA recommended tool for installing Python packages." +groups = ["build"] +files = [ + {file = "pip-24.3.1-py3-none-any.whl", hash = "sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed"}, + {file = "pip-24.3.1.tar.gz", hash = "sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["dev", "user"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["dev", "test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "prometheus-client" +version = "0.21.0" +requires_python = ">=3.8" +summary = "Python client for the Prometheus monitoring system." +groups = ["user"] +files = [ + {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, + {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +groups = ["dev", "user"] +dependencies = [ + "wcwidth", +] +files = [ + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, +] + +[[package]] +name = "psutil" +version = "6.1.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Cross-platform lib for process and system monitoring in Python." +groups = ["user"] +files = [ + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +summary = "Run a subprocess in a pseudo terminal" +groups = ["dev", "user"] +marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\" or os_name != \"nt\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +summary = "Safely evaluate AST nodes without side effects" +groups = ["dev", "user"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +requires_python = ">=3.8" +summary = "C parser in Python" +groups = ["user"] +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["dev", "user"] +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[[package]] +name = "pytest" +version = "8.3.3" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["dev", "test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +requires_python = ">=3.9" +summary = "Pytest plugin for measuring coverage." +groups = ["dev", "test"] +dependencies = [ + "coverage[toml]>=7.5", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +requires_python = ">=3.8" +summary = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +groups = ["test"] +dependencies = [ + "execnet>=2.1", + "pytest>=7.0.0", +] +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +groups = ["user"] +dependencies = [ + "six>=1.5", +] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[[package]] +name = "python-json-logger" +version = "2.0.7" +requires_python = ">=3.6" +summary = "A python library adding a json log formatter" +groups = ["user"] +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, +] + +[[package]] +name = "pywin32" +version = "308" +summary = "Python for Window Extensions" +groups = ["user"] +marker = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.14" +requires_python = ">=3.8" +summary = "Pseudo terminal support for Windows from Python." +groups = ["user"] +marker = "os_name == \"nt\"" +files = [ + {file = "pywinpty-2.0.14-cp310-none-win_amd64.whl", hash = "sha256:0b149c2918c7974f575ba79f5a4aad58bd859a52fa9eb1296cc22aa412aa411f"}, + {file = "pywinpty-2.0.14-cp311-none-win_amd64.whl", hash = "sha256:cf2a43ac7065b3e0dc8510f8c1f13a75fb8fde805efa3b8cff7599a1ef497bc7"}, + {file = "pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737"}, + {file = "pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819"}, + {file = "pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["user"] +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.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +requires_python = ">=3.7" +summary = "Python bindings for 0MQ" +groups = ["user"] +dependencies = [ + "cffi; implementation_name == \"pypy\"", +] +files = [ + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, +] + +[[package]] +name = "referencing" +version = "0.35.1" +requires_python = ">=3.8" +summary = "JSON Referencing + Python" +groups = ["user"] +dependencies = [ + "attrs>=22.2.0", + "rpds-py>=0.7.0", +] +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default", "user"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "A pure python RFC3339 validator" +groups = ["user"] +dependencies = [ + "six", +] +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Pure python rfc3986 validator" +groups = ["user"] +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + +[[package]] +name = "rpds-py" +version = "0.21.0" +requires_python = ">=3.9" +summary = "Python bindings to Rust's persistent data structures (rpds)" +groups = ["user"] +files = [ + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, +] + +[[package]] +name = "ruff" +version = "0.8.1" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["lint"] +files = [ + {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, + {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, + {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, + {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, + {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, + {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, + {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Send file to trash natively under Mac OS X, Windows and Linux" +groups = ["user"] +files = [ + {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, + {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, +] + +[[package]] +name = "setuptools" +version = "75.6.0" +requires_python = ">=3.9" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" +groups = ["user"] +files = [ + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, +] + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +groups = ["user"] +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" +groups = ["user"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["user"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +summary = "Extract data from python stack frames and tracebacks for informative displays" +groups = ["dev", "user"] +dependencies = [ + "asttokens>=2.1.0", + "executing>=1.2.0", + "pure-eval", +] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[[package]] +name = "terminado" +version = "0.18.1" +requires_python = ">=3.8" +summary = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +groups = ["user"] +dependencies = [ + "ptyprocess; os_name != \"nt\"", + "pywinpty>=1.1.0; os_name == \"nt\"", + "tornado>=6.1.0", +] +files = [ + {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, + {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +requires_python = ">=3.8" +summary = "A tiny CSS parser" +groups = ["user"] +dependencies = [ + "webencodings>=0.4", +] +files = [ + {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, + {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +requires_python = ">=3.8" +summary = "A lil' TOML parser" +groups = ["dev", "test", "user"] +marker = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "tornado" +version = "6.4.2" +requires_python = ">=3.8" +summary = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +groups = ["user"] +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 = "traitlets" +version = "5.14.3" +requires_python = ">=3.8" +summary = "Traitlets Python configuration system" +groups = ["dev", "user"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241003" +requires_python = ">=3.8" +summary = "Typing stubs for python-dateutil" +groups = ["user"] +files = [ + {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, + {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["dev", "user"] +marker = "python_version < \"3.12\"" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +requires_python = ">=3.7" +summary = "RFC 6570 URI Template Processor" +groups = ["user"] +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default", "user"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[[package]] +name = "virtualenv" +version = "20.28.0" +requires_python = ">=3.8" +summary = "Virtual Python Environment builder" +groups = ["dev"] +dependencies = [ + "distlib<1,>=0.3.7", + "filelock<4,>=3.12.2", + "importlib-metadata>=6.6; python_version < \"3.8\"", + "platformdirs<5,>=3.9.1", +] +files = [ + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +summary = "Measures the displayed width of unicode strings in a terminal" +groups = ["dev", "user"] +dependencies = [ + "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", +] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +requires_python = ">=3.9" +summary = "A library for working with the color formats defined by HTML and CSS." +groups = ["user"] +files = [ + {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, + {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +summary = "Character encoding aliases for legacy web content" +groups = ["user"] +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +requires_python = ">=3.8" +summary = "WebSocket client for Python with low level API options" +groups = ["user"] +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +requires_python = ">=3.7" +summary = "Jupyter interactive widgets for Jupyter Notebook" +groups = ["user"] +files = [ + {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, + {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a5d4230 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,195 @@ +[project] + +# PEP 621 project metadata +# See https://www.python.org/dev/peps/pep-0621/ + +# For the standard reference on Python packaging, including using +# ``pyproject.toml`` for build definition and configuration see: +# +# https://packaging.python.org/en/latest/tutorials/packaging-projects/ +# +name = "fsrapiclient" +description = "Lightweight Python client library for the UK Financial Services Register (FSR) RESTful API" + +authors = [ + {name = "S. R. Murthy", email = "s.murthy@tutanota.com"}, +] + +maintainers = [ + {name = "S. R. Murthy", email = "s.murthy@tutanota.com"} +] + +dynamic = ["version"] + +requires-python = ">=3.10" + +license = {text = "MPL"} + +dependencies = [ + "requests", +] + +readme = "README.md" + +keywords = [ + "authorised firm", + "financial conduct authority", + "financial data", + "financial services register", + "prudential regulation authority", + "regulated firm", + "restful api", +] + +classifiers = [ + "Environment :: Console", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Development Status :: 5 - Production/Stable", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", +] +version = "0.1.0" + +[project.optional-dependencies] +user = [ + "jupyter", +] + +[project.scripts] + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[project.urls] +Homepage = "https://github.com/sr-murthy/fsrapiclient" +Documentation = "https://github.com/sr-murthy/fsrapiclient" +Repository = "https://github.com/sr-murthy/fsrapiclient" + +# For working with PDM, including configuration and usage see: +# +# https://pdm.fming.dev/latest/ +# https://pdm.fming.dev/latest/reference/pep621/ +# https://pdm.fming.dev/latest/reference/cli/ +# +[tool.pdm] +distribution = true + +[tool.pdm.build] +package-dir = "src" +includes = [ + "src/fsrapiclient", +] +# File patterns to exclude, the paths are relative to the project root. +excludes = [ + "docs", + "build", + "dist", + "tests", + ".pytest_cache", + "*.pyc", + "*.env", + "*__pycache__*", +] +source-includes = [ + "README.md", +] +editable-backend = "path" + +[tool.pdm.dev-dependencies] +build = [ + "pip>=23.1.2", +] +dev = [ + "dotenv", + "pytest", + "pytest-cov", + "coverage", + "ipython", + "ipdb", + "virtualenv", +] +lint = [ + "ruff", +] +test = [ + "coverage[toml]", + "pytest", + "pytest-cov", + "pytest-xdist", +] + +[tool.pdm.scripts] +lint = {shell = "make lint", help = "Lint source with Ruff"} +doctests = {shell = "make doctests", help = "Run doctests"} +unittests = {shell = "make unittests", help = "Run unit tests & measure coverage"} + +[tool.pdm.version] +source = "file" +path = "src/__version__.py" + +# For ``pytest`` configuration, including TOML-specific configuration see: +# +# https://docs.pytest.org/en/7.3.x/reference/customize.html#configuration +# https://docs.pytest.org/en/7.3.x/reference/customize.html#pyproject-toml +# + +[tool.pytest.ini_options] +pythonpath = "src" +addopts = """\ + --cache-clear \ + --code-highlight=yes \ + --color=yes \ + --cov=src \ + --cov-config=pyproject.toml \ + --cov-report=xml \ + --cov-report=html \ + --dist worksteal \ + --numprocesses=auto \ + -ra \ + --tb=native \ + --verbosity=3 \ +""" +filterwarnings = [ + "ignore::DeprecationWarning" +] +python_files = ["test_*.py",] +python_classes = ["Test", "Acceptance",] +python_functions = ["test"] +testpaths = [ + "tests/", +] +markers = [ + # Test tags/labels/markers - not using any currently +] + +# For ``coverage`` configuration see the reference from version ``7.4.1``: +# +# https://coverage.readthedocs.io/en/7.4.1/config.html +# +[tool.coverage.run] +branch = true +omit = [ + "./build", + "./dist", + "./docs", + "*/tests*", + ".pytest_cache", + "*.pyc", + "*.env", + "*__pycache__*", + "__version__.py" +] +source = ["src"] + +[tool.coverage.report] +precision = 3 +show_missing = true +skip_covered = true +skip_empty = true +sort = "Name" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..70003c2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,278 @@ +# This file is @generated by PDM. +# Please do not edit it manually. + +asttokens==3.0.0 \ + --hash=sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7 \ + --hash=sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2 +certifi==2024.8.30 \ + --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ + --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 +charset-normalizer==3.4.0 \ + --hash=sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6 \ + --hash=sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8 \ + --hash=sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912 \ + --hash=sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c \ + --hash=sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b \ + --hash=sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d \ + --hash=sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95 \ + --hash=sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e \ + --hash=sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565 \ + --hash=sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64 \ + --hash=sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e \ + --hash=sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907 \ + --hash=sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23 \ + --hash=sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc \ + --hash=sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284 \ + --hash=sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca \ + --hash=sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b \ + --hash=sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594 \ + --hash=sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc \ + --hash=sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db \ + --hash=sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b \ + --hash=sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6 \ + --hash=sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920 \ + --hash=sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749 \ + --hash=sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7 \ + --hash=sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99 \ + --hash=sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129 \ + --hash=sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2 \ + --hash=sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee \ + --hash=sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b \ + --hash=sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe \ + --hash=sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3 \ + --hash=sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5 \ + --hash=sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631 \ + --hash=sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7 \ + --hash=sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15 \ + --hash=sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c \ + --hash=sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea \ + --hash=sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250 \ + --hash=sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88 \ + --hash=sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99 \ + --hash=sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d \ + --hash=sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90 \ + --hash=sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9 \ + --hash=sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1 \ + --hash=sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719 \ + --hash=sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236 \ + --hash=sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c \ + --hash=sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944 \ + --hash=sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc \ + --hash=sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6 \ + --hash=sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27 \ + --hash=sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114 \ + --hash=sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf \ + --hash=sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d \ + --hash=sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed \ + --hash=sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03 \ + --hash=sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67 \ + --hash=sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365 \ + --hash=sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b \ + --hash=sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079 \ + --hash=sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482 +colorama==0.4.6; sys_platform == "win32" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +coverage[toml]==7.6.8 \ + --hash=sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5 \ + --hash=sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf \ + --hash=sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb \ + --hash=sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638 \ + --hash=sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4 \ + --hash=sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed \ + --hash=sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a \ + --hash=sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d \ + --hash=sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649 \ + --hash=sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b \ + --hash=sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4 \ + --hash=sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443 \ + --hash=sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83 \ + --hash=sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee \ + --hash=sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e \ + --hash=sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3 \ + --hash=sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0 \ + --hash=sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb \ + --hash=sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb \ + --hash=sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787 \ + --hash=sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1 \ + --hash=sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce \ + --hash=sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801 \ + --hash=sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764 \ + --hash=sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365 \ + --hash=sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf \ + --hash=sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6 \ + --hash=sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71 \ + --hash=sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002 \ + --hash=sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c \ + --hash=sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8 \ + --hash=sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4 \ + --hash=sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146 \ + --hash=sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc \ + --hash=sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4 \ + --hash=sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad \ + --hash=sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28 \ + --hash=sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451 \ + --hash=sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50 \ + --hash=sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63 \ + --hash=sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e \ + --hash=sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc \ + --hash=sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022 \ + --hash=sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d \ + --hash=sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94 \ + --hash=sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b \ + --hash=sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d \ + --hash=sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331 \ + --hash=sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a \ + --hash=sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee \ + --hash=sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a \ + --hash=sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9 +decorator==5.1.1 \ + --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ + --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 +dotenv==0.0.5 \ + --hash=sha256:b58d2ab3f83dbd4f8a362b21158a606bee87317a9444485566b3c8f0af847091 +exceptiongroup==1.2.2; python_version < "3.11" \ + --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ + --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc +execnet==2.1.1 \ + --hash=sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc \ + --hash=sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3 +executing==2.1.0 \ + --hash=sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf \ + --hash=sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 +ipdb==0.13.13 \ + --hash=sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4 \ + --hash=sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726 +ipython==8.30.0 \ + --hash=sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321 \ + --hash=sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e +jedi==0.19.2 \ + --hash=sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0 \ + --hash=sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9 +matplotlib-inline==0.1.7 \ + --hash=sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90 \ + --hash=sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f +parso==0.8.4 \ + --hash=sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18 \ + --hash=sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d +pexpect==4.9.0; sys_platform != "win32" and sys_platform != "emscripten" \ + --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \ + --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f +pip==24.3.1 \ + --hash=sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed \ + --hash=sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 +prompt-toolkit==3.0.48 \ + --hash=sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90 \ + --hash=sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e +ptyprocess==0.7.0; sys_platform != "win32" and sys_platform != "emscripten" or os_name != "nt" \ + --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \ + --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220 +pure-eval==0.2.3 \ + --hash=sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 \ + --hash=sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42 +pygments==2.18.0 \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a +pytest==8.3.3 \ + --hash=sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181 \ + --hash=sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2 +pytest-cov==6.0.0 \ + --hash=sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35 \ + --hash=sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0 +pytest-xdist==3.6.1 \ + --hash=sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7 \ + --hash=sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 +ruff==0.8.1 \ + --hash=sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871 \ + --hash=sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1 \ + --hash=sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d \ + --hash=sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6 \ + --hash=sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5 \ + --hash=sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f \ + --hash=sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1 \ + --hash=sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737 \ + --hash=sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790 \ + --hash=sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9 \ + --hash=sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540 \ + --hash=sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26 \ + --hash=sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c \ + --hash=sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa \ + --hash=sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087 \ + --hash=sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209 \ + --hash=sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5 \ + --hash=sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5 +stack-data==0.6.3 \ + --hash=sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9 \ + --hash=sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 +tomli==2.2.1; python_version < "3.11" \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 +traitlets==5.14.3 \ + --hash=sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7 \ + --hash=sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f +typing-extensions==4.12.2; python_version < "3.12" \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 +urllib3==2.2.3 \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 +virtualenv==20.28.0 \ + --hash=sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0 \ + --hash=sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa +wcwidth==0.2.13 \ + --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ + --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 diff --git a/src/__version__.py b/src/__version__.py new file mode 100644 index 0000000..f38884b --- /dev/null +++ b/src/__version__.py @@ -0,0 +1,2 @@ +__version__ = "0.1.0" + diff --git a/src/fsrapiclient/api.py b/src/fsrapiclient/api.py new file mode 100644 index 0000000..5506d4f --- /dev/null +++ b/src/fsrapiclient/api.py @@ -0,0 +1,1438 @@ +from __future__ import annotations + + +__all__ = ['FsrApiClient', + 'FsrApiResponse', + 'FsrApiSession',] + + +# -- IMPORTS -- + +# -- Standard libraries -- +import pathlib +import sys +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from urllib.parse import urlencode + +# -- 3rd party libraries -- +import requests + +from requests.models import Response + +# -- Internal libraries -- +from fsrapiclient.constants import FSR_API_CONSTANTS +from fsrapiclient.exceptions import ( + FsrApiRequestException, + FsrApiResponseException, +) + + +class FsrApiSession(requests.Session): + """A simple ``requests.Session``-based class for an FSR API session. + + Examples + -------- + >>> import os + >>> sess = FsrApiSession(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> type(sess) + + >>> assert sess.api_username == os.environ['API_USERNAME'] + >>> assert sess.api_key == os.environ['API_KEY'] + >>> assert sess.headers == {'Accept': 'application/json', 'X-Auth-Email': os.environ['API_USERNAME'], 'X-Auth-Key': os.environ['API_KEY']} + """ + + _api_username: str + _api_key: str + + def __init__(self, api_username: str, api_key: str): + super().__init__() + + self._api_username = api_username + self._api_key = api_key + self.headers = { + 'Accept': 'application/json', + 'X-Auth-Email': self._api_username, + 'X-Auth-Key': self._api_key + } + + @property + def api_username(self) -> str: + return self._api_username + + @property + def api_key(self) -> str: + return self._api_key + + +class FsrApiResponse(requests.models.Response): + """A simple ``requests.Response``-based wrapper for FSR API responses. + + Examples + -------- + >>> import os; from urllib.parse import urlencode + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.common_search(urlencode({'q': 'Hastings Direct', 'type': 'firm'})) + >>> res + + """ + + __attrs__ = Response.__attrs__ + + def __init__(self, response): + self.__dict__.update(**response.__dict__) + + @property + def fsr_status(self) -> str: + return self.json().get('Status') + + @property + def fsr_resultinfo(self) -> dict: + return self.json().get('ResultInfo') + + @property + def fsr_message(self) -> str: + return self.json().get('Message') + + @property + def fsr_data(self) -> str: + return self.json().get('Data') + + +class FsrApiClient: + """API client for the FSR API (V1.0). + + Consult the developer documentation for further details on the underlying + API. + + https://register.fca.org.uk/Developer/s/ + + Examples + -------- + >>> import os; from urllib.parse import urlencode + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.common_search(urlencode({'q': 'Hastings Direct', 'type': 'firm'})) + >>> res + + >>> res.fsr_data + [{'URL': 'https://register.fca.org.uk/services/V0.1/Firm/969197', 'Status': 'Authorised', 'Reference Number': '969197', 'Type of business or Individual': 'Firm', 'Name': 'Hastings Financial Services Limited (Postcode: TN39 3LW)'}, {'URL': 'https://register.fca.org.uk/services/V0.1/Firm/311492', 'Status': 'Authorised', 'Reference Number': '311492', 'Type of business or Individual': 'Firm', 'Name': 'Hastings Insurance Services Limited (Postcode: TN39 3LW)'}, {'URL': 'https://register.fca.org.uk/services/V0.1/Firm/536726', 'Status': 'Authorised', 'Reference Number': '536726', 'Type of business or Individual': 'Firm', 'Name': 'I Go 4 Ltd. (Postcode: PE4 6JT)'}] + >>> res.fsr_status + 'FSR-API-04-01-00' + >>> res.fsr_message + 'Ok. Search successful' + >>> res.fsr_resultinfo + {'page': '1', 'per_page': '20', 'total_count': '3'} + >>> client.search_frn("Hastings Insurance Services Limited") + '311492' + >>> client.search_frn('direct line') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple firms returned. Firm name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_frn('direct line insurance plc') + '202684' + """ + + _api_session: FsrApiSession + + def __init__(self, api_username: str, api_key: str): + self._api_session = FsrApiSession(api_username, api_key) + + @property + def api_session(self) -> FsrApiSession: + return self._api_session + + @property + def api_version(self) -> str: + return FSR_API_CONSTANTS.API_VERSION.value + + def common_search(self, search_str: str) -> FsrApiResponse: + """Returns the results of the common search API endpoint. + + Directly calls on the FSR API common search endpoint + :: + + /V0.1/Search? + + to perform a case-insensitive search in the FSR on the given search + string and entity type ("firm", "individual", "fund"). + + Returns an ``FsrApiResponse`` object if the API call completes + without exceptions or errors. + + Parameters + ---------- + search_str : str + The search string - this should be a URL-encoded, parameterised + search string of the form: + :: + + q=&type= + + where ```` is the name of a firm, individual or fund + (collective investment scheme), and ```` is one of + the strings ``"firm"``, ``"individual"``, or ``"fund"``. + + Returns + ------- + FsrApiResponse + An FSR API response object, if the request completed successfully. + + Raises + ------ + FsrApiRequestException + If there was a ``requests.RequestException`` in making the original + request. + + Examples + -------- + >>> import os; from urllib.parse import urlencode + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.common_search(urlencode({'q': 'Hastings Direct', 'type': 'firm'})) + >>> res + + >>> assert res.fsr_data + >>> assert res.fsr_status + >>> assert res.fsr_message + >>> assert res.fsr_resultinfo + >>> client.common_search(urlencode({'q': 'Hsatings Direct', 'type': 'firm'})) + + """ + url = f'{FSR_API_CONSTANTS.BASEURL.value}/{self.api_version}/Search?{search_str}' + + try: + return FsrApiResponse(self.api_session.get(url)) + except requests.RequestException as e: + raise FsrApiRequestException(e) + + def search_frn(self, firm_name: str) -> str: + """Searches for the firm reference number (FRN) of a given firm. + + Uses the FSR API common search endpoint + :: + + /V0.1/Search?q=&type=firm + + to perform a case-insensitive firm-type search in the FSR on the given + firm name. + + Returns a non-null string of the FRN if the firm name is found, + otherwise ``None`` is returned. + + Parameters + ---------- + firm_name : str + The firm name - need not be in any particular case. The name + needs to be precise enough to guarantee a unique return value, + otherwise multiple records exist and an exception is raised. + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the common search + handler. + + FsrApiResponseError + If there was an error in the FSR API response or in processing the + response + + Returns + ------- + str + A string version of the firm reference number (FRN), if found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> client.search_frn("Hastings Insurance Services Limited") + '311492' + >>> client.search_frn('hiscox insurance company limited') + '113849' + >>> client.search_frn('direct line') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple firms returned. Firm name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_frn('direct line insurance') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple firms returned. Firm name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_frn('direct line insurance plc') + '202684' + >>> client.search_frn('Hiscxo Insurance Company') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. + >>> client.search_frn('hiscox insurance company') + '113849' + """ + try: + res = self.common_search(urlencode({'q': firm_name, 'type': 'firm'})) + except FsrApiRequestException: + raise + + if res.ok and res.fsr_data: + if len(res.fsr_data) > 1: + raise FsrApiResponseException( + 'Multiple firms returned. Firm name needs to be more ' + 'precise. If you are unsure of the results please use the ' + 'common search endpoint' + ) + + try: + return res.fsr_data[0]['Reference Number'] + except (KeyError, IndexError): + raise FsrApiResponseException( + 'Unexpected response data structure from the FSR API for ' + 'general firm search by name! Please check the FSR API ' + 'developer documentation at https://register.fca.org.uk/Developer/s/' + ) + elif not res.fsr_data: + raise FsrApiResponseException( + 'No data found in FSR API response. Please check the search ' + 'parameters and try again.' + ) + else: + raise FsrApiResponseException( + f'FSR API search request failed for some other reason: ' + f'{res.reason}' + ) + + def _firm_info(self, frn: str, modifiers: tuple[str] = None) -> FsrApiResponse: + """A private, base handler for firm information API handlers. + + Is the base handler for the following firm-specific FSR API endpoints + (in alphabetical order): + :: + + /V0.1/Firm/{FRN} + /V0.1/Firm/{FRN}/Address + /V0.1/Firm/{FRN}/AR + /V0.1/Firm/{FRN}/CF + /V0.1/Firm/{FRN}/DisciplinaryHistory + /V0.1/Firm/{FRN}/Exclusions + /V0.1/Firm/{FRN}/Individuals + /V0.1/Firm/{FRN}/Names + /V0.1/Firm/{FRN}/Passports + /V0.1/Firm/{FRN}/Passports/{Country}/Permission + /V0.1/Firm/{FRN}/Permissions + /V0.1/Firm/{FRN}/Regulators + /V0.1/Firm/{FRN}/Requirements + /V0.1/Firm/{FRN}/Requirements/{ReqRef}/InvestmentTypes + /V0.1/Firm/{FRN}/Waiver + + + + + .. note:: + + This is a private method and is **not** intended for direct use by + end users. + + Uses the FSR API firm endpoint(s) + :: + + /V0.1/Firm/{FRN}[/] + + and returns an ``FsrApiResponse``, e.g. a request for information + on Hiscox Insurance Company Limited, which has the FRN 113849. + :: + + /V0.1/Firm/113849 + + The optional modifiers, given as a tuple of strings, should represent a + valid ordered combination of actions and/or resources related to the + firm given by the FRN. + + The modifier strings should **NOT** contain any leading or trailing + forward slashes (``"/"``) as this can lead to badly formed URLs + and to responses with no FSR data - in any case, any leading or + trailing forward slashes are stripped before the request. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + modifiers : tuple, default=None + Optional tuple of strings indicating a valid ordered combination of + resource and/or action modifiers for the firm in question. Should + **NOT** have a leading or trailing forward slashes (``"/"``). + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the firm search + endpoint. + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + """ + url = f'{FSR_API_CONSTANTS.BASEURL.value}/{self.api_version}/Firm/{frn}' + + if modifiers: + url += f'/{"/".join(modifiers)}' + + try: + return FsrApiResponse(self.api_session.get(url)) + except requests.RequestException as e: + raise FsrApiRequestException(e) + + def get_firm(self, frn: str) -> FsrApiResponse: + """Returns firm details, given its firm reference number (FRN) + + Handler for the top-level firm details API endpoint: + :: + + /v1.0/Firm/{FRN} + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm('122702') + >>> res + + >>> assert res.fsr_data[0]['Organisation Name'] == 'Barclays Bank Plc' + >>> res = client.get_firm('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn) + + def get_firm_names(self, frn: str) -> FsrApiResponse: + """Returns the alternative trading names of a firm, given its firm reference number (FRN). + + Handler for the firm names FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Names + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_names('122702') + >>> res + + >>> assert res.fsr_data[0]['Current Names'][0]['Name'] == 'Barclays Bank' + >>> assert res.fsr_data[1]['Previous Names'] + >>> res = client.get_firm_names('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Names',)) + + def get_firm_addresses(self, frn: str) -> FsrApiResponse: + """Returns the address details of a firm, given its firm reference number (FRN). + + Handler for the firm address details FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Address + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_addresses('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_addresses('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Address',)) + + def get_firm_controlled_functions(self, frn: str) -> FsrApiResponse: + """Returns the controlled functions associated with a firm ,given its firm reference number (FRN). + + Handler for the firm controlled functions FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/CF + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_controlled_functions('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_controlled_functions('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('CF',)) + + def get_firm_individuals(self, frn: str) -> FsrApiResponse: + """Returns the individuals associated with a firm, given its firm reference number (FRN). + + Handler for the firm individuals FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Individuals + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_individuals('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_individuals('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Individuals',)) + + def get_firm_permissions(self, frn: str) -> FsrApiResponse: + """Returns the permissions associated with a firm, given its firm reference number (FRN). + + Handler for the firm permissions FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Permissions + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_permissions('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_permissions('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Permissions',)) + + def get_firm_requirements(self, frn: str) -> FsrApiResponse: + """Returns the requirements associated with a firm, given its firm reference number (FRN). + + Handler for the firm requirements FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Requirements + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_requirements('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_requirements('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Requirements',)) + + def get_firm_requirement_investment_types(self, frn: str, req_ref: str) -> FsrApiResponse: + """Returns any investment types listed for a specific requirement associated with a firm, given its firm reference number (FRN). + + Handler for the firm requirement investment types FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Requirements//InvestmentTypes + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + req_ref : str + The requirement reference number as a string. + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_requirement_investment_types('122702', 'OR-0262545') + >>> assert res.fsr_data + >>> res = client.get_firm_requirement_investment_types('1234567890', 'OR-0262545') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Requirements', req_ref, 'InvestmentTypes')) + + def get_firm_regulators(self, frn: str) -> FsrApiResponse: + """Returns the regulators associated with a firm, given its firm reference number (FRN). + + Handler for the firm regulators FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Regulators + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_regulators('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_regulators('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Regulators',)) + + def get_firm_passports(self, frn: str) -> FsrApiResponse: + """Returns the passports associated with a firm, given its firm reference number (FRN). + + Handler for the firm passports FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Passports + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_passports('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_passports('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Passports',)) + + def get_firm_passport_permissions(self, frn: str, country: str) -> FsrApiResponse: + """Returns country-specific passport permissions for a firm and a country, given its firm reference number (FRN) and country name. + + Handler for the firm passport permissions FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Requirements/{Country}/Permission + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + country : str + The country name. + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_passport_permissions('122702', 'Gibraltar') + >>> assert res.fsr_data + >>> res = client.get_firm_passport_permissions('1234567890', 'Gibraltar') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Passports', country, 'Permission')) + + def get_firm_waivers(self, frn: str) -> FsrApiResponse: + """Returns any waivers applying to a firm, given its firm reference number (FRN). + + Handler for the firm waivers FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Waivers + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_waivers('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_waivers('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Waivers',)) + + def get_firm_exclusions(self, frn: str) -> FsrApiResponse: + """Returns any exclusions applying to a firm, given its firm reference number (FRN). + + Handler for the firm exclusions FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/Exclusions + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_exclusions('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_exclusions('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('Exclusions',)) + + def get_firm_disciplinary_history(self, frn: str) -> FsrApiResponse: + """Returns the disciplinary history of a firm, given its firm reference number (FRN). + + Handler for the firm disciplinary history FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/DisciplinaryHistory + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_disciplinary_history('122702') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_firm_disciplinary_history('1234567890') + >>> assert not res.fsr_data + """ + return self._firm_info(frn, modifiers=('DisciplinaryHistory',)) + + def get_firm_appointed_representatives(self, frn: str) -> FsrApiResponse: + """Returns information on the appointed representatives of a firm, given its firm reference number (FRN). + + Handler for the firm appointed representatives FSR API endpoint: + :: + + /v1.0/Firm/{FRN}/AR + + Returns an ``FsrApiResponse``, which could have data if the + FRN exists, or null if it not. + + Parameters + ---------- + frn : str + The firm reference number (FRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_firm_appointed_representatives('122702') + >>> res + + >>> assert res.fsr_data + >>> assert any([res.fsr_data['PreviousAppointedRepresentatives'], res.fsr_data['CurrentAppointedRepresentatives']]) + >>> res = client.get_firm_appointed_representatives('1234567890') + >>> assert not any([res.fsr_data['PreviousAppointedRepresentatives'], res.fsr_data['CurrentAppointedRepresentatives']]) + """ + return self._firm_info(frn, modifiers=('AR',)) + + def search_irn(self, individual_name: str) -> str: + """Searches for the individual reference number (IRN) of a given individual. + + Uses the FSR API common search endpoint + :: + + /V0.1/Search?q=&type=individual + + to perform a case-insensitive individual-type search in the FSR on the + given name. + + Returns a non-null string of the IRN if the individual name is found, + otherwise ``None`` is returned. + + Parameters + ---------- + individual_name : str + The individual name - need not be in any particular case. The name + needs to be precise enough to guarantee a unique return value, + otherwise multiple records exist and an exception is raised. + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the common search + handler. + + FsrApiResponseError + If there was an error in the FSR API response or in processing the + response. + + Returns + ------- + str + A string version of the individual reference number (IRN), if + found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> client.search_irn('Mark Carney') + 'MXC29012' + >>> client.search_irn('mark Carney') + 'MXC29012' + >>> client.search_irn('Mark C') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple individuals returned. The individual name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_irn('A Nonexistent Person') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. + """ + try: + res = self.common_search(urlencode({'q': individual_name, 'type': 'individual'})) + except FsrApiRequestException: + raise + + if res.ok and res.fsr_data: + if len(res.fsr_data) > 1: + raise FsrApiResponseException( + 'Multiple individuals returned. The individual name needs ' + 'to be more precise. If you are unsure of the results ' + 'please use the common search endpoint' + ) + + try: + return res.fsr_data[0]['Reference Number'] + except (KeyError, IndexError): + raise FsrApiResponseException( + 'Unexpected response data structure from the FSR API for ' + 'general individual search by name! Please check the FSR API ' + 'developer documentation at https://register.fca.org.uk/Developer/s/' + ) + elif not res.fsr_data: + raise FsrApiResponseException( + 'No data found in FSR API response. Please check the search ' + 'parameters and try again.' + ) + else: + raise FsrApiResponseException( + f'FSR API search request failed for some other reason: ' + f'{res.reason}' + ) + + def _individual_info(self, irn: str, modifiers: tuple[str] = None) -> FsrApiResponse: + """A private, base handler for individual information API handlers. + + Is the base handler for the following individual-specific FSR API endpoints: + :: + + /V0.1/Individuals/{IRN} + /V0.1/Individuals/{IRN}/CF + /V0.1/Individuals/{IRN}/DisciplinaryHistory + + .. note:: + + This is a private method and is **not** intended for direct use by + end users. + + Uses the FSR API individual endpoint(s) + :: + + /V0.1/Individuals/{IRN}[/] + + and returns an ``FsrApiResponse``, e.g. a request for information + on "Mark Carney", who has the IRN 'MXC29012'. + :: + + /V0.1/Individuals/MXC29012 + + The optional modifiers, given as a tuple of strings, should represent a + valid ordered combination of actions and/or resources related to the + individual given by the IRN. + + The modifier strings should **NOT** contain any leading or trailing + forward slashes (``"/"``) as this can lead to badly formed URLs + and to responses with no FSR data - in any case, any leading or + trailing forward slashes are stripped before the request. + + Parameters + ---------- + irn : str + The individual reference number (IRN). + + modifiers : tuple, default=None + Optional tuple of strings indicating a valid ordered combination of + resource and/or action modifiers for the individual in question. + Should**NOT** have a leading or trailing forward slashes (``"/"``). + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the firm search + endpoint. + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + """ + url = f'{FSR_API_CONSTANTS.BASEURL.value}/{self.api_version}/Individuals/{irn}' + + if modifiers: + url += f'/{"/".join(modifiers)}' + + try: + return FsrApiResponse(self.api_session.get(url)) + except requests.RequestException as e: + raise FsrApiRequestException(e) + + def get_individual(self, irn: str) -> FsrApiResponse: + """Returns individual details, given their individual reference number (IRN) + + Handler for top-level individual details API endpoint: + :: + + /v1.0/Individuals/{IRN} + + Returns an ``FsrApiResponse``, which could have data if the + IRN exists, or null if it not. + + Parameters + ---------- + irn : str + The individual reference number (IRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the IRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_individual('MXC29012') + >>> res + + >>> assert res.fsr_data[0]['Details']['Full Name'] == 'Mark Carney' + >>> res = client.get_individual('1234567890') + >>> assert not res.fsr_data + """ + return self._individual_info(irn) + + def get_individual_controlled_functions(self, irn: str) -> FsrApiResponse: + """Returns the controlled functions associated with an individual, given their individual reference number (FRN). + + Handler for the individual controlled functions FSR API endpoint: + :: + + /v1.0/Firm/{IRN}/CF + + Returns an ``FsrApiResponse``, which could have data if the + IRN exists, or null if it not. + + Parameters + ---------- + irn : str + The individual reference number (IRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the IRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_individual_controlled_functions('MXC29012') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_individual_controlled_functions('1234567890') + >>> assert not res.fsr_data + """ + return self._individual_info(irn, modifiers=('CF',)) + + def get_individual_disciplinary_history(self, irn: str) -> FsrApiResponse: + """Returns the disciplinary history of an individual, given their individual reference number (FRN). + + Handler for the individual disciplinary history FSR API endpoint: + :: + + /v1.0/Firm/{IRN}/DisciplinaryHistory + + Returns an ``FsrApiResponse``, which could have data if the + IRN exists, or null if it not. + + Parameters + ---------- + irn : str + The individual reference number (IRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the IRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> client.search_irn('Leigh Mackey') + 'LXM01328' + >>> res = client.get_individual_disciplinary_history('LXM01328') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_individual_disciplinary_history('1234567890') + >>> assert not res.fsr_data + """ + return self._individual_info(irn, modifiers=('DisciplinaryHistory',)) + + def search_prn(self, fund_name: str) -> str: + """Searches for the product reference number (IRN) of a given fund (or collective investment scheme (CIS)), including subfunds. + + Uses the FSR API common search endpoint + :: + + /V0.1/Search?q=&type=fund + + to perform a case-insensitive fund-type search in the FSR on the given + name. + + Returns a non-null string of the PRN if the fund name is found, + otherwise ``None`` is returned. + + Parameters + ---------- + fund_name : str + The fund name - need not be in any particular case. The name needs + to be precise enough to guarantee a unique return value, otherwise + multiple records exist and an exception is raised. + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the common search + handler. + + FsrApiResponseError + If there was an error in the FSR API response or in processing the + response. + + Returns + ------- + str + A string version of the product reference number (PRN), if found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> client.search_prn('Northern Trust') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple funds returned. The fund name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_prn('Northern Trust High Dividend ESG World Equity') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: Multiple funds returned. The fund name needs to be more precise. If you are unsure of the results please use the common search endpoint + >>> client.search_prn('Northern Trust High Dividend ESG World Equity Feeder Fund') + '913937' + >>> client.search_prn('A nonexistent fund') + Traceback (most recent call last): + ... + fsrapiclient.exceptions.FsrApiResponseException: No data found in FSR API response. Please check the search parameters and try again. + """ + try: + res = self.common_search(urlencode({'q': fund_name, 'type': 'fund'})) + except FsrApiRequestException: + raise + + if res.ok and res.fsr_data: + if len(res.fsr_data) > 1: + raise FsrApiResponseException( + 'Multiple funds returned. The fund name needs ' + 'to be more precise. If you are unsure of the results ' + 'please use the common search endpoint' + ) + + try: + return res.fsr_data[0]['Reference Number'] + except (KeyError, IndexError): + raise FsrApiResponseException( + 'Unexpected response data structure from the FSR API for ' + 'general fund search by name! Please check the FSR API ' + 'developer documentation at https://register.fca.org.uk/Developer/s/' + ) + elif not res.fsr_data: + raise FsrApiResponseException( + 'No data found in FSR API response. Please check the search ' + 'parameters and try again.' + ) + else: + raise FsrApiResponseException( + f'FSR API search request failed for some other reason: ' + f'{res.reason}' + ) + + def _fund_info(self, prn: str, modifiers: tuple[str] = None) -> FsrApiResponse: + """A private, base handler for fund (or collective investment scheme (CIS)) information API handlers. + + Is the base handler for the following fund-specific FSR API endpoints: + :: + + /V0.1/CIS/{PRN} + /V0.1/CIS/{PRN}/Subfund + /V0.1/CIS/{PRN}/Names + + .. note:: + + This is a private method and is **not** intended for direct use by + end users. + + Uses the FSR API individual endpoint(s) + :: + + /V0.1/CIS/{PRN}[/] + + and returns an ``FsrApiResponse``, e.g. a request for information + on "'Northern Trust High Dividend ESG World Equity Feeder Fund", which + has the PRN '913937'. + :: + + /V0.1/CIS/913937 + + The optional modifiers, given as a tuple of strings, should represent a + valid ordered combination of actions and/or resources related to the + fund given by the PRN. + + The modifier strings should **NOT** contain any leading or trailing + forward slashes (``"/"``) as this can lead to badly formed URLs + and to responses with no FSR data - in any case, any leading or + trailing forward slashes are stripped before the request. + + Parameters + ---------- + prn : str + The product reference number (IRN). + + modifiers : tuple, default=None + Optional tuple of strings indicating a valid ordered combination of + resource and/or action modifiers for the fund in question. + Should**NOT** have a leading or trailing forward slashes (``"/"``). + + Raises + ------ + FsrApiRequestException + If there was a request exception from calling the firm search + endpoint. + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the FRN isn't found. + """ + url = f'{FSR_API_CONSTANTS.BASEURL.value}/{self.api_version}/CIS/{prn}' + + if modifiers: + url += f'/{"/".join(modifiers)}' + + try: + return FsrApiResponse(self.api_session.get(url)) + except requests.RequestException as e: + raise FsrApiRequestException(e) + + def get_fund(self, prn: str) -> FsrApiResponse: + """Returns fund (or collective investment scheme (CIS)) details, given its product reference number (PRN) + + Handler for top-level fund details API endpoint: + :: + + /v1.0/CIS/{PRN} + + Returns an ``FsrApiResponse``, which could have data if the PRN exists, + or null if it not. + + Parameters + ---------- + prn : str + The product reference number (PRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the PRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_fund('185045') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_fund('1234567890') + >>> assert not res.fsr_data + """ + return self._fund_info(prn) + + def get_fund_names(self, prn: str) -> FsrApiResponse: + """Returns the alternative or secondary trading names of a fund (or collective investment scheme (CIS)), given its product reference number (PRN). + + Handler for top-level fund names API endpoint: + :: + + /v1.0/CIS/{PRN}/Names + + Returns an ``FsrApiResponse``, which could have data if the PRN exists, + or null if it not. + + Parameters + ---------- + prn : str + The product reference number (PRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the PRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_fund_names('185045') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_fund_names('1234567890') + >>> assert not res.fsr_data + """ + return self._fund_info(prn, modifiers=('Names',)) + + def get_fund_subfunds(self, prn: str) -> FsrApiResponse: + """Returns subfund details for a fund (or collective investment scheme (CIS)), given its product reference number (PRN). + + Handler for top-level subfund details API endpoint: + :: + + /v1.0/CIS/{PRN}/Subfund + + Returns an ``FsrApiResponse``, which could have data if the PRN exists, + or null if it not. + + Parameters + ---------- + prn : str + The product reference number (PRN). + + Returns + ------- + FsrApiResponse + The FSR API response object - there may still be no data in the + response if the PRN isn't found. + + Examples + -------- + >>> import os + >>> client = FsrApiClient(os.environ['API_USERNAME'], os.environ['API_KEY']) + >>> res = client.get_fund_subfunds('185045') + >>> res + + >>> assert res.fsr_data + >>> res = client.get_fund_subfunds('1234567890') + >>> assert not res.fsr_data + """ + return self._fund_info(prn, modifiers=('Subfund',)) + +if __name__ == "__main__": # pragma: no cover + # Doctest the module from the project root using + # + # export API_USERNAME= && export API_KEY= && python -m doctest -v src/fsrapiclient/api.py && unset API_USERNAME && unset API_KEY + # + import doctest + doctest.testmod() diff --git a/src/fsrapiclient/constants.py b/src/fsrapiclient/constants.py new file mode 100644 index 0000000..cad9838 --- /dev/null +++ b/src/fsrapiclient/constants.py @@ -0,0 +1,26 @@ +__all__ = ['FSR_API_CONSTANTS',] + + +# -- IMPORTS -- + +# -- Standard libraries -- +from enum import Enum + +# -- 3rd party libraries -- + +# -- Internal libraries -- + + +class FSR_API_CONSTANTS(Enum): + """An enum to store FSR API-level constants. + + Examples + -------- + >>> FSR_API_CONSTANTS.BASEURL.value + 'https://register.fca.org.uk/services' + >>> FSR_API_CONSTANTS.API_VERSION.value + 'V0.1' + """ + + BASEURL = 'https://register.fca.org.uk/services' + API_VERSION = 'V0.1' diff --git a/src/fsrapiclient/exceptions.py b/src/fsrapiclient/exceptions.py new file mode 100644 index 0000000..38e654c --- /dev/null +++ b/src/fsrapiclient/exceptions.py @@ -0,0 +1,24 @@ +__all__ = ['FsrApiException', + 'FsrApiRequestException', + 'FsrApiResponseException',] + + +# -- IMPORTS -- + +# -- Standard libraries -- + +# -- 3rd party libraries -- + +# -- Internal libraries -- + + +class FsrApiException(Exception): + ... + + +class FsrApiRequestException(FsrApiException): + ... + + +class FsrApiResponseException(FsrApiException): + ... diff --git a/tests/units/test_api.py b/tests/units/test_api.py new file mode 100644 index 0000000..44291b7 --- /dev/null +++ b/tests/units/test_api.py @@ -0,0 +1,973 @@ +# -- IMPORTS -- + +# -- Standard libraries -- +import os +import unittest.mock as mock + +from urllib.parse import urlencode + +# -- 3rd party libraries -- +import pytest +import requests + +# -- Internal libraries -- +import fsrapiclient.api + +from fsrapiclient.api import FsrApiSession, FsrApiClient +from fsrapiclient.constants import FSR_API_CONSTANTS +from fsrapiclient.exceptions import FsrApiRequestException, FsrApiResponseException + + +class _TestFsrApi: + + _api_username = os.environ['API_USERNAME'] + _api_key = os.environ['API_KEY'] + + +class TestFsrApiSession(_TestFsrApi): + + def test_fsr_api_session(self): + test_session = FsrApiSession(self._api_username, self._api_key) + + assert test_session.api_username == self._api_username + assert test_session.api_key == self._api_key + assert test_session.headers == { + 'Accept': 'application/json', + 'X-Auth-Email': self._api_username, + 'X-Auth-Key': self._api_key + } + + +class TestFsrApiClient(_TestFsrApi): + + def test_fsr_api_client____init__(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + assert test_client.api_session.api_username == self._api_username + assert test_client.api_session.api_key == self._api_key + assert test_client.api_session.headers == { + 'Accept': 'application/json', + 'X-Auth-Email': self._api_username, + 'X-Auth-Key': self._api_key + } + assert test_client.api_version == FSR_API_CONSTANTS.API_VERSION.value + + def test_fsr_api_client__common_search__api_request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client.common_search(urlencode({'q': 'exceptional query', 'type': 'firm'})) + + def test_fsr_api_client__common_search__no_api_request_exception(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + recv_response = test_client.common_search(urlencode({'q': 'hastings direct', 'type': 'firm'})) + assert recv_response.ok + assert recv_response.fsr_data + assert len(recv_response.fsr_data) + assert recv_response.fsr_status == 'FSR-API-04-01-00' + assert recv_response.fsr_message == 'Ok. Search successful' + assert recv_response.fsr_resultinfo + + recv_response = test_client.common_search(urlencode({'q': 'non existent firm', 'type': 'firm'})) + assert recv_response.ok + assert not recv_response.fsr_data + assert recv_response.fsr_status == 'FSR-API-04-01-11' + assert recv_response.fsr_message == 'No search result found' + assert not recv_response.fsr_resultinfo + + def test_fsr_api_client__search_frn__api_request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client.search_frn('exceptional search') + + def test_fsr_api_client__search_frn__response_not_ok__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiClient.common_search', return_value=mock.MagicMock(ok=False)): + with pytest.raises(FsrApiResponseException): + test_client.search_frn('exceptional search') + + def test_fsr_api_client__search_frn__no_fsr_data_in_response__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value=dict()) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_frn('exceptional search') + + def test_fsr_api_client__search_frn__fsr_data_with_index_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': []}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_frn('exceptional search') + + def test_fsr_api_client__search_frn__fsr_data_with_key_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': [{'not a Reference Number': None}]}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_frn('exceptional search') + + def test_fsr_api_client__search_frn__no_request_exception(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a successful FRN search for an existing firm + recv_frn = test_client.search_frn('hiscox insurance company') + assert isinstance(recv_frn, str) + assert recv_frn + + # Covers the case of a failed FRN search for an incorrectly specified firm + with pytest.raises(FsrApiResponseException): + test_client.search_frn('nonexistent123 insurance company') + + # Covers the case of an FRN search based on an inadequately specified firm + # that produces multiple results + with pytest.raises(FsrApiResponseException): + test_client.search_frn('direct line') + + def test_fsr_api_client___firm_info__no_modifiers__request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._firm_info('test_frn') + + def test_fsr_api_client___firm_info__modifiers__request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._firm_info('test_frn', modifiers=('test_modifier1', 'test_modifier2',)) + + def test_fsr_api_client___firm_info(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited with the FRN 113849 + recv_response = test_client._firm_info('113849') + assert recv_response.ok + assert recv_response.fsr_data + assert recv_response.fsr_data[0]['Organisation Name'] == 'Hiscox Insurance Company Limited' + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client._firm_info('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the secondary or + # alternative business or trading names used by Hiscox Insurance + # Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Names',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the secondary or alternative business + # or trading names of a non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Names',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the listed business + # address of Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Address',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the listed business address of a non- + # existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Address',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the controlled functions + # (CF) of Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('CF',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the controlled functions (CF) of a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('CF',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the individuals + # associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Individuals',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the individuals associated with a + # existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Individuals',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the activities and + # permissions associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Permissions',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the activities and permissions + # associated with a non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Permissions',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the requirements + # associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Requirements',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the requirements associated with a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Requirements',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the investment types + # associated with a specific requirement associated with Hiscox Insurance + # Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Requirements', 'OR-0131728', 'InvestmentTypes',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the regulators + # listed for Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Regulators',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the regulators listed for a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Regulators',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the passports + # associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Passports',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the passports associated with a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Passports',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the passports + # for a specific country, Gibraltar, associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Passports', 'Gibraltar', 'Permission',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the passports for a specific country + # associated with a non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Passports', 'Gibraltar', 'Permission',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for any waivers + # associated with Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('Waivers',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for any waivers associated with a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Waivers',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for any exclusions + # applying to Barclays Bank plc (FRN 122702) + recv_response = test_client._firm_info('122702', modifiers=('Exclusions',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for any exclusions applying to a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('Exclusions',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the disciplinary history + # of Barclays Bank plc (FRN 122702) + recv_response = test_client._firm_info('122702', modifiers=('DisciplinaryHistory',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the disciplinary history of a + # non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('DisciplinaryHistory',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the appointed representatives of + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client._firm_info('113849', modifiers=('AR',)) + assert recv_response.ok + assert ( + recv_response.fsr_data['PreviousAppointedRepresentatives'] or + recv_response.fsr_data['CurrentAppointedRepresentatives'] + ) + + # Covers the case of a request for the appointed representatives + # of a non-existent firm + recv_response = test_client._firm_info('1234567890', modifiers=('AR',)) + assert recv_response.ok + assert not recv_response.fsr_data['PreviousAppointedRepresentatives'] + assert not recv_response.fsr_data['CurrentAppointedRepresentatives'] + + def test_fsr_api_client__get_firm(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for the firm details of + # an existing firm, Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm('113849') + assert recv_response.ok + assert recv_response.fsr_data + assert recv_response.fsr_data[0]['Organisation Name'] == 'Hiscox Insurance Company Limited' + + # Covers the case of a request for the firm details of + # an existing firm, Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_names(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_names('113849') + assert recv_response.ok + assert recv_response.fsr_data + assert recv_response.fsr_data[0]['Current Names'][0]['Name'] == 'Hiscox' + assert recv_response.fsr_data[1]['Previous Names'] + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_names('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_addresses(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_addresses('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_addresses('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_controlled_functions(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_controlled_functions('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_controlled_functions('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_individuals(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_individuals('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_individuals('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_permissions(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_permissions('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_permissions('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_requirements(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_requirements('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_requirements('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_requirement_investment_types(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Barclays Bank Plc (FRN 122702) + recv_response = test_client.get_firm_requirement_investment_types('122702', 'OR-0262545') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of an unrequest for an existing firm which is + # Barclays Bank Plc (FRN 122702) + recv_response = test_client.get_firm_requirement_investment_types('122702', 'OR-1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_requirement_investment_types('1234567890', 'OR-0262545') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_regulators(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_regulators('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_regulators('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_passports(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_passports('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_passports('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_passport_permissions(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_passport_permissions('113849', 'Gibraltar') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a unrequest for an existing firm which is + # Hiscox Insurance Comnpany Limited (FRN 113849) + recv_response = test_client.get_firm_passport_permissions('113849', 'Germany') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_passport_permissions('1234567890', 'Gibraltar') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_waivers(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_waivers('113849') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_waivers('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_exclusions(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Barclays Bank Plc (FRN 122702) + recv_response = test_client.get_firm_exclusions('122702') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of an unrequest for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_exclusions('113849') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_exclusions('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_disciplinary_history(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Barclays Bank Plc (FRN 122702) + recv_response = test_client.get_firm_disciplinary_history('122702') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of an unrequest for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_disciplinary_history('113849') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_disciplinary_history('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_firm_appointed_representatives(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing firm which is + # Hiscox Insurance Company Limited (FRN 113849) + recv_response = test_client.get_firm_appointed_representatives('113849') + assert recv_response.ok + assert recv_response.fsr_data + assert any([ + recv_response.fsr_data['PreviousAppointedRepresentatives'], + recv_response.fsr_data['CurrentAppointedRepresentatives'] + ]) + + # Covers the case of a request for an non-existent firm given by + # a non-existent FRN 1234567890 + recv_response = test_client.get_firm_appointed_representatives('1234567890') + assert recv_response.ok + assert not any([ + recv_response.fsr_data['PreviousAppointedRepresentatives'], + recv_response.fsr_data['CurrentAppointedRepresentatives'] + ]) + + def test_fsr_api_client__search_irn__api_request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client.search_irn('exceptional search') + + def test_fsr_api_client__search_irn__response_not_ok__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiClient.common_search', return_value=mock.MagicMock(ok=False)): + with pytest.raises(FsrApiResponseException): + test_client.search_irn('exceptional search') + + def test_fsr_api_client__search_irn__no_fsr_data_in_response__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value=dict()) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_irn('exceptional search') + + def test_fsr_api_client__search_irn__fsr_data_with_index_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': []}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_irn('exceptional search') + + def test_fsr_api_client__search_irn__fsr_data_with_key_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': [{'not a Reference Number': None}]}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_irn('exceptional search') + + def test_fsr_api_client__search_irn__no_api_request_exception(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a successful IRN searh for an existing individual + recv_frn = test_client.search_irn('mark carney') + assert isinstance(recv_frn, str) + assert recv_frn + + # Covers the case of a failed IRN search for an incorrectly specified individual + with pytest.raises(FsrApiResponseException): + test_client.search_irn('a nonexistent person') + + # Covers the case of an IRN search based on an inadequately specified individual + # that produces multiple results + with pytest.raises(FsrApiResponseException): + test_client.search_irn('mark C') + + def test_fsr_api_client___individual_info__no_modifiers__request_exception_raised_and_propagated(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._individual_info('test_irn') + + def test_fsr_api_client___individual_info__modifiers__request_exception_raised_and_propagated(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._individual_info('test_irn', modifiers=('test_modifier1', 'test_modifier2',)) + + def test_fsr_api_client___individual_info(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing individual + # 'Mark Carney'(IRN 'MXC29012') + recv_response = test_client._individual_info('MXC29012') + assert recv_response.ok + assert recv_response.fsr_data + assert recv_response.fsr_data[0]['Details']['Full Name'] == 'Mark Carney' + + # Covers the case of a request for an non-existent individual + recv_response = test_client._individual_info('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the controlled functions + # associated with an existing individual, 'Mark Carney' + recv_response = test_client._individual_info('MXC29012', modifiers=('CF',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the controlled functions associated + # with a non-existent individual + recv_response = test_client._individual_info('1234567890', modifiers=('CF',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the disciplinary history + # of an individual, 'Leigh Mackey' (IRN 'LXM01328') + recv_response = test_client._individual_info('LXM01328', modifiers=('DisciplinaryHistory',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of an unrequest for the disciplinary history + # of an individual, 'Mark Carney' (IRN 'MXC29012') + recv_response = test_client._individual_info('MXC29012', modifiers=('DisciplinaryHistory',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the disciplinary history of a non- + # existent individual + recv_response = test_client._firm_info('1234567890', modifiers=('DisciplinaryHistory',)) + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_individual(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for the details of an + # existing individual, 'Mark Carney' (IRN 'MXC29012') + recv_response = test_client.get_individual('MXC29012') + assert recv_response.ok + assert recv_response.fsr_data + assert recv_response.fsr_data[0]['Details']['Full Name'] == 'Mark Carney' + + # Covers the case of a request for the details of a non-existent individual + recv_response = test_client.get_individual('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_individual_controlled_functions(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing individual - + # 'Mark Carney' (IRN 'MXC29012') + recv_response = test_client.get_individual_controlled_functions('MXC29012') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent individual given by + # a non-existent IRN '1234567890' + recv_response = test_client.get_individual_controlled_functions('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_individual_disciplinary_history(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing individual with - + # disciplinary history, 'Leigh Mackey' (IRN 'LXM01328') + recv_response = test_client.get_individual_disciplinary_history('LXM01328') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent individual given by + # a non-existent IRN '1234567890' + recv_response = test_client.get_individual_disciplinary_history('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__search_prn__api_request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client.search_prn('exceptional search') + + def test_fsr_api_client__search_prn__response_not_ok__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiClient.common_search', return_value=mock.MagicMock(ok=False)): + with pytest.raises(FsrApiResponseException): + test_client.search_prn('exceptional search') + + def test_fsr_api_client__search_prn__no_fsr_data_in_response__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value=dict()) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_prn('exceptional search') + + def test_fsr_api_client__search_prn__fsr_data_with_index_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': []}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_prn('exceptional search') + + def test_fsr_api_client__search_prn__fsr_data_with_key_error__api_response_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_response = mock.create_autospec(requests.Response) + mock_response.json = mock.MagicMock(name='json', return_value={'Data': [{'not a Reference Number': None}]}) + mock_api_session_get.return_value = mock_response + + with pytest.raises(FsrApiResponseException): + test_client.search_prn('exceptional search') + + def test_fsr_api_client__search_prn__no_api_request_exception(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a successful PRN searh for an existing fund + recv_prn = test_client.search_prn('jupiter asia pacific income fund') + assert isinstance(recv_prn, str) + assert recv_prn + + # Covers the case of a failed PRN search for an incorrectly specified fund + with pytest.raises(FsrApiResponseException): + test_client.search_prn('a nonexistent fund') + + # Covers the case of a PRN search based on an inadequately specified fund + # name that produces multiple results + with pytest.raises(FsrApiResponseException): + test_client.search_prn('jupiter asia pacific') + + def test_fsr_api_client___fund_info__no_modifiers__request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._fund_info('test_prn') + + def test_fsr_api_client___fund_info__modifiers__request_exception_raised(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + with mock.patch('fsrapiclient.api.FsrApiSession.get') as mock_api_session_get: + mock_api_session_get.side_effect = requests.RequestException('test RequestException') + + with pytest.raises(FsrApiRequestException): + test_client._fund_info('test_prn', modifiers=('test_modifier1', 'test_modifier2',)) + + def test_fsr_api_client___fund_info(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for an existing fund which is + # 'Jupiter Asia Pacific Income Fund (IRL)' with the PRN 1006826 + recv_response = test_client._fund_info('1006826') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for an non-existent fund given by + # a non-existent PRN 1234567890 + recv_response = test_client._fund_info('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the subfund details of an + # existing fund with PRN 185045 + recv_response = test_client._fund_info('185045', modifiers=('Subfund',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the non-existent subfund details of an + # existing fund with PRN 1006826 + recv_response = test_client._fund_info('1006826', modifiers=('Subfund',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the non-existent subfund details + # of a non-existent fund + recv_response = test_client._fund_info('1234567890', modifiers=('Subfund',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the alternative or secondary trading + # names of an existing fund with PRN 185045 + recv_response = test_client._fund_info('185045', modifiers=('Names',)) + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the non-existent alternative or + # secondary trading names of an existing fund with PRN 1006826 + recv_response = test_client._fund_info('1006826', modifiers=('Names',)) + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the non-existent alternative or + # secondary trading names of a non-existing fund with a non-existent + # PRN 1234567890 + recv_response = test_client._fund_info('1234567890', modifiers=('Names',)) + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_fund(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for the details of an + # existing fund, 'Jupiter Asia Pacific Income Fund (IRL)' (PRN '635641') + recv_response = test_client.get_fund('635641') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the details of a non-existent fund + recv_response = test_client.get_fund('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_fund_names(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for the alternate/secondary names + # details of existing fund with PRN 185045 + recv_response = test_client.get_fund_names('185045') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the alternate/secondary name + # details of an existing fund with PRN 1006826 + recv_response = test_client.get_fund_names('1006826') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the alternate/secondary name + # details of a non-existent fund + recv_response = test_client.get_fund_names('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data + + def test_fsr_api_client__get_fund_subfunds(self): + test_client = FsrApiClient(self._api_username, self._api_key) + + # Covers the case of a request for the subfund details of an + # existing fund with PRN 185045 + recv_response = test_client.get_fund_subfunds('185045') + assert recv_response.ok + assert recv_response.fsr_data + + # Covers the case of a request for the subfund details of an + # existing fund with PRN 1006826 + recv_response = test_client.get_fund_subfunds('1006826') + assert recv_response.ok + assert not recv_response.fsr_data + + # Covers the case of a request for the subfund details of a + # non-existent fund + recv_response = test_client.get_fund_subfunds('1234567890') + assert recv_response.ok + assert not recv_response.fsr_data diff --git a/tests/units/test_constants.py b/tests/units/test_constants.py new file mode 100644 index 0000000..6e4f06a --- /dev/null +++ b/tests/units/test_constants.py @@ -0,0 +1,15 @@ +# -- IMPORTS -- + +# -- Standard libraries -- + +# -- 3rd party libraries -- + +# -- Internal libraries -- +from fsrapiclient.constants import FSR_API_CONSTANTS + + +class TestFsrApiConstants: + + def test_fsr_api_constants(self): + assert FSR_API_CONSTANTS.BASEURL.value == 'https://register.fca.org.uk/services' + assert FSR_API_CONSTANTS.API_VERSION.value == 'V0.1'