From 672eac881641a9d013397961159d4f5a684bca25 Mon Sep 17 00:00:00 2001 From: Gerard Alonso Date: Tue, 24 Jan 2023 10:57:15 +0100 Subject: [PATCH] LITE-26347: standarise with mkdocs and poetry --- .coveragerc | 5 - .devcontainer/Dockerfile | 30 +- .devcontainer/devcontainer.json | 5 +- .github/workflows/build.yml | 42 +- .github/workflows/deploy.yml | 35 +- .gitignore | 6 +- .readthedocs.yaml | 16 + .travis.yml | 45 - LICENSE | 2 +- MANIFEST.in | 5 - README.md | 24 +- dj_rql/__init__.py | 2 +- dj_rql/_dataclasses.py | 2 +- dj_rql/constants.py | 2 +- dj_rql/drf/__init__.py | 2 +- dj_rql/drf/_utils.py | 2 +- dj_rql/drf/backend.py | 18 +- dj_rql/drf/compat.py | 2 +- dj_rql/drf/paginations.py | 5 +- dj_rql/drf/serializers.py | 2 +- dj_rql/fields.py | 2 +- dj_rql/filter_cls.py | 122 +- .../management/commands/generate_rql_class.py | 2 +- dj_rql/openapi.py | 35 +- dj_rql/qs.py | 54 +- dj_rql/transformer.py | 2 +- dj_rql/utils.py | 2 +- docs/Makefile | 20 - docs/conf.py | 57 - docs/css/custom.css | 20 + docs/getting_started.md | 134 ++ docs/getting_started.rst | 149 -- docs/images/favicon.ico | Bin 0 -> 1405 bytes docs/images/logo_full.png | Bin 0 -> 14782 bytes docs/index.md | 17 + docs/index.rst | 40 - docs/macros.py | 16 + docs/quickstart.md | 86 - docs/reference.md | 205 +++ docs/reference.rst | 128 -- docs/select.md | 122 ++ docs/select.rst | 126 -- docs/user_guide.md | 422 +++++ docs/user_guide.rst | 448 ----- mkdocs.yml | 70 + poetry.lock | 1520 +++++++++++++++++ pyproject.toml | 105 ++ requirements/dev.txt | 2 - requirements/docs.txt | 44 +- requirements/extra.txt | 1 - requirements/test.txt | 24 - setup.cfg | 26 - setup.py | 54 - tests/__init__.py | 2 +- tests/constants.py | 2 +- tests/data.py | 2 +- tests/dj_rf/__init__.py | 2 +- tests/dj_rf/filters.py | 2 +- tests/dj_rf/models.py | 2 +- tests/dj_rf/serializers.py | 2 +- tests/dj_rf/settings.py | 2 +- tests/dj_rf/urls.py | 2 +- tests/dj_rf/view.py | 2 +- .../test_commands/test_generate_rql_class.py | 2 +- tests/test_drf/__init__.py | 2 +- tests/test_drf/conftest.py | 2 +- tests/test_drf/test_common_drf_backend.py | 2 +- tests/test_drf/test_django_filters_backend.py | 2 +- tests/test_drf/test_dynamic_filter.py | 2 +- tests/test_drf/test_pagination.py | 2 +- tests/test_drf/test_select.py | 2 +- tests/test_drf/test_serializers.py | 2 +- tests/test_filter_cls/__init__.py | 2 +- tests/test_filter_cls/conftest.py | 31 + tests/test_filter_cls/test_apply_filters.py | 40 +- .../test_filter_cls/test_fields_filtering.py | 28 +- tests/test_filter_cls/test_initialization.py | 2 +- tests/test_filter_cls/test_select.py | 2 +- tests/test_filter_cls/utils.py | 22 +- tests/test_openapi.py | 2 +- tests/test_qs.py | 2 +- tests/test_utils.py | 2 +- tox.ini | 6 + 83 files changed, 3023 insertions(+), 1461 deletions(-) delete mode 100644 .coveragerc create mode 100644 .readthedocs.yaml delete mode 100644 .travis.yml delete mode 100644 MANIFEST.in delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py create mode 100644 docs/css/custom.css create mode 100644 docs/getting_started.md delete mode 100644 docs/getting_started.rst create mode 100644 docs/images/favicon.ico create mode 100644 docs/images/logo_full.png create mode 100644 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/macros.py delete mode 100644 docs/quickstart.md create mode 100644 docs/reference.md delete mode 100644 docs/reference.rst create mode 100644 docs/select.md delete mode 100644 docs/select.rst create mode 100644 docs/user_guide.md delete mode 100644 docs/user_guide.rst create mode 100644 mkdocs.yml create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements/dev.txt delete mode 100644 requirements/extra.txt delete mode 100644 requirements/test.txt delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 tests/test_filter_cls/conftest.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index b0bbd3b..0000000 --- a/.coveragerc +++ /dev/null @@ -1,5 +0,0 @@ -[run] -branch = True - -[xml] -output = tests/reports/coverage.xml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3d75c3e..fb7b6e9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,27 +1,7 @@ -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- +FROM python:3.8 -# Update the VARIANT arg in devcontainer.json to pick a Python version: 3, 3.8, 3.7, 3.6 -# To fully customize the contents of this image, use the following Dockerfile instead: -# https://github.com/microsoft/vscode-dev-containers/tree/v0.128.0/containers/python-3/.devcontainer/base.Dockerfile -ARG VARIANT="3" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} - -COPY ./requirements /app/requirements - -RUN pip install -r /app/requirements/docs.txt - -# [Optional] If your requirements rarely change, uncomment this section to add them to the image. -# -# COPY requirements.txt /tmp/pip-tmp/ -# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ -# && rm -rf /tmp/pip-tmp - -# [Optional] Uncomment this section to install additional packages. -# -# RUN apt-get update \ -# && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends +COPY pyproject.toml pyproject.toml +RUN pip install --upgrade pip +RUN pip install poetry +#RUN poetry install diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e11fc2b..6e09136 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,9 +5,6 @@ "build": { "dockerfile": "Dockerfile", "context": "..", - // Update 'VARIANT' to pick a Python version. Rebuild the container - // if it already exists to update. Available variants: 3, 3.6, 3.7, 3.8 - "args": { "VARIANT": "3" } }, // Set *default* container specific settings.json values on container create. @@ -33,7 +30,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip3 install --user -r requirements.txt", + // "postCreateCommand": "pip install --upgrade pip && pip install poetry && poetry install" // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "vscode" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64a2820..c31716c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,50 +10,66 @@ on: jobs: build: - name: Build and test on Python ${{ matrix.python-version }} + name: Build and test ${{ matrix.python-version }} - ${{ matrix.django-version }} runs-on: ubuntu-latest strategy: - matrix: + matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + django-version: ['3.2', '4.0', '4.1'] + exclude: + - python-version: '3.7' + django-version: '4.0' + - python-version: '3.7' + django-version: '4.1' steps: - name: Checkout project - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Install poetry + run: | + python -m pip install --upgrade pip + pip install poetry + - name: Replace python version + if: ${{ matrix.python-version != '3.7' }} + run: sed -i "s/python = \">=3.7,<4\"/python = \">=3.8,<4\"/" ./pyproject.toml - name: Install dependencies run: | - pip install -r requirements/dev.txt - pip install -r requirements/test.txt + poetry install + - name: Force django version ${{ matrix.django-version }} + run: | + poetry add django==${{ matrix.django-version }}.* - name: Linting run: | - flake8 + poetry run flake8 - name: Testing run: | - python setup.py test + poetry run pytest sonar: name: Quality Analysis by sonar needs: [build] runs-on: ubuntu-latest steps: - name: Checkout project - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.10' - name: Install dependencies run: | - pip install -r requirements/dev.txt - pip install -r requirements/test.txt + python -m pip install --upgrade pip + pip install poetry + poetry install - name: Testing run: | - python setup.py test + poetry run pytest - name: Fix coverage report for Sonar run: | sed -i 's/\/home\/runner\/work\/django-rql\/django-rql\//\/github\/workspace\//g' ./tests/reports/coverage.xml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2b2d718..bc9d97f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,28 +8,26 @@ on: jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: '3.8' - name: Install dependencies run: | - pip install -r requirements/dev.txt - pip install -r requirements/test.txt + python -m pip install --upgrade pip + pip install poetry + poetry install pip install twine - name: Linting run: | - flake8 + poetry run flake8 - name: Testing run: | - python setup.py test + poetry run pytest - name: Fix coverage report for Sonar run: | sed -i 's/\/home\/runner\/work\/django-rql\/django-rql\//\/github\/workspace\//g' ./tests/reports/coverage.xml @@ -47,10 +45,19 @@ jobs: timeout-minutes: 5 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - name: Publish to PyPI + - name: Extract tag name + uses: actions/github-script@v6 + id: tag + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + return context.payload.ref.replace(/refs\/tags\//, '') + - name: Build and publish to PyPI env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel - twine upload dist/* + poetry version ${{ steps.tag.outputs.result }} + poetry build + poetry publish -u $TWINE_USERNAME -p $TWINE_PASSWORD diff --git a/.gitignore b/.gitignore index 0249bba..d74fa85 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,8 @@ tests/reports/ docs/_build -_generated_filters*.py \ No newline at end of file +_generated_filters*.py + +.devcontainer/ +poetry.toml +.vscode/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a66ce1c --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# Required +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.9" + +mkdocs: + configuration: mkdocs.yml + +python: + install: + - requirements: requirements/docs.txt + - method: pip + path: . \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 395aca0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -dist: trusty -language: python -matrix: - include: - - os: linux - python: 3.6 - dist: xenial - sudo: true - - os: linux - python: 3.7 - dist: xenial - sudo: true - env: BUILD=PYPI - - os: linux - python: 3.8 - dist: xenial - sudo: true -install: -- pip install -r requirements/dev.txt -- pip install -r requirements/test.txt -- pip install pytest-cov -script: -- flake8 -- python setup.py test -- sonar-scanner -after_success: -- bash <(curl -s https://codecov.io/bash) -addons: - sonarcloud: - organization: cloudbluesonarcube - token: - secure: VJcQOn8TekNPIEn/vnf4fHGZoHha1hoeIVGGT9sYHfqe6zjofbQqAaGO3HMVR7Oliqxe1GQkiw2g+J5YdnRSvqqt2B6EPRiJXJW2jF0irwZ+zsrs/h5LcnQvp6KcIEjC9XmYh3gtkL7GF4Afola0Co4kic7+G2icExKE9JZCfEPExIgjZ6tkx74i0sIbUpdVNas8ODiPwXYcia0DBBh/1NT6p0X4wduSsWEC98rkxWZEyqtMLgL9hkO8TyihnNxsPCpK+ocyAtVmBG0P52A7iGHOi3JjzxVOQXntXhutdJsFXs+wcnFZfKHZf5lwfSKuY45DBr6s5iXo7mWADau3TJ2WeKasK/Bs20iLu2yJArNVqCAKqyA92YBRltowxYlNt3ewK6NrMyETvK0GVzrnBet0jVBfWPztJYi5HdUQZjfHMOiFE4k2kX1sWWI3uSDMS03vppzwo3T+InaZoj/fJaqsOGXMSlkRyKWa8jeK6556pYKo784WdYthijR2sOxdLck19ZaeplI+t3Pre9mGPlKw1gn/TqCPfQaFstYwede4qk+OzPthfDJh/jSobQgHiGhq9C0vzAn+ftSAEYJupNRt33uX18NSf2nH6N9mBaBF7cuSZrWTKmR1D00f4laIv5w+hI/xVq079BJe9xdK0gtxfZcdGMUowILcA+i2PV4= -deploy: - skip_cleanup: true - skip_existing: true - provider: pypi - user: "__token__" - password: - secure: Wzg60u6udwzVIfquCwyge0x0VFPKMvfi0k/lavgJ4dAIpSRJeZESn9gTPnE8nwmDlK7LLezfHGVZnV94428zthzh3CmeLbMx+eD1l7s5/x0s/FJa6Y1AvO+q82N6AmjkstCFcyhliTL6c39X4sQUpN/CQb1Xo0la7qlooFjXf4UvRD3KTnLi4LbQZ5iAp7eLFFYyfRfP8k8IpCWBzt8vWPmQzL9V+xfS7lt4wk/VOeX0wyTNoSPoSnsTI5bBLc6KhxUweGMdIvGgpEL2QvJ3kL4nnAB31NJ6/wJhHp5busTzNruMFnRMwlIjjVIN6PIpjZ7G1qBP3o19vwLkpZ3DT3ALZexZoeBZtIxxmWoQr4i5D4OGeHRKVOHD7SOELOGp1jvmhMilkNtJwFjtozuBOoJSk0W+Ie1fimPK3WJkTjtJZ1vHf6K3cuV8Ejul7C2Qy9Y8qojyfcQY4EwVriZvRKaCnMfNal6swJrpdYoDIsnZFC8u5jTsWPJCiS60XZbcWv6X/i9kZD/R+OHyDZJ4T4fSVprZ4WQysAKUNoLjftCxGlvUz3ir+D20cRO378kdvNar8XzUhKcaUC5anm+8NvjAKxLEWxo5Eklegg+sBlBF/kJSOVVhJ/KrqHlisaX1UhsZs2Pl3MO+eXRGIrHoeRCoBBrulg9Kb6hYKx2l7b8= - on: - tags: true - all_branches: true - distributions: sdist - condition: "$BUILD = PYPI" - repo: cloudblue/django-rql diff --git a/LICENSE b/LICENSE index a0fd3f0..d7ce57a 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2020 Ingram Micro Inc. + Copyright 2023 Ingram Micro Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 734bb02..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include LICENSE -include README.md -include requirements/dev.txt -include requirements/test.txt -prune tests diff --git a/README.md b/README.md index 0eef412..e96167e 100644 --- a/README.md +++ b/README.md @@ -248,9 +248,9 @@ class PublisherFilters(RQLFilterClass): Django Rest Framework Extensions ================================ 1. Pagination (limit, offset) -0. Support for custom fields, inherited at any depth from basic model fields, like CharField(). -0. Backend `DjangoFiltersRQLFilterBackend` with automatic conversion of [Django-Filters](https://django-filter.readthedocs.io/en/master/) query to RQL query. -0. OpenAPI docs are autogenerated for filter classes. +2. Support for custom fields, inherited at any depth from basic model fields, like CharField(). +3. Backend `DjangoFiltersRQLFilterBackend` with automatic conversion of [Django-Filters](https://django-filter.readthedocs.io/en/master/) query to RQL query. +4. OpenAPI docs are autogenerated for filter classes. Best Practices ============== @@ -270,20 +270,22 @@ Best Practices Development =========== -1. Python 3.6+ -2. Install dependencies `requirements/dev.txt` and `requirements/extra.txt` -3. We use `isort` library to order and format our imports, and we check it using `flake8-isort` library (automatically on `flake8` run). +1. Python 3.7+ +2. Install poetry: `pip install poetry` +3. Install dependencies: `poetry install` +4. We use `isort` library to order and format our imports, and we check it using `flake8-isort` library (automatically on `flake8` run). For convenience you may run `isort .` to order imports. +5. Run flake8: `poetry run flake8` Testing ======= -1. Python 3.6+ -0. Install dependencies `requirements/test.txt` -0. `export PYTHONPATH=/your/path/to/django-rql/` +1. Python 3.7+ +2. Install poetry: `pip install poetry` +3. Install dependencies: `poetry install` -Check code style: `flake8` -Run tests: `pytest` +Check code style: `poetry run flake8` +Run tests: `poetry run pytest` Tests reports are generated in `tests/reports`. * `out.xml` - JUnit test results diff --git a/dj_rql/__init__.py b/dj_rql/__init__.py index f61c46d..ff53ddd 100644 --- a/dj_rql/__init__.py +++ b/dj_rql/__init__.py @@ -1,3 +1,3 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/dj_rql/_dataclasses.py b/dj_rql/_dataclasses.py index 0db7315..175873d 100644 --- a/dj_rql/_dataclasses.py +++ b/dj_rql/_dataclasses.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/dj_rql/constants.py b/dj_rql/constants.py index b4e3a59..71d524a 100644 --- a/dj_rql/constants.py +++ b/dj_rql/constants.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from django.db import models diff --git a/dj_rql/drf/__init__.py b/dj_rql/drf/__init__.py index 97b5d75..a8c9659 100644 --- a/dj_rql/drf/__init__.py +++ b/dj_rql/drf/__init__.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from dj_rql.drf._utils import get_query diff --git a/dj_rql/drf/_utils.py b/dj_rql/drf/_utils.py index fe45de2..6c196db 100644 --- a/dj_rql/drf/_utils.py +++ b/dj_rql/drf/_utils.py @@ -1,5 +1,5 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from urllib.parse import unquote diff --git a/dj_rql/drf/backend.py b/dj_rql/drf/backend.py index d547903..603a5aa 100644 --- a/dj_rql/drf/backend.py +++ b/dj_rql/drf/backend.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from threading import Lock @@ -20,20 +20,22 @@ def clear(cls): class RQLFilterBackend(BaseFilterBackend): - """ RQL filter backend for DRF GenericAPIViews. + """ + RQL filter backend for DRF GenericAPIViews. - Set the backend filter for the ``GenericAPIView`` class-based view, and set the - ``rql_filter_class`` class attribute to the ``RQLFilterClass`` to use: + Set the backend filter for the `GenericAPIView` class-based view, and set the + `rql_filter_class` class attribute to the `RQLFilterClass` to use: - .. code-block:: python + ``` py3 class ViewSet(mixins.ListModelMixin, GenericViewSet): filter_backends = (RQLFilterBackend,) rql_filter_class = ModelFilterClass + ``` - Yo can also add a ``get_rql_filter_class()`` method to the view to get the filter class: + Yo can also add a `get_rql_filter_class()` method to the view to get the filter class: - .. code-block:: python + ``` py3 class ViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet): filter_backends = (RQLFilterBackend,) @@ -42,12 +44,14 @@ def get_rql_filter_class(self): if self.action == 'retrieve': return ModelDetailFilterClass return ModelFilterClass + ``` """ OPENAPI_RETRIEVE_SPECIFICATION = False _CACHES = {} def filter_queryset(self, request, queryset, view): + """Return a filtered queryset.""" filter_class = self.get_filter_class(view) if not filter_class: return queryset diff --git a/dj_rql/drf/compat.py b/dj_rql/drf/compat.py index c6f078e..a82bfca 100644 --- a/dj_rql/drf/compat.py +++ b/dj_rql/drf/compat.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from collections import Counter diff --git a/dj_rql/drf/paginations.py b/dj_rql/drf/paginations.py index 908b567..431960b 100644 --- a/dj_rql/drf/paginations.py +++ b/dj_rql/drf/paginations.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from lark.exceptions import LarkError @@ -14,6 +14,7 @@ class RQLLimitOffsetPagination(LimitOffsetPagination): """ RQL limit offset pagination. """ + def __init__(self, *args, **kwargs): super(RQLLimitOffsetPagination, self).__init__(*args, **kwargs) @@ -84,10 +85,12 @@ class RQLContentRangeLimitOffsetPagination(RQLLimitOffsetPagination): """ RQL RFC2616 limit offset pagination. Examples: + ``` Response 200 OK Content-Range: items -/ + ``` """ def get_paginated_response(self, data): diff --git a/dj_rql/drf/serializers.py b/dj_rql/drf/serializers.py index f5c1449..640b7e9 100644 --- a/dj_rql/drf/serializers.py +++ b/dj_rql/drf/serializers.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from collections import OrderedDict diff --git a/dj_rql/fields.py b/dj_rql/fields.py index c671ba1..7ba68e4 100644 --- a/dj_rql/fields.py +++ b/dj_rql/fields.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from django.db.models import Field diff --git a/dj_rql/filter_cls.py b/dj_rql/filter_cls.py index c73ed24..af99bec 100644 --- a/dj_rql/filter_cls.py +++ b/dj_rql/filter_cls.py @@ -1,11 +1,11 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # - import decimal import re from collections import defaultdict from datetime import datetime +from typing import Set from uuid import uuid4 from django.db.models import ( @@ -60,27 +60,27 @@ class RQLFilterClass: EXTENDED_SEARCH_ORM_ROUTES = () DISTINCT = False - """If True, a `SELECT DISTINCT` will always be executed.""" + """If True, a `SELECT DISTINCT` will always be executed (default `False`).""" SELECT = False - """If True, this FilterClass supports the ``select`` operator.""" + """If True, this FilterClass supports the `select` operator (default `False`).""" OPENAPI_SPECIFICATION = RQLFilterClassSpecification - """Class for OpenAPI specifications generation.""" + """Class for OpenAPI specifications generation (default `RQLFilterClassSpecification`).""" QUERIES_CACHE_BACKEND = None """Class for query caching.""" QUERIES_CACHE_SIZE = 20 - """Default number of cached queries.""" + """Default number of cached queries (default 20).""" Q_CLS = Q - """Class for building nodes of the query, generated by django.""" + """Class for building nodes of the query, generated by django (default `Q`).""" FILTER_TYPES_CLS = FilterTypes - """Class for the mapping of model field types to filter types.""" + """Class for the mapping of model field types to filter types (default `FilterTypes`).""" - def __init__(self, queryset, instance=None): + def __init__(self, queryset: Q, instance=None): self.queryset = queryset self._is_distinct = self.DISTINCT self._request = None @@ -136,33 +136,46 @@ def _init_from_class(self, instance): for attr in copied_attributes: setattr(self, attr, getattr(instance, attr)) - def build_q_for_custom_filter(self, data): - """ Django Q() builder for custom filter. + def build_q_for_custom_filter(self, data: FilterArgs) -> Q: + """Django Q() builder for custom filter. + + Args: + data (FilterArgs): Prepared FilterArgs filter data for custom filtering. - :param FilterArgs data: Prepared filter data for custom filtering. - :rtype: django.db.models.Q + Returns: + A Django queryset `django.db.models.Q` instance. + + Raises: + RQLFilterParsingError: Filter logic is not implemented. """ raise RQLFilterParsingError(details={ 'error': 'Filter logic is not implemented: {0}.'.format(data.filter_name), }) - def build_name_for_custom_ordering(self, filter_name): - """ Builder for ordering name of custom filter. + def build_name_for_custom_ordering(self, filter_name: str) -> str: + """Builder for ordering name of custom filter. + + Args: + filter_name (str): Full filter name string (f.e. ns1.ns2.filter1). - :param str filter_name: Full filter name (f.e. ns1.ns2.filter1) - :return: Django field str path - :rtype: str + Returns: + A Django field str path. + + Raises: + RQLFilterParsingError: Ordering logic is not implemented. """ raise RQLFilterParsingError(details={ 'error': 'Ordering logic is not implemented: {0}.'.format(filter_name), }) - def optimize_field(self, data): - """ This method can be overridden to apply complex DB optimization logic. + def optimize_field(self, data: OptimizationArgs): + """This method can be overridden to apply complex DB optimization logic. + + Args: + data (OptimizationArgs): An OptimizationArgs instance. - :param OptimizationArgs data: - :return: Optimized queryset - :rtype: django.db.models.QuerySet or None + Returns: + An Optimized QuerySet (could be None). """ pass @@ -170,14 +183,15 @@ def optimize_field(self, data): def openapi_specification(self): return self.OPENAPI_SPECIFICATION.get(self) - def apply_annotations(self, filter_names, queryset=None): + def apply_annotations(self, filter_names: Set[str], queryset: Q = None): """ This method is used from RQL Transformer to apply annotations before filtering on queryset, but after it's understood which filters are used. Also, it's used to apply annotations for select() optimization. - :param set of str filter_names: Set of filter names - :param django.db.models.QuerySet or None queryset: Queryset for annotation + Args: + filter_names (Set[str]): A Set of filter names. + queryset (Q): A Queryset for annotation (could be None). """ if queryset is None: qs = self.queryset.all() @@ -203,13 +217,16 @@ def apply_annotations(self, filter_names, queryset=None): return qs - def apply_filters(self, query, request=None, view=None): - """ Main entrypoint for request filtering. + def apply_filters(self, query: str, request=None, view=None): + """Main entrypoint for request filtering. - :param str query: RQL query string - :param request: Request from API view - :param view: API view - :return: Lark AST, Filtered QuerySet + Args: + query (str): RQL query string. + request (Request): Request from API view. + view (View): API view. + + Returns: + A Lark AST, Filtered QuerySet (could be None). """ self._request = request self._view = view @@ -252,12 +269,15 @@ def apply_filters(self, query, request=None, view=None): return rql_ast, qs - def build_q_for_filter(self, data): - """ Django Q() builder for extracted from query RQL expression. + def build_q_for_filter(self, data: FilterArgs) -> Q: + """Django Q() builder for extracted from query RQL expression. In general, this method should not be overridden. - :param FilterArgs data: Prepared filter data for custom filtering. - :rtype: django.db.models.Q + Args: + data (FilterArgs): Prepared FilterArgs filter data for custom filtering. + + Returns: + A Q instance. """ filter_name, operator, str_value = data.filter_name, data.operator, data.str_value list_operator = data.list_operator @@ -335,7 +355,7 @@ def build_q_for_filter(self, data): q |= item_q return q - def get_filter_base_item(self, filter_name): + def get_filter_base_item(self, filter_name: str): filter_item = self.filters.get(filter_name) if filter_item: return filter_item[0] if isinstance(filter_item, iterable_types) else filter_item @@ -486,11 +506,7 @@ def _apply_optimizations(self, queryset, select_data): OptimizationArgs(queryset, select_data, self.select_tree), ) - def __apply_optimizations(self, data): - """ - :param OptimizationArgs data: - :return: - """ + def __apply_optimizations(self, data: OptimizationArgs): qs, select_data, filter_tree = data.queryset, data.select_data, data.filter_tree if filter_tree: @@ -760,7 +776,7 @@ def _get_field_related_model(cls, field): @classmethod def _get_field(cls, base_model, field_name, get_related=False): - """ Django ORM field getter. + """Django ORM field getter. Notes: field_name can have dots or double underscores in them. They are interpreted as @@ -1089,8 +1105,16 @@ def _get_error_details(filter_name, filter_lookup, str_value): } @staticmethod - def remove_quotes(str_value): - # Values can start with single or double quotes, if they have special chars inside them + def remove_quotes(str_value: str) -> str: + """Remove the quotes of a string. Values can start with single or double quotes, + if they have special chars inside them. + + Args: + str_value (str): The string. + + Returns: + str: The string without the quotes. + """ return str_value[1:-1] if str_value and str_value[0] in ('"', "'") else str_value @staticmethod @@ -1162,13 +1186,13 @@ class NestedAutoRQLFilterClass(AutoRQLFilterClass): Filter class that automatically collects filters for all model fields with specified depth for related models. """ + SELECT = True + """If True, this FilterClass supports the `select` operator (default `True`).""" DEPTH = 1 - """ - Specifies how deep model relations will be traversed. - If `DEPTH = 0` this class behaves as `AutoRQLFilterClass`. - """ + """Specifies how deep model relations will be traversed. If `DEPTH = 0` this class behaves as + `AutoRQLFilterClass`. (default 1).""" def _get_init_filters(self): if self.DEPTH == 0: diff --git a/dj_rql/management/commands/generate_rql_class.py b/dj_rql/management/commands/generate_rql_class.py index 6d6edce..639ce09 100644 --- a/dj_rql/management/commands/generate_rql_class.py +++ b/dj_rql/management/commands/generate_rql_class.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import json diff --git a/dj_rql/openapi.py b/dj_rql/openapi.py index bd62c00..7f12c25 100644 --- a/dj_rql/openapi.py +++ b/dj_rql/openapi.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from copy import copy @@ -26,12 +26,15 @@ class RQLFilterDescriptionTemplate: ) @classmethod - def render(cls, filter_item, filter_instance): - """ - :param dict filter_item: Extended Filter item - :param dj_rql.filter_cls.RQLFilterClass filter_instance: Instance of Filter Class - :return: Rendered description for filter item - :rtype: str + def render(cls, filter_item: dict, filter_instance): + """Render for the given item and instance. + + Args: + filter_item (dict) : Extended Filter item dict. + filter_instance (RQLFilterClass) : Instance of RQLFilterClass Class. + + Returns: + Rendered description string for filter item. """ result = cls._render_base(filter_item, filter_instance) @@ -112,9 +115,11 @@ def get(cls, filter_instance): Returns OpenAPI specification for filters. Filter sorting is alphabetic with deprecated filters in the end. - :param dj_rql.filter_cls.RQLFilterClass filter_instance: Instance of Filter Class - :return: OpenAPI compatible specification of Filter Class Filters - :rtype: list of dict + Args: + filter_instance (RQLFilterClass): Instance of RQLFilterClass Class. + + Returns: + An OpenAPI compatible specification of Filter Class Filters list or dict. """ extended_filter_items = {} common_filter_names, deprecated_filter_names = [], [] @@ -145,12 +150,12 @@ def get(cls, filter_instance): return result @classmethod - def get_for_field(cls, filter_item, filter_instance): - """ This method can be overridden to support custom specs for certain filters. + def get_for_field(cls, filter_item: dict, filter_instance): + """This method can be overridden to support custom specs for certain filters. - :param dict filter_item: Extended Filter Item - :param dj_rql.filter_cls.RQLFilterClass filter_instance: Instance of Filter Class - :rtype: dict or None + Args: + filter_item (dict): Extended Filter Item dict. + filter_instance (RQLFilterClass): Instance of RQLFilterClass Class. """ pass diff --git a/dj_rql/qs.py b/dj_rql/qs.py index 0cc4518..4388088 100644 --- a/dj_rql/qs.py +++ b/dj_rql/qs.py @@ -1,13 +1,15 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from collections import namedtuple -from django.db.models import Prefetch +from django.db.models import Prefetch, QuerySet class DBOptimization: + """Optimize the DB queries base class.""" + def __init__(self, *relations, **kwargs): assert relations, 'At least one optimization must be specified.' @@ -34,25 +36,57 @@ def apply(self, queryset): class Annotation(DBOptimization): + """Apply an `annotate` optimization to the queryset.""" + def __init__(self, **kwargs): super(Annotation, self).__init__('None', **kwargs) def rebuild(self, parent_optimization=None): return self - def apply(self, queryset): + def apply(self, queryset: QuerySet) -> QuerySet: + """ + Apply an annotate operation for the given queryset. + + Args: + queryset (QuerySet): queryset instance to optimize. + + Returns: + QuerySet: queryset optimized. + + """ return queryset.annotate(**self._extensions) class SelectRelated(DBOptimization): - """Apply a ``select_related`` optimization to the queryset.""" - def apply(self, queryset): + """Apply a `select_related` optimization to the queryset.""" + + def apply(self, queryset: QuerySet) -> QuerySet: + """ + Apply a select related operation for the given queryset. + + Args: + queryset (QuerySet): queryset instance to optimize. + + Returns: + QuerySet: queryset optimized. + """ return queryset.select_related(*self._relations) class PrefetchRelated(DBOptimization): - """Apply a ``prefetch_related`` optimization to the queryset.""" - def apply(self, queryset): + """Apply a `prefetch_related` optimization to the queryset.""" + + def apply(self, queryset: QuerySet) -> QuerySet: + """ + Apply a prefetch related operation for the given queryset. + + Args: + queryset (QuerySet): queryset instance to optimize. + + Returns: + QuerySet: queryset optimized. + """ return queryset.prefetch_related(*self._relations) @@ -95,6 +129,8 @@ def _join_relation(parent_relation, relation): class NestedPrefetchRelated(_NestedOptimizationMixin, PrefetchRelated): + """Apply a `prefetch_related` optimization to a nested attribute of the queryset.""" + def _rebuild_nested(self, parent_data): rebuilt_relations = [] @@ -114,6 +150,8 @@ def _rebuild_nested(self, parent_data): class NestedSelectRelated(_NestedOptimizationMixin, SelectRelated): + """Apply a `select_related` optimization to a nested attribute of the queryset.""" + def _rebuild_nested(self, parent_data): optimization_cls = NSR if parent_data.type == self._SR else NPR rebuilt_relations = [self._join_relation(parent_data.relation, r) for r in self._relations] @@ -122,6 +160,8 @@ def _rebuild_nested(self, parent_data): class Chain(_NestedOptimizationMixin, DBOptimization): + """Apply a chain of optimizations to the queryset.""" + def __init__(self, *relations, **extensions): e = 'Wrong Chain() optimization configuration.' assert all(isinstance(rel, DBOptimization) for rel in relations), e diff --git a/dj_rql/transformer.py b/dj_rql/transformer.py index 32b3690..14501de 100644 --- a/dj_rql/transformer.py +++ b/dj_rql/transformer.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from lark import Tree diff --git a/dj_rql/utils.py b/dj_rql/utils.py index a45472a..373f9bf 100644 --- a/dj_rql/utils.py +++ b/dj_rql/utils.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cb..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 2c2c540..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -from datetime import datetime - -import django -from setuptools_scm import get_version - - -sys.path.insert(0, os.path.abspath('..')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.dj_rf.settings' -django.setup() - - -# -- Project information ----------------------------------------------------- - -project = 'django-rql' -copyright = '{0}, CloudBlue Inc.'.format(datetime.now().year) -author = 'CloudBlue' - -# The full version, including alpha/beta/rc tags -release = get_version(root='..', relative_to=__file__) - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosectionlabel', - 'sphinx_copybutton', -] - -autosectionlabel_prefix_document = True -autodoc_member_order = 'bysource' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] diff --git a/docs/css/custom.css b/docs/css/custom.css new file mode 100644 index 0000000..16f42bf --- /dev/null +++ b/docs/css/custom.css @@ -0,0 +1,20 @@ +:root>* { + --md-primary-fg-color: #1565c0; + --md-primary-fg-color--light: #1565c0; + --md-primary-fg-color--dark: #1565c0; +} + +div.autodoc-docstring { + padding-left: 20px; + margin-bottom: 30px; + border-left: 5px solid rgba(230, 230, 230); +} + +div.autodoc-members { + padding-left: 20px; + margin-bottom: 15px; +} + +:not([data-md-state="blur"]) + nav { + display: none; +} diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 0000000..daae465 --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,134 @@ +Getting started +=============== + +# Requirements + +`django-rql` works with Python 3.7 or newer and has the +following dependencies: + +- Django >= 3.2.* and <= 4.1 +- lark-parser 0.8.2 + +And the following optional dependency: + +- djangorestframework >= 3.12 + +# Install + +`django-rql` can be installed from pypi.org with pip: + +``` shell +$ pip install django-rql +``` + +If you want to use `django-rql` with Django Rest Framework +you have to install the optional dependency: + +``` shell +$ pip install django-rql[drf] +``` + +# Write your first RQL Filter Class + +For writing your first RQL Filter Class you need some models to be +ready. Let's imagine you have simple Domain Model in your project, that +can be represented as several models like below: + +``` py3 +from django.db import models + + +class Product(models.Model): + name = models.CharField() +``` + +Let's create an RQL Filter Class for `Product` model. All you need is +to inherit from `dj_rql.filter_cls.RQLFilterClass`, define `MODEL` +property and add supported `FILTERS` for class: + +``` py3 +from dj_rql.filter_cls import RQLFilterClass + + +class ProductFilters(RQLFilterClass): + MODEL = Product + FILTERS = ( + 'id', + 'name', + ) +``` + +Using simple strings in `FILTERS` property you can define what fields +are available for filtering. In example above you allow filtering only +by `id` and `name` filter. + +If you have a pretty simple model and want everything out of the box +(all filters with search and ordering), there is even a simpler +automated way! All you have to do is inherit from +`dj_rql.filter_cls.AutoRQLFilterClass`: + +``` py3 +from dj_rql.filter_cls import AutoRQLFilterClass + + +class ProductFilters(AutoRQLFilterClass): + MODEL = Product +``` + +# Use your RQL filter class in your views + +``` py3 +from urllib.parse import unquote + +from products.filters import ProductFilters +from products.models import Product + + +def search_products_by_name(request): + query = unquote(request.meta['QUERY_STRING']) + + base_queryset = Product.objects.all() + + my_filter = ProductFilters(base_queryset) + + _, filtered_qs = my_filter.apply_filters(query) + + return render(request, 'products/search.html', {'products': filtered_qs}) +``` + +``` shell +$ curl http://127.0.0.1:8080/api/v1/products?like(name,Unicorn*)|eq(name,LLC) +``` + +# Use django-rql with Django Rest Framework + +## Configuring Django settings + +Setup default `filter_backends` in your Django settings file: + +``` py3 +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ['dj_rql.drf.RQLFilterBackend'] +} +``` + +Now your APIs are supporting RQL syntax for query strings. + +## Add RQL Filter Class to DRF View + +In your latest step you need to add `ProductFilters` class as a +`rql_filter_class` property inside your View: + +``` py3 +class ProductsViewSet(mixins.ListModelMixin, GenericViewSet): + queryset = Product.objects.all() + serializer_class = ProductSerializer + rql_filter_class = ProductFilters +``` + +And that's it! Now you are able to start your local server and try to +filter using RQL syntax + +``` shell +$ curl http://127.0.0.1:8080/api/v1/products?like(name,Unicorn*)|eq(name,LLC) +``` diff --git a/docs/getting_started.rst b/docs/getting_started.rst deleted file mode 100644 index abc5a80..0000000 --- a/docs/getting_started.rst +++ /dev/null @@ -1,149 +0,0 @@ -Getting started -=============== - -Requirements ------------- - -`django-rql` works with Python 3.6 or newer and has the following dependencies: - -* Django >= 1.11.20 and <= 3.0 -* lark-parser 0.8.2 - -And the following optional dependency: - -* djangorestframework >= 3.9 - - -Install -------- - -`django-rql` can be installed from pypi.org with pip: - -.. code-block:: shell - - $ pip install django-rql - - -If you want to use `django-rql` with Django Rest Framework you have to install the optional dependency: - -.. code-block:: shell - - $ pip install django-rql[drf] - - - -Write your first RQL Filter Class ---------------------------------- - -For writing your first RQL Filter Class you need some models to be ready. Let's imagine you have simple Domain Model -in your project, that can be represented as several models like below: - -.. code-block:: python - - from django.db import models - - - class Product(models.Model): - name = models.CharField() - - -Let's create an RQL Filter Class for ``Product`` model. -All you need is to inherit from ``dj_rql.filter_cls.RQLFilterClass``, -define ``MODEL`` property and add supported ``FILTERS`` for class: - -.. code-block:: python - - from dj_rql.filter_cls import RQLFilterClass - - - class ProductFilters(RQLFilterClass): - MODEL = Product - FILTERS = ( - 'id', - 'name', - ) - - -Using simple strings in ``FILTERS`` property you can define what fields are available for filtering. -In example above you allow filtering only by ``id`` and ``name`` filter. - -If you have a pretty simple model and want everything out of the box (all filters with search and ordering), there is even a simpler automated way! -All you have to do is inherit from ``dj_rql.filter_cls.AutoRQLFilterClass``: - -.. code-block:: python - - from dj_rql.filter_cls import AutoRQLFilterClass - - - class ProductFilters(AutoRQLFilterClass): - MODEL = Product - - - -Use your RQL filter class in your views ---------------------------------------- - - -.. code-block:: python - - from urllib.parse import unquote - - from products.filters import ProductFilters - from products.models import Product - - - def search_products_by_name(request): - query = unquote(request.meta['QUERY_STRING']) - - base_queryset = Product.objects.all() - - my_filter = ProductFilters(base_queryset) - - _, filtered_qs = my_filter.apply_filters(query) - - return render(request, 'products/search.html', {'products': filtered_qs}) - - -.. code-block:: shell - - $ curl http://127.0.0.1:8080/api/v1/products?like(name,Unicorn*)|eq(name,LLC) - - - -Use django-rql with Django Rest Framework ------------------------------------------ - -Configuring Django settings -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Setup default `filter_backends` in your Django settings file: - -.. code-block:: python - - REST_FRAMEWORK = { - 'DEFAULT_FILTER_BACKENDS': ['dj_rql.drf.RQLFilterBackend'] - } - - -Now your APIs are supporting RQL syntax for query strings. - - -Add RQL Filter Class to DRF View -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In your latest step you need to add ``ProductFilters`` class as a ``rql_filter_class`` property inside your View: - -.. code-block:: python - - class ProductsViewSet(mixins.ListModelMixin, GenericViewSet): - queryset = Product.objects.all() - serializer_class = ProductSerializer - rql_filter_class = ProductFilters - - -And that's it! Now you are able to start your local server and try to filter using RQL syntax - -.. code-block:: shell - - $ curl http://127.0.0.1:8080/api/v1/products?like(name,Unicorn*)|eq(name,LLC) - diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..599405cdc14c91f9b3364bec36be475924c9a11a GIT binary patch literal 1405 zcmZQzU}Rus5D;Jh0tE&WMg|5mAWK35!hZwgX92N-0)(&5#J~{7%)rnY;OEZECB+34 z<@NM%2?Ej}69hQefTaBA|A|2Ax2KC^NX4ABm%Y810|i(wOqS?*bY!ZE#<`tL( z;p(H$JL^l&a5vf=oAHp<{Y6NFEYHWT{sZ5F3&e{>DvpKU2+nD}*Y;kvKk@uVcK#p7 z6u9ypDLI%=ePUd~Tr6N&zTv=QY-T z|9E$72J5+vf-@X_>{4eQQCB_9s>i%1R`b(J6|0FmCNF#OP%8h_a;u(qnRiNM+;f)I z9E-4nfd{>qJi7`}L08GLh&6$S{G6Ga6vVzq2OHn7+;{%Boqgod|1CQ6 zMD~iUTlw-YoB6{f_aFSyJr}H(z47EK>2CoEwtNZl+a7Vhdnh$ewQOod%Ug?j zcXUesnBZpA+gfm;=H8=<8()Pk2JU%%f#U9vmcfS*cWhC-q|GoGRn&(y-@9EUE|(YRZn+F?BhDqG1*NbjmYYU%L3Q1raC<+`w=Q>sFr6wbW8W8Frrf44SyU)(n9|BYR7NB1B0KXq_| zaA5kBWCC%m0F+k%$8~X4)fZq~zk|m0RnKfuM-kQs`htQ-X1D|{wieeDRnFr1DgH=} zuimvl`H_c%$fK6?idtO1*h?063ot1)H9c^OkvOVo=w&7u6SDQn>MzyXUVmv^bbodA zxt+1KtMdXEyY)`(>zlMw)}o~@=OE9$1%mTKH;A)uUrnz@32LBF^8F^c^y-d zTd>~uk#B{_&Qv+>9YV#XiMDD{9iKjGt}{3;SF_@Up=9yDqi1_QNb?u+m48(3KA)s< zk^ktk~F~Bv*tPCeGYzS`b z>d$w+g)FNl|G(Y3Z^FJKFU@}S&N6?i>J;qBJ1?oYvq0zA`s}=i)*p30?`V8?CV6$i zk>Gpllq$5j&v5m=2S(I)} literal 0 HcmV?d00001 diff --git a/docs/images/logo_full.png b/docs/images/logo_full.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca63205a464963602d46a457defb7ea9a565953 GIT binary patch literal 14782 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%0A0o|Q`xveOC z%Fd?>J^AWkN6l>Y|T%7x>aX;;QpBoO;j~;#sVS+jLbVo2WF<3HH(jYed2op?Us3eML8LdZ@NZ*b;WxkmlwE^rh}QX6h&HDHt!bl=kclq zSCrh4SNA-M12ExbJnMNZZ2QzSekW13Ce`#eA7CUxCkvK8wQ~}1!tC*u=Boq$6^)tMh`mdH2^eBJ>Gd*AnR;M2XzQpzP9%J8)LJ8Tn60VimYdWSI8w)w`#1)#1DTh3fH?+0C%5l^;SDcIH5URzSZCxMEBv83V) z+?a0v^Jbw5#KQe@&HUMQCVxgVA<>e5$iGHlBM?5BCRBmBqbMtDqBlrT^q>_ z_P*{yNH`?A_)P2S6+Rn)dsr;do#b)Sho)oJUYf%!BNw=$cLUYkf8Jn{_eh+mbznkqTHr`Wi(ukld(&Ocb^f} z9ks35*S(oyAq5liH&=mTk75td(@x=ERQD@u z%(1TBAA@TEu}cX<6DAerQiNXwH0-0iWbBn33$X5~lc1POcqnFS(n7oT@kfcOg6=AY z0myIqF2Z#4k=(J$Qcd=f!KpEDtCoZ?7LRv2HZVp{e#ngK7qCxHmIz7pKo^QUw%13i zjUS!m=Vqt6WQm}xmlJ^_Iy0G*-G`Ot-NNHlm`!r;~K}qoH`mPus0+C zEiQd%&x@*cD>(YdTb8JvPsM{+BtaT&58`af9J1mB=E8M6z&atBk! f7;HB~u-{qgYSTbpMLhhMiKU{bp-?Sv5%zxoHPb-Q literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ee9dc3b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +Welcome to django-rql's documentation! +======================================= + +`django-rql` is an Django application, that implements RQL +filter backend for your web application. + +## RQL + +RQL (Resource query language) is designed for modern application +development. It is built for the web, ready for NoSQL, and highly +extensible with simple syntax. This is a query language fast and +convenient database interaction. RQL was designed for use in URLs to +request object-style data structures. + +[RQL Reference](https://connect.cloudblue.com/community/api/rql/) + +[RQL for Web](https://www.sitepen.com/blog/resource-query-language-a-query-language-for-the-web-nosql/) diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 58e8ba6..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. django-rql documentation master file, created by - sphinx-quickstart on Fri Aug 7 10:27:30 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to django-rql's documentation! -====================================== - -`django-rql` is an Django application, that implements RQL filter backend for your web application. - - -RQL ---- - -RQL (Resource query language) is designed for modern application development. It is built for the web, ready for NoSQL, and highly extensible with simple syntax. -This is a query language fast and convenient database interaction. RQL was designed for use in URLs to request object-style data structures. - - -`RQL Reference `_ - -`RQL for Web `_ - - - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - getting_started - user_guide - select - reference - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/macros.py b/docs/macros.py new file mode 100644 index 0000000..07b6bb6 --- /dev/null +++ b/docs/macros.py @@ -0,0 +1,16 @@ +import django +from django.conf import settings + + +def define_env(env): + """ + This is the hook for defining variables, macros and filters + + - variables: the dictionary that contains the environment variables + - macro: a decorator function, to declare a macro. + - filter: a function with one of more arguments, + used to perform a transformation + """ + if not settings.configured: + settings.configure(DEBUG=True) + django.setup() diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 60a880e..0000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,86 +0,0 @@ -Quickstart -========== - -We're going to create a simple API and configure filters to support RQL syntax. - - -Installation ------------- -Install `django-rql` library in your existing or new Django and Django REST Framework project using command - -``` -pip install django-rql -``` - -Configuring Django settings ---------------------------- - -Setup default `filter_backends` in your Django settings file - -``` -REST_FRAMEWORK = { - 'DEFAULT_FILTER_BACKENDS': ['dj_rql.drf.RQLFilterBackend'] -} -``` - -Now your APIs are supporting RQL syntax for query strings. Let's write some filters - -Write your first RQL Filter Class ---------------------------------- - -For writing your first RQL Filter Class you need some models to be ready. Let's imagine you have simple Domain Model in your project, that can be represented as several models like below - -``` -from django.db import models - - -class Product(models.Model): - name = models.CharField() -``` - -Let's create an RQL Filter Class for `Product` model. All you need is to inherit from `dj_rql.filter_cls.RQLFilterClass`, define `MODEL` property and add supported `FILTERS` for class - -``` -from dj_rql.filter_cls import RQLFilterClass - - -class ProductFilters(RQLFilterClass): - MODEL = Product - FILTERS = ( - 'id', - 'name', - ) - -``` - -Using simple strings in `FILTERS` property you can define what fields are available for filtering. In example above you allow filtering only by `id` and `name` filter - -Add RQL Filter Class to DRF View --------------------------------- - -In your latest step you need to add `ProductFilters` class as a `rql_filter_class` property inside your View - -``` -class ProductsViewSet(mixins.ListModelMixin, GenericViewSet): - queryset = Product.objects.all() - serializer_class = ProductSerializer - rql_filter_class = ProductFilters -``` - -And that's it! Now you are able to start your local server and try to filter using RQL syntax - -``` -curl http://127.0.0.1:8080/api/v1/products?like(name,Unicorn*)|eq(name,LLC) -``` - -For learning RQL Syntax use following links: - -[RQL Reference][rql_reference] - -[RQL for Web][rql_for_web] - -For learning how to define more complex filters use [Filters Guide][filters_guide] - -[rql_reference]: https://connect.cloudblue.com/community/api/rql/ -[rql_for_web]: https://www.sitepen.com/blog/resource-query-language-a-query-language-for-the-web-nosql/ -[filters_guide]: ./filters_guide.md diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000..59841db --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,205 @@ +## Default lookups by field type + +| Model fields | Lookup operators | +| ------------------------- | -------------------------------------------------------------- | +| AutoField | `eq`, `ne`, `ge`, `gt`, `le`, `lt`, `in`, `out` {: rowspan=11} | +| BigAutoField | ⁠ {: style="padding:0"} | +| BigIntegerField | ⁠ {: style="padding:0"} | +| DateField | ⁠ {: style="padding:0"} | +| DateTimeField | ⁠ {: style="padding:0"} | +| DecimalField | ⁠ {: style="padding:0"} | +| FloatField | ⁠ {: style="padding:0"} | +| IntegerField | ⁠ {: style="padding:0"} | +| PositiveIntegerField | ⁠ {: style="padding:0"} | +| PositiveSmallIntegerField | ⁠ {: style="padding:0"} | +| SmallIntegerField | ⁠ {: style="padding:0"} | +| BooleanField | `eq`, `ne` {: rowspan=2} | +| NullBooleanField | ⁠ {: style="padding:0"} | +| CharField | `eq`, `ne`, `in`,`out`, `like`,`ilike` {: rowspan=6} | +| EmailField | ⁠ {: style="padding:0"} | +| SlugField | ⁠ {: style="padding:0"} | +| TextField | ⁠ {: style="padding:0"} | +| URLField | ⁠ {: style="padding:0"} | +| UUIDField | ⁠ {: style="padding:0"} | + + +## Constants + +More details about the specifications for the constants `py_rql.constants` could be found in the library [lib-rql](https://lib-rql.readthedocs.io/en/latest/). + +### py_rql.constants.FilterLookups + +::: py_rql.constants.FilterLookups + options: + heading_level: 3 + +## Exceptions + +More details about the specifications for the exceptions `py_rql.exceptions` could be found in the library [lib-rql](https://lib-rql.readthedocs.io/en/latest/). + +### RQLFilterError + +::: py_rql.exceptions.RQLFilterError + options: + heading_level: 3 + +### RQLFilterParsingError + +::: py_rql.exceptions.RQLFilterParsingError + options: + heading_level: 3 + +### RQLFilterLookupError + +::: py_rql.exceptions.RQLFilterLookupError + options: + heading_level: 3 + +### RQLFilterValueError + +::: py_rql.exceptions.RQLFilterValueError + options: + heading_level: 3 + + +## Filter classes + +### dj_rql.filter_cls.RQLFilterClass + +::: dj_rql.filter_cls.RQLFilterClass + options: + members: + - build_q_for_custom_filter + - build_name_for_custom_ordering + - optimize_field apply_annotations + - apply_filters + - build_q_for_filter + - get_filter_base_item + heading_level: 3 + +### dj_rql.filter_cls.AutoRQLFilterClass + +::: dj_rql.filter_cls.AutoRQLFilterClass + options: + heading_level: 3 + +### dj_rql.filter_cls.NestedAutoRQLFilterClass + +::: dj_rql.filter_cls.NestedAutoRQLFilterClass + options: + heading_level: 3 + +## DB optimization + +The following DB optimizations could be done found on `dj_rql.filter_cls`. + +### Annotation + +::: dj_rql.qs.Annotation + options: + members: + - apply + heading_level: 3 + +### SelectRelated + +::: dj_rql.qs.SelectRelated + options: + members: + - apply + heading_level: 3 + +### PrefetchRelated + +::: dj_rql.qs.PrefetchRelated + options: + members: + - apply + heading_level: 3 + +### NestedPrefetchRelated + +::: dj_rql.qs.NestedPrefetchRelated + options: + members: + - apply + heading_level: 3 + +### NestedSelectRelated + +::: dj_rql.qs.NestedSelectRelated + options: + members: + - apply + heading_level: 3 + +### Chain + +::: dj_rql.qs.Chain + options: + members: + - apply + heading_level: 3 + +## Django Rest Framework extensions + +### Filter backend dj_rql.drf.backend.RQLFilterBackend + +::: dj_rql.drf.backend.RQLFilterBackend + options: + members: + - filter_queryset + heading_level: 3 + +## Pagination + +The following pagination classes found on `dj_rql.drf.paginations`: + +### RQLLimitOffsetPagination + +::: dj_rql.drf.paginations.RQLLimitOffsetPagination + options: + heading_level: 3 + +### RQLContentRangeLimitOffsetPagination + +::: dj_rql.drf.paginations.RQLContentRangeLimitOffsetPagination + options: + heading_level: 3 + +## Serialization + +### dj_rql.drf.serializers.RQLMixin + +::: dj_rql.drf.serializers.RQLMixin + options: + heading_level: 3 + +## OpenAPI + +The following OpenAPI classes found on `dj_rql.openapi`: + +### RQLFilterClassSpecification + +::: dj_rql.openapi.RQLFilterClassSpecification + options: + members: + - get + - get_for_field + heading_level: 3 + +### RQLFilterClassSpecification + +::: dj_rql.openapi.RQLFilterDescriptionTemplate + options: + members: + - render + heading_level: 3 + +## Testing + +### dj_rql.utils.assert_filter_cls + +::: dj_rql.utils.assert_filter_cls + options: + heading_level: 3 diff --git a/docs/reference.rst b/docs/reference.rst deleted file mode 100644 index 74e6c3f..0000000 --- a/docs/reference.rst +++ /dev/null @@ -1,128 +0,0 @@ -============= -API Reference -============= - -.. _default-lookups: - -Default lookups by field type ------------------------------ - -+---------------------------+------------------+ -| Model fields | Lookup operators | -+===========================+==================+ -| AutoField | ``eq``, ``ne``, | -+---------------------------+ ``ge``, ``gt``, | -| BigAutoField | ``le``, ``lt``, | -+---------------------------+ ``in``, ``out`` | -| BigIntegerField | | -+---------------------------+ | -| DateField | | -+---------------------------+ | -| DateTimeField | | -+---------------------------+ | -| DecimalField | | -+---------------------------+ | -| FloatField | | -+---------------------------+ | -| IntegerField | | -+---------------------------+ | -| PositiveIntegerField | | -+---------------------------+ | -| PositiveSmallIntegerField | | -+---------------------------+ | -| SmallIntegerField | | -+---------------------------+------------------+ -| BooleanField | ``eq``, ``ne`` | -+---------------------------+ | -| NullBooleanField | | -+---------------------------+------------------+ -| CharField | ``eq``, ``ne``, | -+---------------------------+ ``in``, ``out``, | -| EmailField | ``like``, | -+---------------------------+ ``ilike`` | -| SlugField | | -+---------------------------+ | -| TextField | | -+---------------------------+ | -| URLField | | -+---------------------------+ | -| UUIDField | | -+---------------------------+------------------+ - - -Constants ---------- - -.. autoclass:: dj_rql.constants.FilterLookups - :members: - - -Exceptions ----------- - -.. automodule:: dj_rql.exceptions - :members: - - -Filter classes ------------- - -.. autoclass:: dj_rql.filter_cls.RQLFilterClass - :members: - - -.. autoclass:: dj_rql.filter_cls.AutoRQLFilterClass - :members: - - -.. autoclass:: dj_rql.filter_cls.NestedAutoRQLFilterClass - :members: - - -DB optimization ---------------- - -.. autoclass:: dj_rql.qs.SelectRelated - :members: - - -.. autoclass:: dj_rql.qs.PrefetchRelated - :members: - -.. automodule:: dj_rql.qs - :members: SR, PR - -Django Rest Framework extensions --------------------------------- - - -Filter backend -^^^^^^^^^^^^^^ - -.. autoclass:: dj_rql.drf.backend.RQLFilterBackend - :members: - - -Pagination -^^^^^^^^^^ - -.. automodule:: dj_rql.drf.paginations - :members: - -Serialization -^^^^^^^^^^^^^ - -.. automodule:: dj_rql.drf.serializers.RQLMixin - :members: - - - -OpenAPI -^^^^^^^ - -.. autoclass:: dj_rql.openapi.RQLFilterClassSpecification - :members: - - -.. autoclass:: dj_rql.openapi.RQLFilterDescriptionTemplate - :members: \ No newline at end of file diff --git a/docs/select.md b/docs/select.md new file mode 100644 index 0000000..d263ae3 --- /dev/null +++ b/docs/select.md @@ -0,0 +1,122 @@ +# The "Power of Select" + + +## The `select` operator + +The `select` operator is very powerful and is expecially useful for REST +APIs. + +Suppose you have the following models: + +``` py3 +class Category(models.Model): + name = models.CharField(max_length=100) + +class Company(models.Model): + name = models.CharField(max_length=100) + vat_number = models.CharField(max_length=15) + +class Product(models.Model): + name = models.CharField(max_length=100) + category = models.ForeignKey(Category, on_delete=models.CASCADE) + manufacturer = models.ForeignKey(Company, on_delete=models.CASCADE) +``` + +and the following filter class: + +``` py3 +from dj_rql.filter_cls import RQLFilterClass +from dj_rql.qs import SelectRelated + +class ProductFilters(RQLFilterClass): + + MODEL = Product + SELECT = True + FILTERS = ( + 'name', + { + 'namespace': 'category', + 'filters': ('name',), + 'qs': SelectRelated('category'), + }, + { + 'namespace': 'manufacturer', + 'filters': ('name', 'vat_number'), + 'hidden': True, + 'qs': SelectRelated('manufacturer'), + } + ) +``` + +Issuing the following query: + +``` +GET /products?ilike(name,*rql*) +``` + +Behind the scenes ** django-rql ** applies a `select_releted` +optimization to the queryset to retrive the category of each product +doing a SQL JOIN. + +Since the `manufacturer` has been declared `hidden` +django-rql doesn't retrive the related manufacturer unless you write: + +``` +GET /products?ilike(name,*rql*)&select(manufacturer) +``` + +If you issue such query, ** django-rql ** apply the `qs` +database optimization so it adds a JOIN with the `Company` +model to optimize database access. + +The `select` operator can also be used to exclude fields so if you want +to retrieve products without retrieving the associated category you can +write: + +``` +GET /products?ilike(name,*rql*)&select(-category) +``` + +So the category will be not fetched. + +## Django Rest Framework support + +If you are writing a REST API with Django Rest Framework, +** django-rql ** offers an utility mixin +`dj_rql.drf.serializers.RQLMixin` for your model serializers to +automatically adjust the serialization of related models depending on +select. + +``` py3 +from rest_framework import serializers + +from dj_rql.drf.serializers import RQLMixin + +from ..models import Category, Company, Product + + +class CategorySerializer(RQLMixin, serializers.ModelSerializer): + class Meta: + model = Category + fields = ('id', 'name') + + +class CompanySerializer(RQLMixin, serializers.ModelSerializer): + class Meta: + model = Company + fields = ('id', 'name') + + +class ProductSerializer(RQLMixin, serializers.ModelSerializer): + category = CategorySerializer() + company = CompanySerializer() + + class Meta: + model = Product + fields = ('id', 'name', 'category', 'company') +``` + +!!! note + + A complete working example of how the `select` operator works can be + found [here](https://github.com/maxipavlovic/django_rql_select_example). diff --git a/docs/select.rst b/docs/select.rst deleted file mode 100644 index f63f1ca..0000000 --- a/docs/select.rst +++ /dev/null @@ -1,126 +0,0 @@ -The "Power of Select" -===================== - -The ``select`` operator ------------------------ - -The ``select`` operator is very powerful and is expecially useful for REST APIs. - - -Suppose you have the following models: - -.. code-block:: python - - class Category(models.Model): - name = models.CharField(max_length=100) - - class Company(models.Model): - name = models.CharField(max_length=100) - vat_number = models.CharField(max_length=15) - - class Product(models.Model): - name = models.CharField(max_length=100) - category = models.ForeignKey(Category, on_delete=models.CASCADE) - manufacturer = models.ForeignKey(Company, on_delete=models.CASCADE) - - - -and the following filter class: - -.. code-block:: python - - from dj_rql.filter_cls import RQLFilterClass - from dj_rql.qs import SelectRelated - - class ProductFilters(RQLFilterClass): - - MODEL = Product - SELECT = True - FILTERS = ( - 'name', - { - 'namespace': 'category', - 'filters': ('name',), - 'qs': SelectRelated('category'), - }, - { - 'namespace': 'manufacturer', - 'filters': ('name', 'vat_number'), - 'hidden': True, - 'qs': SelectRelated('manufacturer'), - } - ) - - -Issuing the following query: - -.. code-block:: - - GET /products?ilike(name,*rql*) - -Behind the scenes `django-rql` applies a ``select_releted`` optimization -to the queryset to retrive the category of each product doing a SQL JOIN. - -Since the `manufacturer` has been declared ``hidden`` django-rql doesn't -retrive the related manufacturer unless you write: - -.. code-block:: - - GET /products?ilike(name,*rql*)&select(manufacturer) - -if you issue such query, `django-rql` apply the ``qs`` database optimization -so it adds a JOIN with the `Company` model to optimize database access. - -The ``select`` operator can also be used to exclude fields so if you want to -retrieve products without retrieving the associated category you can write: - -.. code-block:: - - GET /products?ilike(name,*rql*)&select(-category) - - -So the category will be not fetched. - -Django Rest Framework support ------------------------------ - -If you are writing a REST API with Django Rest Framework, `django-rql` offers -an utility mixin (dj_rql.drf.serializers.RQLMixin) for your model serializers to automatically adjust the serialization -of related models depending on select. - -.. code-block:: python - - from rest_framework import serializers - - from dj_rql.drf.serializers import RQLMixin - - from ..models import Category, Company, Product - - - class CategorySerializer(RQLMixin, serializers.ModelSerializer): - class Meta: - model = Category - fields = ('id', 'name') - - - class CompanySerializer(RQLMixin, serializers.ModelSerializer): - class Meta: - model = Company - fields = ('id', 'name') - - - class ProductSerializer(RQLMixin, serializers.ModelSerializer): - category = CategorySerializer() - company = CompanySerializer() - - class Meta: - model = Product - fields = ('id', 'name', 'category', 'company') - - -.. note:: - - A complete working example of how the ``select`` operator works can be found at: - - `https://github.com/maxipavlovic/django_rql_select_example `_. - diff --git a/docs/user_guide.md b/docs/user_guide.md new file mode 100644 index 0000000..d954aa2 --- /dev/null +++ b/docs/user_guide.md @@ -0,0 +1,422 @@ +# User guide + +## Supported operators + +The following operators are currently supported by `django-rql`: + +1. Comparison (eq, ne, gt, ge, lt, le, like, ilike, search) +2. List (in, out) +3. Logical (and, or, not) +4. Constants (null(), empty()) +5. Ordering (ordering) +6. Select (select) + +!!! note + + This guide assumes that you have already read the [RQL + Reference](https://connect.cloudblue.com/community/api/rql/). + + +## Write your filter classes + +A simple filter class looks like: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ('a_field', 'another_field',) +``` + +Filter fields must be specified using the `FILTERS` attribute of the +RQLFilterClass subclass. + +For each field listed through the `FILTERS` attribute, +`django-rql` determines defaults (lookup operators, null +values, etc). For example if your field is a `models.CharField` by default +you can use the operators `eq`, `ne`, `in`, `out`, `like`, `ilike` as +long as the `null` constant. + +Please refers to `default-lookups` for a complete list of defaults. + +If you want a fine grained control of your filters (allowed lookups, +null values, aliases, etc) you can do that using a dictionary instead of +a string with the name of the field. + +### Overriding default lookups + +If you want for a certain filter to specify which lookups it supports +you can do that using the `lookups` property: + +``` py3 +from dj_rql.constants import FilterLookups + +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + { + 'filter': 'title', + 'lookups': {FilterLookups.EQ, FilterLookups.LIKE, FilterLookups.I_LIKE} + }, + ) +``` + +### ordering + +You can allow users to sort by a specific filter using the `ordering` +property: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'published_at', + 'ordering': True, + }, + ) +``` + +On such filter you can sort in ascending order giving: + +``` +GET /books?ordering(published_at) +``` + +To sort in descending order you can use the `-` symbol: + +``` +GET /books?ordering(-published_at) +``` + +!!! note + + Ordering can only be specified for database fields. + +### distinct + +If you want to apply a SELECT DISTINCT to the resulting queryset you can +use the `distinct` property: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'published_at', + 'distinct': True, + }, + ) +``` + +This way, if the [published_at] fielter is present in the +query, a SELECT DISTINCT will be applied. + +!!! note + + If you want to perform a `SELECT DISTINCT` regardless of + which filter is involved in the query, you can do that by adding the + `DISTINCT` attribute to your filter class set to True. See + `dj_rql.filter_cls.RQLFilterClass`. + +### search + +Search allows filtering by all properties supporting such lookups that +match a given pattern. + +If you want to use the `search` operator you must set the `search` +property to True: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'synopsis', + 'search': True, + }, + ) +``` + +This way you can issue the following query: + +``` +GET /books?search(synopsis,murder) +``` + +this is equivalent to: + +``` +GET /books?ilike(synopsis,*murder*) +``` + +!!! note + + The `search` property can be applied only to text database fields, which + have the `ilike` lookup. + + +### use_repr + +For fields with choices, you may want to allow users to filter for the +choice label instead of its database value, so in this case you can set +the `use_repr` property to True: + +``` py3 +STATUSES = ( + ('1', 'Available'), + ('2', 'Reprint'), + ... +) + +class Book(models.Model): + ... + + status = models.CharField(max_length=2, choices=STATUSES) + + +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'status', + 'use_repr': True, + }, + ) +``` + +So you can filter for status like: + +``` +GET /books?eq(status,Available) +``` + +!!! note + + `use_repr` can be used neither with `ordering` nor `search`. + + +### source and sources + +Sometimes it is better to use a name other than the field name for the +filter. In this case you can use the `source` property to specify the +name of the field: + +``` py3 +class MyFilterClass(RQLFilterClass): + + MODEL = MyModel + FILTERS = ( + 'a_field', + { + 'filter': 'filter_name', + 'source': 'field_name', + }, + ) +``` + +A typical use case is to define filters for fields on related models: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'author', + 'source': 'author__name', + }, + ) +``` + +If you want to use a filter to search in two or more fields you can use +the property `sources`: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'author', + 'sources': ('author__name', 'author__surname'), + }, + ) +``` + +### dynamic and field + +`django-rql` allows to filter for dynamic fields (aggregations and +annotations). + +Suppose you have an initial queryset like: + +``` py3 +queryset = Book.objects.annotate(num_authors=Count('authors')) +``` + +And you want to allows to filter by the number of authors that +contribute to the book, you can do that by setting the `dynamic` +property to True and specify the data type for the +[num_authors] column through the `field` property: + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'num_authors', + 'dynamic': True, + 'field': models.IntegerField(), + }, + ) +``` + +So you can write queries like this: + +``` +GET /books?ge(num_authors,2) +``` + +And obtain all the books that have two or more authors. + +### null_values + +In some circumstances you may have some of the values for a field that +you would like to consider equivalent to a database NULL. + +In this case you can specify which values can be considered equivalent to +NULL so you can use the `null()` contant to filter: + +``` py3 +from dj_rql.filter_cls import RQLFilterClass, RQL_NULL + +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'filter': 'isbn', + 'null_values': {RQL_NULL, '0-0000000-0-0'} + }, + ) +``` + +So if you issue the following query: + +``` +GET /books?eq(isbn,null()) +``` + +The resulting queries will contains both records where the +`isbn` column is NULL and records that has the +`isbn` column equal to `0-0000000-0-0`. + +### namespace + +You can allow users to filter by fields on related models. Namespaces +allow to do that and is usefull for api consistency. + +Consider the following filter class: + +``` py3 +class Author(models.Model): + name = models.CharField(max_length=50) + surname = models.CharField(max_length=100) + +class Book(models.Model): + title = models.CharField(max_length=255) + autor = models.ForeignKey(Author, on_delete=models.CASCADE) + +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + 'title', + { + 'namespace': 'author', + 'filters': ('name', 'surname'), + }, + ) +``` + +With this filters definition you can filter also for author's name and +surname the following way: + +``` +GET /books?and(eq(author.name,Ken),eq(author.surname,Follett)) +``` + +### custom + +Sometimes you may want to apply your specific filtering logic for a +filter. + +To do so, you have to set the `custom` property for that filter to True, +define available `lookups` and override the `build_q_for_custom_filter` +method of your filter class. + +``` py3 +class BookFilters(RQLFilterClass): + + MODEL = Book + FILTERS = ( + { + 'filter': 'title', + 'lookups': {FilterLookups.EQ, FilterLookups.IN}, + 'custom': True, + }, + ) + + def build_q_for_custom_filter(self, filter_name, operator, str_value, **kwargs): + pass # Put your filtering logic here and return a ``django.db.models.Q`` object. +``` + +## Django Rest Framework extensions + +### Pagination + +** django-rql ** supports pagination for your api view through +the `dj_rql.drf.paginations.RQLLimitOffsetPagination`. + +### OpenAPI specifications + +If you are using ** django-rql ** with Django Rest Framework to +expose filters for your REST API, the `openapi` property allow you to +describe the filter as long as control how specs for that filter will be +generated. + +``` py3 +'openapi': { + 'description': 'Good description', +} +``` + +Additional properties are: + +> - `required`: You can do a filter mandatory by set it to True. +> - `deprecated`: You can mark a filter as deprecated set it to True. +> - `hidden`: Set it to True if you don't want this filter to be +> included in specs. +> - `type`: Allow overriding the filter data type inferred by the +> underlying model field. +> - `format`: Allow overriding the default field format inferred by +> the underlying model field. + +For the `type` and `format` attributes please refers to the [Data +Types](http://spec.openapis.org/oas/v3.0.3#data-types) section of the +OpenAPI specifications. diff --git a/docs/user_guide.rst b/docs/user_guide.rst deleted file mode 100644 index f174504..0000000 --- a/docs/user_guide.rst +++ /dev/null @@ -1,448 +0,0 @@ -User guide -========== - - -Supported operators -------------------- - -The following operators are currently supported by `django-rql`: - -1. Comparison (eq, ne, gt, ge, lt, le, like, ilike, search) -#. List (in, out) -#. Logical (and, or, not) -#. Constants (null(), empty()) -#. Ordering (ordering) -#. Select (select) - - -.. note:: - - This guide assumes that you have already read the `RQL Reference `_. - - -Write your filter classes -------------------------- - -A simple filter class looks like: - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ('a_field', 'another_field',) - - - -Filter fields must be specified using the ``FILTERS`` attribute of the RQLFilterClass subclass. - -For each field listed through the ``FILTERS`` attribute, `django-rql` determines defaults (lookup operators, null values, etc). -For example if your field is a models.CharField by default you can use the operators -``eq``, ``ne``, ``in``, ``out``, ``like``, ``ilike`` as long as the ``null`` constant. - -Please refers to :ref:`default-lookups` for a complete list of defaults. - -If you want a fine grained control of your filters (allowed lookups, null values, aliases, etc) you can do that -using a dictionary instead of a string with the name of the field. - - -Overriding default lookups -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you want for a certain filter to specify which lookups it supports you can do that using the ``lookups`` property: - - -.. code-block:: python - - from dj_rql.constants import FilterLookups - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - { - 'filter': 'title', - 'lookups': {FilterLookups.EQ, FilterLookups.LIKE, FilterLookups.I_LIKE} - }, - ) - - -ordering -^^^^^^^^ - -You can allow users to sort by a specific filter using the ``ordering`` property: - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'published_at', - 'ordering': True, - }, - ) - -On such filter you can sort in ascending order giving: - -.. code-block:: - - GET /books?ordering(published_at) - - -To sort in descending order you can use the ``-`` symbol: - -.. code-block:: - - GET /books?ordering(-published_at) - - -.. note:: - - Ordering can only be specified for database fields. - - - -distinct -^^^^^^^^ - -If you want to apply a SELECT DISTINCT to the resulting queryset you can use the ``distinct`` property: - - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'published_at', - 'distinct': True, - }, - ) - -This way, if the `published_at` fielter is present in the query, a SELECT DISTINCT will be applied. - -.. note:: - - If you want to perform a `SELECT DISTINCT` regardless of which filter is involved in the query, - you can do that by adding the ``DISTINCT`` attribute to your filter class set to True. - See :class:`dj_rql.filter_cls.RQLFilterClass`. - - -search -^^^^^^ - -Search allows filtering by all properties supporting such lookups that match a given pattern. - -If you want to use the ``search`` operator you must set the ``search`` property to True: - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'synopsis', - 'search': True, - }, - ) - - -This way you can issue the following query: - -.. code-block:: - - GET /books?search(synopsis,murder) - - -this is equivalent to: - -.. code-block:: - - GET /books?ilike(synopsis,*murder*) - - -.. note:: - - The ``search`` property can be applied only to text database fields, which have the ``ilike`` lookup. - - - -use_repr -^^^^^^^^ - -For fields with choices, you may want to allow users to filter for the choice label instead of its database value, -so in this case you can set the ``use_repr`` property to True: - - -.. code-block:: python - - STATUSES = ( - ('1', 'Available'), - ('2', 'Reprint'), - ... - ) - - class Book(models.Model): - ... - - status = models.CharField(max_length=2, choices=STATUSES) - - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'status', - 'use_repr': True, - }, - ) - - -So you can filter for status like: - -.. code-block:: - - GET /books?eq(status,Available) - - -.. note:: - - ``use_repr`` can be used neither with ``ordering`` nor ``search``. - - -source and sources -^^^^^^^^^^^^^^^^^^ - -Sometimes it is better to use a name other than the field name for the filter. -In this case you can use the ``source`` property to specify the name of the field: - -.. code-block:: python - - class MyFilterClass(RQLFilterClass): - - MODEL = MyModel - FILTERS = ( - 'a_field', - { - 'filter': 'filter_name', - 'source': 'field_name', - }, - ) - -A typical use case is to define filters for fields on related models: - - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'author', - 'source': 'author__name', - }, - ) - - -If you want to use a filter to search in two or more fields you can use the property ``sources``: - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'author', - 'sources': ('author__name', 'author__surname'), - }, - ) - - - -dynamic and field -^^^^^^^^^^^^^^^^^ - -``django-rql`` allows to filter for dynamic fields (aggregations and annotations). - -Suppose you have an initial queryset like: - -.. code-block:: python - - queryset = Book.objects.annotate(num_authors=Count('authors')) - - -And you want to allows to filter by the number of authors that contribute to the book, -you can do that by setting the ``dynamic`` property to True and specify the data type for -the `num_authors` column through the ``field`` property: - - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'num_authors', - 'dynamic': True, - 'field': models.IntegerField(), - }, - ) - - -So you can write queries like this: - -.. code-block:: - - GET /books?ge(num_authors,2) - -And obtain all the books that have two or more authors. - - -null_values -^^^^^^^^^^^ - -In some circumstances you may have some of the values for a field that you would like to consider equivalent to a database NULL. - -In this case you can specify which values can considered equivalent to NULL so you can use the ``null()`` contant to filter: - - -.. code-block:: python - - from dj_rql.filter_cls import RQLFilterClass, RQL_NULL - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'filter': 'isbn', - 'null_values': {RQL_NULL, '0-0000000-0-0'} - }, - ) - - -So if you issue the following query: - -.. code-block:: - - GET /books?eq(isbn,null()) - - -The resulting queries will contains both records where the `isbn` column is NULL and records -that has the `isbn` column equal to `0-0000000-0-0`. - - -namespace -^^^^^^^^^ - -You can allow users to filter by fields on related models. Namespaces allow to do that and is usefull -for api consistency. - -Consider the following filter class: - - -.. code-block:: python - - class Author(models.Model): - name = models.CharField(max_length=50) - surname = models.CharField(max_length=100) - - class Book(models.Model): - title = models.CharField(max_length=255) - autor = models.ForeignKey(Author, on_delete=models.CASCADE) - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - 'title', - { - 'namespace': 'author', - 'filters': ('name', 'surname'), - }, - ) - - -With this filters definition you can filter also for author's name and surname the following way: - -.. code-block:: - - GET /books?and(eq(author.name,Ken),eq(author.surname,Follett)) - - -custom -^^^^^^ - -Sometimes you may want to apply your specific filtering logic for a filter. - -To do so, you have to set the ``custom`` property for that filter to True, define available ``lookups`` -and override the ``build_q_for_custom_filter`` method of your filter class. - -.. code-block:: python - - class BookFilters(RQLFilterClass): - - MODEL = Book - FILTERS = ( - { - 'filter': 'title', - 'lookups': {FilterLookups.EQ, FilterLookups.IN}, - 'custom': True, - }, - ) - - def build_q_for_custom_filter(self, filter_name, operator, str_value, **kwargs): - pass # Put your filtering logic here and return a ``django.db.models.Q`` object. - - - -Django Rest Framework extensions --------------------------------- - -Pagination -^^^^^^^^^^ - -`django-rql` supports pagination for your api view through the :class:`dj_rql.drf.paginations.RQLLimitOffsetPagination`. - - -OpenAPI specifications -^^^^^^^^^^^^^^^^^^^^^^ - -If you are using `django-rql` with Django Rest Framework to expose filters for your REST API, -the ``openapi`` property allow you to describe the filter as long as control how specs for -that filter will be generated. - -.. code-block:: python - - 'openapi': { - 'description': 'Good description', - } - - -Additional properties are: - - * ``required``: You can do a filter mandatory by set it to True. - * ``deprecated``: You can mark a filter as deprecated set it to True. - * ``hidden``: Set it to True if you don't want this filter to be included in specs. - * ``type``: Allow overriding the filter data type inferred by the underlying model field. - * ``format``: Allow overriding the default field format inferred by the underlying model field. - - -For the ``type`` and ``format`` attributes please refers to the `Data Types `_ -section of the OpenAPI specifications. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..d4439bb --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,70 @@ +site_name: Django RQL Library +site_url: https://github.com/cloudblue/django-rql +repo_name: cloudblue/django-rql +repo_url: https://github.com/cloudblue/django-rql +edit_uri: "" +copyright: Copyright © 2023 Ingram Micro. All Rights Reserved. +extra: + generator: false + social: + - icon: fontawesome/brands/github + link: https://github.com/cloudblue + - icon: material/home + link: https://connect.cloudblue.com/community/ +extra_css: + - css/custom.css +theme: + name: material + logo: images/logo_full.png + favicon: images/favicon.ico + palette: + - scheme: "default" + media: "(prefers-color-scheme: light)" + toggle: + icon: "material/lightbulb" + name: "Switch to dark mode" + - scheme: "slate" + media: "(prefers-color-scheme: dark)" + primary: "blue" + toggle: + icon: "material/lightbulb-outline" + name: "Switch to light mode" +markdown_extensions: + - admonition + - pymdownx.highlight + - pymdownx.superfences + - pymdownx.keys + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - attr_list + - toc: + toc_depth: 3 + - tables +plugins: + - glightbox + - macros: + module_name: docs/macros + - search: + lang: en + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_signature_annotations: true + show_source: false + show_bases: false + show_root_toc_entry: false + - autorefs +watch: + - docs + - dj_rql +nav: + - Home: index.md + - Getting started: getting_started.md + - User guide: user_guide.md + - The "Power of Select": select.md + - API Reference: reference.md diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..8bcce21 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1520 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.6.0" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, + {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "beautifulsoup4" +version = "4.11.2" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.11.2-py3-none-any.whl", hash = "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39"}, + {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "cachetools" +version = "5.3.0" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = false +python-versions = "~=3.7" +files = [ + {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"}, + {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"}, +] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.0.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "cognitive-complexity" +version = "1.3.0" +description = "Library to calculate Python functions cognitive complexity via code" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cognitive_complexity-1.3.0.tar.gz", hash = "sha256:a0cfbd47dee0b19f4056f892389f501694b205c3af69fb703cc744541e03dde5"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "django" +version = "3.2.17" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Django-3.2.17-py3-none-any.whl", hash = "sha256:59c39fc342b242fb42b6b040ad8b1b4c15df438706c1d970d416d63cdd73e7fd"}, + {file = "Django-3.2.17.tar.gz", hash = "sha256:644288341f06ebe4938eec6801b6bd59a6534a78e4aedde2a153075d11143894"}, +] + +[package.dependencies] +asgiref = ">=3.3.2,<4" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-fsm" +version = "2.8.1" +description = "Django friendly finite state machine support." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "django-fsm-2.8.1.tar.gz", hash = "sha256:fd9f8de9f33188e50f876ce53908fbd7289e5031a44ffdb97d43909e56699ef8"}, + {file = "django_fsm-2.8.1-py2.py3-none-any.whl", hash = "sha256:e2c02cbf273fb9691aa9a907c29990afdd21a4adea09c5640344c93fbe03f8d9"}, +] + +[[package]] +name = "django-model-utils" +version = "4.3.1" +description = "Django model mixins and utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-model-utils-4.3.1.tar.gz", hash = "sha256:2e2e4f13e4f14613134a9777db7ad4265f59a1d8f1384107bcaa3028fe3c87c1"}, + {file = "django_model_utils-4.3.1-py3-none-any.whl", hash = "sha256:8c0b0177bab909a8635b602d960daa67e80607aa5469217857271a60726d7a4b"}, +] + +[package.dependencies] +Django = ">=3.2" + +[[package]] +name = "djangorestframework" +version = "3.14.0" +description = "Web APIs for Django, made easy." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, +] + +[package.dependencies] +django = ">=3.0" +pytz = "*" + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "flake8-broken-line" +version = "0.6.0" +description = "Flake8 plugin to forbid backslashes for line breaks" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "flake8-broken-line-0.6.0.tar.gz", hash = "sha256:a02268f11a18837c83c59013a36cc00fee9e17a042745cc0c9895f1c9f6acc16"}, + {file = "flake8_broken_line-0.6.0-py3-none-any.whl", hash = "sha256:c0ab336ff7de228dbffbe56d67b3615bb21fb15f3ed0604fa7bdf9feb72d7d88"}, +] + +[package.dependencies] +flake8 = ">=3.5,<6" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "flake8-bugbear" +version = "22.12.6" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, + {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=3.0.0" + +[package.extras] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "tox"] + +[[package]] +name = "flake8-cognitive-complexity" +version = "0.1.0" +description = "An extension for flake8 that validates cognitive functions complexity" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "flake8_cognitive_complexity-0.1.0.tar.gz", hash = "sha256:f202df054e4f6ff182b659c261922b9c684628a47beb19cb0973c50d6a7831c1"}, +] + +[package.dependencies] +cognitive_complexity = "*" +setuptools = "*" + +[[package]] +name = "flake8-commas" +version = "2.1.0" +description = "Flake8 lint for trailing commas." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"}, + {file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"}, +] + +[package.dependencies] +flake8 = ">=2" + +[[package]] +name = "flake8-future-import" +version = "0.4.7" +description = "__future__ import checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "flake8-future-import-0.4.7.tar.gz", hash = "sha256:ed826cca28fd4b55c486d69c4bfb32e9a052efc66714bf22bf471f50ecf8e0de"}, + {file = "flake8_future_import-0.4.7-py2.py3-none-any.whl", hash = "sha256:7ecb1b89f5e026afb0cfdb8642d150bf56efbb6fb86a713ea4568398142f47ae"}, +] + +[package.dependencies] +flake8 = "*" + +[[package]] +name = "flake8-isort" +version = "5.0.3" +description = "flake8 plugin that integrates isort ." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8-isort-5.0.3.tar.gz", hash = "sha256:0951398c343c67f4933407adbbfb495d4df7c038650c5d05753a006efcfeb390"}, + {file = "flake8_isort-5.0.3-py3-none-any.whl", hash = "sha256:8c4ab431d87780d0c8336e9614e50ef11201bc848ef64ca017532dec39d4bf49"}, +] + +[package.dependencies] +flake8 = "*" +isort = ">=4.3.5,<6" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "0.25.4" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"}, + {file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"}, +] + +[package.dependencies] +cached-property = {version = "*", markers = "python_version < \"3.8\""} +colorama = ">=0.4" + +[package.extras] +async = ["aiofiles (>=0.7,<1.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, + {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +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 = "isort" +version = "5.11.5" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lark-parser" +version = "0.11.0" +description = "a modern parsing library" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "lark-parser-0.11.0.tar.gz", hash = "sha256:d047e680418221d21be587cd8f36843df97479a5623b74a7d811c1d832e04989"}, + {file = "lark_parser-0.11.0-py2.py3-none-any.whl", hash = "sha256:1d710d4c4664b52579aae80324ff409dedc22b5c592684429f6bd11bfeb7fd49"}, +] + +[package.extras] +nearley = ["js2py"] +regex = ["regex"] + +[[package]] +name = "lib-rql" +version = "1.1.7" +description = "Python RQL Filtering" +category = "main" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "lib_rql-1.1.7-py3-none-any.whl", hash = "sha256:ea45f177fccaa16532dcf85e1ca375898e8d49818cc83110b663175146192c37"}, + {file = "lib_rql-1.1.7.tar.gz", hash = "sha256:10a72c9b7aa6ecb5290536e58b8c2670acf391719513fe441dda649927be6045"}, +] + +[package.dependencies] +cachetools = ">=4.2.4" +lark-parser = "0.11.0" +python-dateutil = ">=2.8.2" + +[[package]] +name = "markdown" +version = "3.3.7" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, + {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1,<3.4" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +description = "Automatically link across pages in MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, + {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, +] + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-glightbox" +version = "0.3.1" +description = "MkDocs plugin supports image lightbox with GLightbox." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mkdocs-glightbox-0.3.1.tar.gz", hash = "sha256:ac85e2d4d422cc4a670fa276840f0aa3064a1ec4ad25ccb6d6e82d11bb11e513"}, + {file = "mkdocs_glightbox-0.3.1-py3-none-any.whl", hash = "sha256:1974f505e3272b617b5e7552fd09d8d918d267631ed991772b4bd103dc74bea2"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.11.1" + +[[package]] +name = "mkdocs-macros-plugin" +version = "0.7.0" +description = "Unleash the power of MkDocs with macros and variables" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mkdocs-macros-plugin-0.7.0.tar.gz", hash = "sha256:9e64e1cabcf6925359de29fe54f62d5847fb455c2528c440b87f8f1240650608"}, + {file = "mkdocs_macros_plugin-0.7.0-py3-none-any.whl", hash = "sha256:96bdabeb98b96139544f0048ea2f5cb80c7befde6b21e94c6d4596c22774cbcf"}, +] + +[package.dependencies] +jinja2 = "*" +mkdocs = ">=0.17" +python-dateutil = "*" +pyyaml = "*" +termcolor = "*" + +[package.extras] +test = ["mkdocs-include-markdown-plugin", "mkdocs-macros-test", "mkdocs-material (>=6.2)"] + +[[package]] +name = "mkdocs-material" +version = "9.0.11" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material-9.0.11-py3-none-any.whl", hash = "sha256:90a1e1ed41e90de5d0ab97c874b7bf6af488d0faf4aaea8e5868e01f3f1ed923"}, + {file = "mkdocs_material-9.0.11.tar.gz", hash = "sha256:aff49e4ce622a107ed563b3a6a37dc3660a45a0e4d9e7d4d2c13ce9dc02a7faf"}, +] + +[package.dependencies] +colorama = ">=0.4" +jinja2 = ">=3.0" +markdown = ">=3.2" +mkdocs = ">=1.4.2" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +regex = ">=2022.4.24" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.20.0" +description = "Automatic documentation from sources, for MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "0.8.3" +description = "A Python handler for mkdocstrings." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, + {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, +] + +[package.dependencies] +griffe = ">=0.24" +mkdocstrings = ">=0.19" + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.9.2" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, + {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, +] + +[package.dependencies] +markdown = ">=3.2" + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-django" +version = "4.5.2" +description = "A Django plugin for pytest." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, + {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, +] + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["Django", "django-configurations (>=2.0)"] + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-pythonpath" +version = "0.7.4" +description = "pytest plugin for adding to the PYTHONPATH from command line or configs." +category = "dev" +optional = false +python-versions = ">=2.6, <4" +files = [ + {file = "pytest-pythonpath-0.7.4.tar.gz", hash = "sha256:64e195b23a8f8c0c631fb16882d9ad6fa4137ed1f2961ddd15d52065cd435db6"}, + {file = "pytest_pythonpath-0.7.4-py3-none-any.whl", hash = "sha256:e73e11dab2f0b83e73229e261242b251f0a369d7f527dbfec068822fd26a6ce5"}, +] + +[package.dependencies] +pytest = ">=2.5.2,<7" + +[[package]] +name = "pytest-randomly" +version = "3.12.0" +description = "Pytest plugin to randomly order tests and control random.seed." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, + {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +pytest = "*" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2022.7.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, +] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "regex" +version = "2022.10.31" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, + {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66"}, + {file = "regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1"}, + {file = "regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7"}, + {file = "regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af"}, + {file = "regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61"}, + {file = "regex-2022.10.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4"}, + {file = "regex-2022.10.31-cp36-cp36m-win32.whl", hash = "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066"}, + {file = "regex-2022.10.31-cp36-cp36m-win_amd64.whl", hash = "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6"}, + {file = "regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95"}, + {file = "regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394"}, + {file = "regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c"}, + {file = "regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc"}, + {file = "regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, + {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, + {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, + {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, +] + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "67.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, + {file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] + +[[package]] +name = "sqlparse" +version = "0.4.3" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, + {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, +] + +[[package]] +name = "termcolor" +version = "2.2.0" +description = "ANSI color formatting for output in terminal" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"}, + {file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +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.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] + +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "watchdog" +version = "2.2.1" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"}, + {file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"}, + {file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"}, + {file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"}, + {file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"}, + {file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"}, + {file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"}, + {file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"}, + {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"}, + {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "zipp" +version = "3.12.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.12.1-py3-none-any.whl", hash = "sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3"}, + {file = "zipp-3.12.1.tar.gz", hash = "sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.7,<4" +content-hash = "dec724591c8196380d8d860e4cac3b65d752ca99606b772cb3ecf6a0e856c1b1" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dc843bd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,105 @@ +[tool.poetry] +name = "django-rql" +version = "27.0.0" +description = "Django RQL Filtering" +authors = ["CloudBlue LLC"] +license = "Apache-2.0" +packages = [ + { include = "dj_rql" } +] +readme = "./README.md" +homepage = "https://connect.cloudblue.com/community/api/rql/" +repository = "https://github.com/cloudblue/django-rql" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Framework :: Django :: 3.2', + 'Framework :: Django :: 4.0', + 'Framework :: Django :: 4.1', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Text Processing :: Filters', +] +keywords = [ + "django", + "rql", + "filter", + "rest", + "api", +] + +[tool.poetry.dependencies] +python = ">=3.7,<4" +lib-rql = "^1.1.5" +django = ">=3.2" + +[tool.poetry.group.test.dependencies] +pytest = ">=6.1.2,<8" +pytest-cov = ">=2.10.1,<5" +pytest-mock = "^3.3.1" +coverage = {extras = ["toml"], version = ">=5.3,<7"} +flake8 = ">=3.8,<6" +flake8-bugbear = ">=20,<23" +flake8-cognitive-complexity = "^0.1" +flake8-commas = "~2.1" +flake8-future-import = "~0.4" +flake8-isort = "^5.0" +flake8-broken-line = ">=0.3,<0.7" +pytest-django = ">=4.4.0" +djangorestframework = ">=3.12" +isort = "^5.10" +importlib-metadata = "<5" +django-fsm = ">=2.7.1" +django-model-utils = ">=3.2.0" +uritemplate = "^4.1.1" +pytest-pythonpath = { version = "^0.7.4", python = "<3.8" } +pytest-randomly = ">=3.12" + +[tool.poetry.group.docs.dependencies] +mkdocs = ">=1.4" +mkdocs-material = ">=9" +mkdocs-glightbox = "^0.3.1" +mkdocs-macros-plugin = "^0.7.0" +mkdocstrings = ">=0.19.1" +mkdocstrings-python = "^0.8.3" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +testpaths = ["tests"] +log_cli = false +addopts = "--show-capture=no --create-db --nomigrations --junitxml=tests/reports/out.xml --cov=dj_rql --cov-report xml:tests/reports/coverage.xml" +filterwarnings = "ignore::UserWarning" +python_files = "test_*.py" +DJANGO_SETTINGS_MODULE = "tests.dj_rf.settings" +django_find_project = false +pythonpath = ["."] +python_paths = ["."] + +[tool.coverage.run] +branch = true + +[tool.coverage.xml] +output = "tests/reports/coverage.xml" + +[virtualenvs] +create = false + +[tool.isort] +src_paths = "*" +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] +group_by_package = true +multi_line_output = 3 +force_grid_wrap = 4 +combine_as_imports = true +use_parentheses = true +include_trailing_comma = true +line_length = 100 +lines_after_imports = 2 diff --git a/requirements/dev.txt b/requirements/dev.txt deleted file mode 100644 index 955da77..0000000 --- a/requirements/dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -lib-rql>=1.1.5,<2 -Django>=2.2.19 diff --git a/requirements/docs.txt b/requirements/docs.txt index d84d244..8ef7ed3 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,7 +1,37 @@ --r test.txt --r dev.txt -Sphinx==3.1.2 -sphinx-rtd-theme==0.4.3 -sphinx-copybutton==0.2.11 -setuptools-scm==3.5.0 -docutils==0.17.1 \ No newline at end of file +beautifulsoup4==4.11.2 ; python_version >= "3.7" and python_version < "4" +cached-property==1.5.2 ; python_version >= "3.7" and python_version < "3.8" +certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4" +charset-normalizer==3.0.1 ; python_version >= "3.7" and python_version < "4" +click==8.1.3 ; python_version >= "3.7" and python_version < "4" +colorama==0.4.6 ; python_version >= "3.7" and python_version < "4" +ghp-import==2.1.0 ; python_version >= "3.7" and python_version < "4" +griffe==0.25.4 ; python_version >= "3.7" and python_version < "4" +idna==3.4 ; python_version >= "3.7" and python_version < "4" +importlib-metadata==4.13.0 ; python_version >= "3.7" and python_version < "3.10" +jinja2==3.1.2 ; python_version >= "3.7" and python_version < "4" +markdown==3.3.7 ; python_version >= "3.7" and python_version < "4" +markupsafe==2.1.2 ; python_version >= "3.7" and python_version < "4" +mergedeep==1.3.4 ; python_version >= "3.7" and python_version < "4" +mkdocs-autorefs==0.4.1 ; python_version >= "3.7" and python_version < "4" +mkdocs-glightbox==0.3.1 ; python_version >= "3.7" and python_version < "4" +mkdocs-macros-plugin==0.7.0 ; python_version >= "3.7" and python_version < "4" +mkdocs-material-extensions==1.1.1 ; python_version >= "3.7" and python_version < "4" +mkdocs-material==9.0.11 ; python_version >= "3.7" and python_version < "4" +mkdocs==1.4.2 ; python_version >= "3.7" and python_version < "4" +mkdocstrings-python==0.8.3 ; python_version >= "3.7" and python_version < "4" +mkdocstrings==0.20.0 ; python_version >= "3.7" and python_version < "4" +packaging==23.0 ; python_version >= "3.7" and python_version < "4" +pygments==2.14.0 ; python_version >= "3.7" and python_version < "4" +pymdown-extensions==9.9.2 ; python_version >= "3.7" and python_version < "4" +python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "4" +pyyaml-env-tag==0.1 ; python_version >= "3.7" and python_version < "4" +pyyaml==6.0 ; python_version >= "3.7" and python_version < "4" +regex==2022.10.31 ; python_version >= "3.7" and python_version < "4" +requests==2.28.2 ; python_version >= "3.7" and python_version < "4" +six==1.16.0 ; python_version >= "3.7" and python_version < "4" +soupsieve==2.3.2.post1 ; python_version >= "3.7" and python_version < "4" +termcolor==2.2.0 ; python_version >= "3.7" and python_version < "4" +typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "3.8" +urllib3==1.26.14 ; python_version >= "3.7" and python_version < "4" +watchdog==2.2.1 ; python_version >= "3.7" and python_version < "4" +zipp==3.12.1 ; python_version >= "3.7" and python_version < "3.10" diff --git a/requirements/extra.txt b/requirements/extra.txt deleted file mode 100644 index b6b5641..0000000 --- a/requirements/extra.txt +++ /dev/null @@ -1 +0,0 @@ -djangorestframework>=3.11.1 \ No newline at end of file diff --git a/requirements/test.txt b/requirements/test.txt deleted file mode 100644 index 88c0ff2..0000000 --- a/requirements/test.txt +++ /dev/null @@ -1,24 +0,0 @@ -coverage==6.0.2 -flake8==3.9.2 -pytest==6.2.5 -pytest-cov==3.0.0 -pytest-django==4.4.0 -pytest-mock==3.6.1 -pytest-deadfixtures==2.2.1 -pytest-randomly==3.10.1 -pytz==2021.3 -django-fsm==2.7.1 -django-model-utils==4.1.1 -djangorestframework==3.12.4 -pyyaml==5.4.1 -uritemplate==4.1.0 -flake8-bugbear==21.9.2 -flake8-broken-line==0.3.0 -flake8-commas==2.0.0 -flake8-comprehensions==3.7.0 -flake8-debugger==4.0.0 -flake8-eradicate==1.1.0 -flake8-isort==5.0.3 -flake8-string-format==0.3.0 -importlib-metadata>=4.0.0,<5.0.0 -isort==5.10.1 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b766da5..0000000 --- a/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -[aliases] -test = pytest - -[flake8] -exclude = .idea,.git,venv*/,.eggs/,*.egg-info,_generated_filters*.py -max-line-length = 100 -show-source = True -ignore = W503,W605 - -[tool:pytest] -addopts = --show-capture=no --create-db --nomigrations --junitxml=tests/reports/out.xml --cov=dj_rql --cov-report xml:tests/reports/coverage.xml -filterwarnings = - ignore::UserWarning -DJANGO_SETTINGS_MODULE = tests.dj_rf.settings - -[isort] -src_paths = * -sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER -group_by_package = True -multi_line_output = 3 -force_grid_wrap = 4 -combine_as_imports = True -use_parentheses = True -include_trailing_comma = True -line_length = 100 -lines_after_imports = 2 diff --git a/setup.py b/setup.py deleted file mode 100644 index 0364d42..0000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# Copyright © 2021 Ingram Micro Inc. All rights reserved. -# - -from setuptools import find_packages, setup - - -def read_file(name): - with open(name, 'r') as f: - content = f.read().rstrip('\n') - return content - - -setup( - name='django-rql', - author='CloudBlue', - url='https://connect.cloudblue.com/community/api/rql/', - description='Django RQL Filtering', - long_description=read_file('README.md'), - long_description_content_type='text/markdown', - license='Apache License, Version 2.0', - - python_requires='>=3.6', - zip_safe=True, - packages=find_packages(exclude=('tests',)), - include_package_data=True, - install_requires=read_file('requirements/dev.txt').splitlines(), - tests_require=read_file('requirements/test.txt').splitlines(), - setup_requires=['setuptools_scm<7', 'pytest-runner', 'wheel'], - extras_require={ - 'drf': read_file('requirements/extra.txt').splitlines(), - }, - use_scm_version=True, - - keywords='django rql filter rest api', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Django :: 2.2', - 'Framework :: Django :: 3.0', - 'Framework :: Django :: 3.1', - 'Framework :: Django :: 3.2', - 'Framework :: Django :: 4.0', - 'Framework :: Django :: 4.1', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: Unix', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Topic :: Text Processing :: Filters', - ], -) diff --git a/tests/__init__.py b/tests/__init__.py index f61c46d..ff53ddd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,3 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/tests/constants.py b/tests/constants.py index f27c325..7add1e3 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from py_rql.constants import ( diff --git a/tests/data.py b/tests/data.py index 7d9ef61..c0d32e8 100644 --- a/tests/data.py +++ b/tests/data.py @@ -1,5 +1,5 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/tests/dj_rf/__init__.py b/tests/dj_rf/__init__.py index f61c46d..ff53ddd 100644 --- a/tests/dj_rf/__init__.py +++ b/tests/dj_rf/__init__.py @@ -1,3 +1,3 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/tests/dj_rf/filters.py b/tests/dj_rf/filters.py index f4a6dc3..62d0aae 100644 --- a/tests/dj_rf/filters.py +++ b/tests/dj_rf/filters.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from copy import deepcopy diff --git a/tests/dj_rf/models.py b/tests/dj_rf/models.py index 6979ecf..b6c3733 100644 --- a/tests/dj_rf/models.py +++ b/tests/dj_rf/models.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from uuid import uuid4 diff --git a/tests/dj_rf/serializers.py b/tests/dj_rf/serializers.py index 07fcbd4..105ed4c 100644 --- a/tests/dj_rf/serializers.py +++ b/tests/dj_rf/serializers.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from rest_framework import serializers diff --git a/tests/dj_rf/settings.py b/tests/dj_rf/settings.py index 4a45e4e..09b4965 100644 --- a/tests/dj_rf/settings.py +++ b/tests/dj_rf/settings.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import os diff --git a/tests/dj_rf/urls.py b/tests/dj_rf/urls.py index 0ef4a77..ca02dd2 100644 --- a/tests/dj_rf/urls.py +++ b/tests/dj_rf/urls.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from django.conf.urls import include diff --git a/tests/dj_rf/view.py b/tests/dj_rf/view.py index 994aa95..70a6869 100644 --- a/tests/dj_rf/view.py +++ b/tests/dj_rf/view.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from django.db.models import CharField, IntegerField, Value diff --git a/tests/test_commands/test_generate_rql_class.py b/tests/test_commands/test_generate_rql_class.py index b2b00db..3ef90ce 100644 --- a/tests/test_commands/test_generate_rql_class.py +++ b/tests/test_commands/test_generate_rql_class.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import os diff --git a/tests/test_drf/__init__.py b/tests/test_drf/__init__.py index f61c46d..ff53ddd 100644 --- a/tests/test_drf/__init__.py +++ b/tests/test_drf/__init__.py @@ -1,3 +1,3 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/tests/test_drf/conftest.py b/tests/test_drf/conftest.py index c6b495e..74e8956 100644 --- a/tests/test_drf/conftest.py +++ b/tests/test_drf/conftest.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_drf/test_common_drf_backend.py b/tests/test_drf/test_common_drf_backend.py index b45aaa5..a2951a5 100644 --- a/tests/test_drf/test_common_drf_backend.py +++ b/tests/test_drf/test_common_drf_backend.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest from cachetools import LFUCache, LRUCache diff --git a/tests/test_drf/test_django_filters_backend.py b/tests/test_drf/test_django_filters_backend.py index 505514c..cc539d8 100644 --- a/tests/test_drf/test_django_filters_backend.py +++ b/tests/test_drf/test_django_filters_backend.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_drf/test_dynamic_filter.py b/tests/test_drf/test_dynamic_filter.py index 37bb571..e456b24 100644 --- a/tests/test_drf/test_dynamic_filter.py +++ b/tests/test_drf/test_dynamic_filter.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_drf/test_pagination.py b/tests/test_drf/test_pagination.py index 7a285b8..58082c2 100644 --- a/tests/test_drf/test_pagination.py +++ b/tests/test_drf/test_pagination.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from unittest import TestCase diff --git a/tests/test_drf/test_select.py b/tests/test_drf/test_select.py index d4c6227..b4c65ae 100644 --- a/tests/test_drf/test_select.py +++ b/tests/test_drf/test_select.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_drf/test_serializers.py b/tests/test_drf/test_serializers.py index 4789a4b..2481aa3 100644 --- a/tests/test_drf/test_serializers.py +++ b/tests/test_drf/test_serializers.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from collections import OrderedDict diff --git a/tests/test_filter_cls/__init__.py b/tests/test_filter_cls/__init__.py index f61c46d..ff53ddd 100644 --- a/tests/test_filter_cls/__init__.py +++ b/tests/test_filter_cls/__init__.py @@ -1,3 +1,3 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # diff --git a/tests/test_filter_cls/conftest.py b/tests/test_filter_cls/conftest.py new file mode 100644 index 0000000..d3a745a --- /dev/null +++ b/tests/test_filter_cls/conftest.py @@ -0,0 +1,31 @@ +# +# Copyright © 2023 Ingram Micro Inc. All rights reserved. +# + +from datetime import timedelta + +import pytest +from django.utils import timezone + +from tests.dj_rf.models import Author, Book, Publisher +from tests.dj_rf.view import apply_annotations + + +@pytest.fixture +def generate_books(): + def _generate_books(count=2): + for i in range(count): + author = Author.objects.create( + name=f'author{i}', + email=f'author{i}@example.com', + is_male=True, + publisher=Publisher.objects.create(), + ) + b = Book.objects.create(author=author, published_at=timezone.now() - timedelta(days=i)) + if b.published_at is None: + b.published_at = timezone.now() + b.save() + book_qs = apply_annotations(Book.objects.order_by('id')) + books = list(book_qs) + return books + return _generate_books diff --git a/tests/test_filter_cls/test_apply_filters.py b/tests/test_filter_cls/test_apply_filters.py index 6726585..cc6b79a 100644 --- a/tests/test_filter_cls/test_apply_filters.py +++ b/tests/test_filter_cls/test_apply_filters.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from functools import partial @@ -14,7 +14,7 @@ from dj_rql.filter_cls import RQLFilterClass from tests.dj_rf.filters import BooksFilterClass from tests.dj_rf.models import Author, Book, Publisher -from tests.test_filter_cls.utils import book_qs, create_books +from tests.test_filter_cls.utils import book_qs def apply_filters(query): @@ -156,8 +156,8 @@ def apply_listing_filters(operator, *values): @pytest.mark.django_db -def test_in(): - books = create_books() +def test_in(generate_books): + books = generate_books() assert apply_in_listing_filters(str(books[0].pk)) == [books[0]] assert apply_in_listing_filters(str(books[1].pk), '23') == [books[1]] assert apply_in_listing_filters(str(books[1].pk), str(books[0].pk)) == books @@ -165,8 +165,8 @@ def test_in(): @pytest.mark.django_db -def test_out(): - books = create_books() +def test_out(generate_books): + books = generate_books() assert apply_out_listing_filters(str(books[0].pk)) == [books[1]] assert apply_out_listing_filters(str(books[1].pk), '23') == [books[0]] assert apply_out_listing_filters(str(books[1].pk), str(books[0].pk)) == [] @@ -192,8 +192,8 @@ def test_out(): 'in(author,(t(publisher.id=null()),t(email={email})))', 'out(author,(t(email={second_book_email})))', )) -def test_tuple(filter_string): - books = create_books() +def test_tuple(generate_books, filter_string): + books = generate_books() comp_filter = filter_string.format( email=books[0].author.email, published_at=books[0].published_at.date(), @@ -216,8 +216,8 @@ def test_tuple(filter_string): 'author=t(limit=1)', 'author=t(offset=1)', )) -def test_tuple_syntax_terms_not_fail(filter_string): - books = create_books() +def test_tuple_syntax_terms_not_fail(generate_books, filter_string): + books = generate_books() assert apply_filters(filter_string) == books @@ -262,15 +262,15 @@ def test_tuple_lookup_error(): @pytest.mark.django_db -def test_null(): - books = create_books() +def test_null(generate_books): + books = generate_books() assert apply_filters('title={0}'.format(RQL_NULL)) == books assert apply_filters('title=ne={0}'.format(RQL_NULL)) == [] @pytest.mark.django_db -def test_null_with_in_or(): - books = create_books() +def test_null_with_in_or(generate_books): + books = generate_books() title = 'null' books[0].title = title @@ -312,8 +312,8 @@ def test_ordering_source(): @pytest.mark.django_db -def test_ordering_sources(): - books = create_books() +def test_ordering_sources(generate_books): + books = generate_books() assert apply_filters('ordering(d_id)') == [books[0], books[1]] assert apply_filters('ordering(-d_id)') == [books[1], books[0]] @@ -333,8 +333,8 @@ def test_ordering_by_several_filters(): @pytest.mark.django_db -def test_ordering_by_empty_value(): - books = create_books() +def test_ordering_by_empty_value(generate_books): + books = generate_books() assert apply_filters('ordering()') == books @@ -400,7 +400,7 @@ def build_q_for_custom_filter(self, *args, **kwargs): @pytest.mark.django_db -def test_custom_filter_ordering(): +def test_custom_filter_ordering(generate_books): class CustomCls(BooksFilterClass): def build_name_for_custom_ordering(self, filter_name): return 'id' @@ -408,7 +408,7 @@ def build_name_for_custom_ordering(self, filter_name): def assert_ordering(self, filter_name, expected): assert list(self.apply_filters('ordering({0})'.format(filter_name))[1]) == expected - books = create_books() + books = generate_books() CustomCls(book_qs).assert_ordering('ordering_filter', [books[0], books[1]]) CustomCls(book_qs).assert_ordering('-ordering_filter', [books[1], books[0]]) diff --git a/tests/test_filter_cls/test_fields_filtering.py b/tests/test_filter_cls/test_fields_filtering.py index 3626485..ba2b7d3 100644 --- a/tests/test_filter_cls/test_fields_filtering.py +++ b/tests/test_filter_cls/test_fields_filtering.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from datetime import date, datetime @@ -24,7 +24,7 @@ Page, Publisher, ) -from tests.test_filter_cls.utils import book_qs, create_books +from tests.test_filter_cls.utils import book_qs def filter_field(filter_name, operator, value): @@ -48,9 +48,9 @@ def assert_filter_field_error(error_cls, filter_name, operator, value): @pytest.mark.django_db -def test_id(): +def test_id(generate_books): filter_name = 'id' - books = create_books() + books = generate_books() assert filter_field(filter_name, CO.EQ, books[0].pk) == [books[0]] assert filter_field(filter_name, CO.EQ, 3) == [] assert filter_field(filter_name, CO.NE, books[1].pk) == [books[0]] @@ -297,8 +297,8 @@ def test_fsm(): @pytest.mark.django_db @pytest.mark.parametrize('filter_name', ('anno_int', 'anno_int_ref')) -def test_anno_int_ok(filter_name): - books = create_books() +def test_anno_int_ok(generate_books, filter_name): + books = generate_books() assert filter_field(filter_name, CO.EQ, 10) == [] assert filter_field(filter_name, CO.EQ, 1000) == books @@ -319,19 +319,19 @@ def test_anno_int_fail_value(filter_name): @pytest.mark.django_db -def test_anno_str_ok(): +def test_anno_str_ok(generate_books): filter_name = 'anno_str' - books = create_books() + books = generate_books() assert filter_field(filter_name, CO.EQ, 'te') == [] assert filter_field(filter_name, CO.EQ, 'text') == books @pytest.mark.django_db -def test_anno_title_non_dynamic(): +def test_anno_title_non_dynamic(generate_books): filter_name = 'anno_title_non_dynamic' - books = create_books() + books = generate_books() books[0].title = 'text' books[0].save(update_fields=['title']) @@ -340,10 +340,10 @@ def test_anno_title_non_dynamic(): @pytest.mark.django_db -def test_anno_title_dynamic(): +def test_anno_title_dynamic(generate_books): filter_name = 'anno_title_dynamic' - books = create_books() + books = generate_books() books[0].title = 'text' books[0].save(update_fields=['title']) @@ -449,8 +449,8 @@ def test_null_empty_value_lookup_fail(bad_operator, value): @pytest.mark.django_db @pytest.mark.parametrize('operator', [CO.EQ, CO.NE, CO.GT]) @pytest.mark.parametrize('filter_name', ['invalid']) -def test_ignored_filters(filter_name, operator): - books = create_books() +def test_ignored_filters(generate_books, filter_name, operator): + books = generate_books() assert filter_field(filter_name, operator, 'value') == books diff --git a/tests/test_filter_cls/test_initialization.py b/tests/test_filter_cls/test_initialization.py index 9b4c08c..62ae76c 100644 --- a/tests/test_filter_cls/test_initialization.py +++ b/tests/test_filter_cls/test_initialization.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_filter_cls/test_select.py b/tests/test_filter_cls/test_select.py index b6bc7bf..c23238f 100644 --- a/tests/test_filter_cls/test_select.py +++ b/tests/test_filter_cls/test_select.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_filter_cls/utils.py b/tests/test_filter_cls/utils.py index 24da21c..339f72a 100644 --- a/tests/test_filter_cls/utils.py +++ b/tests/test_filter_cls/utils.py @@ -1,26 +1,8 @@ # -# Copyright © 2020 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # - -from datetime import timedelta - -from django.utils import timezone - -from tests.dj_rf.models import Author, Book, Publisher +from tests.dj_rf.models import Book from tests.dj_rf.view import apply_annotations book_qs = apply_annotations(Book.objects.order_by('id')) - - -def create_books(count=2): - for i in range(count): - author = Author.objects.create( - name=f'author{i}', - email=f'author{i}@example.com', - is_male=True, - publisher=Publisher.objects.create(), - ) - Book.objects.create(author=author, published_at=timezone.now() - timedelta(days=i)) - books = list(book_qs) - return books diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 54d5230..bd332e4 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1,5 +1,5 @@ # -# Copyright © 2022 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # from copy import copy diff --git a/tests/test_qs.py b/tests/test_qs.py index 91a63e7..9692ac7 100644 --- a/tests/test_qs.py +++ b/tests/test_qs.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tests/test_utils.py b/tests/test_utils.py index 43571dd..4167150 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,5 @@ # -# Copyright © 2021 Ingram Micro Inc. All rights reserved. +# Copyright © 2023 Ingram Micro Inc. All rights reserved. # import pytest diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..58dc8dd --- /dev/null +++ b/tox.ini @@ -0,0 +1,6 @@ +[flake8] +exclude = .idea,.git,venv*/,.eggs/,*.egg-info,_generated_filters*.py +max-line-length = 100 +show-source = True +max-cognitive-complexity = 20 +ignore = FI1,W503,W605