diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7949eec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,50 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. + +Feel free to attach screenshots as well. + +**To Reproduce** + +Steps to reproduce the behavior. + +Any sample data would help greatly. + +**Version** + +What is the version of Horreum python client ? + +If you are using a development branch; what is the commit id ? + +``` +git rev-parse HEAD +``` + +What is the version of Horreum ? + +If you are using a development branch; what is the commit id ? + +``` +git rev-parse HEAD +``` + +**Java** + +What is the version of Python ? + +``` +python --version +``` + +**Tip** + +Use \`\`\` before and after the text to keep the output as is. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..1c23e60 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature +assignees: '' + +--- + +**Feature idea.** + +A clear and concise description of what the feature is. + +**Describe the solution you'd like** + +A clear and concise description of what you want to happen. + +**Additional information** + +Any additional information you can provide, like an overall design description \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..585e880 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ + + +## Fixes Issue + + + + + +## Changes proposed + + + + + + +## Check List (Check all the applicable boxes) + +- [ ] My code follows the code style of this project. +- [ ] My change requires changes to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] All new and existing tests passed. \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..4caa2fd --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,33 @@ +# This workflow will run the full CI for the Horreum python library including the build and the tests execution +# This is going to be triggered on every pull request as well as on main branch. +# TODO: trigger tests once implemented +name: Python client ci + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + name: ${{ matrix.session }} ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: [ "3.9", "3.10", "3.11" ] + env: + FORCE_COLOR: "1" + PRE_COMMIT_COLOR: "always" + steps: + - name: Check out the repository + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Install development dependencies + run: pip install -r dev-requirements.txt + - name: Build python library + run: poetry build diff --git a/.github/workflows/generate-client.yaml b/.github/workflows/generate-client.yaml new file mode 100644 index 0000000..8b25a5e --- /dev/null +++ b/.github/workflows/generate-client.yaml @@ -0,0 +1,54 @@ +# This workflow will fetch the new openapi from the provided branch of https://github.com/Hyperfoil/Horreum and based on +# that it will re-generate the Horreum raw client and creates a new pull request against the corresponding branch here. +# It could be tested running `gh act workflow_dispatch -e ./test/workflow_dispatch_event_example.json`. Remember to +# comment out the pull request creation :) +name: Autogenerate Horreum client + +on: + workflow_dispatch: + inputs: + branch: + description: Branch or tag of https://github.com/Hyperfoil/Horreum + required: true + repository_dispatch: + types: [ generate-horreum-client ] + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - name: Fetch branch + id: fetch-branch + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + # Generated by peter-evans/repository-dispatch@v3 + echo "HORREUM_BRANCH=${{ github.event.client_payload.branch }}" >> $GITHUB_OUTPUT + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "HORREUM_BRANCH=${{ github.event.inputs.branch }}" >> $GITHUB_OUTPUT + else + echo "Unknown event: ${{ github.event_name }}" + exit 1 + fi + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install development dependencies + run: pip install -r dev-requirements.txt + - name: Generate horreum client + run: make HORREUM_BRANCH=${{ steps.fetch-branch.outputs.HORREUM_BRANCH }} generate + - run: git --no-pager diff + - name: Create Pull Request + uses: gr2m/create-or-update-pull-request-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + title: Generate Horreum client from github.com/Hyperfoil/Horreum:${{ steps.fetch-branch.outputs.HORREUM_BRANCH }} + body: | + There is a new change in the openapi spec of Horreum in branch ${{ steps.fetch-branch.outputs.HORREUM_BRANCH }}. + Verify that there are no breaking changes. + branch: horreum-openapi-${{ steps.fetch-branch.outputs.HORREUM_BRANCH }} + commit-message: Horreum openapi client updated diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..c8a971c --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,41 @@ +# This workflow will build the python distribution and it will publish to Pypi +# This is going to be triggered on on every tag `v*`, e.g., `v0.13`. +# TODO: trigger tests once implemented, to ensure everything is working before publishing +name: Publish Horreum library + +on: + push: + tags: + - v* + +jobs: + test: + name: ${{ matrix.session }} ${{ matrix.python }} + runs-on: ubuntu-latest + env: + FORCE_COLOR: "1" + steps: + - name: Check out the repository + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install development dependencies + run: pip install -r dev-requirements.txt + - name: Check version coherence + run: | + PROJECT_VERSION=$(poetry version | cut -d' ' -f2) + GIT_TAG=$(git describe --tags --match="v*") + if [[ "${GIT_TAG:1}" =~ $PROJECT_VERSION ]]; then + echo "::error title='$GIT_TAG tag does not match project version'::" + exit 1 + fi + - name: Build python library + run: make generate && poetry build --ansi + - name: Publish package on PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + print-hash: true + packages-dir: ./dist/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdd715c --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# IDE configs +.idea + +# Dist +dist/ +bin/ + +# Py cache +**/__pycache__ + +# Kiota +**/.kiota.log diff --git a/GET_STARTED.md b/GET_STARTED.md new file mode 100644 index 0000000..a01a281 --- /dev/null +++ b/GET_STARTED.md @@ -0,0 +1,52 @@ +
+ +# Get Started Guide + +
+ +In this document you can find all information to get started using the Horreum python library from scratch. + +Right now the library is not published anywhere, therefore the only way to install it is from source. + +--- +## Prerequisites + +* Python environment, e.g., `pyenv` or `miniconda` with all development dependencies installed: +```bash +pip install -r dev-requirements.txt +``` + +## Installation + +Once all dependencies are installed simply build the `whl` by running: + +```bash +poetry build +``` + +Now you can install the local build of `horreum` python client: + +```bash +pip install dist/horreum-*.dev0-py3-none-any.whl --force-reinstall +``` + +## Usage + +```bash +>>> import asyncio + +# Import the constructor function +>>> from horreum.horreum_client import new_horreum_client + +# Initialize the client +>>> client = await new_horreum_client(base_url="http://localhost:8080", username="..", password="..") + +# Call the api using the underlying raw client, in this case retrieve the Horreum server version +>>> await client.raw_client.api.config.version.get() +VersionInfo(additional_data={}, privacy_statement=None, start_timestamp=1710864862253, version='0.13.0') +``` + +The previous api call is equivalent to the following `cURL`: +```bash +curl --silent -X 'GET' 'http://localhost:8080/api/config/version' -H 'accept: application/json' | jq '.' +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a3a6b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +# useful paths +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +PROJECT_PATH := $(patsubst %/,%,$(dir $(MKFILE_PATH))) +PROJECT_BIN := $(PROJECT_PATH)/bin +PROJECT_DIST := $(PROJECT_PATH)/dist + +OS_NAME := $(shell uname -s | tr A-Z a-z) +OS_ARCH := $(shell uname -m | tr A-Z a-z) + +ifeq ($(OS_NAME),linux) + OS_NAME = "linux" +endif +ifeq ($(OS_NAME),darwin) + OS_NAME = "osx" +endif + +ifeq ($(OS_ARCH),x86_64) + OS_ARCH = x64 +endif +ifneq ($(filter %86,$(OS_ARCH)),) + OS_ARCH = x86 +endif +ifneq ($(filter arm%,$(OS_ARCH)),) + OS_ARCH = arm64 +endif + +# env variables +KIOTA_VERSION ?= "v1.12.0" +HORREUM_BRANCH ?= "master" +HORREUM_OPENAPI_PATH ?= "https://raw.githubusercontent.com/Hyperfoil/Horreum/${HORREUM_BRANCH}/docs/site/content/en/openapi/openapi.yaml" +GENERATED_CLIENT_PATH = "${PROJECT_PATH}/src/horreum/raw_client" + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: clean +clean: ## Clean external tools and output dirs + @rm -rf ${PROJECT_BIN} ${PROJECT_DIST} ${GENERATED_CLIENT_PATH}/api ${GENERATED_CLIENT_PATH}/models ${GENERATED_CLIENT_PATH}/horreum_raw_client.py ${GENERATED_CLIENT_PATH}/kiota-lock.json + +.PHONY: kiota +kiota: ${PROJECT_BIN}/kiota ## Install kiota tool under ${PROJECT_PATH}/bin + +${PROJECT_BIN}/kiota: + @{\ + set -e ;\ + echo "installing kiota version ${KIOTA_VERSION}" ;\ + mkdir -p ${PROJECT_BIN}/kiota-installation ;\ + cd ${PROJECT_BIN}/kiota-installation ;\ + curl -sLO https://github.com/microsoft/kiota/releases/download/${KIOTA_VERSION}/${OS_NAME}-${OS_ARCH}.zip ;\ + unzip -o ${OS_NAME}-${OS_ARCH}.zip ;\ + mv kiota ${PROJECT_BIN}/ ;\ + rm -rf ${PROJECT_BIN}/kiota-installation ;\ + } + +.PHONY: tools +tools: kiota ## Install external tools. + +.PHONY: generate +generate: tools ## Generate the Horreum client + @{\ + set -e ;\ + curl -sSfL -o ${PROJECT_PATH}/openapi/openapi.yaml ${HORREUM_OPENAPI_PATH} ;\ + ${PROJECT_BIN}/kiota generate -l python -c HorreumRawClient -n raw_client -d ${PROJECT_PATH}/openapi/openapi.yaml -o ${GENERATED_CLIENT_PATH} ;\ + } diff --git a/README.md b/README.md index 7dc87e3..68e5daf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,16 @@ --- -## What is Horreum ? +## What is Horreum Python Client? + +Horreum python client is a high-level python library to interact with [Horreum](#what-is-horreum) web server. + +The raw client is generated using [kiota](https://github.com/microsoft/kiota) openapi generator tool starting from +the [Horreum OpenAPI spec](https://github.com/Hyperfoil/Horreum/blob/master/docs/site/content/en/openapi/openapi.yaml). + +Refer to the [get started guide](./GET_STARTED.md) for comprehensive instructions on installing and utilizing this library. + +## What is Horreum? [Horreum](https://github.com/Hyperfoil/Horreum) is a service for storing performance data and regression analysis. @@ -22,7 +31,7 @@ Please, visit our project website: for more information. [Horreum](https://github.com/Hyperfoil/Horreum) is a [Quarkus](https://quarkus.io/) based application which uses -[Quinoa](https://quarkiverse.github.io/quarkiverse-docs/quarkus-quinoa/dev/) as its [nodejs](https://nodejs.org/en) engine. +[Quinoa](https://quarkiverse.github.io/quarkiverse-docs/quarkus-quinoa/dev/) as its [Node.js](https://nodejs.org/en) engine. This project is about providing a simplified setup and examples to use [Horreum](https://github.com/Hyperfoil/Horreum) using the [Python](https://www.python.org/) programming language. @@ -31,6 +40,35 @@ This project is about providing a simplified setup and examples to use Contributions to `horreum-client-python` Please check our [CONTRIBUTING.md](./CONTRIBUTING.md) +### Development + +Install all dev dependencies (consider using Python virtual environments): +```bash +pip install -r dev-requirements.txt +``` + +Generate source files +```bash +make generate +``` + +Build the library using `poetry`: +```bash +poetry build +``` + +#### Tests + +Right now tests are not automated, therefore you need to start up the Horreum server manually, +you can check more details in [Horreum README](https://github.com/Hyperfoil/Horreum/blob/master/README.md#getting-started-with-development-server). + +> **_NOTE_**: The database should be empty to get all tests working + +Once the Horreum server is up and running on `localhost:8080`, you can trigger integration tests by running: +```bash +pytest test/ +``` + ### If you have any idea or doubt 👇 * [Ask a question](https://github.com/Hyperfoil/horreum-client-python/discussions) diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..60a3f21 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +poetry==1.8.2 +pytest==8.1.1 +pytest-asyncio==0.23.6 \ No newline at end of file diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml new file mode 100644 index 0000000..92cd711 --- /dev/null +++ b/openapi/openapi.yaml @@ -0,0 +1,4610 @@ +--- +openapi: 3.0.3 +info: + title: Horreum REST API + description: "Horreum automated change anomaly detection. For more information,\ + \ please see [https://horreum.hyperfoil.io/](https://horreum.hyperfoil.io/)" + version: "0.13" +tags: +- name: Config + description: Endpoint providing configuration for the Horreum System + x-smallrye-profile-external: "" +- name: Dataset + description: Datasets are used as the basis for all change detection and reporting + x-smallrye-profile-external: "" +- name: Experiment + description: Experiments allow users to apply change detection rules to two different + datasets. This allows for pass/fail of KPIS based on A/B testing + x-smallrye-profile-external: "" +- name: Run + description: Manage test runs. Runs are instances of results of a benchmark execution + x-smallrye-profile-external: "" +- name: Schema + description: Manage schemas + x-smallrye-profile-external: "" +- name: Test + description: Endpoint giving access to tests defined in Horreum. + x-smallrye-profile-external: "" +paths: + /api/config/datastore: + put: + tags: + - Config + description: Update an existing Datastore definition + operationId: updateDatastore + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Datastore' + responses: + "200": + description: The ID of the updated Datastore + content: + application/json: + schema: + format: int32 + type: integer + post: + tags: + - Config + description: Create a new Datastore + operationId: newDatastore + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Datastore' + responses: + "200": + description: The ID for the new Datastore + content: + application/json: + schema: + format: int32 + type: integer + /api/config/datastore/{id}: + delete: + tags: + - Config + description: Test a Datastore + operationId: deleteDatastore + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: No Content + /api/config/datastore/{id}/test: + get: + tags: + - Config + description: Test a Datastore connection + operationId: testDatastore + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DatastoreTestResponse' + /api/config/datastore/{team}: + get: + tags: + - Config + description: Obtain list of configured datastores for particular team + operationId: datastores + parameters: + - name: team + in: path + description: name of the team to search for defined datastores + required: true + schema: + type: string + example: perf-team + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Datastore' + /api/config/keycloak: + get: + tags: + - Config + description: Obtain configuration information about keycloak server securing + Horreum instance + operationId: keycloak + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/KeycloakConfig' + /api/config/version: + get: + tags: + - Config + description: Obtain version of the running Horreum instance + operationId: version + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/VersionInfo' + /api/dataset/bySchema: + get: + tags: + - Dataset + description: "Retrieve a paginated list of Datasets, with total count, by Schema" + operationId: listBySchema + parameters: + - name: uri + in: query + description: Schema URI + required: true + schema: + type: string + example: uri:techempower:0.1 + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Schemas + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + default: start + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetList' + /api/dataset/list/{testId}: + get: + tags: + - Dataset + description: "Retrieve a paginated list of Datasets, with total count, by Test" + operationId: listByTest + parameters: + - name: testId + in: path + description: Test ID of test to retrieve list of Datasets + required: true + schema: + format: int32 + type: integer + example: 101 + - name: filter + in: query + description: JOSN Filter expression to apply to query + schema: + type: string + example: + buildID: 111111 + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Schemas + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + - name: viewId + in: query + description: Optional View ID to filter datasets by view + schema: + format: int32 + type: integer + example: 202 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetList' + /api/dataset/{datasetId}/labelValues: + get: + tags: + - Dataset + operationId: labelValues + parameters: + - name: datasetId + in: path + required: true + schema: + format: int32 + type: integer + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelValue' + /api/dataset/{datasetId}/previewLabel: + post: + tags: + - Dataset + operationId: previewLabel + parameters: + - name: datasetId + in: path + required: true + schema: + format: int32 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Label' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LabelPreview' + /api/dataset/{datasetId}/summary: + get: + tags: + - Dataset + operationId: getSummary + parameters: + - name: datasetId + in: path + required: true + schema: + format: int32 + type: integer + - name: viewId + in: query + schema: + format: int32 + type: integer + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DatasetSummary' + /api/dataset/{id}: + get: + tags: + - Dataset + description: Retrieve Dataset by ID + operationId: getDataset + parameters: + - name: id + in: path + description: Dataset ID to retrieve + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "404": + description: No Dataset with the given id was found + content: + application/json: {} + "200": + description: JVM system properties of a particular host. + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + /api/experiment/models: + get: + tags: + - Experiment + description: Retrieve a list of Condition Config models + operationId: models + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ConditionConfig' + /api/experiment/run: + get: + tags: + - Experiment + description: Run an experiment for a given dataset and experiment profile + operationId: runExperiments + parameters: + - name: datasetId + in: query + description: The dataset to run the experiment on + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: Array of experiment results + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ExperimentResult' + /api/experiment/{testId}/profiles: + get: + tags: + - Experiment + description: Retrieve Experiment Profiles by Test ID + operationId: profiles + parameters: + - name: testId + in: path + description: Test ID to retrieve Experiment Profiles for + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ExperimentProfile' + post: + tags: + - Experiment + description: 'Save new or update existing Experiment Profiles for a Test ' + operationId: addOrUpdateProfile + parameters: + - name: testId + in: path + description: Test ID to retrieve Experiment Profiles for + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ExperimentProfile' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + format: int32 + type: integer + /api/experiment/{testId}/profiles/{profileId}: + delete: + tags: + - Experiment + description: Delete an Experiment Profiles for a Test + operationId: deleteProfile + parameters: + - name: testId + in: path + description: Test ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: profileId + in: path + description: Experiment Profile ID + required: true + schema: + format: int32 + type: integer + example: 202 + responses: + "204": + description: No Content + /api/run/autocomplete: + get: + tags: + - Run + operationId: autocomplete + parameters: + - name: query + in: query + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + /api/run/bySchema: + get: + tags: + - Run + description: Retrieve a paginated list of Runs with available count for a given + Schema URI + operationId: listBySchema + parameters: + - name: uri + in: query + description: Schema URI + required: true + schema: + type: string + example: uri:my-schema:0.1 + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Tests + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RunsSummary' + /api/run/count: + get: + tags: + - Run + description: Run count summary for given Test ID + operationId: runCount + parameters: + - name: testId + in: query + description: Test ID + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RunCount' + /api/run/data: + post: + tags: + - Run + description: Upload a new Run + operationId: addRunFromData + parameters: + - name: start + in: query + description: "start timestamp of run, or json path expression" + required: true + schema: + type: string + examples: + scalar value: + value: 2023-10-23T00:13:35Z + json path: + value: $.buildTimeStamp + - name: stop + in: query + description: "stop timestamp of run, or json path expression" + required: true + schema: + type: string + examples: + scalar value: + value: 2023-10-23T00:13:35Z + json path: + value: $.buildTimeStamp + - name: test + in: query + description: test name of ID + required: true + schema: + type: string + example: my-benchmark + - name: owner + in: query + description: Name of the new owner + schema: + type: string + example: perf-team + - name: access + in: query + description: New Access level + schema: + $ref: '#/components/schemas/Access' + example: 0 + - name: token + in: query + description: Horreum internal token. Incompatible with Keycloak + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + - name: schema + in: query + description: Schema URI + schema: + type: string + example: uri:my-benchmark:0.2 + - name: description + in: query + description: Run description + schema: + type: string + example: AWS runs + requestBody: + content: + application/json: + schema: + type: string + example: + - tag: main + score: 2031.7424089224041 + params: + size: "1000" + useTreeSet: "true" + $schema: urn:jmh:0.2 + testName: org.drools.benchmarks.datastructures.QueueBenchmark.benchmark + - $schema: urn:horreum:jenkins-plugin:0.1 + jobName: upstream-perf-bre-datastructures + buildUrl: https://qe.com/job/TESTING/job/upstream-perfx-datastructures/125/ + startTime: 1698020160763 + uploadTime: 1698020592674 + buildNumber: 125 + jobFullName: TESTING/RHBA/_upstream/decisions/8.x/performance/nightly/upstream-perf-bre-datastructures + scheduleTime: 1698020160756 + jobDisplayName: upstream-perf-bre-datastructures + buildDisplayName: '#125' + responses: + "200": + description: id of the newly generated run + content: + application/json: + schema: + format: int32 + type: integer + example: 101 + "400": + description: Some fields are missing or invalid + content: + application/json: {} + /api/run/list: + get: + tags: + - Run + description: Retrieve a paginated list of Runs with available count + operationId: listAllRuns + parameters: + - name: query + in: query + description: query string to filter runs + schema: + type: string + - name: matchAll + in: query + description: match all Runs? + schema: + type: boolean + example: false + - name: roles + in: query + description: "__my, __all or a comma delimited list of roles" + schema: + type: string + example: __my + - name: trashed + in: query + description: show trashed runs + schema: + type: boolean + example: false + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Tests + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RunsSummary' + /api/run/list/{testId}: + get: + tags: + - Run + description: Retrieve a paginated list of Runs with available count for a given + Test ID + operationId: listTestRuns + parameters: + - name: testId + in: path + description: Test ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: trashed + in: query + description: include trashed runs + schema: + type: boolean + example: false + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Tests + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RunsSummary' + /api/run/recalculateAll: + post: + tags: + - Run + description: Recalculate Datasets for Runs between two dates + operationId: recalculateAll + parameters: + - name: from + in: query + description: start timestamp + schema: + type: string + example: 1698013206000 + - name: to + in: query + description: end timestamp + schema: + type: string + example: 1698013206000 + responses: + "201": + description: Created + /api/run/test: + post: + tags: + - Run + description: Upload a new Run + operationId: add + parameters: + - name: test + in: query + description: test name of ID + schema: + type: string + example: my-benchmark + - name: owner + in: query + description: Name of the new owner + schema: + type: string + example: perf-team + - name: access + in: query + description: New Access level + schema: + $ref: '#/components/schemas/Access' + example: 0 + - name: token + in: query + description: API token + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Run' + required: true + responses: + "200": + description: OK + /api/run/{id}: + get: + tags: + - Run + description: Get extended Run information by Run ID + operationId: getRun + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 202 + - name: token + in: query + description: Run API token + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + responses: + "404": + description: If no Run have been found with the given id + content: + application/json: {} + "200": + description: Run data with the referenced schemas and generated datasets + content: + application/json: + schema: + $ref: '#/components/schemas/RunExtended' + /api/run/{id}/data: + get: + tags: + - Run + description: Get Run data by Run ID + operationId: getData + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 202 + - name: token + in: query + description: Run API token + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + - name: schemaUri + in: query + description: FIlter by Schmea URI + schema: + type: string + example: uri:my-benchmark:0.1 + responses: + "200": + description: Run payload + content: + application/json: + schema: + type: object + example: "{ \"buildID\": 1709, ...}" + /api/run/{id}/description: + post: + tags: + - Run + description: Update Run description + operationId: updateDescription + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + text/plain: + schema: + type: string + required: true + responses: + "201": + description: Created + /api/run/{id}/dropToken: + post: + tags: + - Run + description: Remove access token for Run + operationId: dropToken + parameters: + - name: id + in: path + description: Token ID + required: true + schema: + format: int32 + type: integer + example: 102 + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + /api/run/{id}/labelValues: + get: + tags: + - Run + description: Get all the label values for the run + operationId: labelValues + parameters: + - name: id + in: path + description: Run Id + required: true + schema: + format: int32 + type: integer + example: 101 + - name: filter + in: query + description: either a required json sub-document or path expression + schema: + default: "{}" + type: string + examples: + object: + description: json object that must exist in the values object + value: "{labelName:necessaryValue,...}" + string: + description: valid filtering jsonpath that returns null if not found (not + predicates) + value: $.count ? (@ < 20 && @ > 10) + - name: sort + in: query + description: label name for sorting + schema: + default: "" + type: string + - name: direction + in: query + description: either Ascending or Descending + schema: + default: Ascending + type: string + example: count + - name: limit + in: query + description: the maximum number of results to include + schema: + format: int32 + default: 2147483647 + type: integer + example: 10 + - name: page + in: query + description: which page to skip to when using a limit + schema: + format: int32 + default: 0 + type: integer + example: 2 + responses: + "200": + description: label Values + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ExportedLabelValues' + example: "[ { \"datasetId\" : 101, \"runId\": 201, \"values\" : { [labelName]\ + \ : labelValue } },...]" + /api/run/{id}/metadata: + get: + tags: + - Run + description: Get Run meta data by Run ID + operationId: getMetadata + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 202 + - name: token + in: query + description: Run API token + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + - name: schemaUri + in: query + description: Filter by Schmea URI + schema: + type: string + example: uri:my-benchmark:0.1 + responses: + "200": + description: Run payload + content: + application/json: + schema: + type: object + example: "{ \"metaDataID\": 1709, ...}" + /api/run/{id}/recalculate: + post: + tags: + - Run + description: Recalculate Datasets for Run + operationId: recalculateDatasets + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: Array of generated Datasets + content: + application/json: + schema: + type: array + items: + format: int32 + type: integer + example: + - 101 + - 102 + - 103 + /api/run/{id}/resetToken: + post: + tags: + - Run + description: Regenerate access token for Run + operationId: resetToken + parameters: + - name: id + in: path + description: Token ID + required: true + schema: + format: int32 + type: integer + example: 102 + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + /api/run/{id}/schema: + post: + tags: + - Run + description: Update Run schema for part of JSON data + operationId: updateSchema + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: path + in: query + description: JSON path expression to update schema + schema: + type: string + example: $.schemaURI + requestBody: + content: + text/plain: + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + additionalProperties: + type: string + /api/run/{id}/summary: + get: + tags: + - Run + description: Get Run Summary information by Run ID + operationId: getRunSummary + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 202 + - name: token + in: query + description: Run API token + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + responses: + "404": + description: If no Run have been found with the given id + content: + application/json: {} + "200": + description: Run summary with the referenced schemas and generated datasets + content: + application/json: + schema: + $ref: '#/components/schemas/RunSummary' + /api/run/{id}/trash: + post: + tags: + - Run + description: Trash a Run with a given ID + operationId: trash + parameters: + - name: id + in: path + description: Run ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: isTrashed + in: query + description: should run be trashed? + schema: + type: boolean + example: true + responses: + "201": + description: Created + /api/run/{id}/updateAccess: + post: + tags: + - Run + description: Update the Access configuration for a Run + operationId: updateAccess + parameters: + - name: id + in: path + description: Run ID to update Access + required: true + schema: + format: int32 + type: integer + example: 101 + - name: owner + in: query + description: Name of the new owner + required: true + schema: + type: string + example: perf-team + - name: access + in: query + description: New Access level + required: true + schema: + $ref: '#/components/schemas/Access' + example: 0 + responses: + "201": + description: Created + /api/schema: + get: + tags: + - Schema + description: Retrieve a paginated list of Schemas with available count + operationId: list + parameters: + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Schemas + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SchemaQueryResult' + post: + tags: + - Schema + description: Save a new Schema + operationId: add + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Schema' + responses: + "200": + description: Import a new Schema + content: + application/json: + schema: + format: int32 + type: integer + example: 103 + /api/schema/allLabels: + get: + tags: + - Schema + description: Retrieve list of Labels for ny name. Allows users to retrieve all + Label Definitions that have the same name + operationId: allLabels + parameters: + - name: name + in: query + description: Label name + schema: + type: string + example: buildID + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelInfo' + /api/schema/allTransformers: + get: + tags: + - Schema + description: Retrieve all transformers + operationId: allTransformers + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TransformerInfo' + /api/schema/descriptors: + get: + tags: + - Schema + description: Retrieve a list of Schema Descriptors + operationId: descriptors + parameters: + - name: id + in: query + description: Limit to a single Schema by ID + schema: + type: array + items: + format: int32 + type: integer + example: 102 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SchemaDescriptor' + /api/schema/findUsages: + get: + tags: + - Schema + description: Find all usages of a Schema by label name + operationId: findUsages + parameters: + - name: label + in: query + description: Name of label to search for + required: true + schema: + type: string + example: Throughput + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelLocation' + /api/schema/idByUri/{uri}: + get: + tags: + - Schema + description: Retrieve Schema ID by uri + operationId: idByUri + parameters: + - name: uri + in: path + description: Schema uri + required: true + schema: + type: string + example: uri:my-schema:0.1 + responses: + "200": + description: OK + content: + application/json: + schema: + format: int32 + type: integer + example: 101 + /api/schema/import: + post: + tags: + - Schema + description: Import an previously exported Schema either as a new Schema or + to update an existing Schema + operationId: importSchema + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SchemaExport' + required: true + responses: + "201": + description: Import a new Schema or update an existing Schema + /api/schema/{id}: + get: + tags: + - Schema + description: Retrieve Schema by ID + operationId: getSchema + parameters: + - name: id + in: path + description: Schema ID to retrieve + required: true + schema: + format: int32 + type: integer + example: 101 + - name: token + in: query + description: API token for authorization + schema: + type: string + example: 101 + responses: + "404": + description: No Schema with the given id was found + content: + application/json: {} + "200": + description: Returns Schema if a matching id is found + content: + application/json: + schema: + $ref: '#/components/schemas/Schema' + delete: + tags: + - Schema + description: Delete a Schema by id + operationId: delete + parameters: + - name: id + in: path + description: Schema ID to delete + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "204": + description: No Content + /api/schema/{id}/dropToken: + delete: + tags: + - Schema + description: Remove access token for schema + operationId: dropToken + parameters: + - name: id + in: path + description: Token ID + required: true + schema: + format: int32 + type: integer + example: 102 + responses: + "204": + description: No Content + /api/schema/{id}/export: + get: + tags: + - Schema + description: Export a Schema + operationId: exportSchema + parameters: + - name: id + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: A JSON representation of the SchemaExport object + content: + application/json: + schema: + $ref: '#/components/schemas/SchemaExport' + /api/schema/{id}/resetToken: + post: + tags: + - Schema + description: Regenerate access token for schema + operationId: resetToken + parameters: + - name: id + in: path + description: Token ID + required: true + schema: + format: int32 + type: integer + example: 102 + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + /api/schema/{id}/updateAccess: + post: + tags: + - Schema + description: Update the Access configuration for a Schema + operationId: updateAccess + parameters: + - name: id + in: path + description: Schema ID to update Access + required: true + schema: + format: int32 + type: integer + example: 101 + - name: owner + in: query + description: Name of the new owner + required: true + schema: + type: string + example: perf-team + - name: access + in: query + description: New Access level + required: true + schema: + $ref: '#/components/schemas/Access' + example: 0 + responses: + "201": + description: Created + /api/schema/{schemaId}/labels: + get: + tags: + - Schema + description: Retrieve list of Labels for a Schema by Schema ID + operationId: labels + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Label' + post: + tags: + - Schema + description: Save new or update existing Label for a Schema + operationId: addOrUpdateLabel + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Label' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + format: int32 + type: integer + /api/schema/{schemaId}/labels/{labelId}: + delete: + tags: + - Schema + description: Delete existing Label from a Schema + operationId: deleteLabel + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: labelId + in: path + description: Label ID + required: true + schema: + format: int32 + type: integer + example: 202 + responses: + "204": + description: No Content + /api/schema/{schemaId}/transformers: + get: + tags: + - Schema + description: List all Transformers defined for a Schema + operationId: listTransformers + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Transformer' + post: + tags: + - Schema + description: Save new or update existing Transformer defintion + operationId: addOrUpdateTransformer + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Transformer' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + format: int32 + type: integer + /api/schema/{schemaId}/transformers/{transformerId}: + delete: + tags: + - Schema + description: Delete a Transformer defined for a Schema + operationId: deleteTransformer + parameters: + - name: schemaId + in: path + description: Schema ID + required: true + schema: + format: int32 + type: integer + example: 101 + - name: transformerId + in: path + description: Transformer ID + required: true + schema: + format: int32 + type: integer + example: 202 + responses: + "204": + description: No Content + /api/test: + get: + tags: + - Test + description: Retrieve a paginated list of Tests with available count + operationId: list + parameters: + - name: roles + in: query + description: "__my, __all or a comma delimited list of roles" + schema: + type: string + example: __my + - name: limit + in: query + description: limit the number of results + schema: + format: int32 + type: integer + example: 20 + - name: page + in: query + description: filter by page number of a paginated list of Tests + schema: + format: int32 + type: integer + example: 2 + - name: sort + in: query + description: Field name to sort results + schema: + default: name + type: string + example: name + - name: direction + in: query + description: Sort direction + schema: + $ref: '#/components/schemas/SortDirection' + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestQueryResult' + post: + tags: + - Test + description: Create a new test + operationId: add + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Test' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Test' + /api/test/byName/{name}: + get: + tags: + - Test + description: Retrieve a test by name + operationId: getByNameOrId + parameters: + - name: name + in: path + description: Name of test to retrieve + required: true + schema: + type: string + example: my-comprehensive-benchmark + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Test' + /api/test/folders: + get: + tags: + - Test + description: Retrieve a list of all folders + operationId: folders + parameters: + - name: roles + in: query + description: "\"__my\", \"__all\" or a comma delimited list of roles" + schema: + type: string + example: __my + responses: + "200": + description: List of all folders + content: + application/json: + schema: + type: array + items: + type: string + example: + - quarkus + - ocp-perf-team + /api/test/import: + post: + tags: + - Test + description: Import a previously exported Test either as a new Test or to update + an existing Test + operationId: importTest + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TestExport' + required: true + responses: + "201": + description: Import a new Test or update an existing Test + /api/test/summary: + get: + tags: + - Test + description: Retrieve a summary of Tests in a folder + operationId: summary + parameters: + - name: roles + in: query + description: "\"__my\", \"__all\" or a comma delimited list of roles" + schema: + type: string + example: __my + - name: folder + in: query + description: name of the Folder containing the Tests + schema: + type: string + example: My Team Folder + - name: limit + in: query + description: limit the result count + schema: + default: 20 + type: integer + example: 20 + - name: page + in: query + description: 'filter by page number of a paginated list of ' + schema: + default: 1 + type: integer + example: 1 + - name: direction + in: query + description: Sort direction + schema: + allOf: + - $ref: '#/components/schemas/SortDirection' + - default: Ascending + example: Ascending + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestListing' + /api/test/{id}: + get: + tags: + - Test + description: Retrieve a test by id + operationId: get + parameters: + - name: id + in: path + required: true + schema: + format: int32 + type: integer + - name: token + in: query + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Test' + delete: + tags: + - Test + description: Delete a Test by id + operationId: delete + parameters: + - name: id + in: path + required: true + schema: + format: int32 + type: integer + responses: + "204": + description: No Content + /api/test/{id}/addToken: + post: + tags: + - Test + description: "Add a Test API Token for access to provide access to a test data\ + \ for integrated tooling, e.g. reporting services" + operationId: addToken + parameters: + - name: id + in: path + description: ID of test to add token to + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TestToken' + responses: + "200": + description: OK + content: + application/json: + schema: + format: int32 + type: integer + /api/test/{id}/export: + get: + tags: + - Test + operationId: export + parameters: + - name: id + in: path + required: true + schema: + format: int32 + type: integer + responses: + "200": + description: A Test definition formatted as json + content: + application/json: + schema: + $ref: '#/components/schemas/TestExport' + /api/test/{id}/fingerprint: + get: + tags: + - Test + description: List all Fingerprints for a Test + operationId: listFingerprints + parameters: + - name: id + in: path + description: Test ID to retrieve Fingerprints for + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Fingerprints' + /api/test/{id}/labelValues: + get: + tags: + - Test + description: List all Label Values for a Test + operationId: listLabelValues + parameters: + - name: id + in: path + description: Test ID to retrieve Label Values for + required: true + schema: + format: int32 + type: integer + example: 101 + - name: filtering + in: query + description: Retrieve values for Filtering Labels + schema: + default: true + type: boolean + example: true + - name: metrics + in: query + description: Retrieve values for Metric Labels + schema: + default: true + type: boolean + example: false + - name: filter + in: query + description: either a required json sub-document or path expression + schema: + default: "{}" + type: string + examples: + object: + description: json object that must exist in the values object + value: "{labelName:necessaryValue,...}" + string: + description: valid filtering jsonpath that returns null if not found (not + predicates) + value: $.count ? (@ < 20 && @ > 10) + - name: before + in: query + description: ISO-like date time string or epoch millis + schema: + default: "" + type: string + example: 1970-01-01T00:00:00+00:00 or an integer + - name: after + in: query + description: ISO-like date time string or epoch millis + schema: + default: "" + type: string + example: 1970-01-01T00:00:00+00:00 or an integer + - name: sort + in: query + description: json path to sortable value or start or stop for sorting by time + schema: + default: "" + type: string + example: $.label or start or stop + - name: direction + in: query + description: either Ascending or Descending + schema: + default: Ascending + type: string + example: count + - name: limit + in: query + description: the maximum number of results to include + schema: + format: int32 + default: 2147483647 + type: integer + example: 10 + - name: page + in: query + description: which page to skip to when using a limit + schema: + format: int32 + default: 0 + type: integer + example: 2 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ExportedLabelValues' + /api/test/{id}/move: + post: + tags: + - Test + description: Update the folder for a Test. Tests can be moved to different folders + operationId: updateFolder + parameters: + - name: id + in: path + description: Test ID to update + required: true + schema: + format: int32 + type: integer + example: 101 + - name: folder + in: query + description: New folder to store the tests + schema: + type: string + example: My Benchmark Folder + responses: + "201": + description: Created + /api/test/{id}/notifications: + post: + tags: + - Test + description: "Update notifications for a Test. It is possible to disable notifications\ + \ for a Test, so that no notifications are sent to subscribers" + operationId: updateNotifications + parameters: + - name: id + in: path + description: Test ID to update + required: true + schema: + format: int32 + type: integer + example: 101 + - name: enabled + in: query + description: Whether notifications are enabled + required: true + schema: + type: boolean + example: false + responses: + "201": + description: Created + /api/test/{id}/recalculate: + get: + tags: + - Test + description: Get recalculation status for Test + operationId: getRecalculationStatus + parameters: + - name: id + in: path + description: Test ID to retrieve recalculation status for + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RecalculationStatus' + post: + tags: + - Test + description: Recalculate Datasets for Test + operationId: recalculateDatasets + parameters: + - name: id + in: path + description: Test ID to recalculate datasets for + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "201": + description: Created + /api/test/{id}/revokeToken/{tokenId}: + delete: + tags: + - Test + description: Revoke a Token defined for a Test + operationId: dropToken + parameters: + - name: id + in: path + description: Test ID to revoke token + required: true + schema: + format: int32 + type: integer + example: 101 + - name: tokenId + in: path + description: ID of token to revoke + required: true + schema: + format: int32 + type: integer + example: 202 + responses: + "204": + description: No Content + /api/test/{id}/tokens: + get: + tags: + - Test + description: A collection of Test Tokens for a given Test + operationId: tokens + parameters: + - name: id + in: path + description: ID of test to retrieve list of tokens + required: true + schema: + format: int32 + type: integer + example: 101 + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TestToken' + /api/test/{id}/transformers: + post: + tags: + - Test + description: Update transformers for Test + operationId: updateTransformers + parameters: + - name: id + in: path + description: Test ID to retrieve Label Values for + required: true + schema: + format: int32 + type: integer + example: 101 + requestBody: + content: + application/json: + schema: + type: array + items: + format: int32 + type: integer + required: true + responses: + "201": + description: Created + /api/test/{id}/updateAccess: + post: + tags: + - Test + description: Update the Access configuration for a Test + operationId: updateAccess + parameters: + - name: id + in: path + description: Test ID to revoke token + required: true + schema: + format: int32 + type: integer + example: 101 + - name: owner + in: query + description: Name of the new owner + required: true + schema: + type: string + example: perf-team + - name: access + in: query + description: New Access level for the Test + required: true + schema: + $ref: '#/components/schemas/Access' + example: 0 + responses: + "201": + description: Created +components: + schemas: + Access: + description: "Resources have different visibility within the UI. 'PUBLIC', 'PROTECTED'\ + \ and 'PRIVATE'. Restricted resources are not visible to users who do not\ + \ have the correct permissions" + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + Action: + required: + - id + - event + - type + - config + - secrets + - testId + - active + - runAlways + type: object + properties: + id: + format: int32 + type: integer + event: + type: string + type: + type: string + config: + type: object + oneOf: + - $ref: '#/components/schemas/HttpAction' + - $ref: '#/components/schemas/GithubIssueCommentAction' + - $ref: '#/components/schemas/GithubIssueCreateAction' + secrets: + type: object + allOf: + - $ref: '#/components/schemas/Secret' + testId: + format: int32 + type: integer + active: + type: boolean + runAlways: + type: boolean + ActionLog: + description: Action Log + required: + - testId + - event + type: object + allOf: + - $ref: '#/components/schemas/PersistentLog' + properties: + testId: + format: int32 + type: integer + event: + type: string + type: + type: string + BetterOrWorse: + description: Result of running an Experiment + enum: + - BETTER + - SAME + - WORSE + type: string + ChangeDetection: + required: + - id + - model + - config + type: object + properties: + id: + format: int32 + type: integer + model: + type: string + config: + type: object + oneOf: + - $ref: '#/components/schemas/RelativeDifferenceDetectionConfig' + - $ref: '#/components/schemas/FixedThresholdDetectionConfig' + discriminator: + propertyName: model + mapping: + relativeDifference: '#/components/schemas/RelativeDifferenceDetectionConfig' + fixedThreshold: '#/components/schemas/FixedThresholdDetectionConfig' + ChangeDetectionModelType: + description: Type of Change Detection Model + enum: + - FIXED_THRESHOLD + - RELATIVE_DIFFERENCE + type: string + ComparisonResult: + description: Result of performing a Comparison + type: object + properties: + overall: + description: Was the Experiment dataset better or worse than the baseline + dataset + enum: + - BETTER + - SAME + - WORSE + type: string + allOf: + - $ref: '#/components/schemas/BetterOrWorse' + experimentValue: + format: double + description: Experiment value + type: number + baselineValue: + format: double + description: Baseline value + type: number + result: + description: The relative difference between the Experiment and Baseline + Datasets + type: string + ConditionComponent: + required: + - name + - title + - description + - type + - properties + type: object + properties: + name: + description: Change detection model component name + type: string + example: min + title: + description: Change detection model component title + type: string + example: Minimum + description: + description: Change detection model component description + type: string + example: Lower bound for acceptable datapoint values. + type: + description: UI Component type + enum: + - LOG_SLIDER + - ENUM + - NUMBER_BOUND + - SWITCH + type: object + example: '"LOG_SLIDER"' + properties: + description: Map of properties for component + type: object + additionalProperties: {} + example: "" + ConditionConfig: + description: A configuration object for Change detection models + required: + - name + - title + - description + - ui + type: object + properties: + name: + description: Name of Change detection model + type: string + example: fixedThreshold + title: + description: UI name for change detection model + type: string + example: Fixed Threshold + description: + description: Change detection model description + type: string + example: This model checks that the datapoint value is within fixed bounds. + ui: + description: A list of UI components for dynamically building the UI components + type: array + items: + $ref: '#/components/schemas/ConditionComponent' + defaults: + description: A dictionary of UI default configuration items for dynamically + building the UI components + type: object + additionalProperties: {} + Dataset: + description: A dataset is the JSON document used as the basis for all comparisons + and reporting + required: + - testid + - data + - ordinal + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + - $ref: '#/components/schemas/ProtectedTimeType' + properties: + id: + format: int32 + description: Dataset Unique ID + type: integer + example: 101 + description: + description: Run description + type: string + example: Run on AWS with m7g.large + testid: + format: int32 + description: Test ID that Dataset relates to + type: integer + example: 101 + data: + description: Data payload + type: string + ordinal: + format: int32 + description: Dataset ordinal for ordered list of Datasets derived from a + Run + type: integer + example: 1 + validationErrors: + description: List of Validation Errors + type: array + items: + $ref: '#/components/schemas/ValidationError' + runId: + format: int32 + description: Run ID that Dataset relates to + type: integer + example: 101 + DatasetInfo: + required: + - id + - runId + - ordinal + - testId + type: object + properties: + id: + format: int32 + description: Dataset ID for Dataset + type: integer + example: 101 + runId: + format: int32 + description: Run ID that Dataset relates to + type: integer + example: 101 + ordinal: + format: int32 + description: Ordinal position in ordered list + type: integer + example: 2 + testId: + format: int32 + description: Test ID that Dataset relates to + type: integer + example: 103 + DatasetList: + description: Result containing a subset of Dataset Summaries and the total count + of available. Used in paginated tables + required: + - total + - datasets + type: object + properties: + total: + format: int64 + description: Total number of Dataset Summaries available + type: integer + example: 64 + datasets: + description: List of Dataset Summaries. This is often a subset of total + available. + type: array + items: + $ref: '#/components/schemas/DatasetSummary' + DatasetLog: + description: Dataset Log + required: + - source + - testId + - runId + - datasetId + - datasetOrdinal + type: object + allOf: + - $ref: '#/components/schemas/PersistentLog' + properties: + source: + type: string + testId: + format: int32 + type: integer + runId: + format: int32 + type: integer + datasetId: + format: int32 + type: integer + datasetOrdinal: + format: int32 + type: integer + DatasetSummary: + required: + - id + - runId + - ordinal + - testId + - testname + - schemas + type: object + allOf: + - $ref: '#/components/schemas/ProtectedTimeType' + properties: + id: + format: int32 + description: Unique Dataset ID + type: integer + example: 101 + runId: + format: int32 + description: Run ID that Dataset relates to + type: integer + example: 202 + ordinal: + format: int32 + description: Ordinal position of Dataset Summary on returned List + type: integer + example: 3 + testId: + format: int32 + description: Test ID that Dataset relates to + type: integer + example: 202 + testname: + description: Test name that the Dataset relates to + type: string + example: my-comprehensive-benchmark + description: + description: Dataset description + type: string + example: Run on AWS with m7g.large + view: + description: map of view component ids to the LabelValueMap to render the + component for this dataset + type: object + allOf: + - $ref: '#/components/schemas/IndexedLabelValueMap' + example: "{ \"[view_component_id]\": { \"[labelName]\": labelValue} }" + schemas: + description: List of Schema usages + type: array + items: + $ref: '#/components/schemas/SchemaUsage' + validationErrors: + description: List of Validation Errors + type: array + items: + $ref: '#/components/schemas/ValidationError' + Datastore: + description: Type of backend datastore + required: + - access + - owner + - id + - name + - builtIn + - config + - type + type: object + properties: + access: + description: Access rights for the test. This defines the visibility of + the Test in the UI + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + allOf: + - $ref: '#/components/schemas/Access' + example: PUBLIC + owner: + description: Name of the team that owns the test. Users must belong to the + team that owns a test to make modifications + type: string + example: performance-team + id: + format: int32 + description: Unique Datastore id + type: integer + example: 101 + name: + description: "Name of the datastore, used to identify the datastore in the\ + \ Test definition" + type: string + example: Perf Elasticsearch + builtIn: + description: Is this a built-in datastore? Built-in datastores cannot be + deleted or modified + type: boolean + example: false + config: + type: object + oneOf: + - $ref: '#/components/schemas/ElasticsearchDatastoreConfig' + - $ref: '#/components/schemas/PostgresDatastoreConfig' + type: + description: Type of backend datastore + enum: + - POSTGRES + - ELASTICSEARCH + type: string + example: ELASTICSEARCH + DatastoreTestResponse: + type: object + properties: + msg: + type: string + success: + type: boolean + DatastoreType: + description: Type of backend datastore + enum: + - POSTGRES + - ELASTICSEARCH + type: string + example: ELASTICSEARCH + ElasticsearchDatastoreConfig: + description: Type of backend datastore + required: + - builtIn + - url + type: object + properties: + builtIn: + description: Built In + type: boolean + apiKey: + description: Elasticsearch API KEY + type: string + url: + description: Elasticsearch url + type: string + username: + description: Elasticsearch username + type: string + password: + description: Elasticsearch password + type: string + ErrorDetails: + required: + - type + - message + type: object + properties: + type: + description: Validation Error type + type: string + code: + type: string + path: + type: string + evaluationPath: + type: string + schemaPath: + type: string + deprecated: true + schemaLocation: + type: string + instanceLocation: + type: string + property: + type: string + arguments: + type: array + items: + type: string + details: + type: string + messageKey: + type: string + valid: + type: boolean + message: + type: string + ExperimentComparison: + required: + - model + - config + - variableId + type: object + properties: + model: + description: Name of comparison model + type: string + example: relativeDifference + config: + description: Model JSON configuration + type: string + variableId: + format: int32 + description: Variable ID to run experiment against + type: integer + example: 101 + variableName: + description: Variable Name to run experiment against + type: string + example: Throughput + ExperimentProfile: + description: An Experiment Profile defines the labels and filters for the dataset + and baseline + required: + - id + - name + - selectorLabels + - baselineLabels + - comparisons + type: object + properties: + id: + format: int32 + description: Experiment Profile unique ID + type: integer + example: 101 + name: + description: Name of Experiment Profile + type: string + example: Techempower comparison + testId: + format: int32 + description: Test ID that Experiment Profile relates to + type: integer + example: 101 + selectorLabels: + description: Array of selector labels + type: array + items: + type: string + example: + - Framework + selectorFilter: + description: Selector filter to apply to Selector label values + type: string + example: value => value === 'quarkus-resteasy-reactive-hibernate-reactive' + baselineLabels: + description: Array of selector labels for comparison Baseline + type: array + items: + type: string + example: + - timestamp + baselineFilter: + description: Selector filter to apply to Baseline label values + type: string + example: value => value === 1666955225547 + comparisons: + description: Collection of Experiment Comparisons to run during an Experiment + evaluation + type: array + items: + $ref: '#/components/schemas/ExperimentComparison' + extraLabels: + description: These labels are not used by Horreum but are added to the result + event and therefore can be used e.g. when firing an Action. + type: array + items: + type: string + ExperimentResult: + description: Result of running an Experiment + type: object + properties: + profile: + description: Experiment profile that results relates to + type: object + allOf: + - $ref: '#/components/schemas/ExperimentProfile' + logs: + description: A list of log statements recorded while Experiment was evaluated + type: array + items: + $ref: '#/components/schemas/DatasetLog' + datasetInfo: + description: Dataset Info about dataset used for experiment + type: object + allOf: + - $ref: '#/components/schemas/DatasetInfo' + baseline: + description: A list of Dataset Info for experiment baseline(s) + type: array + items: + $ref: '#/components/schemas/DatasetInfo' + results: + description: A Map of all comparisons and results evaluated during an Experiment + type: object + additionalProperties: + $ref: '#/components/schemas/ComparisonResult' + extraLabels: + type: string + notify: + type: boolean + ExportedLabelValues: + description: A map of label names to label values with the associated datasetId + and runId + required: + - start + - stop + type: object + properties: + values: + $ref: '#/components/schemas/LabelValueMap' + runId: + format: int32 + description: the run id that created the dataset + type: integer + example: 101 + datasetId: + format: int32 + description: the unique dataset id + type: integer + example: 101 + start: + format: date-time + description: Start timestamp + type: string + example: 2019-09-26T07:58:30.996+0200 + stop: + format: date-time + description: Stop timestamp + type: string + example: 2019-09-26T07:58:30.996+0200 + Extractor: + description: "An Extractor defines how values are extracted from a JSON document,\ + \ for use in Labels etc." + required: + - name + - jsonpath + - isarray + type: object + properties: + name: + description: Name of extractor. This name is used in Combination Functions + to refer to values by name + type: string + example: buildID + jsonpath: + description: JSON path expression defining the location of the extractor + value in the JSON document. This is a pSQL json path expression + type: string + example: $.buildInfo.buildID + isarray: + description: Does the JSON path expression reference an Array? + type: boolean + example: false + FingerprintValue: + description: Representation of Fingerprint. If the Fingerprint has children + the value will be null. + type: object + properties: + name: + description: Fingerprint name + type: string + example: Mode + value: + description: Fingerprint name + example: Library + children: + description: List of Fingerprint children + type: array + items: + description: Cyclic reference to io.hyperfoil.tools.horreum.api.data.FingerprintValue + Fingerprints: + description: A list of Fingerprints representing one dataset + type: object + properties: + values: + type: array + items: + $ref: '#/components/schemas/FingerprintValue' + FixThresholdConfig: + required: + - value + - enabled + - inclusive + type: object + properties: + value: + description: Threshold Value + type: integer + example: 95 + enabled: + description: Threshold enabled/disabled + type: boolean + example: true + inclusive: + description: Is threshold inclusive of defined value? + type: boolean + example: false + FixedThresholdDetectionConfig: + required: + - builtIn + - min + - max + type: object + properties: + builtIn: + description: Built In + type: boolean + min: + description: Lower bound for acceptable datapoint values + type: object + allOf: + - $ref: '#/components/schemas/FixThresholdConfig' + max: + description: Upper bound for acceptable datapoint values + type: object + allOf: + - $ref: '#/components/schemas/FixThresholdConfig' + GithubIssueCommentAction: + type: object + properties: + issueUrl: + type: string + owner: + type: string + repo: + type: string + issue: + type: string + formatter: + type: string + GithubIssueCreateAction: + type: object + properties: + owner: + type: string + repo: + type: string + title: + type: string + formatter: + type: string + HttpAction: + type: object + properties: + url: + type: string + IndexedLabelValueMap: + type: object + additionalProperties: + $ref: '#/components/schemas/LabelValueMap' + KeycloakConfig: + type: object + properties: + realm: + description: Keycloak realm securing Horreum instance + type: string + example: horreum + url: + description: URL of Keycloak instance securing Horreum + type: string + example: https://horreum-keycloak.example.com + clientId: + description: Keycloak client ID in Horreum realm for User Interface + type: string + example: horreum-ui + Label: + description: "A Label is a core component of Horreum, defining which components\ + \ of the JSON document are part of a KPI and how the metric values are calculated" + required: + - id + - name + - extractors + - filtering + - metrics + - schemaId + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + properties: + id: + format: int32 + description: Unique ID for Label + type: integer + example: 101 + name: + description: "Name for label. NOTE: all Labels are considered to have the\ + \ same semantic meaning throughout the entire system" + type: string + example: Throughput + extractors: + description: "A collection of Extractors, that will be combined in the Combination\ + \ Function" + type: array + items: + $ref: '#/components/schemas/Extractor' + function: + description: A Combination Function that defines how values from Extractors + are combined to produce a Label Value + type: string + example: "value => { return ((value.reduce((a,b) => a+b))/value.length*1000).toFixed(3);\ + \ }" + filtering: + description: Is Label a filtering label? Filtering labels contains values + that are used to filter datasets for comparison + type: boolean + example: true + metrics: + description: Is Label a metrics label? Metrics labels are contain Metrics + that are used for comparison + type: boolean + example: true + schemaId: + format: int32 + description: Schema ID that the Label relates to + type: integer + example: 101 + LabelInFingerprint: + type: object + allOf: + - $ref: '#/components/schemas/LabelLocation' + LabelInReport: + type: object + allOf: + - $ref: '#/components/schemas/LabelLocation' + properties: + configId: + format: int32 + type: integer + title: + type: string + where: + type: string + name: + type: string + LabelInRule: + type: object + allOf: + - $ref: '#/components/schemas/LabelLocation' + properties: + ruleId: + format: int32 + type: integer + ruleName: + type: string + LabelInVariable: + type: object + allOf: + - $ref: '#/components/schemas/LabelLocation' + properties: + variableId: + format: int32 + type: integer + variableName: + type: string + LabelInView: + type: object + allOf: + - $ref: '#/components/schemas/LabelLocation' + properties: + viewId: + format: int32 + type: integer + viewName: + type: string + componentId: + format: int32 + type: integer + header: + type: string + LabelInfo: + required: + - name + - metrics + - filtering + - schemas + type: object + properties: + name: + description: Label name + type: string + example: buildID + metrics: + description: Is label a metrics label? + type: boolean + example: true + filtering: + description: Is label a filtering label? + type: boolean + example: false + schemas: + description: List of schemas where label is referenced + type: array + items: + $ref: '#/components/schemas/SchemaDescriptor' + LabelLocation: + type: object + properties: + type: + description: Location of Label usage + type: string + example: VIEW + testId: + format: int32 + description: Unique ID for location that references Schema + type: integer + example: 101 + testName: + description: Test name that references Schema + type: string + example: My Benchmark + LabelPreview: + description: Preview a Label Value derived from a Dataset Data. A preview allows + users to apply a Label to a dataset and preview the Label Value result and + processing errors in the UI + type: object + properties: + value: + description: "Value value extracted from Dataset. This can be a scalar,\ + \ array or JSON object" + type: string + output: + description: Description of errors occurred attempting to generate Label + Value Preview + type: string + LabelValue: + description: Label Value derived from Label definition and Dataset Data + required: + - id + - name + - schema + type: object + properties: + id: + format: int32 + description: Unique ID for Label Value + type: integer + example: 101 + name: + description: Label name + type: string + example: buildID + schema: + description: Summary description of Schema + type: object + allOf: + - $ref: '#/components/schemas/SchemaDescriptor' + value: + description: "Value value extracted from Dataset. This can be a scalar,\ + \ array or JSON object" + type: string + example: "1724" + LabelValueMap: + description: a map of label name to value + type: object + example: "{ \"[labelName]\": labelValue}" + MissingDataRule: + required: + - id + - maxStaleness + - testId + type: object + properties: + id: + format: int32 + type: integer + name: + type: string + labels: + type: array + items: + type: string + condition: + type: string + maxStaleness: + format: int64 + type: integer + lastNotification: + format: date-time + type: string + example: 2022-03-10T16:15:50Z + testId: + format: int32 + type: integer + PersistentLog: + description: Persistent Log + required: + - id + - level + - timestamp + - message + type: object + properties: + id: + format: int64 + type: integer + level: + format: int32 + type: integer + timestamp: + format: date-time + type: string + example: 2022-03-10T16:15:50Z + message: + type: string + PostgresDatastoreConfig: + description: Built in backend datastore + required: + - builtIn + type: object + properties: + builtIn: + description: Built In + type: boolean + ProtectedTimeType: + required: + - access + - owner + - start + - stop + type: object + properties: + access: + description: Access rights for the test. This defines the visibility of + the Test in the UI + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + allOf: + - $ref: '#/components/schemas/Access' + example: PUBLIC + owner: + description: Name of the team that owns the test. Users must belong to the + team that owns a test to make modifications + type: string + example: performance-team + start: + format: int64 + description: Run Start timestamp + type: integer + example: 1704965908267 + stop: + format: int64 + description: Run Stop timestamp + type: integer + example: 1704965908267 + ProtectedType: + required: + - access + - owner + type: object + properties: + access: + description: Access rights for the test. This defines the visibility of + the Test in the UI + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + allOf: + - $ref: '#/components/schemas/Access' + example: PUBLIC + owner: + description: Name of the team that owns the test. Users must belong to the + team that owns a test to make modifications + type: string + example: performance-team + RecalculationStatus: + required: + - timestamp + - totalRuns + - finished + - datasets + type: object + properties: + timestamp: + format: int64 + description: Recalculation timestamp + type: integer + example: 1698013206000 + totalRuns: + format: int64 + description: Total number of Runs being recalculated + type: integer + example: 152 + finished: + format: int64 + description: Total number of completed recalculations + type: integer + example: 93 + datasets: + format: int64 + description: Total number of generated datasets + type: integer + example: 186 + RelativeDifferenceDetectionConfig: + required: + - builtIn + - filter + - window + - threshold + - minPrevious + type: object + properties: + builtIn: + description: Built In + type: boolean + filter: + description: Relative Difference Detection filter + type: string + example: mean + window: + format: int32 + description: Number of most recent datapoints used for aggregating the value + for comparison. + type: integer + example: 5 + threshold: + format: double + description: Maximum difference between the aggregated value of last + datapoints and the mean of preceding values. + type: number + example: 0.2 + minPrevious: + format: int32 + description: Minimal number of preceding datapoints + type: integer + example: 5 + ReportComponent: + description: Report Component + required: + - name + - order + - labels + type: object + properties: + id: + format: int32 + type: integer + name: + type: string + order: + format: int32 + type: integer + labels: + description: Array of labels + type: array + items: + type: string + example: + - Framework + function: + type: string + unit: + type: string + reportId: + format: int32 + type: integer + ReportLog: + description: Report Log + required: + - reportId + type: object + allOf: + - $ref: '#/components/schemas/PersistentLog' + properties: + reportId: + format: int32 + type: integer + Run: + required: + - access + - owner + - start + - stop + - id + - testid + - data + - trashed + type: object + properties: + access: + description: Access rights for the test. This defines the visibility of + the Test in the UI + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + allOf: + - $ref: '#/components/schemas/Access' + example: PUBLIC + owner: + description: Name of the team that owns the test. Users must belong to the + team that owns a test to make modifications + type: string + example: performance-team + start: + format: int64 + description: Run Start timestamp + type: integer + example: 1704965908267 + stop: + format: int64 + description: Run Stop timestamp + type: integer + example: 1704965908267 + id: + format: int32 + description: Unique Run ID + type: integer + example: 101 + description: + description: Run description + type: string + example: Run on AWS with m7g.large + testid: + format: int32 + description: Test ID run relates to + type: integer + example: 101 + data: + description: Run result payload + type: string + metadata: + description: "JSON metadata related to run, can be tool configuration etc" + type: string + trashed: + description: Has Run been deleted from UI + type: boolean + example: false + datasets: + description: Collection of Datasets derived from Run payload + type: array + items: + $ref: '#/components/schemas/Dataset' + validationErrors: + description: Collection of Validation Errors in Run payload + type: array + items: + $ref: '#/components/schemas/ValidationError' + RunCount: + required: + - total + - active + - trashed + type: object + properties: + total: + format: int64 + description: Total count of Runs visible + type: integer + example: 100 + active: + format: int64 + description: Total count of active Runs visible + type: integer + example: 95 + trashed: + format: int64 + description: Total count of trashed Runs + type: integer + example: 5 + RunExtended: + required: + - access + - owner + - start + - stop + - id + - testid + - data + - trashed + - schemas + - testname + - datasets + type: object + properties: + access: + description: Access rights for the test. This defines the visibility of + the Test in the UI + enum: + - PUBLIC + - PROTECTED + - PRIVATE + type: string + allOf: + - $ref: '#/components/schemas/Access' + example: PUBLIC + owner: + description: Name of the team that owns the test. Users must belong to the + team that owns a test to make modifications + type: string + example: performance-team + start: + format: int64 + description: Run Start timestamp + type: integer + example: 1704965908267 + stop: + format: int64 + description: Run Stop timestamp + type: integer + example: 1704965908267 + id: + format: int32 + description: Unique Run ID + type: integer + example: 101 + description: + description: Run description + type: string + example: Run on AWS with m7g.large + testid: + format: int32 + description: Test ID run relates to + type: integer + example: 101 + data: + description: Run result payload + type: string + metadata: + description: "JSON metadata related to run, can be tool configuration etc" + type: string + trashed: + description: Has Run been deleted from UI + type: boolean + example: false + validationErrors: + description: Collection of Validation Errors in Run payload + type: array + items: + $ref: '#/components/schemas/ValidationError' + schemas: + description: List of Schema Usages + type: array + items: + $ref: '#/components/schemas/SchemaUsage' + testname: + description: Test name run references + type: string + example: My benchmark + datasets: + description: List of DatasetIDs + type: array + items: + format: int32 + type: integer + example: + - 101 + - 102 + - 104 + - 106 + RunSummary: + required: + - id + - testid + - testname + - trashed + - hasMetadata + - datasets + type: object + allOf: + - $ref: '#/components/schemas/ProtectedTimeType' + properties: + id: + format: int32 + description: Run unique ID + type: integer + example: 202 + testid: + format: int32 + description: test ID run relates to + type: integer + example: 101 + token: + type: string + testname: + description: test ID run relates to + type: string + example: My benchmark + trashed: + description: has Run been trashed in the UI + type: boolean + example: false + hasMetadata: + description: does Run have metadata uploaded alongside Run data + type: boolean + example: false + description: + description: Run description + type: string + example: Run on AWS with m7g.large + schemas: + description: List of all Schema Usages for Run + type: array + items: + $ref: '#/components/schemas/SchemaUsage' + datasets: + description: Array of datasets ids + type: array + items: + format: int32 + type: integer + example: + - 101 + - 102 + - 103 + validationErrors: + description: Array of validation errors + type: array + items: + $ref: '#/components/schemas/ValidationError' + RunsSummary: + required: + - total + - runs + type: object + properties: + total: + format: int64 + description: Total count of Runs visible + type: integer + example: 1 + runs: + description: List of Run Summaries + type: array + items: + $ref: '#/components/schemas/RunSummary' + Schema: + description: Data object that describes the schema definition for a test + required: + - id + - uri + - name + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + properties: + id: + format: int32 + description: Unique Schema ID + type: integer + example: 101 + uri: + description: "Unique, versioned schema URI" + type: string + example: uri:my-schema:0.1 + name: + description: Schema name + type: string + example: My benchmark schema + description: + description: Schema Description + type: string + example: Schema for processing my benchmark + schema: + description: JSON validation schema. Used to validate uploaded JSON documents + type: string + example: "{ \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\ + , \"$id\": \"https://example.com/product.schema.json\", \"title\": \"\ + Product\", \"description\": \"A product in the catalog\", \"type\":\ + \ \"object\"}" + token: + description: Array of API tokens associated with test + type: string + example: "" + SchemaDescriptor: + required: + - id + - name + - uri + type: object + properties: + id: + format: int32 + description: Schema unique ID + type: integer + example: 1 + name: + description: Schema name + type: string + example: my-benchmark-schema + uri: + description: Schema name + type: string + example: uri:my-schmea:0.1 + SchemaExport: + description: Represents a Schema with all associated data used for export/import + operations. + type: object + allOf: + - $ref: '#/components/schemas/Schema' + properties: + labels: + description: Array of Labels associated with schema + type: array + items: + $ref: '#/components/schemas/Label' + transformers: + description: Array of Transformers associated with schema + type: array + items: + $ref: '#/components/schemas/Transformer' + SchemaQueryResult: + required: + - schemas + - count + type: object + properties: + schemas: + description: Array of Schemas + type: array + items: + $ref: '#/components/schemas/Schema' + count: + format: int64 + description: Count of available Schemas. This is a count of Schemas that + the current user has access to + type: integer + example: 64 + SchemaUsage: + required: + - id + - name + - uri + - source + - type + - hasJsonSchema + type: object + properties: + id: + format: int32 + description: Schema unique ID + type: integer + example: 1 + name: + description: Schema name + type: string + example: my-benchmark-schema + uri: + description: Schema name + type: string + example: uri:my-schmea:0.1 + source: + format: int32 + description: "Source of schema usage, 0 is data, 1 is metadata. DataSets\ + \ always use 0" + type: integer + example: 1 + type: + format: int32 + description: "Location of Schema Usage, 0 for Run, 1 for Dataset" + type: integer + example: 1 + key: + description: Ordinal position of schema usage in Run/Dataset + type: string + example: "1" + hasJsonSchema: + description: Does schema have a JSON validation schema defined? + type: boolean + example: false + Secret: + type: object + properties: + token: + type: string + modified: + type: boolean + SortDirection: + enum: + - Ascending + - Descending + type: string + TableReport: + description: Table Report + required: + - id + - config + - created + - comments + - data + - logs + type: object + properties: + id: + format: int32 + type: integer + config: + description: Table Report Config + required: + - id + - title + - seriesLabels + - components + type: object + allOf: + - $ref: '#/components/schemas/TableReportConfig' + properties: + id: + format: int32 + type: integer + title: + type: string + test: + $ref: '#/components/schemas/Test' + filterLabels: + description: ArrayNode of filter labels + type: array + items: + type: string + filterFunction: + type: string + categoryLabels: + description: ArrayNode of category labels + type: array + items: + type: string + categoryFunction: + type: string + categoryFormatter: + type: string + seriesLabels: + description: ArrayNode of series labels + type: array + items: + type: string + seriesFunction: + type: string + seriesFormatter: + type: string + scaleLabels: + description: ArrayNode of filter labels + type: array + items: + type: string + scaleFunction: + type: string + scaleFormatter: + type: string + scaleDescription: + type: string + components: + description: List of ReportComponents + type: array + items: + description: Report Component + required: + - name + - order + - labels + type: object + properties: + id: + format: int32 + type: integer + name: + type: string + order: + format: int32 + type: integer + labels: + description: Array of labels + type: array + items: + type: string + example: + - Framework + function: + type: string + unit: + type: string + reportId: + format: int32 + type: integer + created: + format: date-time + description: Created timestamp + type: string + example: 2019-09-26T07:58:30.996+0200 + comments: + description: List of ReportComments + type: array + items: + required: + - level + - comment + type: object + properties: + id: + format: int32 + type: integer + level: + format: int32 + type: integer + category: + type: string + componentId: + format: int32 + type: integer + comment: + type: string + data: + description: List of TableReportData + type: array + items: + description: Table Report Data + required: + - datasetId + - runId + - ordinal + - category + - series + - scale + - values + type: object + properties: + datasetId: + format: int32 + type: integer + runId: + format: int32 + type: integer + ordinal: + format: int32 + type: integer + category: + type: string + series: + type: string + scale: + type: string + values: + description: Array of values + type: array + items: + type: number + logs: + description: List of ReportLogs + type: array + items: + description: Report Log + required: + - reportId + type: object + allOf: + - $ref: '#/components/schemas/PersistentLog' + properties: + reportId: + format: int32 + type: integer + TableReportConfig: + description: Table Report Config + required: + - id + - title + - seriesLabels + - components + type: object + properties: + id: + format: int32 + type: integer + title: + type: string + test: + $ref: '#/components/schemas/Test' + filterLabels: + description: ArrayNode of filter labels + type: array + items: + type: string + filterFunction: + type: string + categoryLabels: + description: ArrayNode of category labels + type: array + items: + type: string + categoryFunction: + type: string + categoryFormatter: + type: string + seriesLabels: + description: ArrayNode of series labels + type: array + items: + type: string + seriesFunction: + type: string + seriesFormatter: + type: string + scaleLabels: + description: ArrayNode of filter labels + type: array + items: + type: string + scaleFunction: + type: string + scaleFormatter: + type: string + scaleDescription: + type: string + components: + description: List of ReportComponents + type: array + items: + description: Report Component + required: + - name + - order + - labels + type: object + properties: + id: + format: int32 + type: integer + name: + type: string + order: + format: int32 + type: integer + labels: + description: Array of labels + type: array + items: + type: string + example: + - Framework + function: + type: string + unit: + type: string + reportId: + format: int32 + type: integer + TableReportData: + description: Table Report Data + required: + - datasetId + - runId + - ordinal + - category + - series + - scale + - values + type: object + properties: + datasetId: + format: int32 + type: integer + runId: + format: int32 + type: integer + ordinal: + format: int32 + type: integer + category: + type: string + series: + type: string + scale: + type: string + values: + description: Array of values + type: array + items: + type: number + Test: + description: Represents a Test. Tests are typically equivalent to a particular + benchmark + required: + - id + - name + - datastoreId + - notificationsEnabled + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + properties: + id: + format: int32 + description: Unique Test id + type: integer + example: 101 + name: + description: Test name + type: string + example: my-comprehensive-benchmark + folder: + description: Name of folder that the test is stored in. Folders allow tests + to be organised in the UI + type: string + example: My Team Folder + description: + description: Description of the test + type: string + example: Comprehensive benchmark to tests the limits of any system it is + run against + datastoreId: + format: int32 + description: backend ID for backing datastore + type: integer + tokens: + description: Array of API tokens associated with test + type: array + items: + $ref: '#/components/schemas/TestToken' + timelineLabels: + description: List of label names that are used for determining metric to + use as the time series + type: array + items: + type: string + example: + - timestamp + timelineFunction: + description: Label function to modify timeline labels to a produce a value + used for ordering datapoints + type: string + example: timestamp => timestamp + fingerprintLabels: + description: 'Array of Label names that are used to create a fingerprint ' + type: array + items: + type: string + example: + - build_tag + fingerprintFilter: + description: Filter function to filter out datasets that are comparable + for the purpose of change detection + type: string + example: value => value === "true" + compareUrl: + description: URL to external service that can be called to compare runs. This + is typically an external reporting/visulization service + type: string + example: "(ids, token) => 'http://repoting.example.com/report/specj?q='\ + \ + ids.join('&q=') + \"&token=\"+token" + transformers: + description: Array for transformers defined for the Test + type: array + items: + $ref: '#/components/schemas/Transformer' + notificationsEnabled: + description: Are notifications enabled for the test + type: boolean + example: true + TestExport: + description: Represents a Test with all associated data used for export/import + operations. + type: object + allOf: + - $ref: '#/components/schemas/Test' + properties: + variables: + description: Array of Variables associated with test + type: array + items: + $ref: '#/components/schemas/Variable' + missingDataRules: + description: Array of MissingDataRules associated with test + type: array + items: + $ref: '#/components/schemas/MissingDataRule' + experiments: + description: Array of ExperimentProfiles associated with test + type: array + items: + $ref: '#/components/schemas/ExperimentProfile' + actions: + description: Array of Actions associated with test + type: array + items: + $ref: '#/components/schemas/Action' + subscriptions: + description: Watcher object associated with test + type: object + allOf: + - $ref: '#/components/schemas/Watch' + datastore: + description: Datastore associated with test + type: object + allOf: + - $ref: '#/components/schemas/Datastore' + TestListing: + required: + - tests + - count + type: object + properties: + tests: + description: Array of Test Summaries + type: array + items: + $ref: '#/components/schemas/TestSummary' + count: + format: int64 + description: Number of tests when pagination is ignored + type: integer + TestQueryResult: + required: + - tests + - count + type: object + properties: + tests: + description: Array of Tests + type: array + items: + $ref: '#/components/schemas/Test' + count: + format: int64 + description: Count of available tests. This is a count of tests that the + current user has access to + type: integer + example: 64 + TestSummary: + required: + - id + - name + - datastoreId + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + properties: + id: + format: int32 + description: ID of tests + type: integer + example: 101 + name: + description: Test name + type: string + example: my-comprehensive-benchmark + folder: + description: Name of folder that the test is stored in. Folders allow tests + to be organised in the UI + type: string + example: My Team Folder + description: + description: Description of the test + type: string + example: Comprehensive benchmark to tests the limits of any system it is + run against + datasets: + description: Total number of Datasets for the Test + type: number + example: 202 + runs: + description: Total number of Runs for the Test + type: number + example: 101 + watching: + description: Subscriptions for each test for authenticated user + uniqueItems: true + type: array + items: + type: string + example: [] + datastoreId: + format: int32 + description: Datastore id + type: integer + example: 1 + TestToken: + required: + - id + - value + - permissions + - description + type: object + properties: + id: + format: int32 + description: Unique Token id + type: integer + example: 101 + testId: + format: int32 + description: Test ID to apply Token + type: integer + example: 201 + value: + description: Test value + type: string + example: 094678029a2aaf9a2847502273099bb3a1b2338c2b9c618ed09aef0181666e38 + permissions: + format: int32 + type: integer + description: + description: Token description + type: string + example: my reporting service token + TransformationLog: + description: Transformation Log + required: + - id + - level + - timestamp + - message + type: object + properties: + id: + format: int64 + type: integer + level: + format: int32 + type: integer + timestamp: + format: date-time + type: string + example: 2022-03-10T16:15:50Z + message: + type: string + testId: + format: int32 + type: integer + runId: + format: int32 + type: integer + Transformer: + description: A transformer extracts labals and applies a Function to convert + a Run into one or more Datasets + required: + - id + - name + - extractors + - schemaId + - schemaUri + - schemaName + type: object + allOf: + - $ref: '#/components/schemas/ProtectedType' + properties: + id: + format: int32 + description: Unique Transformer id + type: integer + example: 101 + name: + description: Transformer name + type: string + example: normalize-techempower-result + description: + description: Transformer description + type: string + example: Normalizers a techempower output file to separate each framework + into a dataset and normalize the JSON structure + targetSchemaUri: + description: "The schema associated with the calculated Datasets. Where\ + \ a transformer creates a new JSON object with a new structure, this Schema\ + \ is used to extafct values from the new Dataset JSON document" + type: string + example: uri:normalized-techempower:0.1 + extractors: + description: A collection of extractors to extract JSON values to create + new Dataset JSON document + type: array + items: + $ref: '#/components/schemas/Extractor' + function: + type: string + schemaId: + format: int32 + description: Schema ID that the transform is registered against + type: integer + example: 101 + schemaUri: + description: Schema Uri that the transform is registered against + type: string + example: urn:techempower:0.1 + schemaName: + description: Schema name that the transform is registered against + type: string + example: techempower + TransformerInfo: + required: + - schemaId + - schemaUri + - schemaName + - transformerId + - transformerName + type: object + properties: + schemaId: + format: int32 + description: Schema ID + type: integer + example: 101 + schemaUri: + description: Schema uri + type: string + example: uri:my-schema:0.1 + schemaName: + description: Schema name + type: string + example: my-benchmark-schema + transformerId: + format: int32 + description: Transformer ID + type: integer + example: 201 + transformerName: + description: Transformer name + type: string + example: my-dataset-transformer + ValidationError: + required: + - schemaId + - error + type: object + properties: + schemaId: + format: int32 + description: Schema ID that Validation Error relates to + type: integer + example: 101 + error: + description: Validation Error Details + type: object + allOf: + - $ref: '#/components/schemas/ErrorDetails' + Variable: + required: + - id + - testId + - name + - order + - labels + - changeDetection + type: object + properties: + id: + format: int32 + type: integer + testId: + format: int32 + type: integer + name: + type: string + group: + type: string + order: + format: int32 + type: integer + labels: + type: array + items: + type: string + calculation: + type: string + changeDetection: + type: array + items: + $ref: '#/components/schemas/ChangeDetection' + VersionInfo: + required: + - version + - startTimestamp + type: object + properties: + version: + description: Version of Horreum + type: string + example: 0.9.4 + startTimestamp: + format: int64 + description: Timestamp of server startup + type: integer + example: 2023-10-18 18:00:57 + privacyStatement: + description: Privacy statement + type: string + example: link/to/external/privacy/statement + Watch: + required: + - users + - optout + - teams + - testId + type: object + properties: + id: + format: int32 + type: integer + users: + type: array + items: + type: string + optout: + type: array + items: + type: string + teams: + type: array + items: + type: string + testId: + format: int32 + type: integer diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..0bb9249 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1018 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "42.0.5" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +optional = false +python-versions = "*" +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +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 = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + +[[package]] +name = "httpcore" +version = "1.0.4" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.25.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.11.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, + {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jwcrypto" +version = "1.5.6" +description = "Implementation of JOSE Web standards" +optional = false +python-versions = ">= 3.8" +files = [ + {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, + {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, +] + +[package.dependencies] +cryptography = ">=3.4" +typing-extensions = ">=4.5.0" + +[[package]] +name = "microsoft-kiota-abstractions" +version = "1.3.1" +description = "Core abstractions for kiota generated libraries in Python" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_abstractions-1.3.1-py2.py3-none-any.whl", hash = "sha256:76930f9fe44e90afb849ecfae8a08acb2effe61a2975441022e0f5229383d73c"}, + {file = "microsoft_kiota_abstractions-1.3.1.tar.gz", hash = "sha256:9e082212edf9e5a533eff4139cf1f7a3afc4a74938b95fa486c10ee9f7654d58"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.19.0" +opentelemetry-sdk = ">=1.19.0" +std-uritemplate = ">=0.0.38" + +[[package]] +name = "microsoft-kiota-http" +version = "1.3.1" +description = "Kiota http request adapter implementation for httpx library" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_http-1.3.1-py2.py3-none-any.whl", hash = "sha256:d62972c6ed4c785f9808a15479a7421abb38a9519b39e6933e5d05555b9fb427"}, + {file = "microsoft_kiota_http-1.3.1.tar.gz", hash = "sha256:09d85310379f88af0a0967925d1fcbe82f2520a9fe6fa1fd50e79af813bc451d"}, +] + +[package.dependencies] +httpx = {version = ">=0.23.0", extras = ["http2"]} +microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.20.0" +opentelemetry-sdk = ">=1.20.0" + +[[package]] +name = "microsoft-kiota-serialization-form" +version = "0.1.0" +description = "Implementation of Kiota Serialization Interfaces for URI-Form encoded serialization" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_serialization_form-0.1.0-py2.py3-none-any.whl", hash = "sha256:5bc76fb2fc67d7c1f878f876d252ea814e4fc38df505099b9b86de52d974380a"}, + {file = "microsoft_kiota_serialization_form-0.1.0.tar.gz", hash = "sha256:663ece0cb1a41fe9ddfc9195aa3f15f219e14d2a1ee51e98c53ad8d795b2785d"}, +] + +[package.dependencies] +microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" +pendulum = ">=3.0.0" + +[[package]] +name = "microsoft-kiota-serialization-json" +version = "1.1.0" +description = "Implementation of Kiota Serialization interfaces for JSON" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_serialization_json-1.1.0-py2.py3-none-any.whl", hash = "sha256:a32c73dd92ef2677dbf1deca2fb44b5111c0ef572b15c47205bff521800cd750"}, + {file = "microsoft_kiota_serialization_json-1.1.0.tar.gz", hash = "sha256:72bf65d81c0356ad87c229694733f4eb558628a13c5ee8dd274980c4b2d9b64b"}, +] + +[package.dependencies] +microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" +pendulum = ">=3.0.0b1" + +[[package]] +name = "microsoft-kiota-serialization-multipart" +version = "0.1.0" +description = "Implementation of Kiota Serialization Interfaces for Multipart serialization" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_serialization_multipart-0.1.0-py2.py3-none-any.whl", hash = "sha256:ef183902e77807806b8a181cdde53ba5bc04c6c9bdb2f7d80f8bad5d720e0015"}, + {file = "microsoft_kiota_serialization_multipart-0.1.0.tar.gz", hash = "sha256:14e89e92582e6630ddbc70ac67b70bf189dacbfc41a96d3e1d10339e86c8dde5"}, +] + +[package.dependencies] +microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" + +[[package]] +name = "microsoft-kiota-serialization-text" +version = "1.0.0" +description = "Implementation of Kiota Serialization interfaces for text/plain" +optional = false +python-versions = "*" +files = [ + {file = "microsoft_kiota_serialization_text-1.0.0-py2.py3-none-any.whl", hash = "sha256:1d3789e012b603e059a36cc675d1fd08cb81e0dde423d970c0af2eabce9c0d43"}, + {file = "microsoft_kiota_serialization_text-1.0.0.tar.gz", hash = "sha256:c3dd3f409b1c4f4963bd1e41d51b65f7e53e852130bb441d79b77dad88ee76ed"}, +] + +[package.dependencies] +microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" +python-dateutil = ">=2.8.2" + +[[package]] +name = "opentelemetry-api" +version = "1.23.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_api-1.23.0-py3-none-any.whl", hash = "sha256:cc03ea4025353048aadb9c64919099663664672ea1c6be6ddd8fee8e4cd5e774"}, + {file = "opentelemetry_api-1.23.0.tar.gz", hash = "sha256:14a766548c8dd2eb4dfc349739eb4c3893712a0daa996e5dbf945f9da665da9d"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<7.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.23.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_sdk-1.23.0-py3-none-any.whl", hash = "sha256:a93c96990ac0f07c6d679e2f1015864ff7a4f5587122dd5af968034436efb1fd"}, + {file = "opentelemetry_sdk-1.23.0.tar.gz", hash = "sha256:9ddf60195837b59e72fd2033d6a47e2b59a0f74f0ec37d89387d89e3da8cab7f"}, +] + +[package.dependencies] +opentelemetry-api = "1.23.0" +opentelemetry-semantic-conventions = "0.44b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.44b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_semantic_conventions-0.44b0-py3-none-any.whl", hash = "sha256:7c434546c9cbd797ab980cc88bf9ff3f4a5a28f941117cad21694e43d5d92019"}, + {file = "opentelemetry_semantic_conventions-0.44b0.tar.gz", hash = "sha256:2e997cb28cd4ca81a25a9a43365f593d0c2b76be0685015349a89abdf1aa4ffa"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pendulum" +version = "3.0.0" +description = "Python datetimes made easy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, + {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, + {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, + {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, + {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, + {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, + {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, + {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, + {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, + {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, + {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, + {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, + {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, + {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, + {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, + {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, + {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, + {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, + {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, + {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, + {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, + {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, + {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, + {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, + {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, + {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, + {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, + {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, + {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, + {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, + {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, + {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, + {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, + {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, + {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, + {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, + {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, + {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, + {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, + {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, + {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, + {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, + {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, +] + +[package.dependencies] +python-dateutil = ">=2.6" +tzdata = ">=2020.1" + +[package.extras] +test = ["time-machine (>=2.6.0)"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.6" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, + {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +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.dependencies] +six = ">=1.5" + +[[package]] +name = "python-keycloak" +version = "3.9.1" +description = "python-keycloak is a Python package providing access to the Keycloak API." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "python_keycloak-3.9.1-py3-none-any.whl", hash = "sha256:898d1fc73560171d3870251f981e069f854cc67bc0a51b96703355512d8d3cf3"}, + {file = "python_keycloak-3.9.1.tar.gz", hash = "sha256:50c8073172ca0630f3569c6b631134216b60f4e347cc5bb669a57e6ffba50f7e"}, +] + +[package.dependencies] +deprecation = ">=2.1.0" +jwcrypto = ">=1.5.4,<2.0.0" +requests = ">=2.20.0" +requests-toolbelt = ">=0.6.0" + +[package.extras] +docs = ["Sphinx (>=6.1.0,<7.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.9,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=3.0.0,<4.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +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 = "std-uritemplate" +version = "0.0.55" +description = "std-uritemplate implementation for Python" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "std_uritemplate-0.0.55-py3-none-any.whl", hash = "sha256:4c5e3c068db007697c11e6047d16c9b64f07e8259ffa4dd4d9248ed8491ad430"}, + {file = "std_uritemplate-0.0.55.tar.gz", hash = "sha256:9073f56a77e44d0583fb6645c37e4a640a34f22a255d00e3793cd3f30da58a68"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "zipp" +version = "3.18.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "0f259faa8559a5a87db0e93a4368724eef579cd8a321eee8ce8c90afd962d2f3" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..69ecdcb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.poetry] +name = "horreum" +version = "0.13-dev" +description = "Horreum python library" +authors = ["Andrea Lamparelli "] +license = "Apache 2.0" +readme = "README.md" +include = [ + "src/horreum/raw_client/**/*", # all files in "src/horreum/raw_client" and any subfolders +] + +[tool.poetry.dependencies] +python = "^3.10" +microsoft-kiota-abstractions = "^1.3.1" +microsoft-kiota-http = "^1.3.1" +microsoft-kiota-serialization-json = "^1.1.0" +microsoft-kiota-serialization-text = "^1.0.0" +python-keycloak = "^3.9.1" +microsoft-kiota-serialization-form = "^0.1.0" +microsoft-kiota-serialization-multipart = "^0.1.0" + + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" +pytest-asyncio = "^0.23.6" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +asyncio_mode = "auto" diff --git a/src/horreum/__init__.py b/src/horreum/__init__.py new file mode 100644 index 0000000..f296a0e --- /dev/null +++ b/src/horreum/__init__.py @@ -0,0 +1,5 @@ +from horreum.horreum_client import new_horreum_client + +__all__ = [ + new_horreum_client +] diff --git a/src/horreum/horreum_client.py b/src/horreum/horreum_client.py new file mode 100644 index 0000000..cd673d6 --- /dev/null +++ b/src/horreum/horreum_client.py @@ -0,0 +1,79 @@ +from kiota_abstractions.authentication import AuthenticationProvider +from kiota_abstractions.authentication.access_token_provider import AccessTokenProvider +from kiota_abstractions.authentication.anonymous_authentication_provider import AnonymousAuthenticationProvider +from kiota_abstractions.authentication.base_bearer_token_authentication_provider import ( + BaseBearerTokenAuthenticationProvider) +from kiota_http.httpx_request_adapter import HttpxRequestAdapter + +from .keycloak_access_provider import KeycloakAccessProvider +from .raw_client import HorreumRawClient + +from .service import info_service as info + + +async def setup_auth_provider(base_url: str, username: str, password: str) -> AccessTokenProvider: + # Use not authenticated client to fetch the auth mechanism + auth_provider = AnonymousAuthenticationProvider() + req_adapter = HttpxRequestAdapter(auth_provider) + req_adapter.base_url = base_url + auth_client = HorreumRawClient(req_adapter) + + auth_config = await auth_client.api.config.keycloak.get() + # TODO: we could generalize using a generic OIDC client + return KeycloakAccessProvider(auth_config, username, password) + + +class HorreumClient: + __base_url: str + __username: str + __password: str + + # Raw client, this could be used to interact with the low-level api + raw_client: HorreumRawClient + auth_provider: AuthenticationProvider + + def __init__(self, base_url: str, username: str = None, password: str = None): + self.__base_url = base_url + self.__username = username + self.__password = password + + async def setup(self): + """ + Set up the authentication provider, based on the Horreum configuration, and the low-level horreum api client + """ + + if self.__username is not None: + # Bearer token authentication + access_provider = await setup_auth_provider(self.__base_url, self.__username, self.__password) + self.auth_provider = BaseBearerTokenAuthenticationProvider(access_provider) + elif self.__password is not None: + raise RuntimeError("providing password without username, have you missed something?") + else: + # Anonymous authentication + self.auth_provider = AnonymousAuthenticationProvider() + + req_adapter = HttpxRequestAdapter(self.auth_provider) + req_adapter.base_url = self.__base_url + + self.raw_client = HorreumRawClient(req_adapter) + + ################## + # High-level API # + ################## + + def version(self) -> str: + return info.get_client_version() + + +async def new_horreum_client(base_url: str, username: str = None, password: str = None) -> HorreumClient: + """ + Initialize the horreum client + :param base_url: horreum api base url + :param username: auth username + :param password: auth password + :return: HorreumClient instance + """ + client = HorreumClient(base_url, username, password) + await client.setup() + + return client diff --git a/src/horreum/keycloak_access_provider.py b/src/horreum/keycloak_access_provider.py new file mode 100644 index 0000000..9cbef72 --- /dev/null +++ b/src/horreum/keycloak_access_provider.py @@ -0,0 +1,30 @@ +from typing import Dict, Any + +from keycloak import KeycloakOpenID +from kiota_abstractions.authentication import AllowedHostsValidator +from kiota_abstractions.authentication.access_token_provider import AccessTokenProvider + +from .raw_client.models.keycloak_config import KeycloakConfig + + +class KeycloakAccessProvider(AccessTokenProvider): + config: KeycloakConfig + username: str + password: str + + def __init__(self, config: KeycloakConfig, username: str, password: str): + super() + self.config = config + self.username = username + self.password = password + self.keycloak_openid = KeycloakOpenID( + server_url=config.url, + client_id=config.client_id, + realm_name=config.realm + ) + + async def get_authorization_token(self, uri: str, additional_authentication_context: Dict[str, Any] = {}) -> str: + return self.keycloak_openid.token(username=self.username, password=self.password)["access_token"] + + def get_allowed_hosts_validator(self) -> AllowedHostsValidator: + return AllowedHostsValidator(allowed_hosts=[]) diff --git a/src/horreum/raw_client/.gitignore b/src/horreum/raw_client/.gitignore new file mode 100644 index 0000000..266de2d --- /dev/null +++ b/src/horreum/raw_client/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything, this dir will contain generated code +* + +# Keep this file versioned +!__init__.py \ No newline at end of file diff --git a/src/horreum/raw_client/__init__.py b/src/horreum/raw_client/__init__.py new file mode 100644 index 0000000..4f4fe7c --- /dev/null +++ b/src/horreum/raw_client/__init__.py @@ -0,0 +1,5 @@ +from .horreum_raw_client import HorreumRawClient + +__all__ = [ + HorreumRawClient +] diff --git a/src/horreum/service/__init__.py b/src/horreum/service/__init__.py new file mode 100644 index 0000000..4d79f23 --- /dev/null +++ b/src/horreum/service/__init__.py @@ -0,0 +1,5 @@ +# from .info_service import get_client_version + +__all__ = [ + "info_service" +] diff --git a/src/horreum/service/info_service.py b/src/horreum/service/info_service.py new file mode 100644 index 0000000..ea97e66 --- /dev/null +++ b/src/horreum/service/info_service.py @@ -0,0 +1,5 @@ +from importlib.metadata import version + + +def get_client_version(): + return version("horreum") diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..21c6085 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,13 @@ +import asyncio + +import pytest + + +@pytest.fixture(scope="session") +def event_loop(): + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + yield loop + loop.close() diff --git a/test/horreum_client_test.py b/test/horreum_client_test.py new file mode 100644 index 0000000..450a0f7 --- /dev/null +++ b/test/horreum_client_test.py @@ -0,0 +1,101 @@ +import httpx +import pytest +from kiota_abstractions.authentication import BaseBearerTokenAuthenticationProvider +from kiota_abstractions.base_request_configuration import RequestConfiguration +from kiota_abstractions.headers_collection import HeadersCollection +from kiota_abstractions.method import Method +from kiota_abstractions.request_information import RequestInformation + +from horreum.horreum_client import new_horreum_client, HorreumClient +from horreum.raw_client.api.test.test_request_builder import TestRequestBuilder +from horreum.raw_client.models.protected_type_access import ProtectedType_access +from horreum.raw_client.models.test import Test + +username = "user" +password = "secret" + + +@pytest.fixture() +async def anonymous_client_without_check() -> HorreumClient: + print("Setting up anonymous client") + return await new_horreum_client(base_url="http://localhost:8080") + + +@pytest.fixture() +async def anonymous_client() -> HorreumClient: + print("Setting up anonymous client") + client = await new_horreum_client(base_url="http://localhost:8080") + try: + await client.raw_client.api.config.version.get() + except httpx.ConnectError: + pytest.fail("Unable to fetch Horreum version, is Horreum running in the background?") + return client + + +@pytest.fixture() +async def authenticated_client() -> HorreumClient: + print("Setting up authenticated client") + client = await new_horreum_client(base_url="http://localhost:8080", username=username, password=password) + try: + await client.raw_client.api.config.version.get() + except httpx.ConnectError: + pytest.fail("Unable to fetch Horreum version, is Horreum running in the background?") + return client + + +def test_check_client_version(anonymous_client_without_check: HorreumClient): + version = anonymous_client_without_check.version() + # TODO: we could load the toml and check the versions match + assert version != "" + + +@pytest.mark.asyncio +async def test_check_server_version(anonymous_client: HorreumClient): + version = await anonymous_client.raw_client.api.config.version.get() + assert version.version != "" + assert version.start_timestamp != "" + + +@pytest.mark.asyncio +async def test_check_missing_token(anonymous_client: HorreumClient): + req = RequestInformation(Method("GET"), "/api/") + await anonymous_client.auth_provider.authenticate_request(req) + assert len(req.headers.get(BaseBearerTokenAuthenticationProvider.AUTHORIZATION_HEADER)) == 0 + + +@pytest.mark.asyncio +async def test_check_auth_token(authenticated_client: HorreumClient): + req = RequestInformation(Method("GET"), "/api/") + await authenticated_client.auth_provider.authenticate_request(req) + assert len(req.headers.get(BaseBearerTokenAuthenticationProvider.AUTHORIZATION_HEADER)) == 1 + assert req.headers.get(BaseBearerTokenAuthenticationProvider.AUTHORIZATION_HEADER).pop().startswith("Bearer") + + +@pytest.mark.asyncio +async def test_missing_username_with_password(): + try: + await new_horreum_client(base_url="http://localhost:8080", password=password) + pytest.fail("expect RuntimeError here") + except RuntimeError as e: + assert str(e) == "providing password without username, have you missed something?" + + +@pytest.mark.asyncio +async def test_check_no_tests(authenticated_client: HorreumClient): + query_params = TestRequestBuilder.TestRequestBuilderGetQueryParameters(limit=1, page=0) + config = RequestConfiguration(query_parameters=query_params, headers=HeadersCollection()) + assert (await authenticated_client.raw_client.api.test.get(config)).count == 0 + + +@pytest.mark.asyncio +async def test_check_create_test(authenticated_client: HorreumClient): + # Create new test + t = Test(name="TestName", description="Simple test", owner="dev-team", access=ProtectedType_access.PUBLIC) + created = await authenticated_client.raw_client.api.test.post(t) + assert created is not None + assert (await authenticated_client.raw_client.api.test.get()).count == 1 + + # TODO: we could automate setup/teardown process + # Delete test + await authenticated_client.raw_client.api.test.by_id(created.id).delete() + assert (await authenticated_client.raw_client.api.test.get()).count == 0 diff --git a/test/workflow_dispatch_event_example.json b/test/workflow_dispatch_event_example.json new file mode 100644 index 0000000..9e0a715 --- /dev/null +++ b/test/workflow_dispatch_event_example.json @@ -0,0 +1,6 @@ +{ + "action": "workflow_dispatch", + "inputs": { + "branch": "master" + } +}