From e4efb8aab6f4689c2a8462df7ad3de9068f38dd7 Mon Sep 17 00:00:00 2001 From: Sebastian Tramp Date: Thu, 12 Sep 2024 15:22:26 +0200 Subject: [PATCH 01/12] integrate copier project template --- .copier-answers.env | 3 + .copier-answers.yml | 11 + .github/workflows/check.yml | 35 +- .github/workflows/publish.yml | 20 +- .gitignore | 90 +---- .gitlab-ci.yml | 89 +++-- .pre-commit-config.yaml | 53 +-- CONTRIBUTING.md | 7 + README-public.md | 16 +- README.md | 22 +- Taskfile.yaml | 254 +++++++------- cmem_plugin_base/__init__.py | 4 +- poetry.lock | 629 +++++----------------------------- pyproject.toml | 94 ++--- tests/__init__.py | 1 + 15 files changed, 435 insertions(+), 893 deletions(-) create mode 100644 .copier-answers.env create mode 100644 .copier-answers.yml create mode 100644 CONTRIBUTING.md diff --git a/.copier-answers.env b/.copier-answers.env new file mode 100644 index 0000000..0db2573 --- /dev/null +++ b/.copier-answers.env @@ -0,0 +1,3 @@ +# Changes here will be overwritten by Copier +package_dir=cmem_plugin_base + diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..06e87c3 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,11 @@ +# Changes here will be overwritten by Copier +_commit: v7.0.0 +_src_path: gh:eccenca/cmem-plugin-template +author_mail: cmempy-developer@eccenca.com +author_name: eccenca GmbH +github_page: https://github.com/eccenca/cmem-plugin-base +project_description: Base classes for developing eccenca Corporate Memory plugins. +project_slug: cmem-plugin-base +project_type: generic +pypi: true + diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0fdb1f0..96879bf 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,19 +14,22 @@ on: jobs: check: + runs-on: ubuntu-latest + concurrency: testing_environment + steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2 - name: Set up python id: setup-python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: '3.11' - name: Install and configure poetry uses: snok/install-poetry@v1 @@ -35,32 +38,17 @@ jobs: virtualenvs-in-project: true installer-parallel: true - - name: Install poetry-dynamic-versioning plugin + - name: Install dynamic versioning plugin run: | poetry self add "poetry-dynamic-versioning[plugin]" - - name: Load cached venv if cache exists - id: cached-poetry-dependencies - uses: actions/cache@v3 - with: - path: .venv - key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - - - name: bandit - run: | - task check:bandit - - - name: flake8 - run: | - task check:flake8 - - name: mypy run: | task check:mypy - - name: pylint + - name: ruff run: | - task check:pylint + task check:ruff - name: pytest env: @@ -74,7 +62,7 @@ jobs: task check:safety - name: Publish Test Report in Action - uses: mikepenz/action-junit-report@v3 + uses: mikepenz/action-junit-report@v4 if: always() # always run even if the previous step fails with: report_paths: dist/junit-*.xml @@ -85,3 +73,4 @@ jobs: with: junit-path: dist/junit-pytest.xml coverage-path: dist/coverage.xml + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2a1530e..24a4756 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,19 +12,21 @@ permissions: jobs: publish: + runs-on: ubuntu-latest + steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Task - uses: arduino/setup-task@v1 + uses: arduino/setup-task@v2 - name: Set up python id: setup-python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: '3.11' - name: Install and configure poetry uses: snok/install-poetry@v1 @@ -33,20 +35,14 @@ jobs: virtualenvs-in-project: true installer-parallel: true - - name: Install poetry-dynamic-versioning plugin + - name: Install dynamic versioning plugin run: | poetry self add "poetry-dynamic-versioning[plugin]" - - name: Load cached venv if cache exists - id: cached-poetry-dependencies - uses: actions/cache@v3 - with: - path: .venv - key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - - name: Publish Package env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} run: | poetry config pypi-token.pypi "$PYPI_TOKEN" poetry publish --build + diff --git a/.gitignore b/.gitignore index 4057af7..2cb7280 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -### https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore +# https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -138,89 +138,7 @@ dmypy.json # Cython debug symbols cython_debug/ - -### JetBrain ignores -# https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - - -### project build plan specific ignores +# project build plan specific ignores version.py co *.xml @@ -229,7 +147,3 @@ co artifacts .DS_Store .task - -.scannerwork - - diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ca2c8a..13f67e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,46 +1,91 @@ --- default: - image: docker-registry.eccenca.com/eccenca-python:v3.7.0 + image: docker-registry.eccenca.com/eccenca-python:v3.11.4 + # all jobs can be interrupted in case a new commit is pushed + interruptible: true + before_script: + # make sure poetry creates virtual environment as .venv + - poetry config virtualenvs.in-project true + cache: + # cache the virtual environment based on the poetry lock file + key: + files: + - poetry.lock + paths: + - .venv stages: - test - - sonarqube - build - - deploy + - publish + +ruff: + stage: test + script: + - task check:ruff + artifacts: + when: always + reports: + junit: + - dist/junit-ruff.xml -check: +mypy: stage: test script: - - task check + - task check:mypy artifacts: when: always reports: - cobertura: - - ./dist/coverage.xml junit: - - ./dist/junit-bandit.xml - - ./dist/junit-flake8.xml - - ./dist/junit-mypy.xml - - ./dist/junit-pylint.xml - - ./dist/junit-pytest.xml + - dist/junit-mypy.xml + +pytest: + stage: test + coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' + script: + - task check:pytest + artifacts: + when: always + reports: + coverage_report: + coverage_format: cobertura + path: dist/coverage.xml + junit: + - dist/junit-pytest.xml paths: - - ./dist/badge-coverage.svg - - ./dist/badge-tests.svg - - ./dist/coverage - - ./dist/coverage.xml - - ./dist/junit-*.xml + - dist/badge-coverage.svg + - dist/badge-tests.svg + - dist/coverage + - dist/coverage.xml + +safety: + stage: test + script: + - task check:safety build: stage: build + needs: + - mypy + - pytest + - safety script: - task build artifacts: when: always paths: - - ./dist/*.tar.gz - - ./dist/*.whl + - dist/*.tar.gz + - dist/*.whl -deploy: - stage: deploy +pypi: + # publishing only available on a tag + stage: publish + needs: + - ruff + - build + allow_failure: true + when: manual script: - - ls + - poetry config pypi-token.pypi $PYPI_TOKEN + - poetry publish + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef007a3..d117733 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,24 +2,37 @@ repos: - repo: local hooks: - - id: pylint - name: pylint - entry: poetry run pylint - exclude: '^tests/.*$' - language: system - types: [python] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 - hooks: - - id: check-xml - - id: check-yaml - - id: check-toml - - id: trailing-whitespace - - id: check-merge-conflict - - id: check-case-conflict - - repo: https://github.com/psf/black - rev: 19.3b0 - hooks: - - id: black - language_version: python3 + + - id: ruff + name: check:ruff + entry: task check:ruff + language: python + types_or: [python, pyi] + pass_filenames: false + + - id: poetry-check + name: poetry-check + description: run poetry check to validate config + entry: poetry check + language: python + pass_filenames: false + files: ^(.*/)?pyproject\.toml$ + + - id: poetry-lock + name: poetry-lock + description: run poetry lock to update lock file + entry: poetry lock + language: python + pass_filenames: false + files: ^(.*/)?(poetry\.lock|pyproject\.toml)$ + + - id: poetry-install + name: poetry-install + description: > + run poetry install to install dependencies from the lock file + entry: poetry install + language: python + pass_filenames: false + stages: [post-checkout, post-merge] + always_run: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..181a0fb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contribution Guide + +These documents reflect contribution rules: + +- [Keep A Changelog's Format](http://keepachangelog.com/) +- [Semantic Versioning](https://semver.org/) + diff --git a/README-public.md b/README-public.md index 702e710..8645ebc 100644 --- a/README-public.md +++ b/README-public.md @@ -1,6 +1,18 @@ # cmem-plugin-base -Python base classes for developing eccenca Coporate Memory plugins. +Base classes for developing eccenca Corporate Memory plugins. -In order to kick-start developing eccenca Corporate Memory Plugins, please check out this project template: https://github.com/eccenca/cmem-plugin-template + +[![workflow](https://github.com/eccenca/cmem-plugin-base/actions/workflows/check.yml/badge.svg)](https://github.com/eccenca/cmem-plugin-base/actions) [![pypi version](https://img.shields.io/pypi/v/cmem-plugin-base)](https://pypi.org/project/cmem-plugin-base) [![license](https://img.shields.io/pypi/l/cmem-plugin-base)](https://pypi.org/project/cmem-plugin-base) +[![poetry][poetry-shield]][poetry-link] [![ruff][ruff-shield]][ruff-link] [![mypy][mypy-shield]][mypy-link] [![copier][copier-shield]][copier] + + +[poetry-link]: https://python-poetry.org/ +[poetry-shield]: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json +[ruff-link]: https://docs.astral.sh/ruff/ +[ruff-shield]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&label=Code%20Style +[mypy-link]: https://mypy-lang.org/ +[mypy-shield]: https://www.mypy-lang.org/static/mypy_badge.svg +[copier]: https://copier.readthedocs.io/ +[copier-shield]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-purple.json diff --git a/README.md b/README.md index 504b21b..ad9ec0d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ -# cmem_plugin_base +# cmem-plugin-base Base classes for developing eccenca Corporate Memory plugins. -## Usage +[![workflow](https://github.com/eccenca/cmem-plugin-base/actions/workflows/check.yml/badge.svg)](https://github.com/eccenca/cmem-plugin-base/actions) [![pypi version](https://img.shields.io/pypi/v/cmem-plugin-base)](https://pypi.org/project/cmem-plugin-base) [![license](https://img.shields.io/pypi/l/cmem-plugin-base)](https://pypi.org/project/cmem-plugin-base) +[![poetry][poetry-shield]][poetry-link] [![ruff][ruff-shield]][ruff-link] [![mypy][mypy-shield]][mypy-link] [![copier][copier-shield]][copier] -- run [task](https://taskfile.dev/) to see all major development tasks -- use [pre-commit](https://pre-commit.com/) to avoid errors before commit -- used tools: bandit, flake8, mypy, pylint, pytest, safety +## Development +- Run [task](https://taskfile.dev/) to see all major development tasks. +- Use [pre-commit](https://pre-commit.com/) to avoid errors before commit. +- This repository was created with [this copier template](https://github.com/eccenca/cmem-plugin-template). + + +[poetry-link]: https://python-poetry.org/ +[poetry-shield]: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json +[ruff-link]: https://docs.astral.sh/ruff/ +[ruff-shield]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&label=Code%20Style +[mypy-link]: https://mypy-lang.org/ +[mypy-shield]: https://www.mypy-lang.org/static/mypy_badge.svg +[copier]: https://copier.readthedocs.io/ +[copier-shield]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-purple.json diff --git a/Taskfile.yaml b/Taskfile.yaml index 69b7904..3f3835b 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,185 +1,185 @@ # https://taskfile.dev +# +# This is a generated file. We dot not suggest to edit it. +# Instead, create a file `TaskfileCustom.yml` and add your additions there. --- version: '3' +dotenv: ['.copier-answers.env', '.env'] + +.preparation: &preparation + deps: + - poetry:install + - check:prepare + vars: - PACKAGE: cmem_plugin_base + PACKAGE: $package_dir DIST_DIR: dist - PWD: - sh: pwd - DOCKER_IMAGE: docker-registry.eccenca.com/eccenca-python:v3.11.4 - DOCKER_RUN_PLAIN: run -i -t -e SHELL=/bin/bash --workdir /data --rm -v {{.PWD}}:/data {{.DOCKER_IMAGE}} - VERSION: - sh: git describe --always --dirty - VERSION_FILE: "{{.PACKAGE}}/version.py" + +includes: + custom: + taskfile: ./TaskfileCustom.yaml + optional: true + plugin: + taskfile: .tasks-plugin.yml + optional: true tasks: default: summary: | - Just a list of documented tasks. + Just a list of documented tasks silent: true cmds: - task --list - deploy: - desc: Install plugin package in Corporate Memory - deps: - - clean - - build - cmds: - - cmemc admin workspace python install dist/*.tar.gz - - cmemc admin workspace python list-plugins - - clean: - desc: Removes dist dir, *.pyc and version files. - cmds: - - rm -rf {{.DIST_DIR}} - - find . -name "*.pyc" -print0 | xargs -0 rm || echo "" - - rm -f {{.VERSION_FILE}} - - dockerized: - desc: Run tasks dockerized with 'task dockerized -- TASK' - cmds: - - docker {{.DOCKER_RUN_PLAIN}} bash -c "task {{.CLI_ARGS}}" - + # {{{ preparation tasks check:prepare: + internal: true summary: | prepare check targets by creating appropriate directory run: once cmds: - mkdir -p {{.DIST_DIR}}/coverage - patch-version: - cmds: - - rm -f {{.VERSION_FILE}} - - echo '"""Version information."""' > {{.VERSION_FILE}} - - echo "VERSION = '{{.VERSION}}'" >> {{.VERSION_FILE}} + poetry:check: + internal: true + platforms: [darwin, linux] + summary: | + Check poetry versioning plugin. Currently not under Windows + run: once + preconditions: + - sh: '[ -d .git ]' + msg: > + Your newly created project directory needs to be initialized + as a git repository. + - sh: '[[ {{.PDV_VERSION}} > {{.PDV_VERSION_MIN}} ]]' + msg: > + This project needs the poetry-dynamic-versioning + plugin > v{{.PDV_VERSION_MIN}}. + + You can install it with the following command: + poetry self add "poetry-dynamic-versioning[plugin]" + vars: + PDV_VERSION_MIN: 0.20 + PDV_VERSION: + sh: > + poetry self show --addons poetry-dynamic-versioning --tree + | head -1 | cut -d " " -f 2 | cut -d "." -f 1-2 - build: - desc: Build tarball and a wheel package. + poetry:install: + internal: true + desc: Install dependencies managed by Poetry + run: once deps: - - poetry:install - - check:prepare - - patch-version + - poetry:check cmds: - - poetry build + - poetry install + + format:fix: + desc: Format Python files and fix obvious issues + <<: *preparation + cmds: + - poetry run ruff format tests {{.PACKAGE}} + - poetry run ruff check tests {{.PACKAGE}} --fix-only + + format:fix-unsafe: + desc: Format Python files and fix 'unsafe' issues + <<: *preparation + cmds: + - poetry run ruff format tests {{.PACKAGE}} + - poetry run ruff check tests {{.PACKAGE}} --fix-only --unsafe-fixes + + clean: + desc: Removes dist, *.pyc and some caches + cmds: + - rm -rf {{.DIST_DIR}} .mypy_cache .pytest_cache + - find . -name "*.pyc" -print0 | xargs -0 rm || echo "" + + # }}} + # {{{ check tasks check: - desc: Run whole test suite. - deps: - - check:bandit - - check:flake8 - - check:mypy - - check:pylint - - check:pytest - - check:safety + desc: Run whole test suite incl. unit and integration tests + cmds: + - task: check:linters + - task: check:pytest check:linters: - desc: Run linters. + desc: Run all linter and static code analysis tests cmds: - - task: check:bandit - - task: check:flake8 + - task: check:ruff - task: check:mypy - - task: check:pylint - task: check:safety check:pytest: - desc: Run pytest suite. - deps: - - poetry:install - - check:prepare - cmds: - - poetry run pytest --memray --junitxml={{.JUNIT_FILE}} --cov-report term --cov-report xml:{{.COVERAGE_FILE}} --cov-report html:{{.COVERAGE_DIR}} --cov={{.PACKAGE}} - - poetry run genbadge coverage -l -i {{.COVERAGE_FILE}} -o {{.BADGE_COVERAGE}} - - poetry run genbadge tests -l -i {{.JUNIT_FILE}} -o {{.BADGE_TESTS}} + desc: Run unit and integration tests + platforms: [darwin, linux, windows] + <<: *preparation + cmds: + # --memray is not used on windows + - platforms: [windows] + cmd: > + poetry run pytest --junitxml={{.JUNIT_FILE}} + --cov-report term --cov-report xml:{{.COVERAGE_FILE}} + --cov-report html:{{.COVERAGE_DIR}} --cov={{.PACKAGE}} + --html={{.HTML_FILE}} --self-contained-html + - platforms: [darwin, linux] + cmd: > + poetry run pytest --memray --junitxml={{.JUNIT_FILE}} + --cov-report term --cov-report xml:{{.COVERAGE_FILE}} + --cov-report html:{{.COVERAGE_DIR}} --cov={{.PACKAGE}} + --html={{.HTML_FILE}} --self-contained-html + - cmd: > + poetry run genbadge coverage -l + -i {{.COVERAGE_FILE}} -o {{.BADGE_COVERAGE}} + - cmd: > + poetry run genbadge tests -l + -i {{.JUNIT_FILE}} -o {{.BADGE_TESTS}} vars: - JUNIT_FILE: ./{{.DIST_DIR}}/junit-pytest.xml - COVERAGE_FILE: ./{{.DIST_DIR}}/coverage.xml - COVERAGE_DIR: ./{{.DIST_DIR}}/coverage BADGE_COVERAGE: ./{{.DIST_DIR}}/badge-coverage.svg BADGE_TESTS: ./{{.DIST_DIR}}/badge-tests.svg - - - check:pylint: - desc: Check source code with pylint. - deps: - - poetry:install - - check:prepare - cmds: - - poetry run pylint --exit-zero tests {{.PACKAGE}} - - poetry run pylint tests {{.PACKAGE}} {{.XML_PARAMS}} - vars: - FORMAT: --output-format=pylint_junit.JUnitReporter - JUNIT_FILE: ./{{.DIST_DIR}}/junit-pylint.xml - XML_PARAMS: --output={{.JUNIT_FILE}} {{.FORMAT}} + COVERAGE_DIR: ./{{.DIST_DIR}}/coverage + COVERAGE_FILE: ./{{.DIST_DIR}}/coverage.xml + HTML_FILE: ./{{.DIST_DIR}}/pytest.html + JUNIT_FILE: ./{{.DIST_DIR}}/junit-pytest.xml check:mypy: - desc: Check source code with mypy. - deps: - - poetry:install - - check:prepare + desc: Complain about typing errors + <<: *preparation cmds: - poetry run mypy -p tests -p {{.PACKAGE}} --junit-xml {{.JUNIT_FILE}} vars: JUNIT_FILE: ./{{.DIST_DIR}}/junit-mypy.xml check:safety: - desc: Check source code with safety. - deps: - - poetry:install - - check:prepare + desc: Complain about vulnerabilities in dependencies + <<: *preparation cmds: # ignore 51358 safety - dev dependency only # ignore 67599 pip - dev dependency only # ignore 70612 jinja2 - dev dependency only - poetry run safety check -i 51358 -i 67599 -i 70612 - check:bandit: - desc: Check source code with bandit. - deps: - - poetry:install - - check:prepare + check:ruff: + desc: Complain about everything else + <<: *preparation cmds: - - poetry run bandit --exit-zero -r {{.PACKAGE}} - - poetry run bandit --format xml -r {{.PACKAGE}} -o {{.JUNIT_FILE}} + - poetry run ruff check --exit-zero tests {{.PACKAGE}} {{.XML_PARAMS}} + - poetry run ruff check --output-format=concise tests {{.PACKAGE}} + - poetry run ruff format --check tests {{.PACKAGE}} vars: - JUNIT_FILE: ./{{.DIST_DIR}}/junit-bandit.xml + JUNIT_FILE: ./{{.DIST_DIR}}/junit-ruff.xml + XML_PARAMS: --output-format junit --output-file {{.JUNIT_FILE}} - check:flake8: - desc: Check source code with flake8. - deps: - - poetry:install - - check:prepare - cmds: - - poetry run flake8 --exit-zero tests {{.PACKAGE}} {{.XML_PARAMS}} - - poetry run flake8 --show-source tests {{.PACKAGE}} - vars: - JUNIT_FILE: ./{{.DIST_DIR}}/junit-flake8.xml - XML_PARAMS: --format junit-xml --output-file {{.JUNIT_FILE}} + # }}} - python:format: - desc: Format Python files. + build: + desc: Build a tarball and a wheel package + <<: *preparation deps: - - task: poetry:install - cmds: - - poetry run black . - - poetry:install: - desc: Install dependencies managed by Poetry. - run: once - cmds: - - poetry install - - poetry:update: - desc: Update dependencies managed by Poetry to their newest versions. - run: once - cmds: - - poetry update - - poetry:shell: - desc: Open a poetry shell. - interactive: true + - clean + - poetry:check cmds: - - poetry shell + - poetry build diff --git a/cmem_plugin_base/__init__.py b/cmem_plugin_base/__init__.py index 294117d..7cc7b79 100644 --- a/cmem_plugin_base/__init__.py +++ b/cmem_plugin_base/__init__.py @@ -1,2 +1,2 @@ -"""eccenca Corporate Memory Plugin base package""" -__version__ = "0.1.0" +"""cmem-plugin-base""" + diff --git a/poetry.lock b/poetry.lock index a872fc7..098d6a9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,88 +1,5 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} - -[[package]] -name = "bandit" -version = "1.7.9" -description = "Security oriented static analyser for python code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, - {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -PyYAML = ">=5.3.1" -rich = "*" -stevedore = ">=1.20.0" - -[package.extras] -baseline = ["GitPython (>=3.1.30)"] -sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0)"] -yaml = ["PyYAML"] - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2024.8.30" @@ -331,21 +248,6 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - [[package]] name = "dparse" version = "0.6.3" @@ -364,37 +266,6 @@ packaging = "*" conda = ["pyyaml"] pipenv = ["pipenv (<=2022.12.19)"] -[[package]] -name = "flake8" -version = "7.1.1" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, - {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.12.0,<2.13.0" -pyflakes = ">=3.2.0,<3.3.0" - -[[package]] -name = "flake8-formatter-junit-xml" -version = "0.0.6" -description = "JUnit XML Formatter for flake8" -optional = false -python-versions = "*" -files = [ - {file = "flake8_formatter_junit_xml-0.0.6-py2.py3-none-any.whl", hash = "sha256:6358a44ecafdf0f9c8ee5314859b8d6f553dc2e55e946a24c538185e1eba7ce6"}, - {file = "flake8_formatter_junit_xml-0.0.6.tar.gz", hash = "sha256:1ddd9356bb30ba736c3f14c769c837cfacf4f79c3d383ab963ef9d38eea05a9c"}, -] - -[package.dependencies] -flake8 = ">3.0.0" -junit-xml = ">=1.8" - [[package]] name = "genbadge" version = "1.1.1" @@ -408,6 +279,7 @@ files = [ [package.dependencies] click = ">7.0" +defusedxml = {version = "*", optional = true, markers = "extra == \"coverage\""} pillow = "*" requests = "*" setuptools = "*" @@ -454,20 +326,6 @@ files = [ [package.dependencies] six = "*" -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "jinja2" version = "3.1.4" @@ -485,80 +343,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "junit-xml" -version = "1.9" -description = "Creates JUnit XML test result documents that can be read by tools such as Jenkins" -optional = false -python-versions = "*" -files = [ - {file = "junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f"}, - {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "junit-xml-2" -version = "1.9" -description = "Fork of https://github.com/kyrus/python-junit-xml that has tarball published to pypi" -optional = false -python-versions = "*" -files = [ - {file = "junit-xml-2-1.9.tar.gz", hash = "sha256:3b8d9635c5215f754c7807104f6493e3ea3bc9481e2d33db294560da3a1b00f7"}, - {file = "junit_xml_2-1.9-py2.py3-none-any.whl", hash = "sha256:05093d75fe11120ccd51293884b5ee334ff3bc4b5106647c2bcc928486600588"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, -] - [[package]] name = "linkify-it-py" version = "2.0.3" @@ -674,17 +458,6 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -847,28 +620,6 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pbr" -version = "6.1.0" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, - {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, -] - [[package]] name = "pillow" version = "10.4.0" @@ -1008,28 +759,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pycodestyle" -version = "2.12.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, - {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, -] - -[[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, -] - [[package]] name = "pygments" version = "2.18.0" @@ -1044,44 +773,6 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pylint-junit" -version = "0.3.4" -description = "pylint reporter for junit format." -optional = false -python-versions = "*" -files = [ - {file = "pylint_junit-0.3.4-py2.py3-none-any.whl", hash = "sha256:006f8e793a1b64f1e0c8a29c6ee531b8af971deddafbc605c3076270f36c88f0"}, -] - -[package.dependencies] -junit-xml-2 = "*" -pylint = "*" - [[package]] name = "pyparsing" version = "3.1.4" @@ -1098,33 +789,33 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.5,<2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] @@ -1132,7 +823,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-dotenv" @@ -1149,6 +840,26 @@ files = [ pytest = ">=5.0.0" python-dotenv = ">=0.9.1" +[[package]] +name = "pytest-html" +version = "4.1.1" +description = "pytest plugin for generating HTML reports" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71"}, + {file = "pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07"}, +] + +[package.dependencies] +jinja2 = ">=3.0.0" +pytest = ">=7.0.0" +pytest-metadata = ">=2.0.0" + +[package.extras] +docs = ["pip-tools (>=6.13.0)"] +test = ["assertpy (>=1.1)", "beautifulsoup4 (>=4.11.1)", "black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest-mock (>=3.7.0)", "pytest-rerunfailures (>=11.1.2)", "pytest-xdist (>=2.4.0)", "selenium (>=4.3.0)", "tox (>=3.24.5)"] + [[package]] name = "pytest-memray" version = "1.7.0" @@ -1169,6 +880,23 @@ docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sp lint = ["black (==22.12)", "isort (==5.11.4)", "mypy (==0.991)", "ruff (==0.0.272)"] test = ["anyio (>=4.4.0)", "covdefaults (>=2.2.2)", "coverage (>=7.0.5)", "flaky (>=3.7)", "pytest (>=7.2)", "pytest-xdist (>=3.1)"] +[[package]] +name = "pytest-metadata" +version = "3.1.1" +description = "pytest plugin for test session metadata" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b"}, + {file = "pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "tox (>=3.24.5)"] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -1197,68 +925,6 @@ files = [ [package.extras] pydantic = ["pydantic (>=2.0)"] -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - [[package]] name = "rdflib" version = "6.3.2" @@ -1333,6 +999,33 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.6.4" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"}, + {file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"}, + {file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"}, + {file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"}, + {file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"}, + {file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"}, + {file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"}, +] + [[package]] name = "safety" version = "1.10.3" @@ -1382,20 +1075,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "stevedore" -version = "5.3.0" -description = "Manage dynamic plugins for Python applications" -optional = false -python-versions = ">=3.8" -files = [ - {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, - {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, -] - -[package.dependencies] -pbr = ">=2.0.0" - [[package]] name = "textual" version = "0.79.1" @@ -1416,67 +1095,6 @@ typing-extensions = ">=4.4.0,<5.0.0" [package.extras] syntax = ["tree-sitter (>=0.20.1,<0.21.0)", "tree-sitter-languages (==1.10.2)"] -[[package]] -name = "tomlkit" -version = "0.13.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, -] - -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, -] - [[package]] name = "types-requests" version = "2.32.0.20240907" @@ -1533,100 +1151,7 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "wheel" -version = "0.38.4" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, -] - -[package.extras] -test = ["pytest (>=3.0.0)"] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "6e94f38a5abe5fef53341c681fb504015486dd40b2ca708a0db30407c4d7e51f" +content-hash = "6ae9156d549d62cd8ee2038b975b0425e411b5a6ec36a55514ab2285ed0e67b5" diff --git a/pyproject.toml b/pyproject.toml index e3d266c..e87c53f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "cmem-plugin-base" version = "0.0.0" license = "Apache-2.0" description = "Base classes for developing eccenca Corporate Memory plugins." -authors = ["eccenca "] +authors = ["eccenca GmbH "] maintainers = [ "Sebastian Tramp ", "Robert Isele " @@ -18,33 +18,25 @@ keywords = [ "eccenca Corporate Memory", "plugins", "DataIntegration" ] +homepage = "https://github.com/eccenca/cmem-plugin-base" + [tool.poetry.dependencies] python = "^3.11" cmem-cmempy = ">=23.3.0" python-ulid = "^2.2.0" [tool.poetry.group.dev.dependencies] -bandit = "^1.7.5" -black = "^24" +genbadge = {extras = ["coverage"], version = "^1.1.1"} +mypy = "^1.11.1" pip = "^24" -coverage = "^7.2.7" -defusedxml = "^0.7.1" -flake8-formatter-junit-xml = "^0.0.6" -genbadge = "^1.1.0" -mypy = "^1.4.1" -# https://github.com/rasjani/pylint-junit/issues/1 -pylint = "^2" -pylint-junit = "^0.3.2" -pytest = "^7.4.0" -pytest-cov = "^4.1.0" -pytest-memray = "^1.4.1" -safety = "^1.10.3" -typed-ast = "^1.5.4" -types-requests = "^2.31.0.1" -wheel = "^0.38.4" -# added in order to have something to discover (>=0.0.0 <1.0.0) -# cmem-plugin-examples = "*" +pytest = "^8.3.2" +pytest-cov = "^5.0.0" pytest-dotenv = "^0.5.2" +pytest-html = "^4.1.1" +pytest-memray = { version = "^1.7.0", markers = "platform_system != 'Windows'" } +ruff = "^0.6.1" +safety = "^1.10.3" +types-requests = "^2.32.0.20240907" [build-system] requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"] @@ -54,26 +46,7 @@ build-backend = "poetry_dynamic_versioning.backend" enable = true vcs = "git" dirty = true - -[tool.pylint.MASTER] -load-plugins="pylint_junit" - -[tool.pylint.General] -ignore = "version.py" - -[tool.pylint.'MESSAGES CONTROL'] -extension-pkg-whitelist = "pydantic" -disable = "fixme" - -# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#pylint -[tool.pylint.messages_control] -disable = "R0903" - -[tool.pylint.format] -max-line-length = "88" -disable = "" -max-args = "8" -max-attributes = "9" +bump = true [tool.mypy] warn_return_any = true @@ -90,3 +63,44 @@ disable_error_code = "empty-body" [tool.pytest.ini_options] addopts = "" +[tool.coverage.report] +exclude_also = [ + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if TYPE_CHECKING:", + "class .*\\bProtocol\\):", + "@(abc\\.)?abstractmethod", + ] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.format] +line-ending = "lf" # Use `\n` line endings for all files + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "ANN101", # Missing type annotation for self in method + "ANN204", # Missing return type annotation for special method `__init__` + "COM812", # missing-trailing-comma + "D107", # Missing docstring in __init__ + "D203", # [*] 1 blank line required before class docstring + "D211", # No blank lines allowed before class docstring + "D213", # Multi-line docstring summary should start at the second line + "D400", # First line should end with a period + "D415", # First line should end with a period, question mark, or exclamation point + "EM", # Exception texts - https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + "FBT", # The Boolean Trap - https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt + "FIX002", # Allow to add TODO notes in the code + "G004", # Logging statement uses f-string + "ISC001", # single-line-implicit-string-concatenation + "PD", # opinionated linting for pandas code + "S101", # use of assert detected + "TRY003", # Avoid specifying long messages outside the exception class +] + diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..30ea453 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""tests""" From ed0275c4474231d5c9e0a361c7f90b31feee0ec9 Mon Sep 17 00:00:00 2001 From: Sebastian Tramp Date: Thu, 12 Sep 2024 15:23:47 +0200 Subject: [PATCH 02/12] apply safe ruff fixes --- cmem_plugin_base/__init__.py | 1 - cmem_plugin_base/dataintegration/context.py | 34 ++++--- .../dataintegration/description.py | 50 +++++------ cmem_plugin_base/dataintegration/discovery.py | 12 ++- cmem_plugin_base/dataintegration/entity.py | 61 +++++++------ .../dataintegration/parameter/choice.py | 7 +- .../dataintegration/parameter/code.py | 1 + .../dataintegration/parameter/dataset.py | 19 ++-- .../dataintegration/parameter/graph.py | 12 +-- .../dataintegration/parameter/password.py | 6 +- .../dataintegration/parameter/resource.py | 20 ++--- cmem_plugin_base/dataintegration/plugins.py | 24 ++--- cmem_plugin_base/dataintegration/ports.py | 12 ++- cmem_plugin_base/dataintegration/types.py | 11 +-- .../dataintegration/utils/__init__.py | 13 ++- .../dataintegration/utils/entity_builder.py | 89 ++++++++----------- tests/conftest.py | 12 +-- tests/parameter_types/test_choice.py | 7 +- tests/parameter_types/test_code.py | 48 +++++----- tests/parameter_types/test_dataset.py | 8 +- tests/parameter_types/test_graph.py | 6 +- tests/parameter_types/test_password.py | 7 +- tests/parameter_types/test_resource.py | 11 +-- tests/test_description.py | 13 ++- tests/test_discovery.py | 3 +- tests/test_icon.py | 11 ++- tests/test_output_only_plugin.py | 7 +- tests/test_types.py | 22 +++-- tests/test_utils_build_entities_from_data.py | 72 +++++++-------- tests/test_utils_write_to_dataset.py | 25 +++--- tests/utils.py | 6 +- 31 files changed, 315 insertions(+), 315 deletions(-) diff --git a/cmem_plugin_base/__init__.py b/cmem_plugin_base/__init__.py index 7cc7b79..1cb962c 100644 --- a/cmem_plugin_base/__init__.py +++ b/cmem_plugin_base/__init__.py @@ -1,2 +1 @@ """cmem-plugin-base""" - diff --git a/cmem_plugin_base/dataintegration/context.py b/cmem_plugin_base/dataintegration/context.py index 7de59f3..492b694 100644 --- a/cmem_plugin_base/dataintegration/context.py +++ b/cmem_plugin_base/dataintegration/context.py @@ -3,8 +3,9 @@ The classes in this file are only for documentation purposes. The actual classes will be injected by DataIntegration and will follow the signatures of the classes below. """ + from dataclasses import dataclass, field -from typing import Optional, Tuple, Literal +from typing import Literal from cmem_plugin_base.dataintegration.entity import Entities @@ -17,11 +18,13 @@ def di_version(self) -> str: def encrypt(self, value: str) -> str: """Encrypts a value using the secret key, which is configured - in 'plugin.parameters.password.crypt.key'""" + in 'plugin.parameters.password.crypt.key' + """ def decrypt(self, value: str) -> str: """Decrypts a value using the secret key, which is configured - in 'plugin.parameters.password.crypt.key'""" + in 'plugin.parameters.password.crypt.key' + """ class UserContext: @@ -50,19 +53,20 @@ def task_id(self) -> str: @dataclass() class ExecutionReport: """Workflow operators may generate execution reports. An execution report holds - basic information and various statistics about the operator execution.""" + basic information and various statistics about the operator execution. + """ entity_count: int = 0 """The number of entities that have been processed. This value may be displayed in real-time in the UI.""" - operation: Optional[str] = None + operation: str | None = None "Short label for the executed operation, e.g., read or write (optional)." operation_desc: str = "entities processed" "Short description of the operation (plural, past tense)." - summary: list[Tuple[str, str]] = field(default_factory=list) + summary: list[tuple[str, str]] = field(default_factory=list) """Generates a short summary of this report. A sequence of key-value pairs representing the summary table.""" @@ -70,11 +74,11 @@ class ExecutionReport: """If issues occurred during execution, this contains a list of user-friendly messages.""" - error: Optional[str] = None + error: str | None = None """Error message in case a fatal error occurred. If an error is set, the workflow execution will be stopped after the operator has been executed.""" - sample_entities: Optional[Entities] = None + sample_entities: Entities | None = None """Sample of entities that were output by this task.""" @@ -83,7 +87,8 @@ class ReportContext: def update(self, report: ExecutionReport) -> None: """Updates the current execution report. - May be called repeatedly during operator execution.""" + May be called repeatedly during operator execution. + """ class PluginContext: @@ -92,7 +97,7 @@ class PluginContext: system: SystemContext """General system information.""" - user: Optional[UserContext] + user: UserContext | None """The user that creates or updates the plugin. If the plugin is loaded from an existing project, this might be the configured super user. If DataIntegration is run outside of a Corporate Memory environment, no user is available. @@ -108,14 +113,15 @@ class WorkflowContext: def workflow_id(self) -> str: """Retrieve the identifier of the current workflow""" - def status(self) -> Literal['Idle', 'Waiting', 'Running', 'Canceling', 'Finished']: + def status(self) -> Literal["Idle", "Waiting", "Running", "Canceling", "Finished"]: """Retrieve the execution status of this plugin within the current workflow. One of the following: - Idle: Plugin has not been started yet. - Waiting: Plugin has been started and is waiting to be executed. - Running: Plugin is currently being executed. - Canceling: Plugin has been requested to stop. - - Finished: Plugin has finished execution.""" + - Finished: Plugin has finished execution. + """ class ExecutionContext: @@ -124,7 +130,7 @@ class ExecutionContext: system: SystemContext """General system information.""" - user: Optional[UserContext] + user: UserContext | None """The user that issued the plugin execution. If a scheduler initiated the execution, this might be the configured super user. If DataIntegration is run outside of a Corporate Memory environment, no user is available.""" @@ -132,7 +138,7 @@ class ExecutionContext: task: TaskContext """Task metadata about the executed plugin.""" - workflow: Optional[WorkflowContext] + workflow: WorkflowContext | None """Workflow metadata about the executed plugin. None, if this plugin is not executed within a workflow.""" diff --git a/cmem_plugin_base/dataintegration/description.py b/cmem_plugin_base/dataintegration/description.py index f1aeedc..199a862 100644 --- a/cmem_plugin_base/dataintegration/description.py +++ b/cmem_plugin_base/dataintegration/description.py @@ -1,4 +1,5 @@ """Classes for describing plugins""" + import inspect import sys from base64 import b64encode @@ -6,9 +7,9 @@ from inspect import _empty from mimetypes import guess_type from pkgutil import get_data -from typing import Optional, List, Type, Any +from typing import Any -from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin, TransformPlugin +from cmem_plugin_base.dataintegration.plugins import TransformPlugin, WorkflowPlugin from cmem_plugin_base.dataintegration.types import ( ParameterType, ParameterTypes, @@ -29,11 +30,7 @@ class Icon: file from the same module, you can use package=__package__. """ - def __init__( - self, - file_name: str, - package: str - ) -> None: + def __init__(self, file_name: str, package: str) -> None: self.file_name = file_name self.package = package @@ -51,13 +48,9 @@ def __init__( self.mime_type = guess_type(self.file_name)[0] if self.mime_type is None: - raise ValueError( - f"Could not guess the mime type of the file '{self.file_name}'." - ) + raise ValueError(f"Could not guess the mime type of the file '{self.file_name}'.") if not self.mime_type.startswith("image/"): - raise ValueError( - f"Guessed mime type '{self.mime_type}' does not start with 'image/'." - ) + raise ValueError(f"Guessed mime type '{self.mime_type}' does not start with 'image/'.") def __str__(self): data_base64 = b64encode(self.data).decode() @@ -85,8 +78,8 @@ def __init__( name: str, label: str = "", description: str = "", - param_type: Optional[ParameterType] = None, - default_value: Optional[Any] = None, + param_type: ParameterType | None = None, + default_value: Any | None = None, advanced: bool = False, visible: bool = True, ) -> None: @@ -117,12 +110,12 @@ def __init__( # pylint: disable=too-many-arguments self, plugin_class, label: str, - plugin_id: Optional[str] = None, + plugin_id: str | None = None, description: str = "", documentation: str = "", - categories: Optional[List[str]] = None, - parameters: Optional[List[PluginParameter]] = None, - icon: Optional[Icon] = None + categories: list[str] | None = None, + parameters: list[PluginParameter] | None = None, + icon: Icon | None = None, ) -> None: # Set the type of the plugin. Same as the class name of the plugin # base class, e.g., 'WorkflowPlugin'. @@ -190,7 +183,8 @@ class PluginDiscoveryResult: class Categories: """A list of common plugin categories. At the moment, in the UI, - categories are only utilized for rule operators, such as transform plugins.""" + categories are only utilized for rule operators, such as transform plugins. + """ # Plugins in the 'Recommended' category will be shown preferably RECOMMENDED: str = "Recommended" @@ -238,12 +232,12 @@ class Plugin: def __init__( self, label: str, - plugin_id: Optional[str] = None, + plugin_id: str | None = None, description: str = "", documentation: str = "", - categories: Optional[List[str]] = None, - parameters: Optional[List[PluginParameter]] = None, - icon: Optional[Icon] = None + categories: list[str] | None = None, + parameters: list[PluginParameter] | None = None, + icon: Icon | None = None, ): self.label = label self.description = description @@ -268,15 +262,15 @@ def __call__(self, func): documentation=self.documentation, categories=self.categories, parameters=self.retrieve_parameters(func), - icon=self.icon + icon=self.icon, ) Plugin.plugins.append(plugin_desc) return func - def retrieve_parameters(self, plugin_class: Type) -> List[PluginParameter]: + def retrieve_parameters(self, plugin_class: type) -> list[PluginParameter]: """Retrieves parameters from a plugin class and matches them with the user - parameter definitions.""" - + parameter definitions. + """ # Only return parameters for user-defined init methods. if not hasattr(plugin_class.__init__, "__code__"): return [] diff --git a/cmem_plugin_base/dataintegration/discovery.py b/cmem_plugin_base/dataintegration/discovery.py index 312c326..dac30a0 100644 --- a/cmem_plugin_base/dataintegration/discovery.py +++ b/cmem_plugin_base/dataintegration/discovery.py @@ -1,4 +1,5 @@ """Package and plugin discovery module.""" + import importlib import importlib.util import json @@ -9,10 +10,10 @@ from types import ModuleType from cmem_plugin_base.dataintegration.description import ( - PluginDescription, Plugin, - PluginDiscoveryResult, + PluginDescription, PluginDiscoveryError, + PluginDiscoveryResult, ) @@ -36,18 +37,21 @@ def delete_modules(module_name: str = "cmem") -> None: """ if module_name in sys.modules: module = sys.modules[module_name] - if hasattr(module, '__path__'): + if hasattr(module, "__path__"): for _loader, name, _ in pkgutil.walk_packages(module.__path__): delete_modules(module.__name__ + "." + name) del sys.modules[module.__name__] -def import_modules(package_name: str = "cmem",) -> list[PluginDescription]: +def import_modules( + package_name: str = "cmem", +) -> list[PluginDescription]: """Finds and imports all plugins within a base package. :param package_name: The base package. Will recurse into all submodules of this package. """ + def import_submodules(module: ModuleType) -> list[ModuleType]: modules = [] for _loader, name, is_pkg in pkgutil.walk_packages(module.__path__): diff --git a/cmem_plugin_base/dataintegration/entity.py b/cmem_plugin_base/dataintegration/entity.py index 256d96a..d7ff22d 100644 --- a/cmem_plugin_base/dataintegration/entity.py +++ b/cmem_plugin_base/dataintegration/entity.py @@ -1,5 +1,6 @@ """Instance of any given concept.""" -from typing import Sequence, Iterator, Optional + +from collections.abc import Iterator, Sequence class EntityPath: @@ -12,25 +13,26 @@ class EntityPath: nested elements. """ - def __init__(self, path: str, - is_relation: bool = False, - is_single_value: bool = False) -> None: + def __init__(self, path: str, is_relation: bool = False, is_single_value: bool = False) -> None: self.path = path self.is_relation = is_relation self.is_single_value = is_single_value def __repr__(self): obj = { - 'path': self.path, 'is_relation': self.is_relation, - 'is_single_value': self.is_single_value + "path": self.path, + "is_relation": self.is_relation, + "is_single_value": self.is_single_value, } return f"EntityPath({obj})" def __eq__(self, other): - return (isinstance(other, EntityPath) - and self.path == other.path - and self.is_relation == other.is_relation - and self.is_single_value == other.is_single_value) + return ( + isinstance(other, EntityPath) + and self.path == other.path + and self.is_relation == other.is_relation + and self.is_single_value == other.is_single_value + ) class EntitySchema: @@ -43,29 +45,30 @@ class EntitySchema: :param sub_schemata: Nested entity schemata """ - def __init__(self, - type_uri: str, - paths: Sequence[EntityPath], - path_to_root: EntityPath = EntityPath(""), - sub_schemata: Optional[Sequence['EntitySchema']] = None) -> None: + def __init__( + self, + type_uri: str, + paths: Sequence[EntityPath], + path_to_root: EntityPath = EntityPath(""), + sub_schemata: Sequence["EntitySchema"] | None = None, + ) -> None: self.type_uri = type_uri self.paths = paths self.path_to_root = path_to_root self.sub_schemata = sub_schemata def __repr__(self): - obj = { - "type_uri": self.type_uri, "paths": self.paths, - "path_to_root": self.path_to_root - } + obj = {"type_uri": self.type_uri, "paths": self.paths, "path_to_root": self.path_to_root} return f"EntitySchema({obj})" def __eq__(self, other): - return (isinstance(other, EntitySchema) - and self.type_uri == other.type_uri - and self.paths == other.paths - and self.path_to_root == other.path_to_root - and self.sub_schemata == other.sub_schemata) + return ( + isinstance(other, EntitySchema) + and self.type_uri == other.type_uri + and self.paths == other.paths + and self.path_to_root == other.path_to_root + and self.sub_schemata == other.sub_schemata + ) class Entity: @@ -92,10 +95,12 @@ class Entities: :param sub_entities Additional entity collections. """ - def __init__(self, - entities: Iterator[Entity], - schema: EntitySchema, - sub_entities: Optional[Sequence['Entities']] = None) -> None: + def __init__( + self, + entities: Iterator[Entity], + schema: EntitySchema, + sub_entities: Sequence["Entities"] | None = None, + ) -> None: self.entities = entities self.schema = schema self.sub_entities = sub_entities diff --git a/cmem_plugin_base/dataintegration/parameter/choice.py b/cmem_plugin_base/dataintegration/parameter/choice.py index 9918f9f..393d2d9 100644 --- a/cmem_plugin_base/dataintegration/parameter/choice.py +++ b/cmem_plugin_base/dataintegration/parameter/choice.py @@ -1,9 +1,10 @@ """DI Choice String Parameter Type.""" + import collections -from typing import Optional, Any +from typing import Any from cmem_plugin_base.dataintegration.context import PluginContext -from cmem_plugin_base.dataintegration.types import StringParameterType, Autocompletion +from cmem_plugin_base.dataintegration.types import Autocompletion, StringParameterType class ChoiceParameterType(StringParameterType): @@ -20,7 +21,7 @@ def __init__(self, choice_list: collections.OrderedDict[str, str]): def label( self, value: str, depend_on_parameter_values: list[Any], context: PluginContext - ) -> Optional[str]: + ) -> str | None: """Returns the label for the given choice value.""" return self.choice_list[value] diff --git a/cmem_plugin_base/dataintegration/parameter/code.py b/cmem_plugin_base/dataintegration/parameter/code.py index 08510ec..a576de0 100644 --- a/cmem_plugin_base/dataintegration/parameter/code.py +++ b/cmem_plugin_base/dataintegration/parameter/code.py @@ -1,4 +1,5 @@ """DI Code Parameter Type.""" + import typing from typing import TypeVar, Generic diff --git a/cmem_plugin_base/dataintegration/parameter/dataset.py b/cmem_plugin_base/dataintegration/parameter/dataset.py index b08be26..bc821b5 100644 --- a/cmem_plugin_base/dataintegration/parameter/dataset.py +++ b/cmem_plugin_base/dataintegration/parameter/dataset.py @@ -1,11 +1,12 @@ """DI Dataset Parameter Type.""" -from typing import Optional, Any + +from typing import Any from cmem.cmempy.workspace.search import list_items from cmem.cmempy.workspace.tasks import get_task from cmem_plugin_base.dataintegration.context import PluginContext -from cmem_plugin_base.dataintegration.types import StringParameterType, Autocompletion +from cmem_plugin_base.dataintegration.types import Autocompletion, StringParameterType from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access @@ -16,20 +17,18 @@ class DatasetParameterType(StringParameterType): autocomplete_value_with_labels: bool = True - dataset_type: Optional[str] = None + dataset_type: str | None = None - def __init__(self, dataset_type: Optional[str] = None): + def __init__(self, dataset_type: str | None = None): """Dataset parameter type.""" self.dataset_type = dataset_type def label( self, value: str, depend_on_parameter_values: list[Any], context: PluginContext - ) -> Optional[str]: + ) -> str | None: """Returns the label for the given dataset.""" setup_cmempy_user_access(context.user) - task_label = str( - get_task(project=context.project_id, task=value)["metadata"]["label"] - ) + task_label = str(get_task(project=context.project_id, task=value)["metadata"]["label"]) return f"{task_label}" def autocomplete( @@ -39,9 +38,7 @@ def autocomplete( context: PluginContext, ) -> list[Autocompletion]: setup_cmempy_user_access(context.user) - datasets = list_items(item_type="dataset", project=context.project_id)[ - "results" - ] + datasets = list_items(item_type="dataset", project=context.project_id)["results"] result = [] for _ in datasets: diff --git a/cmem_plugin_base/dataintegration/parameter/graph.py b/cmem_plugin_base/dataintegration/parameter/graph.py index cd30181..a625017 100644 --- a/cmem_plugin_base/dataintegration/parameter/graph.py +++ b/cmem_plugin_base/dataintegration/parameter/graph.py @@ -1,10 +1,11 @@ """Knowledge Graph Parameter Type.""" -from typing import Optional, Set, List, Any + +from typing import Any from cmem.cmempy.dp.proxy.graph import get_graphs_list from cmem_plugin_base.dataintegration.context import PluginContext -from cmem_plugin_base.dataintegration.types import StringParameterType, Autocompletion +from cmem_plugin_base.dataintegration.types import Autocompletion, StringParameterType from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access @@ -15,18 +16,17 @@ class GraphParameterType(StringParameterType): autocomplete_value_with_labels: bool = True - classes: Optional[Set[str]] = None + classes: set[str] | None = None def __init__( self, show_di_graphs: bool = False, show_system_graphs: bool = False, show_graphs_without_class: bool = False, - classes: Optional[List[str]] = None, + classes: list[str] | None = None, allow_only_autocompleted_values: bool = True, ): - """ - Knowledge Graph parameter type. + """Knowledge Graph parameter type. :param show_di_graphs: show DI project graphs :param show_system_graphs: show system graphs such as shape and query catalogs diff --git a/cmem_plugin_base/dataintegration/parameter/password.py b/cmem_plugin_base/dataintegration/parameter/password.py index 60770ce..3b06f99 100644 --- a/cmem_plugin_base/dataintegration/parameter/password.py +++ b/cmem_plugin_base/dataintegration/parameter/password.py @@ -27,7 +27,8 @@ class PasswordParameterType(ParameterType[Password]): def from_string(self, value: str, context: PluginContext) -> Password: """Parses strings into parameter values. - Decrypts the password if the encryption preamble is present""" + Decrypts the password if the encryption preamble is present + """ if value is None or value == "": encrypted_value = "" elif value.startswith(self.preamble): @@ -38,7 +39,8 @@ def from_string(self, value: str, context: PluginContext) -> Password: def to_string(self, value: Password) -> str: """Converts parameter values into their string representation. - Encrypts the password so that it won't be stored verbatim.""" + Encrypts the password so that it won't be stored verbatim. + """ if value.encrypted_value == "": return "" return self.preamble + value.encrypted_value diff --git a/cmem_plugin_base/dataintegration/parameter/resource.py b/cmem_plugin_base/dataintegration/parameter/resource.py index 7d8a933..74def09 100644 --- a/cmem_plugin_base/dataintegration/parameter/resource.py +++ b/cmem_plugin_base/dataintegration/parameter/resource.py @@ -1,24 +1,26 @@ """DI Resource Parameter Type.""" + from typing import Any from cmem.cmempy.workspace.projects.resources import get_resources from cmem_plugin_base.dataintegration.context import PluginContext -from cmem_plugin_base.dataintegration.types import StringParameterType, Autocompletion +from cmem_plugin_base.dataintegration.types import Autocompletion, StringParameterType from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access class ResourceParameterType(StringParameterType): """Resource parameter type.""" + allow_only_autocompleted_values: bool = True autocomplete_value_with_labels: bool = True def autocomplete( - self, - query_terms: list[str], - depend_on_parameter_values: list[Any], - context: PluginContext, + self, + query_terms: list[str], + depend_on_parameter_values: list[Any], + context: PluginContext, ) -> list[Autocompletion]: setup_cmempy_user_access(context.user) resources = get_resources(context.project_id) @@ -26,17 +28,15 @@ def autocomplete( Autocompletion( value=f"{_['fullPath']}", label=f"{_['name']}", - ) for _ in resources + ) + for _ in resources ] if query_terms: result = [_ for _ in result if _.value.find(query_terms[0]) > -1] if not result and query_terms: result = [ - Autocompletion( - value=f"{query_terms[0]}", - label=f"{query_terms[0]} (New resource)" - ) + Autocompletion(value=f"{query_terms[0]}", label=f"{query_terms[0]} (New resource)") ] return result diff --git a/cmem_plugin_base/dataintegration/plugins.py b/cmem_plugin_base/dataintegration/plugins.py index 1fe54d6..9892cbd 100644 --- a/cmem_plugin_base/dataintegration/plugins.py +++ b/cmem_plugin_base/dataintegration/plugins.py @@ -1,6 +1,7 @@ """All Plugins base classes.""" + import logging -from typing import Sequence, Optional +from collections.abc import Sequence from cmem_plugin_base.dataintegration.context import ExecutionContext from cmem_plugin_base.dataintegration.entity import Entities @@ -11,7 +12,8 @@ class PluginLogger: """Logging API for Plugins. If a plugin is running within DataIntegration, this class will be replaced to - log into DI using the path: plugins.python..""" + log into DI using the path: plugins.python.. + """ def debug(self, message: str) -> None: """Log a message with severity 'DEBUG'.""" @@ -34,12 +36,14 @@ class PluginConfig: """Configuration API for Plugins. If a plugin is running within DataIntegration, this class will be replaced to - retrieve the DI configuration in the path: plugins.python..""" + retrieve the DI configuration in the path: plugins.python.. + """ def get(self) -> str: """Retrieve plugin configuration as a JSON string. - This test implementation will return an empty string.""" + This test implementation will return an empty string. + """ return "" @@ -57,13 +61,11 @@ class WorkflowPlugin(PluginBase): input_ports: InputPorts """Specifies the input ports that this operator allows.""" - output_port: Optional[Port] + output_port: Port | None """Specifies the output port (if any) of this operator. Should be `None`, if this operator does not return any output.""" - def execute( - self, inputs: Sequence[Entities], context: ExecutionContext - ) -> Optional[Entities]: + def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Entities | None: """Executes the workflow plugin on a given collection of entities. :param inputs: Contains a separate collection of entities for each @@ -80,13 +82,11 @@ def execute( class TransformPlugin(PluginBase): - """ - Base class of all transform operator plugins. + """Base class of all transform operator plugins. """ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: - """ - Transforms a collection of values. + """Transforms a collection of values. :param inputs: A sequence which contains as many elements as there are input operators for this transformation. diff --git a/cmem_plugin_base/dataintegration/ports.py b/cmem_plugin_base/dataintegration/ports.py index 6bae257..dc301a0 100644 --- a/cmem_plugin_base/dataintegration/ports.py +++ b/cmem_plugin_base/dataintegration/ports.py @@ -1,6 +1,7 @@ """Workflow operator input and output ports.""" -from typing import Sequence +from collections.abc import Sequence + from cmem_plugin_base.dataintegration.entity import EntitySchema @@ -20,13 +21,15 @@ class FlexibleSchemaPort(Port): connected port. Flexible input ports will adapt the schema to the connected output. Flexible output ports will adapt the schema to the connected input. - It is not allowed to connect two flexible ports.""" + It is not allowed to connect two flexible ports. + """ class UnknownSchemaPort(Port): """Port for which the schema is not known in advance. This includes output ports with a schema that depends on external factors - (e.g., REST requests).""" + (e.g., REST requests). + """ class InputPorts: @@ -42,4 +45,5 @@ def __init__(self, ports: Sequence[Port]): class FlexibleNumberOfInputs(InputPorts): """Operator accepts a flexible number of inputs. - At the moment, each input is a flexible schema port.""" + At the moment, each input is a flexible schema port. + """ diff --git a/cmem_plugin_base/dataintegration/types.py b/cmem_plugin_base/dataintegration/types.py index 41c8823..3cf50db 100644 --- a/cmem_plugin_base/dataintegration/types.py +++ b/cmem_plugin_base/dataintegration/types.py @@ -1,4 +1,5 @@ """Parameter types.""" + from dataclasses import dataclass from enum import Enum from inspect import Parameter @@ -227,17 +228,11 @@ def get_type(param_type: Type) -> ParameterType: if issubclass(param_type, Enum): return EnumParameterType(param_type) found_type = next( - ( - t - for t in ParameterTypes.registered_types - if issubclass(param_type, t.get_type()) - ), + (t for t in ParameterTypes.registered_types if issubclass(param_type, t.get_type())), None, ) if found_type is None: - mapped = map( - lambda t: str(t.get_type().__name__), ParameterTypes.registered_types - ) + mapped = map(lambda t: str(t.get_type().__name__), ParameterTypes.registered_types) raise ValueError( f"Parameter has an unsupported type {param_type.__name__}. " "Supported types are: Enum, " diff --git a/cmem_plugin_base/dataintegration/utils/__init__.py b/cmem_plugin_base/dataintegration/utils/__init__.py index 0ce80e5..c5bd7d1 100644 --- a/cmem_plugin_base/dataintegration/utils/__init__.py +++ b/cmem_plugin_base/dataintegration/utils/__init__.py @@ -1,4 +1,5 @@ """Utils for dataintegration plugins.""" + import os import re from typing import Optional @@ -15,7 +16,7 @@ def generate_id(name: str) -> str: return re.sub(r"[^a-zA-Z0-9_-]", "", name) -def setup_cmempy_user_access(context: Optional[UserContext]): +def setup_cmempy_user_access(context: UserContext | None): """Setup environment for accessing CMEM with cmempy.""" if context is None: raise ValueError("No UserContext given.") @@ -39,9 +40,7 @@ def setup_cmempy_super_user_access(): if "CMEM_BASE_URI" not in os.environ: os.environ["CMEM_BASE_URI"] = os.environ["DEPLOY_BASE_URL"] if "OAUTH_CLIENT_ID" not in os.environ: - os.environ["OAUTH_CLIENT_ID"] = os.environ[ - "DATAINTEGRATION_CMEM_SERVICE_CLIENT" - ] + os.environ["OAUTH_CLIENT_ID"] = os.environ["DATAINTEGRATION_CMEM_SERVICE_CLIENT"] if "OAUTH_CLIENT_SECRET" not in os.environ: os.environ["OAUTH_CLIENT_SECRET"] = os.environ[ "DATAINTEGRATION_CMEM_SERVICE_CLIENT_SECRET" @@ -61,6 +60,7 @@ def split_task_id(task_id: str) -> tuple: Raises: ValueError: in case the task ID is not splittable + """ try: project_part = task_id.split(":")[0] @@ -70,9 +70,7 @@ def split_task_id(task_id: str) -> tuple: return project_part, task_part -def write_to_dataset( - dataset_id: str, file_resource=None, context: Optional[UserContext] = None -): +def write_to_dataset(dataset_id: str, file_resource=None, context: UserContext | None = None): """Write to a dataset. Args: @@ -87,6 +85,7 @@ def write_to_dataset( Raises: ValueError: in case the task ID is not splittable ValueError: missing parameter + """ setup_cmempy_user_access(context=context) project_id, task_id = split_task_id(dataset_id) diff --git a/cmem_plugin_base/dataintegration/utils/entity_builder.py b/cmem_plugin_base/dataintegration/utils/entity_builder.py index b830f22..4f86dd2 100644 --- a/cmem_plugin_base/dataintegration/utils/entity_builder.py +++ b/cmem_plugin_base/dataintegration/utils/entity_builder.py @@ -1,17 +1,13 @@ """utils module for building entities from python objects dict|list.""" -from typing import Optional, Union + from ulid import ULID -from cmem_plugin_base.dataintegration.entity import ( - Entities, Entity, EntityPath -) -from cmem_plugin_base.dataintegration.entity import EntitySchema +from cmem_plugin_base.dataintegration.entity import Entities, Entity, EntityPath, EntitySchema def merge_path_values(paths_map1, paths_map2): - """ - Merge two dictionaries representing paths and values. + """Merge two dictionaries representing paths and values. This function takes two dictionaries, `paths_map1` and `paths_map2`, each representing paths and corresponding values. It merges these dictionaries @@ -23,6 +19,7 @@ def merge_path_values(paths_map1, paths_map2): Returns: dict: A merged dictionary containing combined values for common paths. + """ for key, value in paths_map2.items(): current_path_map = {} @@ -33,9 +30,8 @@ def merge_path_values(paths_map1, paths_map2): return paths_map1 -def generate_paths_from_data(data, path='root'): - """ - Generate a dictionary representing paths and data types from a nested JSON +def generate_paths_from_data(data, path="root"): + """Generate a dictionary representing paths and data types from a nested JSON structure. This function recursively traverses a nested JSON structure ('data') and builds @@ -48,35 +44,34 @@ def generate_paths_from_data(data, path='root'): Returns: dict: A dictionary representing paths and data types. + """ paths_map = {} if isinstance(data, list): for _ in data: - paths_map = merge_path_values(paths_map, - generate_paths_from_data(_, path=path)) + paths_map = merge_path_values(paths_map, generate_paths_from_data(_, path=path)) if isinstance(data, dict): key_to_type_map = {} for key, value in data.items(): key_to_type_map[key] = type(value).__name__ - if key_to_type_map[key] == 'dict': + if key_to_type_map[key] == "dict": sub_path = f"{path}/{key}" - paths_map = merge_path_values(paths_map, - generate_paths_from_data(data=value, - path=sub_path)) - if key_to_type_map[key] == 'list': + paths_map = merge_path_values( + paths_map, generate_paths_from_data(data=value, path=sub_path) + ) + if key_to_type_map[key] == "list": for _ in value: if isinstance(_, dict): - key_to_type_map[key] = 'list_dict' + key_to_type_map[key] = "list_dict" sub_path = f"{path}/{key}" - paths_map = merge_path_values(paths_map, - generate_paths_from_data( - data=_, - path=sub_path)) + paths_map = merge_path_values( + paths_map, generate_paths_from_data(data=_, path=sub_path) + ) paths_map[path] = key_to_type_map return paths_map -def _get_schema(data: Union[dict, list]): +def _get_schema(data: dict | list): """Get the schema of an entity.""" if not data: return None @@ -88,8 +83,8 @@ def _get_schema(data: Union[dict, list]): schema_paths.append( EntityPath( path=_key, - is_relation=_type in ('dict', 'list_dict'), - is_single_value=_type not in ('list', 'list_dict') + is_relation=_type in ("dict", "list_dict"), + is_single_value=_type not in ("list", "list_dict"), ) ) schema = EntitySchema( @@ -101,8 +96,7 @@ def _get_schema(data: Union[dict, list]): def extend_path_list(path_to_entities, sub_path_to_entities): - """ - Extend a dictionary of paths to entities by merging with another. + """Extend a dictionary of paths to entities by merging with another. This function takes two dictionaries, `path_to_entities` and `sub_path_to_entities`, representing paths and lists of entities. It extends the lists of entities for each @@ -116,6 +110,7 @@ def extend_path_list(path_to_entities, sub_path_to_entities): Returns: None: The result is modified in-place. `path_to_entities` is extended with entities from `sub_path_to_entities`. + """ for key, sub_entities in sub_path_to_entities.items(): entities = path_to_entities.get(key, []) @@ -124,9 +119,9 @@ def extend_path_list(path_to_entities, sub_path_to_entities): def _get_entity( - path_from_root, - path_to_schema_map, - data, + path_from_root, + path_to_schema_map, + data, ): """Get an entity based on the schema and data.""" path_to_entities = {} @@ -135,13 +130,12 @@ def _get_entity( schema = path_to_schema_map[path_from_root] for _ in schema.paths: if data.get(_.path) is None: - values.append(['']) + values.append([""]) elif not _.is_relation: values.append( [f"{data.get(_.path)}"] if _.is_single_value - else - [f"{_v}" for _v in data.get(_.path)] + else [f"{_v}" for _v in data.get(_.path)] ) else: _data = [data.get(_.path)] if _.is_single_value else data.get(_.path) @@ -166,19 +160,16 @@ def _get_entity( def _get_entities( - data: Union[dict, list], - path_to_schema_map: dict[str, EntitySchema], + data: dict | list, + path_to_schema_map: dict[str, EntitySchema], ) -> dict[str, list[Entity]]: - """ - Get entities based on the schema, data, and sub-entities. + """Get entities based on the schema, data, and sub-entities. """ path_to_entities: dict[str, list[Entity]] = {} if isinstance(data, list): for _ in data: sub_path_to_entities = _get_entity( - path_from_root="root", - path_to_schema_map=path_to_schema_map, - data=_ + path_from_root="root", path_to_schema_map=path_to_schema_map, data=_ ) extend_path_list(path_to_entities, sub_path_to_entities) else: @@ -190,9 +181,8 @@ def _get_entities( return path_to_entities -def build_entities_from_data(data: Union[dict, list]) -> Optional[Entities]: - """ - Get entities from a data object. +def build_entities_from_data(data: dict | list) -> Entities | None: + """Get entities from a data object. """ path_to_schema_map = _get_schema(data) if not path_to_schema_map: @@ -202,12 +192,11 @@ def build_entities_from_data(data: Union[dict, list]) -> Optional[Entities]: path_to_schema_map=path_to_schema_map, ) return Entities( - entities=iter(path_to_entities.get('root')), # type: ignore[arg-type] - schema=path_to_schema_map['root'], + entities=iter(path_to_entities.get("root")), # type: ignore[arg-type] + schema=path_to_schema_map["root"], sub_entities=[ - Entities( - entities=iter(value), - schema=path_to_schema_map[key] - ) for key, value in path_to_entities.items() if key != 'root' - ] + Entities(entities=iter(value), schema=path_to_schema_map[key]) + for key, value in path_to_entities.items() + if key != "root" + ], ) diff --git a/tests/conftest.py b/tests/conftest.py index 5faf97a..137832e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,14 @@ """pytest conftest module""" + import io from dataclasses import dataclass import pytest from cmem.cmempy.workspace.projects.datasets.dataset import ( - make_new_dataset, get_dataset, + make_new_dataset, ) -from cmem.cmempy.workspace.projects.project import make_new_project, delete_project +from cmem.cmempy.workspace.projects.project import delete_project, make_new_project from cmem.cmempy.workspace.projects.resources.resource import create_resource PROJECT_NAME = "dateset_test_project" @@ -17,7 +18,7 @@ @pytest.fixture(name="json_dataset", scope="module") def _json_dataset(): - """setup""" + """Setup""" make_new_project(PROJECT_NAME) make_new_dataset( project_name=PROJECT_NAME, @@ -32,7 +33,7 @@ def _json_dataset(): @pytest.fixture(name="json_resource", scope="module") def _json_resource(): - """setup json resource""" + """Setup json resource""" _project_name = "resource_test_project" _resource_name = "sample_test.json" make_new_project(_project_name) @@ -40,12 +41,13 @@ def _json_resource(): project_name=_project_name, resource_name=_resource_name, file_resource=io.StringIO("SAMPLE CONTENT"), - replace=True + replace=True, ) @dataclass class FixtureDate: """fixture dataclass""" + project_name = _project_name resource_name = _resource_name diff --git a/tests/parameter_types/test_choice.py b/tests/parameter_types/test_choice.py index 15f882a..42a3320 100644 --- a/tests/parameter_types/test_choice.py +++ b/tests/parameter_types/test_choice.py @@ -1,4 +1,5 @@ """Choice parameter type tests""" + import collections from cmem_plugin_base.dataintegration.parameter.choice import ChoiceParameterType @@ -8,7 +9,7 @@ def test_dataset_parameter_type_completion(): - """test dataset parameter type completion""" + """Test dataset parameter type completion""" parameter = ChoiceParameterType(choice_list=CHOICE_LIST) context = TestPluginContext() assert "ONE" in { @@ -24,9 +25,7 @@ def test_dataset_parameter_type_completion(): ) } assert len( - parameter.autocomplete( - query_terms=[], depend_on_parameter_values=[], context=context - ) + parameter.autocomplete(query_terms=[], depend_on_parameter_values=[], context=context) ) == len(CHOICE_LIST) assert ( len( diff --git a/tests/parameter_types/test_code.py b/tests/parameter_types/test_code.py index ad31d4d..8f85063 100644 --- a/tests/parameter_types/test_code.py +++ b/tests/parameter_types/test_code.py @@ -1,22 +1,29 @@ """Code parameter types tests""" + import unittest -from typing import Sequence -from tests.utils import TestPluginContext +from collections.abc import Sequence + from cmem_plugin_base.dataintegration.description import Plugin -from cmem_plugin_base.dataintegration.parameter.code import (XmlCode, - CodeParameterType, - JsonCode, - JinjaCode, YamlCode, - SqlCode, SparqlCode, - TurtleCode, PythonCode) +from cmem_plugin_base.dataintegration.parameter.code import ( + CodeParameterType, + JinjaCode, + JsonCode, + PythonCode, + SparqlCode, + SqlCode, + TurtleCode, + XmlCode, + YamlCode, +) from cmem_plugin_base.dataintegration.plugins import TransformPlugin +from tests.utils import TestPluginContext class CodeParameterTest(unittest.TestCase): """Code Parameter Test""" def test__detection(self): - """test detection""" + """Test detection""" Plugin.plugins = [] @Plugin(label="My Transform Plugin") @@ -24,15 +31,16 @@ class MyTransformPlugin(TransformPlugin): """Test My Transform Plugin""" def __init__( # pylint: disable=too-many-arguments - self, - xml: XmlCode = XmlCode(""), - json: JsonCode = JsonCode("{}"), - jinja: JinjaCode = JinjaCode(""), - sql: SqlCode = SqlCode(""), - yaml: YamlCode = YamlCode(""), - sparql: SparqlCode = SparqlCode(""), - turtle: TurtleCode = TurtleCode(""), - python: PythonCode = PythonCode("")) -> None: + self, + xml: XmlCode = XmlCode(""), + json: JsonCode = JsonCode("{}"), + jinja: JinjaCode = JinjaCode(""), + sql: SqlCode = SqlCode(""), + yaml: YamlCode = YamlCode(""), + sparql: SparqlCode = SparqlCode(""), + turtle: TurtleCode = TurtleCode(""), + python: PythonCode = PythonCode(""), + ) -> None: self.xml = xml self.json = json self.jinja = jinja @@ -43,7 +51,7 @@ def __init__( # pylint: disable=too-many-arguments self.python = python def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: - """test transform""" + """Test transform""" return [] MyTransformPlugin() @@ -59,7 +67,7 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: self.assertEqual(plugin.parameters[7].param_type.name, "code-python") def test_serialization(self): - """test serialization from/to strings""" + """Test serialization from/to strings""" jinja_type = CodeParameterType[JinjaCode]("jinja2") # Create a jinja code instance from a string diff --git a/tests/parameter_types/test_dataset.py b/tests/parameter_types/test_dataset.py index eb1efe8..3858515 100644 --- a/tests/parameter_types/test_dataset.py +++ b/tests/parameter_types/test_dataset.py @@ -1,14 +1,14 @@ """graph parameter type tests""" from cmem_plugin_base.dataintegration.parameter.dataset import DatasetParameterType -from tests.utils import needs_cmem, TestPluginContext, get_autocomplete_values +from tests.utils import TestPluginContext, get_autocomplete_values, needs_cmem @needs_cmem def test_dataset_parameter_type_completion(json_dataset): - """test dataset parameter type completion""" - project_name = json_dataset['project'] - dataset_name = json_dataset['id'] + """Test dataset parameter type completion""" + project_name = json_dataset["project"] + dataset_name = json_dataset["id"] parameter = DatasetParameterType(dataset_type="json") context = TestPluginContext(project_name) assert dataset_name in get_autocomplete_values(parameter, [], context) diff --git a/tests/parameter_types/test_graph.py b/tests/parameter_types/test_graph.py index b86ec76..41110bd 100644 --- a/tests/parameter_types/test_graph.py +++ b/tests/parameter_types/test_graph.py @@ -1,11 +1,13 @@ """graph parameter type tests""" + from cmem_plugin_base.dataintegration.parameter.graph import GraphParameterType -from ..utils import needs_cmem, TestPluginContext + +from ..utils import TestPluginContext, needs_cmem @needs_cmem def test_graph_parameter_type_completion(): - """test graph parameter type completion""" + """Test graph parameter type completion""" parameter = GraphParameterType(show_system_graphs=True) context = TestPluginContext() assert "https://ns.eccenca.com/data/queries/" in [ diff --git a/tests/parameter_types/test_password.py b/tests/parameter_types/test_password.py index 894aa1a..ea1a5da 100644 --- a/tests/parameter_types/test_password.py +++ b/tests/parameter_types/test_password.py @@ -1,6 +1,7 @@ """Password parameter type tests""" + import unittest -from typing import Sequence +from collections.abc import Sequence from cmem_plugin_base.dataintegration.description import Plugin from cmem_plugin_base.dataintegration.parameter.password import ( @@ -14,7 +15,7 @@ class PasswordParameterTest(unittest.TestCase): """Password Parameter Test""" def test__detection(self): - """test detection""" + """Test detection""" Plugin.plugins = [] @Plugin(label="My Transform Plugin") @@ -25,7 +26,7 @@ def __init__(self, password: Password) -> None: self.password = password def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: - """test transform""" + """Test transform""" return [] Plugin.plugins.append(MyTransformPlugin) diff --git a/tests/parameter_types/test_resource.py b/tests/parameter_types/test_resource.py index 6f7a1a0..59e7232 100644 --- a/tests/parameter_types/test_resource.py +++ b/tests/parameter_types/test_resource.py @@ -1,11 +1,12 @@ """resource parameter type tests""" + from cmem_plugin_base.dataintegration.parameter.resource import ResourceParameterType -from tests.utils import needs_cmem, TestPluginContext, get_autocomplete_values +from tests.utils import TestPluginContext, get_autocomplete_values, needs_cmem @needs_cmem def test_resource_parameter_type_completion(json_resource): - """test resource parameter type completion""" + """Test resource parameter type completion""" project_name = json_resource.project_name resource_name = json_resource.resource_name parameter = ResourceParameterType() @@ -13,8 +14,4 @@ def test_resource_parameter_type_completion(json_resource): assert resource_name in get_autocomplete_values(parameter, [], context) new_resource_name = "lkshfkdsjfhsd" assert len(get_autocomplete_values(parameter, [new_resource_name], context)) == 1 - assert new_resource_name in get_autocomplete_values( - parameter, - [new_resource_name], - context - ) + assert new_resource_name in get_autocomplete_values(parameter, [new_resource_name], context) diff --git a/tests/test_description.py b/tests/test_description.py index e999d8e..7ad7b64 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,15 +1,14 @@ """test description""" -import unittest - -from typing import Sequence -from cmem_plugin_base.dataintegration.plugins import TransformPlugin +import unittest +from collections.abc import Sequence from cmem_plugin_base.dataintegration.description import Plugin +from cmem_plugin_base.dataintegration.plugins import TransformPlugin from cmem_plugin_base.dataintegration.types import ( - StringParameterType, - FloatParameterType, BoolParameterType, + FloatParameterType, + StringParameterType, ) @@ -17,7 +16,7 @@ class PluginTest(unittest.TestCase): """Plugin Test Class""" def test__basic_parameters(self): - """test basic parameters""" + """Test basic parameters""" Plugin.plugins = [] @Plugin(label="My Transform Plugin") diff --git a/tests/test_discovery.py b/tests/test_discovery.py index c7e1ce1..3fa0937 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -1,4 +1,5 @@ """discovery test module""" + import pytest from cmem_plugin_base.dataintegration.discovery import discover_plugins @@ -6,7 +7,7 @@ @pytest.mark.skip(reason="cmem-plugin-examples is not added") def test_discover_plugins(): - """test plugin discovery.""" + """Test plugin discovery.""" plugins = discover_plugins("cmem_plugin").plugins # cmem_plugin_examples should have at least one plugin diff --git a/tests/test_icon.py b/tests/test_icon.py index 4e16bdd..fe8ee0e 100644 --- a/tests/test_icon.py +++ b/tests/test_icon.py @@ -1,12 +1,13 @@ """tests for icon class.""" -from typing import Sequence + +from collections.abc import Sequence import pytest -from cmem_plugin_base.dataintegration.description import Icon, Plugin -from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin from cmem_plugin_base.dataintegration.context import ExecutionContext +from cmem_plugin_base.dataintegration.description import Icon, Plugin from cmem_plugin_base.dataintegration.entity import Entities +from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin @Plugin( @@ -16,9 +17,7 @@ class MyWorkflowPlugin(WorkflowPlugin): """My Workflow Plugin Class""" - def execute( - self, inputs: Sequence[Entities], context: ExecutionContext - ) -> None: + def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: return None diff --git a/tests/test_output_only_plugin.py b/tests/test_output_only_plugin.py index 70a9f3d..b2cfbf8 100644 --- a/tests/test_output_only_plugin.py +++ b/tests/test_output_only_plugin.py @@ -1,12 +1,13 @@ """test file.""" -from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin + +from cmem_plugin_base.dataintegration.description import Plugin, PluginParameter from cmem_plugin_base.dataintegration.entity import ( Entities, Entity, - EntitySchema, EntityPath, + EntitySchema, ) -from cmem_plugin_base.dataintegration.description import Plugin, PluginParameter +from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin @Plugin( diff --git a/tests/test_types.py b/tests/test_types.py index 3327259..9d95156 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,15 +1,15 @@ """test types""" + import unittest from enum import Enum from cmem_plugin_base.dataintegration.types import ( - EnumParameterType, Autocompletion, + EnumParameterType, ParameterTypes, ) from tests.utils import TestPluginContext - # dummy plugin context to be used in tests context = TestPluginContext() @@ -21,7 +21,7 @@ class MissingType: """Test Missing Type""" def test_missing_type(self): - """test missing type""" + """Test missing type""" self.assertRaisesRegex( ValueError, "unsupported type", @@ -33,23 +33,21 @@ class BasicTypesTest(unittest.TestCase): """Test Basic Types""" def test_detection(self): - """test detection""" + """Test detection""" self.assertEqual(ParameterTypes.get_type(str).name, "string") self.assertEqual(ParameterTypes.get_type(int).name, "Long") self.assertEqual(ParameterTypes.get_type(float).name, "double") self.assertEqual(ParameterTypes.get_type(bool).name, "boolean") def test_conversion(self): - """test conversion""" + """Test conversion""" int_type = ParameterTypes.get_type(int) float_type = ParameterTypes.get_type(float) bool_type = ParameterTypes.get_type(bool) self.assertEqual(int_type.from_string(int_type.to_string(3), context), 3) self.assertEqual(float_type.from_string(int_type.to_string(1.2), context), 1.2) self.assertEqual(bool_type.from_string(int_type.to_string(True), context), True) - self.assertEqual( - bool_type.from_string(int_type.to_string(False), context), False - ) + self.assertEqual(bool_type.from_string(int_type.to_string(False), context), False) class EnumTest(unittest.TestCase): @@ -63,17 +61,17 @@ class Color(Enum): BLUE = 3 def test_detection(self): - """test detection""" + """Test detection""" self.assertEqual(ParameterTypes.get_type(EnumTest.Color).name, "enumeration") def test_conversion(self): - """test conversion""" + """Test conversion""" enum = EnumParameterType(EnumTest.Color) self.assertEqual(enum.to_string(enum.from_string("RED", context)), "RED") self.assertEqual(enum.to_string(enum.from_string("GREEN", context)), "GREEN") def test_invalid_values(self): - """test invalid values""" + """Test invalid values""" enum = EnumParameterType(EnumTest.Color) self.assertRaisesRegex( ValueError, @@ -85,7 +83,7 @@ def test_invalid_values(self): ) def test_autocomplete(self): - """test autocomplete""" + """Test autocomplete""" enum = EnumParameterType(EnumTest.Color) self.assertListEqual( list(enum.autocomplete(["red"], [], context)), diff --git a/tests/test_utils_build_entities_from_data.py b/tests/test_utils_build_entities_from_data.py index c7b34d5..cc3e03b 100644 --- a/tests/test_utils_build_entities_from_data.py +++ b/tests/test_utils_build_entities_from_data.py @@ -1,14 +1,13 @@ """Tests for `utils.build_entities_from_data`""" + import json -from cmem_plugin_base.dataintegration.entity import EntitySchema, EntityPath -from cmem_plugin_base.dataintegration.utils.entity_builder import ( - build_entities_from_data -) +from cmem_plugin_base.dataintegration.entity import EntityPath, EntitySchema +from cmem_plugin_base.dataintegration.utils.entity_builder import build_entities_from_data def test_single_object(): - """test generation of entities and schema for a simple JSON object.""" + """Test generation of entities and schema for a simple JSON object.""" test_data = """ { "name": "sai", @@ -26,13 +25,14 @@ def test_single_object(): paths=[ EntityPath("name", False, is_single_value=True), EntityPath("email", False, is_single_value=True), - ] + ], ) def test_single_object_one_level(): - """test generation of entities and schema for a JSON object with one level of - hierarchy""" + """Test generation of entities and schema for a JSON object with one level of + hierarchy + """ test_data = """ { "name": "sai", @@ -56,7 +56,7 @@ def test_single_object_one_level(): EntityPath("name", False, is_single_value=True), EntityPath("email", False, is_single_value=True), EntityPath("city", True, is_single_value=True), - ] + ], ) # Validate sub entities for _ in entities.sub_entities: @@ -67,14 +67,15 @@ def test_single_object_one_level(): type_uri="", paths=[ EntityPath("name", False, is_single_value=True), - EntityPath("country", False, is_single_value=True) - ] + EntityPath("country", False, is_single_value=True), + ], ) def test_single_object_one_level_array(): - """test generation of entities and schema for a JSON object with array object in - first level of hierarchy""" + """Test generation of entities and schema for a JSON object with array object in + first level of hierarchy + """ test_data = """ { "name": "sai", @@ -102,7 +103,7 @@ def test_single_object_one_level_array(): EntityPath("name", False, is_single_value=True), EntityPath("email", False, is_single_value=True), EntityPath("city", True, is_single_value=False), - ] + ], ) # Validate sub entities for _ in entities.sub_entities: @@ -113,14 +114,15 @@ def test_single_object_one_level_array(): type_uri="", paths=[ EntityPath("name", False, is_single_value=True), - EntityPath("country", False, is_single_value=True) - ] + EntityPath("country", False, is_single_value=True), + ], ) def test_single_object_two_level_array(): - """test generation of entities and schema for a JSON object with two levels of - hierarchy""" + """Test generation of entities and schema for a JSON object with two levels of + hierarchy + """ test_data = """ { "name": "sai", @@ -158,7 +160,7 @@ def test_single_object_two_level_array(): EntityPath("name", False, is_single_value=True), EntityPath("email", False, is_single_value=True), EntityPath("city", True, is_single_value=False), - ] + ], ) # Validate sub entities location_entities = entities.sub_entities[0] @@ -167,25 +169,25 @@ def test_single_object_two_level_array(): assert len(list(location_entities.entities)) == 2 assert city_entities.schema == EntitySchema( - type_uri="", - paths=[ - EntityPath("name", False, is_single_value=True), - EntityPath("country", False, is_single_value=True), - EntityPath("geo_location", True, is_single_value=True), - ] - ) + type_uri="", + paths=[ + EntityPath("name", False, is_single_value=True), + EntityPath("country", False, is_single_value=True), + EntityPath("geo_location", True, is_single_value=True), + ], + ) assert location_entities.schema == EntitySchema( - type_uri="", - paths=[ - EntityPath("lat", False, is_single_value=True), - EntityPath("long", False, is_single_value=True), - ] - ) + type_uri="", + paths=[ + EntityPath("lat", False, is_single_value=True), + EntityPath("long", False, is_single_value=True), + ], + ) def test_array_object(): - """test generation of entities and schema for a simple array JSON object.""" + """Test generation of entities and schema for a simple array JSON object.""" test_data = """ [{ "name": "seebi" @@ -208,12 +210,12 @@ def test_array_object(): paths=[ EntityPath("name", False, is_single_value=True), EntityPath("email", False, is_single_value=True), - ] + ], ) def test_empty_object(): - """test empty json object input""" + """Test empty json object input""" test_data = """[]""" data = json.loads(test_data) assert build_entities_from_data(data) is None diff --git a/tests/test_utils_write_to_dataset.py b/tests/test_utils_write_to_dataset.py index 7d0a182..5b5be0f 100644 --- a/tests/test_utils_write_to_dataset.py +++ b/tests/test_utils_write_to_dataset.py @@ -1,4 +1,5 @@ """graph parameter type tests""" + import io import json @@ -8,23 +9,21 @@ from cmem_plugin_base.dataintegration.parameter.dataset import DatasetParameterType from cmem_plugin_base.dataintegration.utils import write_to_dataset -from tests.utils import needs_cmem, TestPluginContext, get_autocomplete_values +from tests.utils import TestPluginContext, get_autocomplete_values, needs_cmem @needs_cmem def test_write_to_json_dataset(json_dataset): - """test write to json dataset""" - project_name = json_dataset['project'] - dataset_name = json_dataset['id'] + """Test write to json dataset""" + project_name = json_dataset["project"] + dataset_name = json_dataset["id"] parameter = DatasetParameterType(dataset_type="json") dataset_id = f"{project_name}:{dataset_name}" context = TestPluginContext(project_name) assert dataset_name in get_autocomplete_values(parameter, [], context) - write_to_dataset( - dataset_id, io.StringIO(json.dumps(json_dataset)), TestPluginContext().user - ) + write_to_dataset(dataset_id, io.StringIO(json.dumps(json_dataset)), TestPluginContext().user) get_response = get_resource(project_id=project_name, dataset_id=dataset_name) get_response = json.loads(get_response) @@ -33,7 +32,7 @@ def test_write_to_json_dataset(json_dataset): @needs_cmem def test_write_to_not_valid_dataset(): - """test write to not valid dataset""" + """Test write to not valid dataset""" with pytest.raises( requests.exceptions.HTTPError, match=r"404 Client Error: Not Found for url.*datasets/INVALID_DATASET/file.*", @@ -47,10 +46,6 @@ def test_write_to_not_valid_dataset(): @needs_cmem def test_write_to_invalid_format_dataset_id(): - """test write to invalid format dataset id""" - with pytest.raises( - ValueError, match=r"INVALID_DATASET_ID_FORMAT is not a valid task ID." - ): - write_to_dataset( - "INVALID_DATASET_ID_FORMAT", io.StringIO("{}"), TestPluginContext().user - ) + """Test write to invalid format dataset id""" + with pytest.raises(ValueError, match=r"INVALID_DATASET_ID_FORMAT is not a valid task ID."): + write_to_dataset("INVALID_DATASET_ID_FORMAT", io.StringIO("{}"), TestPluginContext().user) diff --git a/tests/utils.py b/tests/utils.py index 2e32fbd..7f5cda7 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,6 @@ """Testing utilities.""" + import os -from typing import Optional import pytest @@ -33,14 +33,14 @@ class TestPluginContext(PluginContext): def __init__( self, project_id: str = "dummyProject", - user: Optional[UserContext] = TestUserContext(), + user: UserContext | None = TestUserContext(), ): self.project_id = project_id self.user = user def get_autocomplete_values(parameter, query_terms, context): - """get autocomplete values""" + """Get autocomplete values""" return [ x.value for x in parameter.autocomplete( From c6b4fbfa8a3fbd0f9818031c86e030647b5b849f Mon Sep 17 00:00:00 2001 From: Sebastian Tramp Date: Thu, 12 Sep 2024 15:26:02 +0200 Subject: [PATCH 03/12] apply unsafe ruff fixes --- cmem_plugin_base/dataintegration/plugins.py | 3 +- .../dataintegration/utils/__init__.py | 4 +-- .../dataintegration/utils/entity_builder.py | 9 ++--- tests/parameter_types/test_choice.py | 2 +- tests/parameter_types/test_code.py | 28 +++++++-------- tests/parameter_types/test_dataset.py | 2 +- tests/parameter_types/test_graph.py | 5 ++- tests/parameter_types/test_password.py | 4 +-- tests/parameter_types/test_resource.py | 2 +- tests/test_description.py | 18 +++++----- tests/test_discovery.py | 2 +- tests/test_icon.py | 8 ++--- tests/test_output_only_plugin.py | 2 +- tests/test_types.py | 36 +++++++++---------- tests/test_utils_build_entities_from_data.py | 12 +++---- tests/test_utils_write_to_dataset.py | 6 ++-- 16 files changed, 69 insertions(+), 74 deletions(-) diff --git a/cmem_plugin_base/dataintegration/plugins.py b/cmem_plugin_base/dataintegration/plugins.py index 9892cbd..f275ae6 100644 --- a/cmem_plugin_base/dataintegration/plugins.py +++ b/cmem_plugin_base/dataintegration/plugins.py @@ -82,8 +82,7 @@ def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Enti class TransformPlugin(PluginBase): - """Base class of all transform operator plugins. - """ + """Base class of all transform operator plugins.""" def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: """Transforms a collection of values. diff --git a/cmem_plugin_base/dataintegration/utils/__init__.py b/cmem_plugin_base/dataintegration/utils/__init__.py index c5bd7d1..729cb6f 100644 --- a/cmem_plugin_base/dataintegration/utils/__init__.py +++ b/cmem_plugin_base/dataintegration/utils/__init__.py @@ -16,7 +16,7 @@ def generate_id(name: str) -> str: return re.sub(r"[^a-zA-Z0-9_-]", "", name) -def setup_cmempy_user_access(context: UserContext | None): +def setup_cmempy_user_access(context: UserContext | None) -> None: """Setup environment for accessing CMEM with cmempy.""" if context is None: raise ValueError("No UserContext given.") @@ -28,7 +28,7 @@ def setup_cmempy_user_access(context: UserContext | None): os.environ["CMEM_BASE_URI"] = os.environ["DEPLOY_BASE_URL"] -def setup_cmempy_super_user_access(): +def setup_cmempy_super_user_access() -> None: """Setup environment for accessing CMEM with cmempy. The helper function is used to setup the environment for accessing CMEM with cmempy. diff --git a/cmem_plugin_base/dataintegration/utils/entity_builder.py b/cmem_plugin_base/dataintegration/utils/entity_builder.py index 4f86dd2..74ca2d8 100644 --- a/cmem_plugin_base/dataintegration/utils/entity_builder.py +++ b/cmem_plugin_base/dataintegration/utils/entity_builder.py @@ -1,6 +1,5 @@ """utils module for building entities from python objects dict|list.""" - from ulid import ULID from cmem_plugin_base.dataintegration.entity import Entities, Entity, EntityPath, EntitySchema @@ -95,7 +94,7 @@ def _get_schema(data: dict | list): return path_to_schema_map -def extend_path_list(path_to_entities, sub_path_to_entities): +def extend_path_list(path_to_entities, sub_path_to_entities) -> None: """Extend a dictionary of paths to entities by merging with another. This function takes two dictionaries, `path_to_entities` and `sub_path_to_entities`, @@ -163,8 +162,7 @@ def _get_entities( data: dict | list, path_to_schema_map: dict[str, EntitySchema], ) -> dict[str, list[Entity]]: - """Get entities based on the schema, data, and sub-entities. - """ + """Get entities based on the schema, data, and sub-entities.""" path_to_entities: dict[str, list[Entity]] = {} if isinstance(data, list): for _ in data: @@ -182,8 +180,7 @@ def _get_entities( def build_entities_from_data(data: dict | list) -> Entities | None: - """Get entities from a data object. - """ + """Get entities from a data object.""" path_to_schema_map = _get_schema(data) if not path_to_schema_map: return None diff --git a/tests/parameter_types/test_choice.py b/tests/parameter_types/test_choice.py index 42a3320..8e358dd 100644 --- a/tests/parameter_types/test_choice.py +++ b/tests/parameter_types/test_choice.py @@ -8,7 +8,7 @@ CHOICE_LIST = collections.OrderedDict({"ONE": "First Option", "TWO": "Second Option"}) -def test_dataset_parameter_type_completion(): +def test_dataset_parameter_type_completion() -> None: """Test dataset parameter type completion""" parameter = ChoiceParameterType(choice_list=CHOICE_LIST) context = TestPluginContext() diff --git a/tests/parameter_types/test_code.py b/tests/parameter_types/test_code.py index 8f85063..292daff 100644 --- a/tests/parameter_types/test_code.py +++ b/tests/parameter_types/test_code.py @@ -22,7 +22,7 @@ class CodeParameterTest(unittest.TestCase): """Code Parameter Test""" - def test__detection(self): + def test__detection(self) -> None: """Test detection""" Plugin.plugins = [] @@ -57,29 +57,29 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: MyTransformPlugin() plugin = Plugin.plugins[0] - self.assertEqual(plugin.parameters[0].param_type.name, "code-xml") - self.assertEqual(plugin.parameters[1].param_type.name, "code-json") - self.assertEqual(plugin.parameters[2].param_type.name, "code-jinja2") - self.assertEqual(plugin.parameters[3].param_type.name, "code-sql") - self.assertEqual(plugin.parameters[4].param_type.name, "code-yaml") - self.assertEqual(plugin.parameters[5].param_type.name, "code-sparql") - self.assertEqual(plugin.parameters[6].param_type.name, "code-turtle") - self.assertEqual(plugin.parameters[7].param_type.name, "code-python") - - def test_serialization(self): + assert plugin.parameters[0].param_type.name == "code-xml" + assert plugin.parameters[1].param_type.name == "code-json" + assert plugin.parameters[2].param_type.name == "code-jinja2" + assert plugin.parameters[3].param_type.name == "code-sql" + assert plugin.parameters[4].param_type.name == "code-yaml" + assert plugin.parameters[5].param_type.name == "code-sparql" + assert plugin.parameters[6].param_type.name == "code-turtle" + assert plugin.parameters[7].param_type.name == "code-python" + + def test_serialization(self) -> None: """Test serialization from/to strings""" jinja_type = CodeParameterType[JinjaCode]("jinja2") # Create a jinja code instance from a string jinja_code = jinja_type.from_string("my code", TestPluginContext(user=None)) - self.assertEqual(jinja_code.code, "my code") + assert jinja_code.code == "my code" # Convert jinja code instance to a string code_str = jinja_type.to_string(jinja_code) - self.assertEqual(code_str, "my code") + assert code_str == "my code" # Make sure __str__ will return the code itself - self.assertEqual(str(jinja_code), "my code") + assert str(jinja_code) == "my code" if __name__ == "__main__": diff --git a/tests/parameter_types/test_dataset.py b/tests/parameter_types/test_dataset.py index 3858515..fae1852 100644 --- a/tests/parameter_types/test_dataset.py +++ b/tests/parameter_types/test_dataset.py @@ -5,7 +5,7 @@ @needs_cmem -def test_dataset_parameter_type_completion(json_dataset): +def test_dataset_parameter_type_completion(json_dataset) -> None: """Test dataset parameter type completion""" project_name = json_dataset["project"] dataset_name = json_dataset["id"] diff --git a/tests/parameter_types/test_graph.py b/tests/parameter_types/test_graph.py index 41110bd..b0100b7 100644 --- a/tests/parameter_types/test_graph.py +++ b/tests/parameter_types/test_graph.py @@ -1,12 +1,11 @@ """graph parameter type tests""" from cmem_plugin_base.dataintegration.parameter.graph import GraphParameterType - -from ..utils import TestPluginContext, needs_cmem +from tests.utils import TestPluginContext, needs_cmem @needs_cmem -def test_graph_parameter_type_completion(): +def test_graph_parameter_type_completion() -> None: """Test graph parameter type completion""" parameter = GraphParameterType(show_system_graphs=True) context = TestPluginContext() diff --git a/tests/parameter_types/test_password.py b/tests/parameter_types/test_password.py index ea1a5da..b443765 100644 --- a/tests/parameter_types/test_password.py +++ b/tests/parameter_types/test_password.py @@ -14,7 +14,7 @@ class PasswordParameterTest(unittest.TestCase): """Password Parameter Test""" - def test__detection(self): + def test__detection(self) -> None: """Test detection""" Plugin.plugins = [] @@ -33,7 +33,7 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: plugin = Plugin.plugins[0] password_param = plugin.parameters[0] - self.assertEqual(password_param.param_type.name, PasswordParameterType.name) + assert password_param.param_type.name == PasswordParameterType.name if __name__ == "__main__": diff --git a/tests/parameter_types/test_resource.py b/tests/parameter_types/test_resource.py index 59e7232..277da1a 100644 --- a/tests/parameter_types/test_resource.py +++ b/tests/parameter_types/test_resource.py @@ -5,7 +5,7 @@ @needs_cmem -def test_resource_parameter_type_completion(json_resource): +def test_resource_parameter_type_completion(json_resource) -> None: """Test resource parameter type completion""" project_name = json_resource.project_name resource_name = json_resource.resource_name diff --git a/tests/test_description.py b/tests/test_description.py index 7ad7b64..f0bf65d 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -15,7 +15,7 @@ class PluginTest(unittest.TestCase): """Plugin Test Class""" - def test__basic_parameters(self): + def test__basic_parameters(self) -> None: """Test basic parameters""" Plugin.plugins = [] @@ -42,20 +42,20 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: plugin = Plugin.plugins[0] no_default_par = plugin.parameters[0] - self.assertEqual(no_default_par.param_type.name, StringParameterType.name) - self.assertIsNone(no_default_par.default_value) + assert no_default_par.param_type.name == StringParameterType.name + assert no_default_par.default_value is None string_par = plugin.parameters[1] - self.assertEqual(string_par.param_type.name, StringParameterType.name) - self.assertEqual(string_par.default_value, "value") + assert string_par.param_type.name == StringParameterType.name + assert string_par.default_value == "value" float_par = plugin.parameters[2] - self.assertEqual(float_par.param_type.name, FloatParameterType.name) - self.assertEqual(float_par.default_value, 1.5) + assert float_par.param_type.name == FloatParameterType.name + assert float_par.default_value == 1.5 bool_par = plugin.parameters[3] - self.assertEqual(bool_par.param_type.name, BoolParameterType.name) - self.assertEqual(bool_par.default_value, True) + assert bool_par.param_type.name == BoolParameterType.name + assert bool_par.default_value is True if __name__ == "__main__": diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 3fa0937..47fe30a 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -6,7 +6,7 @@ @pytest.mark.skip(reason="cmem-plugin-examples is not added") -def test_discover_plugins(): +def test_discover_plugins() -> None: """Test plugin discovery.""" plugins = discover_plugins("cmem_plugin").plugins diff --git a/tests/test_icon.py b/tests/test_icon.py index fe8ee0e..0b67a8c 100644 --- a/tests/test_icon.py +++ b/tests/test_icon.py @@ -21,7 +21,7 @@ def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None return None -def test_for_errors(): +def test_for_errors() -> None: """Test Icon inits with errors.""" with pytest.raises(FileNotFoundError): Icon(file_name="no.file", package=__package__) @@ -31,20 +31,20 @@ def test_for_errors(): Icon(file_name="icons/test.nomime", package=__package__) -def test_svg(): +def test_svg() -> None: """Test SVG icon""" icon = Icon(file_name="icons/test.svg", package=__package__) assert icon.mime_type == "image/svg+xml" assert len(str(icon)) == 906 -def test_png(): +def test_png() -> None: """Test PNG icon""" icon = Icon(file_name="icons/test.png", package=__package__) assert icon.mime_type == "image/png" assert len(str(icon)) == 63818 -def test_plugin_init(): +def test_plugin_init() -> None: """Test initialization of a workflow plugin with custom icon.""" _ = MyWorkflowPlugin() diff --git a/tests/test_output_only_plugin.py b/tests/test_output_only_plugin.py index b2cfbf8..283a5d5 100644 --- a/tests/test_output_only_plugin.py +++ b/tests/test_output_only_plugin.py @@ -42,7 +42,7 @@ def execute(self, inputs=(), context=()) -> Entities: return Entities(entities=iter([entity1, entity2]), schema=schema) -def test_output_only_plugin(): +def test_output_only_plugin() -> None: """Test example Workflow Plugin.""" output_only = OutputOnlyPlugin(param1="test") result = output_only.execute() diff --git a/tests/test_types.py b/tests/test_types.py index 9d95156..7057183 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -20,7 +20,7 @@ class TypesTest(unittest.TestCase): class MissingType: """Test Missing Type""" - def test_missing_type(self): + def test_missing_type(self) -> None: """Test missing type""" self.assertRaisesRegex( ValueError, @@ -32,22 +32,22 @@ def test_missing_type(self): class BasicTypesTest(unittest.TestCase): """Test Basic Types""" - def test_detection(self): + def test_detection(self) -> None: """Test detection""" - self.assertEqual(ParameterTypes.get_type(str).name, "string") - self.assertEqual(ParameterTypes.get_type(int).name, "Long") - self.assertEqual(ParameterTypes.get_type(float).name, "double") - self.assertEqual(ParameterTypes.get_type(bool).name, "boolean") + assert ParameterTypes.get_type(str).name == "string" + assert ParameterTypes.get_type(int).name == "Long" + assert ParameterTypes.get_type(float).name == "double" + assert ParameterTypes.get_type(bool).name == "boolean" - def test_conversion(self): + def test_conversion(self) -> None: """Test conversion""" int_type = ParameterTypes.get_type(int) float_type = ParameterTypes.get_type(float) bool_type = ParameterTypes.get_type(bool) - self.assertEqual(int_type.from_string(int_type.to_string(3), context), 3) - self.assertEqual(float_type.from_string(int_type.to_string(1.2), context), 1.2) - self.assertEqual(bool_type.from_string(int_type.to_string(True), context), True) - self.assertEqual(bool_type.from_string(int_type.to_string(False), context), False) + assert int_type.from_string(int_type.to_string(3), context) == 3 + assert float_type.from_string(int_type.to_string(1.2), context) == 1.2 + assert bool_type.from_string(int_type.to_string(True), context) is True + assert bool_type.from_string(int_type.to_string(False), context) is False class EnumTest(unittest.TestCase): @@ -60,17 +60,17 @@ class Color(Enum): GREEN = 2 BLUE = 3 - def test_detection(self): + def test_detection(self) -> None: """Test detection""" - self.assertEqual(ParameterTypes.get_type(EnumTest.Color).name, "enumeration") + assert ParameterTypes.get_type(EnumTest.Color).name == "enumeration" - def test_conversion(self): + def test_conversion(self) -> None: """Test conversion""" enum = EnumParameterType(EnumTest.Color) - self.assertEqual(enum.to_string(enum.from_string("RED", context)), "RED") - self.assertEqual(enum.to_string(enum.from_string("GREEN", context)), "GREEN") + assert enum.to_string(enum.from_string("RED", context)) == "RED" + assert enum.to_string(enum.from_string("GREEN", context)) == "GREEN" - def test_invalid_values(self): + def test_invalid_values(self) -> None: """Test invalid values""" enum = EnumParameterType(EnumTest.Color) self.assertRaisesRegex( @@ -82,7 +82,7 @@ def test_invalid_values(self): ValueError, "not a valid value", lambda: enum.from_string("CYAN", context) ) - def test_autocomplete(self): + def test_autocomplete(self) -> None: """Test autocomplete""" enum = EnumParameterType(EnumTest.Color) self.assertListEqual( diff --git a/tests/test_utils_build_entities_from_data.py b/tests/test_utils_build_entities_from_data.py index cc3e03b..84a2e70 100644 --- a/tests/test_utils_build_entities_from_data.py +++ b/tests/test_utils_build_entities_from_data.py @@ -6,7 +6,7 @@ from cmem_plugin_base.dataintegration.utils.entity_builder import build_entities_from_data -def test_single_object(): +def test_single_object() -> None: """Test generation of entities and schema for a simple JSON object.""" test_data = """ { @@ -29,7 +29,7 @@ def test_single_object(): ) -def test_single_object_one_level(): +def test_single_object_one_level() -> None: """Test generation of entities and schema for a JSON object with one level of hierarchy """ @@ -72,7 +72,7 @@ def test_single_object_one_level(): ) -def test_single_object_one_level_array(): +def test_single_object_one_level_array() -> None: """Test generation of entities and schema for a JSON object with array object in first level of hierarchy """ @@ -119,7 +119,7 @@ def test_single_object_one_level_array(): ) -def test_single_object_two_level_array(): +def test_single_object_two_level_array() -> None: """Test generation of entities and schema for a JSON object with two levels of hierarchy """ @@ -186,7 +186,7 @@ def test_single_object_two_level_array(): ) -def test_array_object(): +def test_array_object() -> None: """Test generation of entities and schema for a simple array JSON object.""" test_data = """ [{ @@ -214,7 +214,7 @@ def test_array_object(): ) -def test_empty_object(): +def test_empty_object() -> None: """Test empty json object input""" test_data = """[]""" data = json.loads(test_data) diff --git a/tests/test_utils_write_to_dataset.py b/tests/test_utils_write_to_dataset.py index 5b5be0f..74a1319 100644 --- a/tests/test_utils_write_to_dataset.py +++ b/tests/test_utils_write_to_dataset.py @@ -13,7 +13,7 @@ @needs_cmem -def test_write_to_json_dataset(json_dataset): +def test_write_to_json_dataset(json_dataset) -> None: """Test write to json dataset""" project_name = json_dataset["project"] dataset_name = json_dataset["id"] @@ -31,7 +31,7 @@ def test_write_to_json_dataset(json_dataset): @needs_cmem -def test_write_to_not_valid_dataset(): +def test_write_to_not_valid_dataset() -> None: """Test write to not valid dataset""" with pytest.raises( requests.exceptions.HTTPError, @@ -45,7 +45,7 @@ def test_write_to_not_valid_dataset(): @needs_cmem -def test_write_to_invalid_format_dataset_id(): +def test_write_to_invalid_format_dataset_id() -> None: """Test write to invalid format dataset id""" with pytest.raises(ValueError, match=r"INVALID_DATASET_ID_FORMAT is not a valid task ID."): write_to_dataset("INVALID_DATASET_ID_FORMAT", io.StringIO("{}"), TestPluginContext().user) From b15b28f063c897190ef30814b4531373bfca66ec Mon Sep 17 00:00:00 2001 From: Sebastian Tramp Date: Fri, 13 Sep 2024 12:11:06 +0200 Subject: [PATCH 04/12] fix some easy issues --- cmem_plugin_base/dataintegration/__init__.py | 1 + cmem_plugin_base/dataintegration/context.py | 32 +++++++++++-------- .../dataintegration/description.py | 23 +++++++------ cmem_plugin_base/dataintegration/discovery.py | 6 ++-- cmem_plugin_base/dataintegration/entity.py | 12 ++++--- .../dataintegration/parameter/choice.py | 10 +++--- .../dataintegration/parameter/graph.py | 1 + cmem_plugin_base/dataintegration/plugins.py | 4 +-- cmem_plugin_base/dataintegration/ports.py | 6 ++-- cmem_plugin_base/dataintegration/types.py | 21 ++++++------ .../dataintegration/utils/__init__.py | 7 ++-- tests/conftest.py | 2 +- 12 files changed, 71 insertions(+), 54 deletions(-) diff --git a/cmem_plugin_base/dataintegration/__init__.py b/cmem_plugin_base/dataintegration/__init__.py index e69de29..39e1f2e 100644 --- a/cmem_plugin_base/dataintegration/__init__.py +++ b/cmem_plugin_base/dataintegration/__init__.py @@ -0,0 +1 @@ +"""dataintegration""" diff --git a/cmem_plugin_base/dataintegration/context.py b/cmem_plugin_base/dataintegration/context.py index 492b694..4b98cbf 100644 --- a/cmem_plugin_base/dataintegration/context.py +++ b/cmem_plugin_base/dataintegration/context.py @@ -14,16 +14,18 @@ class SystemContext: """Passed into methods to request general system information.""" def di_version(self) -> str: - """The version of the running DataIntegration instance.""" + """Get the version of the running DataIntegration instance""" def encrypt(self, value: str) -> str: - """Encrypts a value using the secret key, which is configured - in 'plugin.parameters.password.crypt.key' + """Encrypt a value using the secret key + + The key is configured in 'plugin.parameters.password.crypt.key'. """ def decrypt(self, value: str) -> str: - """Decrypts a value using the secret key, which is configured - in 'plugin.parameters.password.crypt.key' + """Decrypt a value using the secret key + + The key is configured in 'plugin.parameters.password.crypt.key'. """ @@ -31,28 +33,30 @@ class UserContext: """Passed into methods that are triggered by a user interaction.""" def user_uri(self) -> str: - """The URI of the user.""" + """Get the URI of the user""" def user_label(self) -> str: - """The name of the user.""" + """Get the name of the user""" def token(self) -> str: - """Retrieves the OAuth token for the user.""" + """Retrieve the oAuth token for the user""" class TaskContext: """Passed into objects that are part of a DataIntegration task/project.""" def project_id(self) -> str: - """The identifier of the project.""" + """Get the identifier of the project""" def task_id(self) -> str: - """The identifier of the task.""" + """Get the identifier of the task""" @dataclass() class ExecutionReport: - """Workflow operators may generate execution reports. An execution report holds + """Workflow Execution Report + + Workflow operators may generate execution reports. An execution report holds basic information and various statistics about the operator execution. """ @@ -86,7 +90,8 @@ class ReportContext: """Passed into workflow plugins that may generate a report during execution.""" def update(self, report: ExecutionReport) -> None: - """Updates the current execution report. + """Update the current execution report. + May be called repeatedly during operator execution. """ @@ -115,12 +120,13 @@ def workflow_id(self) -> str: def status(self) -> Literal["Idle", "Waiting", "Running", "Canceling", "Finished"]: """Retrieve the execution status of this plugin within the current workflow. + One of the following: - Idle: Plugin has not been started yet. - Waiting: Plugin has been started and is waiting to be executed. - Running: Plugin is currently being executed. - Canceling: Plugin has been requested to stop. - - Finished: Plugin has finished execution. + - Finished: Plugin has finished execution. """ diff --git a/cmem_plugin_base/dataintegration/description.py b/cmem_plugin_base/dataintegration/description.py index 199a862..22f0fa2 100644 --- a/cmem_plugin_base/dataintegration/description.py +++ b/cmem_plugin_base/dataintegration/description.py @@ -53,6 +53,10 @@ def __init__(self, file_name: str, package: str) -> None: raise ValueError(f"Guessed mime type '{self.mime_type}' does not start with 'image/'.") def __str__(self): + """Get data URI for the icon + + https://en.wikipedia.org/wiki/Data_URI_scheme + """ data_base64 = b64encode(self.data).decode() return f"""data:{self.mime_type};base64,{data_base64}""" @@ -73,7 +77,7 @@ class PluginParameter: :param visible: If true, the parameter will be displayed to the user in the UI. """ - def __init__( + def __init__( # noqa: PLR0913 self, name: str, label: str = "", @@ -104,9 +108,7 @@ class PluginDescription: :param icon: An optional custom plugin icon. """ - # pylint: disable=too-many-instance-attributes - - def __init__( # pylint: disable=too-many-arguments + def __init__( # noqa: PLR0913 self, plugin_class, label: str, @@ -182,8 +184,10 @@ class PluginDiscoveryResult: class Categories: - """A list of common plugin categories. At the moment, in the UI, - categories are only utilized for rule operators, such as transform plugins. + """A list of common plugin categories. + + At the moment, in the UI, categories are only utilized for rule operators, + such as transform plugins. """ # Plugins in the 'Recommended' category will be shown preferably @@ -229,7 +233,7 @@ class Plugin: plugins: list[PluginDescription] = [] - def __init__( + def __init__( # noqa: PLR0913 self, label: str, plugin_id: str | None = None, @@ -254,6 +258,7 @@ def __init__( self.parameters = parameters def __call__(self, func): + """Allow to call the instance""" plugin_desc = PluginDescription( plugin_class=func, label=self.label, @@ -268,9 +273,7 @@ def __call__(self, func): return func def retrieve_parameters(self, plugin_class: type) -> list[PluginParameter]: - """Retrieves parameters from a plugin class and matches them with the user - parameter definitions. - """ + """Retrieve parameters from a plugin class and matches them with the user parameter defs""" # Only return parameters for user-defined init methods. if not hasattr(plugin_class.__init__, "__code__"): return [] diff --git a/cmem_plugin_base/dataintegration/discovery.py b/cmem_plugin_base/dataintegration/discovery.py index dac30a0..81e135f 100644 --- a/cmem_plugin_base/dataintegration/discovery.py +++ b/cmem_plugin_base/dataintegration/discovery.py @@ -30,7 +30,7 @@ def get_packages(): def delete_modules(module_name: str = "cmem") -> None: - """Finds and deletes all plugins within a base package. + """Find and delete all plugins within a base package. :param module_name: The base package. Will recurse into all submodules of this package. @@ -46,7 +46,7 @@ def delete_modules(module_name: str = "cmem") -> None: def import_modules( package_name: str = "cmem", ) -> list[PluginDescription]: - """Finds and imports all plugins within a base package. + """Find and import all plugins within a base package. :param package_name: The base package. Will recurse into all submodules of this package. @@ -96,7 +96,7 @@ def discover_plugins(package_name: str = "cmem_plugin") -> PluginDiscoveryResult try: for plugin in import_modules(package_name=name): plugin_descriptions.plugins.append(plugin) - except BaseException as ex: + except BaseException as ex: # noqa: BLE001 error = PluginDiscoveryError( package_name=name, error_message=str(ex), diff --git a/cmem_plugin_base/dataintegration/entity.py b/cmem_plugin_base/dataintegration/entity.py index d7ff22d..5b39f10 100644 --- a/cmem_plugin_base/dataintegration/entity.py +++ b/cmem_plugin_base/dataintegration/entity.py @@ -18,7 +18,8 @@ def __init__(self, path: str, is_relation: bool = False, is_single_value: bool = self.is_relation = is_relation self.is_single_value = is_single_value - def __repr__(self): + def __repr__(self) -> str: + """Get a string representation""" obj = { "path": self.path, "is_relation": self.is_relation, @@ -26,7 +27,8 @@ def __repr__(self): } return f"EntityPath({obj})" - def __eq__(self, other): + def __eq__(self, other) -> bool: + """Compare""" return ( isinstance(other, EntityPath) and self.path == other.path @@ -57,11 +59,13 @@ def __init__( self.path_to_root = path_to_root self.sub_schemata = sub_schemata - def __repr__(self): + def __repr__(self) -> str: + """Get a string representation""" obj = {"type_uri": self.type_uri, "paths": self.paths, "path_to_root": self.path_to_root} return f"EntitySchema({obj})" - def __eq__(self, other): + def __eq__(self, other) -> bool: + """Compare""" return ( isinstance(other, EntitySchema) and self.type_uri == other.type_uri diff --git a/cmem_plugin_base/dataintegration/parameter/choice.py b/cmem_plugin_base/dataintegration/parameter/choice.py index 393d2d9..9951fe5 100644 --- a/cmem_plugin_base/dataintegration/parameter/choice.py +++ b/cmem_plugin_base/dataintegration/parameter/choice.py @@ -20,17 +20,15 @@ def __init__(self, choice_list: collections.OrderedDict[str, str]): self.choice_list = choice_list def label( - self, value: str, depend_on_parameter_values: list[Any], context: PluginContext + self, value: str, depend_on_parameter_values: list[Any], context: PluginContext # noqa: ARG002 ) -> str | None: - """Returns the label for the given choice value.""" + """Return the label for the given choice value.""" return self.choice_list[value] def autocomplete( - self, - query_terms: list[str], - depend_on_parameter_values: list[Any], - context: PluginContext, + self, query_terms: list[str], depend_on_parameter_values: list[Any], context: PluginContext, # noqa: ARG002 ) -> list[Autocompletion]: + """Autocompletion request - Returns all results that match ALL provided query terms.""" result = [] for identifier in self.choice_list: label = self.choice_list[identifier] diff --git a/cmem_plugin_base/dataintegration/parameter/graph.py b/cmem_plugin_base/dataintegration/parameter/graph.py index a625017..2f99c94 100644 --- a/cmem_plugin_base/dataintegration/parameter/graph.py +++ b/cmem_plugin_base/dataintegration/parameter/graph.py @@ -52,6 +52,7 @@ def autocomplete( depend_on_parameter_values: list[Any], context: PluginContext, ) -> list[Autocompletion]: + """Autocompletion request - Returns all results that match ALL provided query terms""" setup_cmempy_user_access(context=context.user) graphs = get_graphs_list() result = [] diff --git a/cmem_plugin_base/dataintegration/plugins.py b/cmem_plugin_base/dataintegration/plugins.py index f275ae6..71df56e 100644 --- a/cmem_plugin_base/dataintegration/plugins.py +++ b/cmem_plugin_base/dataintegration/plugins.py @@ -66,7 +66,7 @@ class WorkflowPlugin(PluginBase): Should be `None`, if this operator does not return any output.""" def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Entities | None: - """Executes the workflow plugin on a given collection of entities. + """Execute the workflow plugin on a given collection of entities. :param inputs: Contains a separate collection of entities for each input. Currently, DI sends ALWAYS an input. in case no connected @@ -85,7 +85,7 @@ class TransformPlugin(PluginBase): """Base class of all transform operator plugins.""" def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: - """Transforms a collection of values. + """Transform a collection of values. :param inputs: A sequence which contains as many elements as there are input operators for this transformation. diff --git a/cmem_plugin_base/dataintegration/ports.py b/cmem_plugin_base/dataintegration/ports.py index dc301a0..417b9e4 100644 --- a/cmem_plugin_base/dataintegration/ports.py +++ b/cmem_plugin_base/dataintegration/ports.py @@ -17,8 +17,8 @@ def __init__(self, schema: EntitySchema): class FlexibleSchemaPort(Port): - """Port that does not have a fixed schema, but will adapt its schema to the - connected port. + """Port that does not have a fixed schema, but will adapt its schema to the connected port. + Flexible input ports will adapt the schema to the connected output. Flexible output ports will adapt the schema to the connected input. It is not allowed to connect two flexible ports. @@ -27,6 +27,7 @@ class FlexibleSchemaPort(Port): class UnknownSchemaPort(Port): """Port for which the schema is not known in advance. + This includes output ports with a schema that depends on external factors (e.g., REST requests). """ @@ -45,5 +46,6 @@ def __init__(self, ports: Sequence[Port]): class FlexibleNumberOfInputs(InputPorts): """Operator accepts a flexible number of inputs. + At the moment, each input is a flexible schema port. """ diff --git a/cmem_plugin_base/dataintegration/types.py b/cmem_plugin_base/dataintegration/types.py index 3cf50db..5131d2e 100644 --- a/cmem_plugin_base/dataintegration/types.py +++ b/cmem_plugin_base/dataintegration/types.py @@ -1,9 +1,10 @@ """Parameter types.""" +from collections.abc import Iterable from dataclasses import dataclass from enum import Enum from inspect import Parameter -from typing import Optional, TypeVar, Generic, Type, Iterable, Any +from typing import Any, Generic, Optional, Type, TypeVar from cmem_plugin_base.dataintegration.context import PluginContext @@ -15,7 +16,7 @@ class Autocompletion: value: str """The value to which the parameter value should be set.""" - label: Optional[str] + label: str | None """An optional label that a human user would see instead.""" @@ -23,8 +24,10 @@ class Autocompletion: class ParameterType(Generic[T]): - """Represents a plugin parameter type. - Provides string-based serialization and autocompletion.""" + """Represent a plugin parameter type. + + Provides string-based serialization and autocompletion. + """ name: str """The name by which this type can be identified. If available, @@ -45,8 +48,6 @@ class ParameterType(Generic[T]): The values of all parameters specified here will be provided to the autocomplete function.""" - # flake8: noqa - # pylint: disable=no-member def get_type(self): """Retrieves the type that is supported by a given instance.""" return self.__orig_bases__[0].__args__[0] @@ -58,15 +59,13 @@ def to_string(self, value: T) -> str: """Converts parameter values into their string representation.""" return str(value) - # pylint: disable=unused-argument def autocomplete( self, query_terms: list[str], depend_on_parameter_values: list[Any], context: PluginContext, ) -> list[Autocompletion]: - """Autocompletion request. - Returns all results that match ALL provided query terms. + """Autocompletion request - Returns all results that match ALL provided query terms. :param query_terms: A list of lower case conjunctive search terms. :param depend_on_parameter_values The values of the parameters specified by @@ -126,7 +125,8 @@ class BoolParameterType(ParameterType[bool]): name = "boolean" - def from_string(self, value: str, context: PluginContext) -> bool: + def from_string(self, value: str, context: PluginContext) -> bool: # noqa: ARG002 + """Get boolean value from string""" lower = value.lower() if lower in ("true", "1"): return True @@ -135,6 +135,7 @@ def from_string(self, value: str, context: PluginContext) -> bool: raise ValueError("Value must be either 'true' or 'false'") def to_string(self, value: bool) -> str: + """Get string value from boolean""" if value: return "true" return "false" diff --git a/cmem_plugin_base/dataintegration/utils/__init__.py b/cmem_plugin_base/dataintegration/utils/__init__.py index 729cb6f..eaa9dc2 100644 --- a/cmem_plugin_base/dataintegration/utils/__init__.py +++ b/cmem_plugin_base/dataintegration/utils/__init__.py @@ -10,14 +10,15 @@ def generate_id(name: str) -> str: - """Generates a valid DataIntegration identifier from a string. + """Generate a valid DataIntegration identifier from a string. + Characters that are not allowed in an identifier are removed. """ return re.sub(r"[^a-zA-Z0-9_-]", "", name) def setup_cmempy_user_access(context: UserContext | None) -> None: - """Setup environment for accessing CMEM with cmempy.""" + """Set up environment for accessing CMEM with cmempy.""" if context is None: raise ValueError("No UserContext given.") if context.token() is None: @@ -29,7 +30,7 @@ def setup_cmempy_user_access(context: UserContext | None) -> None: def setup_cmempy_super_user_access() -> None: - """Setup environment for accessing CMEM with cmempy. + """Set up environment for accessing CMEM with cmempy. The helper function is used to setup the environment for accessing CMEM with cmempy. It does nothing if there is already a working environment. diff --git a/tests/conftest.py b/tests/conftest.py index 137832e..a905f53 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,7 +33,7 @@ def _json_dataset(): @pytest.fixture(name="json_resource", scope="module") def _json_resource(): - """Setup json resource""" + """Set up json resource""" _project_name = "resource_test_project" _resource_name = "sample_test.json" make_new_project(_project_name) From ee83f2a50937d70a052b378eae54ef9fe53cd12c Mon Sep 17 00:00:00 2001 From: Sebastian Tramp Date: Fri, 13 Sep 2024 14:19:01 +0200 Subject: [PATCH 05/12] fix more issues --- tests/conftest.py | 8 +++++--- tests/parameter_types/__init__.py | 1 + tests/parameter_types/test_dataset.py | 2 +- tests/parameter_types/test_password.py | 2 +- tests/test_icon.py | 15 +++++++++------ tests/utils.py | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a905f53..c2040de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ """pytest conftest module""" import io +from collections.abc import Generator from dataclasses import dataclass import pytest @@ -17,8 +18,8 @@ @pytest.fixture(name="json_dataset", scope="module") -def _json_dataset(): - """Setup""" +def _json_dataset() -> Generator[dict, None, None]: + """Provide a dataset""" make_new_project(PROJECT_NAME) make_new_dataset( project_name=PROJECT_NAME, @@ -27,7 +28,8 @@ def _json_dataset(): parameters={"file": RESOURCE_NAME}, autoconfigure=False, ) - yield get_dataset(PROJECT_NAME, DATASET_NAME) + dataset = get_dataset(PROJECT_NAME, DATASET_NAME) + yield dataset delete_project(PROJECT_NAME) diff --git a/tests/parameter_types/__init__.py b/tests/parameter_types/__init__.py index e69de29..0e9f173 100644 --- a/tests/parameter_types/__init__.py +++ b/tests/parameter_types/__init__.py @@ -0,0 +1 @@ +"""tests""" \ No newline at end of file diff --git a/tests/parameter_types/test_dataset.py b/tests/parameter_types/test_dataset.py index fae1852..b77e394 100644 --- a/tests/parameter_types/test_dataset.py +++ b/tests/parameter_types/test_dataset.py @@ -5,7 +5,7 @@ @needs_cmem -def test_dataset_parameter_type_completion(json_dataset) -> None: +def test_dataset_parameter_type_completion(json_dataset: dict) -> None: """Test dataset parameter type completion""" project_name = json_dataset["project"] dataset_name = json_dataset["id"] diff --git a/tests/parameter_types/test_password.py b/tests/parameter_types/test_password.py index b443765..b2a63ad 100644 --- a/tests/parameter_types/test_password.py +++ b/tests/parameter_types/test_password.py @@ -25,7 +25,7 @@ class MyTransformPlugin(TransformPlugin): def __init__(self, password: Password) -> None: self.password = password - def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: + def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: # noqa: ARG002 """Test transform""" return [] diff --git a/tests/test_icon.py b/tests/test_icon.py index 0b67a8c..b305f74 100644 --- a/tests/test_icon.py +++ b/tests/test_icon.py @@ -17,32 +17,35 @@ class MyWorkflowPlugin(WorkflowPlugin): """My Workflow Plugin Class""" - def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: - return None + def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: # noqa: ARG002 + """Execute the workflow plugin on a given collection of entities.""" + return def test_for_errors() -> None: """Test Icon inits with errors.""" with pytest.raises(FileNotFoundError): Icon(file_name="no.file", package=__package__) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^Guessed mime type.*does not start with.*image.*"): Icon(file_name="icons/test.txt", package=__package__) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="^Could not guess the mime type of the file"): Icon(file_name="icons/test.nomime", package=__package__) def test_svg() -> None: """Test SVG icon""" icon = Icon(file_name="icons/test.svg", package=__package__) + data_iri_length = 906 assert icon.mime_type == "image/svg+xml" - assert len(str(icon)) == 906 + assert len(str(icon)) == data_iri_length def test_png() -> None: """Test PNG icon""" icon = Icon(file_name="icons/test.png", package=__package__) + data_iri_length = 63818 assert icon.mime_type == "image/png" - assert len(str(icon)) == 63818 + assert len(str(icon)) == data_iri_length def test_plugin_init() -> None: diff --git a/tests/utils.py b/tests/utils.py index 7f5cda7..8d88234 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -21,7 +21,7 @@ class TestUserContext(UserContext): def __init__(self): # get access token from default service account - access_token: str = get_token()["access_token"] # type: ignore + access_token: str = get_token()["access_token"] self.token = lambda: access_token From 1e9a6e72d75cb2811c7ead418944e8d4a099f5f0 Mon Sep 17 00:00:00 2001 From: Robert Isele Date: Tue, 24 Sep 2024 11:27:51 +0200 Subject: [PATCH 06/12] Fix mypy issues --- .../dataintegration/description.py | 4 +-- tests/parameter_types/test_code.py | 9 +++++++ tests/parameter_types/test_password.py | 3 +-- tests/test_description.py | 4 +++ tests/test_utils_build_entities_from_data.py | 27 +++++++++++-------- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/cmem_plugin_base/dataintegration/description.py b/cmem_plugin_base/dataintegration/description.py index 22f0fa2..01fefe1 100644 --- a/cmem_plugin_base/dataintegration/description.py +++ b/cmem_plugin_base/dataintegration/description.py @@ -275,11 +275,11 @@ def __call__(self, func): def retrieve_parameters(self, plugin_class: type) -> list[PluginParameter]: """Retrieve parameters from a plugin class and matches them with the user parameter defs""" # Only return parameters for user-defined init methods. - if not hasattr(plugin_class.__init__, "__code__"): + if not hasattr(plugin_class.__init__, "__code__"): # type: ignore[misc] return [] # Collect parameters from init method params = [] - sig = inspect.signature(plugin_class.__init__) + sig = inspect.signature(plugin_class.__init__) # type: ignore[misc] for name in sig.parameters: if name != "self": param = next((p for p in self.parameters if p.name == name), None) diff --git a/tests/parameter_types/test_code.py b/tests/parameter_types/test_code.py index 292daff..d336b99 100644 --- a/tests/parameter_types/test_code.py +++ b/tests/parameter_types/test_code.py @@ -57,6 +57,15 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: MyTransformPlugin() plugin = Plugin.plugins[0] + assert plugin.parameters[0].param_type is not None + assert plugin.parameters[1].param_type is not None + assert plugin.parameters[2].param_type is not None + assert plugin.parameters[3].param_type is not None + assert plugin.parameters[4].param_type is not None + assert plugin.parameters[5].param_type is not None + assert plugin.parameters[6].param_type is not None + assert plugin.parameters[7].param_type is not None + assert plugin.parameters[0].param_type.name == "code-xml" assert plugin.parameters[1].param_type.name == "code-json" assert plugin.parameters[2].param_type.name == "code-jinja2" diff --git a/tests/parameter_types/test_password.py b/tests/parameter_types/test_password.py index b2a63ad..13f3d0b 100644 --- a/tests/parameter_types/test_password.py +++ b/tests/parameter_types/test_password.py @@ -29,10 +29,9 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: # noqa: """Test transform""" return [] - Plugin.plugins.append(MyTransformPlugin) - plugin = Plugin.plugins[0] password_param = plugin.parameters[0] + assert password_param.param_type is not None assert password_param.param_type.name == PasswordParameterType.name diff --git a/tests/test_description.py b/tests/test_description.py index f0bf65d..b200c77 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -42,18 +42,22 @@ def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: plugin = Plugin.plugins[0] no_default_par = plugin.parameters[0] + assert no_default_par.param_type is not None assert no_default_par.param_type.name == StringParameterType.name assert no_default_par.default_value is None string_par = plugin.parameters[1] + assert string_par.param_type is not None assert string_par.param_type.name == StringParameterType.name assert string_par.default_value == "value" float_par = plugin.parameters[2] + assert float_par.param_type is not None assert float_par.param_type.name == FloatParameterType.name assert float_par.default_value == 1.5 bool_par = plugin.parameters[3] + assert bool_par.param_type is not None assert bool_par.param_type.name == BoolParameterType.name assert bool_par.default_value is True diff --git a/tests/test_utils_build_entities_from_data.py b/tests/test_utils_build_entities_from_data.py index 84a2e70..ad6a350 100644 --- a/tests/test_utils_build_entities_from_data.py +++ b/tests/test_utils_build_entities_from_data.py @@ -2,10 +2,17 @@ import json -from cmem_plugin_base.dataintegration.entity import EntityPath, EntitySchema +from cmem_plugin_base.dataintegration.entity import EntityPath, EntitySchema, Entities from cmem_plugin_base.dataintegration.utils.entity_builder import build_entities_from_data +def build_entities_from_json(json_data: str) -> Entities: + data = json.loads(json_data) + entities = build_entities_from_data(data) + assert entities is not None + return entities + + def test_single_object() -> None: """Test generation of entities and schema for a simple JSON object.""" test_data = """ @@ -13,8 +20,7 @@ def test_single_object() -> None: "name": "sai", "email": "saipraneeth@example.com" }""" - data = json.loads(test_data) - entities = build_entities_from_data(data) + entities = build_entities_from_json(test_data) assert len(list(entities.entities)) == 1 for _ in entities.entities: assert len(_.values) == 2 @@ -42,8 +48,7 @@ def test_single_object_one_level() -> None: "country": "United States" } }""" - data = json.loads(test_data) - entities = build_entities_from_data(data) + entities = build_entities_from_json(test_data) assert len(list(entities.entities)) == 1 for _ in entities.entities: assert len(_.values) == 3 @@ -59,6 +64,7 @@ def test_single_object_one_level() -> None: ], ) # Validate sub entities + assert entities.sub_entities is not None for _ in entities.sub_entities: for _entity in _.entities: assert len(_entity.values) == 2 @@ -89,8 +95,7 @@ def test_single_object_one_level_array() -> None: "country": "United States" }] }""" - data = json.loads(test_data) - entities = build_entities_from_data(data) + entities = build_entities_from_json(test_data) assert len(list(entities.entities)) == 1 for _ in entities.entities: assert len(_.values) == 3 @@ -106,6 +111,7 @@ def test_single_object_one_level_array() -> None: ], ) # Validate sub entities + assert entities.sub_entities is not None for _ in entities.sub_entities: assert len(list(_.entities)) == 2 for _entity in _.entities: @@ -146,8 +152,7 @@ def test_single_object_two_level_array() -> None: } ] }""" - data = json.loads(test_data) - entities = build_entities_from_data(data) + entities = build_entities_from_json(test_data) assert len(list(entities.entities)) == 1 for _ in entities.entities: assert len(_.values) == 3 @@ -163,6 +168,7 @@ def test_single_object_two_level_array() -> None: ], ) # Validate sub entities + assert entities.sub_entities is not None location_entities = entities.sub_entities[0] city_entities = entities.sub_entities[1] assert len(list(city_entities.entities)) == 2 @@ -196,8 +202,7 @@ def test_array_object() -> None: "name": "sai", "email": "saipraneeth@example.com" }]""" - data = json.loads(test_data) - entities = build_entities_from_data(data) + entities = build_entities_from_json(test_data) _ = next(entities.entities) assert len(_.values) == 2 assert _.values == [["seebi"], [""]] From da18c98527379f3d673e4813c47251d62d365281 Mon Sep 17 00:00:00 2001 From: Robert Isele Date: Tue, 24 Sep 2024 13:50:18 +0200 Subject: [PATCH 07/12] Fixed lots of Ruff issues (but not all yet) --- .../dataintegration/description.py | 12 ++-- cmem_plugin_base/dataintegration/discovery.py | 4 +- cmem_plugin_base/dataintegration/entity.py | 13 +++-- .../dataintegration/parameter/choice.py | 8 +-- .../dataintegration/parameter/dataset.py | 7 ++- .../dataintegration/parameter/graph.py | 2 +- .../dataintegration/parameter/password.py | 8 ++- .../dataintegration/parameter/resource.py | 1 + cmem_plugin_base/dataintegration/types.py | 58 +++++++++++-------- .../dataintegration/utils/__init__.py | 1 - .../dataintegration/utils/entity_builder.py | 19 +++--- tests/conftest.py | 2 +- tests/parameter_types/__init__.py | 2 +- tests/parameter_types/test_code.py | 18 +++--- tests/parameter_types/test_password.py | 2 +- tests/test_icon.py | 2 +- tests/test_output_only_plugin.py | 5 +- tests/test_utils_build_entities_from_data.py | 17 +++--- tests/test_utils_write_to_dataset.py | 2 +- tests/utils.py | 11 +++- 20 files changed, 110 insertions(+), 84 deletions(-) diff --git a/cmem_plugin_base/dataintegration/description.py b/cmem_plugin_base/dataintegration/description.py index 01fefe1..8654493 100644 --- a/cmem_plugin_base/dataintegration/description.py +++ b/cmem_plugin_base/dataintegration/description.py @@ -7,7 +7,7 @@ from inspect import _empty from mimetypes import guess_type from pkgutil import get_data -from typing import Any +from typing import Any, ClassVar from cmem_plugin_base.dataintegration.plugins import TransformPlugin, WorkflowPlugin from cmem_plugin_base.dataintegration.types import ( @@ -83,7 +83,7 @@ def __init__( # noqa: PLR0913 label: str = "", description: str = "", param_type: ParameterType | None = None, - default_value: Any | None = None, + default_value: Any | None = None, # noqa: ANN401 advanced: bool = False, visible: bool = True, ) -> None: @@ -110,7 +110,7 @@ class PluginDescription: def __init__( # noqa: PLR0913 self, - plugin_class, + plugin_class: type, label: str, plugin_id: str | None = None, description: str = "", @@ -126,7 +126,7 @@ def __init__( # noqa: PLR0913 elif issubclass(plugin_class, TransformPlugin): self.plugin_type = "TransformPlugin" else: - raise ValueError( + raise TypeError( f"Class {plugin_class.__name__} does not implement a supported " f"plugin base class (e.g., WorkflowPlugin)." ) @@ -231,7 +231,7 @@ class Plugin: :param icon: Optional custom plugin icon. """ - plugins: list[PluginDescription] = [] + plugins: ClassVar[list[PluginDescription]] = [] def __init__( # noqa: PLR0913 self, @@ -257,7 +257,7 @@ def __init__( # noqa: PLR0913 else: self.parameters = parameters - def __call__(self, func): + def __call__(self, func: type): """Allow to call the instance""" plugin_desc = PluginDescription( plugin_class=func, diff --git a/cmem_plugin_base/dataintegration/discovery.py b/cmem_plugin_base/dataintegration/discovery.py index 81e135f..cb3b472 100644 --- a/cmem_plugin_base/dataintegration/discovery.py +++ b/cmem_plugin_base/dataintegration/discovery.py @@ -17,7 +17,7 @@ ) -def get_packages(): +def get_packages() -> object: """Get installed python packages. Returns a list of dict with the following keys: @@ -25,7 +25,7 @@ def get_packages(): - version - package version """ return json.loads( - check_output(["pip", "list", "--format", "json"], shell=False) # nosec + check_output(["pip", "list", "--format", "json"], shell=False) # noqa: S603, S607 ) diff --git a/cmem_plugin_base/dataintegration/entity.py b/cmem_plugin_base/dataintegration/entity.py index 5b39f10..1bd0f38 100644 --- a/cmem_plugin_base/dataintegration/entity.py +++ b/cmem_plugin_base/dataintegration/entity.py @@ -27,7 +27,7 @@ def __repr__(self) -> str: } return f"EntityPath({obj})" - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: """Compare""" return ( isinstance(other, EntityPath) @@ -43,7 +43,7 @@ class EntitySchema: :param type_uri: The entity type :param paths: Ordered list of paths :param path_to_root: Specifies a path which defines where this schema is located - in the schema tree + in the schema tree. Empty by default. :param sub_schemata: Nested entity schemata """ @@ -51,12 +51,15 @@ def __init__( self, type_uri: str, paths: Sequence[EntityPath], - path_to_root: EntityPath = EntityPath(""), + path_to_root: EntityPath | None = None, sub_schemata: Sequence["EntitySchema"] | None = None, ) -> None: self.type_uri = type_uri self.paths = paths - self.path_to_root = path_to_root + if path_to_root is None: + self.path_to_root = EntityPath("") + else: + self.path_to_root = path_to_root self.sub_schemata = sub_schemata def __repr__(self) -> str: @@ -64,7 +67,7 @@ def __repr__(self) -> str: obj = {"type_uri": self.type_uri, "paths": self.paths, "path_to_root": self.path_to_root} return f"EntitySchema({obj})" - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: """Compare""" return ( isinstance(other, EntitySchema) diff --git a/cmem_plugin_base/dataintegration/parameter/choice.py b/cmem_plugin_base/dataintegration/parameter/choice.py index 9951fe5..8c7b88a 100644 --- a/cmem_plugin_base/dataintegration/parameter/choice.py +++ b/cmem_plugin_base/dataintegration/parameter/choice.py @@ -20,13 +20,13 @@ def __init__(self, choice_list: collections.OrderedDict[str, str]): self.choice_list = choice_list def label( - self, value: str, depend_on_parameter_values: list[Any], context: PluginContext # noqa: ARG002 + self, value: str, depend_on_parameter_values: list[Any], context: PluginContext ) -> str | None: """Return the label for the given choice value.""" return self.choice_list[value] def autocomplete( - self, query_terms: list[str], depend_on_parameter_values: list[Any], context: PluginContext, # noqa: ARG002 + self, query_terms: list[str], depend_on_parameter_values: list[Any], context: PluginContext, ) -> list[Autocompletion]: """Autocompletion request - Returns all results that match ALL provided query terms.""" result = [] @@ -37,6 +37,6 @@ def autocomplete( result.append(Autocompletion(value=identifier, label=label)) for term in query_terms: if term.lower() in label.lower(): - result.append(Autocompletion(value=identifier, label=label)) - result.sort(key=lambda x: x.label) # type: ignore + result.append(Autocompletion(value=identifier, label=label)) # noqa: PERF401 + result.sort(key=lambda x: x.label) return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/parameter/dataset.py b/cmem_plugin_base/dataintegration/parameter/dataset.py index bc821b5..46d013e 100644 --- a/cmem_plugin_base/dataintegration/parameter/dataset.py +++ b/cmem_plugin_base/dataintegration/parameter/dataset.py @@ -26,7 +26,7 @@ def __init__(self, dataset_type: str | None = None): def label( self, value: str, depend_on_parameter_values: list[Any], context: PluginContext ) -> str | None: - """Returns the label for the given dataset.""" + """Return the label for the given dataset.""" setup_cmempy_user_access(context.user) task_label = str(get_task(project=context.project_id, task=value)["metadata"]["label"]) return f"{task_label}" @@ -37,6 +37,7 @@ def autocomplete( depend_on_parameter_values: list[Any], context: PluginContext, ) -> list[Autocompletion]: + """Autocompletion request - Returns all results that match all provided query terms.""" setup_cmempy_user_access(context.user) datasets = list_items(item_type="dataset", project=context.project_id)["results"] @@ -50,9 +51,9 @@ def autocomplete( continue for term in query_terms: if term.lower() in label.lower(): - result.append(Autocompletion(value=identifier, label=label)) + result.append(Autocompletion(value=identifier, label=label)) # noqa: PERF401 if len(query_terms) == 0: # add any dataset to list if no search terms are given result.append(Autocompletion(value=identifier, label=label)) - result.sort(key=lambda x: x.label) # type: ignore + result.sort(key=lambda x: x.label) return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/parameter/graph.py b/cmem_plugin_base/dataintegration/parameter/graph.py index 2f99c94..bc90b10 100644 --- a/cmem_plugin_base/dataintegration/parameter/graph.py +++ b/cmem_plugin_base/dataintegration/parameter/graph.py @@ -88,5 +88,5 @@ def autocomplete( if term.lower() in label.lower(): result.append(Autocompletion(value=iri, label=label)) continue - result.sort(key=lambda x: x.label) # type: ignore + result.sort(key=lambda x: x.label) return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/parameter/password.py b/cmem_plugin_base/dataintegration/parameter/password.py index 3b06f99..e923379 100644 --- a/cmem_plugin_base/dataintegration/parameter/password.py +++ b/cmem_plugin_base/dataintegration/parameter/password.py @@ -12,7 +12,7 @@ def __init__(self, encrypted_value: str, system: SystemContext): self.system = system def decrypt(self) -> str: - """Returns the decrypted value""" + """Return the decrypted value""" return self.system.decrypt(self.encrypted_value) @@ -26,7 +26,8 @@ class PasswordParameterType(ParameterType[Password]): """Prefix to identify already encrypted values.""" def from_string(self, value: str, context: PluginContext) -> Password: - """Parses strings into parameter values. + """Parse strings into parameter values. + Decrypts the password if the encryption preamble is present """ if value is None or value == "": @@ -38,7 +39,8 @@ def from_string(self, value: str, context: PluginContext) -> Password: return Password(encrypted_value, context.system) def to_string(self, value: Password) -> str: - """Converts parameter values into their string representation. + """Convert parameter values into their string representation. + Encrypts the password so that it won't be stored verbatim. """ if value.encrypted_value == "": diff --git a/cmem_plugin_base/dataintegration/parameter/resource.py b/cmem_plugin_base/dataintegration/parameter/resource.py index 74def09..d79d3ac 100644 --- a/cmem_plugin_base/dataintegration/parameter/resource.py +++ b/cmem_plugin_base/dataintegration/parameter/resource.py @@ -22,6 +22,7 @@ def autocomplete( depend_on_parameter_values: list[Any], context: PluginContext, ) -> list[Autocompletion]: + """Autocompletion request - Returns all results that match ALL provided query terms.""" setup_cmempy_user_access(context.user) resources = get_resources(context.project_id) result = [ diff --git a/cmem_plugin_base/dataintegration/types.py b/cmem_plugin_base/dataintegration/types.py index 5131d2e..b5d2917 100644 --- a/cmem_plugin_base/dataintegration/types.py +++ b/cmem_plugin_base/dataintegration/types.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from enum import Enum from inspect import Parameter -from typing import Any, Generic, Optional, Type, TypeVar +from typing import Any, ClassVar, Generic, TypeVar from cmem_plugin_base.dataintegration.context import PluginContext @@ -42,21 +42,21 @@ class ParameterType(Generic[T]): """Signals that the auto-completed values have labels that must be displayed to the user.""" - autocompletion_depends_on_parameters: list[str] = [] + autocompletion_depends_on_parameters: ClassVar[list[str]] = [] """The other plugin parameters the auto-completion depends on. Without those values given no auto-completion is possible. The values of all parameters specified here will be provided to the autocomplete function.""" - def get_type(self): - """Retrieves the type that is supported by a given instance.""" + def get_type(self) -> type: + """Retrieve the type that is supported by a given instance.""" return self.__orig_bases__[0].__args__[0] def from_string(self, value: str, context: PluginContext) -> T: - """Parses strings into parameter values.""" + """Parse strings into parameter values.""" def to_string(self, value: T) -> str: - """Converts parameter values into their string representation.""" + """Convert parameter values into their string representation.""" return str(value) def autocomplete( @@ -77,8 +77,8 @@ def autocomplete( def label( self, value: str, depend_on_parameter_values: list[Any], context: PluginContext - ) -> Optional[str]: - """Returns the label if exists for the given value. + ) -> str | None: + """Return the label if exists for the given value. :param value: The value for which a label should be generated. :param depend_on_parameter_values The values of the parameters specified @@ -88,8 +88,11 @@ def label( return None def autocompletion_enabled(self) -> bool: - """True, if autocompletion should be enabled on this type. - By default, checks if the type implements its own autocomplete method.""" + """Enable autocompletion. + + True, if autocompletion should be enabled on this type. + By default, checks if the type implements its own autocomplete method. + """ return type(self).autocomplete != ParameterType.autocomplete @@ -99,6 +102,7 @@ class StringParameterType(ParameterType[str]): name = "string" def from_string(self, value: str, context: PluginContext) -> str: + """Return the string.""" return value @@ -108,6 +112,7 @@ class IntParameterType(ParameterType[int]): name = "Long" def from_string(self, value: str, context: PluginContext) -> int: + """Parse string into int.""" return int(value) @@ -117,6 +122,7 @@ class FloatParameterType(ParameterType[float]): name = "double" def from_string(self, value: str, context: PluginContext) -> float: + """Parse string into float.""" return float(value) @@ -125,7 +131,7 @@ class BoolParameterType(ParameterType[bool]): name = "boolean" - def from_string(self, value: str, context: PluginContext) -> bool: # noqa: ARG002 + def from_string(self, value: str, context: PluginContext) -> bool: """Get boolean value from string""" lower = value.lower() if lower in ("true", "1"): @@ -147,9 +153,11 @@ class PluginContextParameterType(ParameterType[PluginContext]): name = "PluginContext" def from_string(self, value: str, context: PluginContext) -> PluginContext: + """Return the plugin context.""" return context def to_string(self, value: PluginContext) -> str: + """Return an empty string, since from_string will always return the plugin context.""" return "" @@ -160,11 +168,12 @@ class EnumParameterType(ParameterType[Enum]): allow_only_autocompleted_values = True - def __init__(self, enum_type: Type[Enum]): + def __init__(self, enum_type: type[Enum]): super().__init__() self.enum_type = enum_type def from_string(self, value: str, context: PluginContext) -> Enum: + """Parse string into the corresponding enum value.""" values = self.enum_type.__members__ if not value: raise ValueError("Empty value is not allowed.") @@ -174,6 +183,7 @@ def from_string(self, value: str, context: PluginContext) -> Enum: return values[value] def to_string(self, value: Enum) -> str: + """Convert an enum into its string value.""" return str(value.name) def autocomplete( @@ -182,6 +192,7 @@ def autocomplete( depend_on_parameter_values: list[str], context: PluginContext, ) -> list[Autocompletion]: + """Autocompletion request - Returns all results that match all provided query terms.""" values = self.enum_type.__members__.keys() return list(self.find_matches(query_terms, values)) @@ -189,14 +200,14 @@ def autocomplete( def find_matches( lower_case_terms: list[str], values: Iterable[str] ) -> Iterable[Autocompletion]: - """Finds auto completions in a list of values""" + """Find auto completions in a list of values""" for value in values: if EnumParameterType.matches_search_term(lower_case_terms, value.lower()): yield Autocompletion(value, value) @staticmethod def matches_search_term(lower_case_terms: list[str], search_in: str) -> bool: - """Tests if a string contains a list of (lower case) search terms.""" + """Test if a string contains a list of (lower case) search terms.""" lower_case_text = search_in.lower() return all(search_term in lower_case_text for search_term in lower_case_terms) @@ -204,7 +215,7 @@ def matches_search_term(lower_case_terms: list[str], search_in: str) -> bool: class ParameterTypes: """Manages the available parameter types.""" - registered_types: list[ParameterType] = [ + registered_types: ClassVar[list[ParameterType]] = [ StringParameterType(), BoolParameterType(), IntParameterType(), @@ -214,18 +225,20 @@ class ParameterTypes: @staticmethod def register_type(param_type: ParameterType) -> None: - """Registers a new custom parameter type. All registered types will be detected + """Register a new custom parameter type. + + All registered types will be detected in plugin constructors. If a type with an existing name is registered, it will - overwrite the previous one.""" + overwrite the previous one. + """ ParameterTypes.registered_types = [ t for t in ParameterTypes.registered_types if t.name != param_type.name ] ParameterTypes.registered_types.append(param_type) @staticmethod - def get_type(param_type: Type) -> ParameterType: - """Retrieves the ParameterType instance for a given type.""" - + def get_type(param_type: type) -> ParameterType: + """Retrieve the ParameterType instance for a given type.""" if issubclass(param_type, Enum): return EnumParameterType(param_type) found_type = next( @@ -233,7 +246,7 @@ def get_type(param_type: Type) -> ParameterType: None, ) if found_type is None: - mapped = map(lambda t: str(t.get_type().__name__), ParameterTypes.registered_types) + mapped = [str(t.get_type().__name__) for t in ParameterTypes.registered_types] raise ValueError( f"Parameter has an unsupported type {param_type.__name__}. " "Supported types are: Enum, " @@ -243,8 +256,7 @@ def get_type(param_type: Type) -> ParameterType: @staticmethod def get_param_type(param: Parameter) -> ParameterType: - """Retrieves the ParameterType instance for a given parameter.""" - + """Retrieve the ParameterType instance for a given parameter.""" if param.annotation == Parameter.empty: # If there is no type annotation, DI should send the parameter as a string return StringParameterType() diff --git a/cmem_plugin_base/dataintegration/utils/__init__.py b/cmem_plugin_base/dataintegration/utils/__init__.py index eaa9dc2..665f616 100644 --- a/cmem_plugin_base/dataintegration/utils/__init__.py +++ b/cmem_plugin_base/dataintegration/utils/__init__.py @@ -2,7 +2,6 @@ import os import re -from typing import Optional from cmem.cmempy.workspace.projects.datasets.dataset import post_resource diff --git a/cmem_plugin_base/dataintegration/utils/entity_builder.py b/cmem_plugin_base/dataintegration/utils/entity_builder.py index 74ca2d8..8f25eee 100644 --- a/cmem_plugin_base/dataintegration/utils/entity_builder.py +++ b/cmem_plugin_base/dataintegration/utils/entity_builder.py @@ -5,7 +5,7 @@ from cmem_plugin_base.dataintegration.entity import Entities, Entity, EntityPath, EntitySchema -def merge_path_values(paths_map1, paths_map2): +def merge_path_values(paths_map1: dict, paths_map2: dict) -> dict: """Merge two dictionaries representing paths and values. This function takes two dictionaries, `paths_map1` and `paths_map2`, @@ -29,9 +29,8 @@ def merge_path_values(paths_map1, paths_map2): return paths_map1 -def generate_paths_from_data(data, path="root"): - """Generate a dictionary representing paths and data types from a nested JSON - structure. +def generate_paths_from_data(data: dict | list, path: str | None = "root") -> dict: + """Generate a dictionary representing paths and data types from a nested JSON structure. This function recursively traverses a nested JSON structure ('data') and builds a dictionary ('paths_map') where keys are paths and values are dictionaries @@ -70,7 +69,7 @@ def generate_paths_from_data(data, path="root"): return paths_map -def _get_schema(data: dict | list): +def _get_schema(data: dict | list) -> dict | None: """Get the schema of an entity.""" if not data: return None @@ -94,7 +93,7 @@ def _get_schema(data: dict | list): return path_to_schema_map -def extend_path_list(path_to_entities, sub_path_to_entities) -> None: +def extend_path_list(path_to_entities: dict, sub_path_to_entities: dict) -> None: """Extend a dictionary of paths to entities by merging with another. This function takes two dictionaries, `path_to_entities` and `sub_path_to_entities`, @@ -118,10 +117,10 @@ def extend_path_list(path_to_entities, sub_path_to_entities) -> None: def _get_entity( - path_from_root, - path_to_schema_map, - data, -): + path_from_root: str, + path_to_schema_map: dict, + data: dict, +) -> dict: """Get an entity based on the schema and data.""" path_to_entities = {} entity_uri = f"urn:x-ulid:{ULID()}" diff --git a/tests/conftest.py b/tests/conftest.py index c2040de..89df83c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,7 +34,7 @@ def _json_dataset() -> Generator[dict, None, None]: @pytest.fixture(name="json_resource", scope="module") -def _json_resource(): +def _json_resource() -> object: """Set up json resource""" _project_name = "resource_test_project" _resource_name = "sample_test.json" diff --git a/tests/parameter_types/__init__.py b/tests/parameter_types/__init__.py index 0e9f173..30ea453 100644 --- a/tests/parameter_types/__init__.py +++ b/tests/parameter_types/__init__.py @@ -1 +1 @@ -"""tests""" \ No newline at end of file +"""tests""" diff --git a/tests/parameter_types/test_code.py b/tests/parameter_types/test_code.py index d336b99..e46211c 100644 --- a/tests/parameter_types/test_code.py +++ b/tests/parameter_types/test_code.py @@ -30,16 +30,16 @@ def test__detection(self) -> None: class MyTransformPlugin(TransformPlugin): """Test My Transform Plugin""" - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments # noqa: PLR0913 self, - xml: XmlCode = XmlCode(""), - json: JsonCode = JsonCode("{}"), - jinja: JinjaCode = JinjaCode(""), - sql: SqlCode = SqlCode(""), - yaml: YamlCode = YamlCode(""), - sparql: SparqlCode = SparqlCode(""), - turtle: TurtleCode = TurtleCode(""), - python: PythonCode = PythonCode(""), + xml: XmlCode = XmlCode(""), # noqa: B008 + json: JsonCode = JsonCode("{}"), # noqa: B008 + jinja: JinjaCode = JinjaCode(""), # noqa: B008 + sql: SqlCode = SqlCode(""), # noqa: B008 + yaml: YamlCode = YamlCode(""), # noqa: B008 + sparql: SparqlCode = SparqlCode(""), # noqa: B008 + turtle: TurtleCode = TurtleCode(""), # noqa: B008 + python: PythonCode = PythonCode(""), # noqa: B008 ) -> None: self.xml = xml self.json = json diff --git a/tests/parameter_types/test_password.py b/tests/parameter_types/test_password.py index 13f3d0b..12aeb60 100644 --- a/tests/parameter_types/test_password.py +++ b/tests/parameter_types/test_password.py @@ -25,7 +25,7 @@ class MyTransformPlugin(TransformPlugin): def __init__(self, password: Password) -> None: self.password = password - def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: # noqa: ARG002 + def transform(self, inputs: Sequence[Sequence[str]]) -> Sequence[str]: """Test transform""" return [] diff --git a/tests/test_icon.py b/tests/test_icon.py index b305f74..8508699 100644 --- a/tests/test_icon.py +++ b/tests/test_icon.py @@ -17,7 +17,7 @@ class MyWorkflowPlugin(WorkflowPlugin): """My Workflow Plugin Class""" - def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: # noqa: ARG002 + def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: """Execute the workflow plugin on a given collection of entities.""" return diff --git a/tests/test_output_only_plugin.py b/tests/test_output_only_plugin.py index 283a5d5..fb5cfd1 100644 --- a/tests/test_output_only_plugin.py +++ b/tests/test_output_only_plugin.py @@ -1,5 +1,7 @@ """test file.""" +from collections.abc import Sequence +from cmem_plugin_base.dataintegration.context import ExecutionContext from cmem_plugin_base.dataintegration.description import Plugin, PluginParameter from cmem_plugin_base.dataintegration.entity import ( Entities, @@ -32,7 +34,8 @@ class OutputOnlyPlugin(WorkflowPlugin): def __init__(self, param1: str) -> None: self.param1 = param1 - def execute(self, inputs=(), context=()) -> Entities: + def execute(self, inputs: Sequence[Entities] = (), context: ExecutionContext = ()) -> Entities: + """Execute the workflow plugin on a given collection of entities.""" entity1 = Entity(uri="urn:my:1", values=(["value1"], ["value2"])) entity2 = Entity(uri="urn:my:2", values=(["value3"], ["value4"])) schema = EntitySchema( diff --git a/tests/test_utils_build_entities_from_data.py b/tests/test_utils_build_entities_from_data.py index ad6a350..c7c7218 100644 --- a/tests/test_utils_build_entities_from_data.py +++ b/tests/test_utils_build_entities_from_data.py @@ -2,11 +2,12 @@ import json -from cmem_plugin_base.dataintegration.entity import EntityPath, EntitySchema, Entities +from cmem_plugin_base.dataintegration.entity import Entities, EntityPath, EntitySchema from cmem_plugin_base.dataintegration.utils.entity_builder import build_entities_from_data def build_entities_from_json(json_data: str) -> Entities: + """Build entities from a test JSON string.""" data = json.loads(json_data) entities = build_entities_from_data(data) assert entities is not None @@ -36,9 +37,7 @@ def test_single_object() -> None: def test_single_object_one_level() -> None: - """Test generation of entities and schema for a JSON object with one level of - hierarchy - """ + """Test generation of entities and schema for a JSON object with one level of hierarchy.""" test_data = """ { "name": "sai", @@ -79,8 +78,9 @@ def test_single_object_one_level() -> None: def test_single_object_one_level_array() -> None: - """Test generation of entities and schema for a JSON object with array object in - first level of hierarchy + """Test generation of entities and schema for a JSON object. + + Test with array object in first level of hierarchy. """ test_data = """ { @@ -126,8 +126,9 @@ def test_single_object_one_level_array() -> None: def test_single_object_two_level_array() -> None: - """Test generation of entities and schema for a JSON object with two levels of - hierarchy + """Test generation of entities and schema. + + Tests a JSON object with two levels of hierarchy. """ test_data = """ { diff --git a/tests/test_utils_write_to_dataset.py b/tests/test_utils_write_to_dataset.py index 74a1319..8173751 100644 --- a/tests/test_utils_write_to_dataset.py +++ b/tests/test_utils_write_to_dataset.py @@ -13,7 +13,7 @@ @needs_cmem -def test_write_to_json_dataset(json_dataset) -> None: +def test_write_to_json_dataset(json_dataset: dict) -> None: """Test write to json dataset""" project_name = json_dataset["project"] dataset_name = json_dataset["id"] diff --git a/tests/utils.py b/tests/utils.py index 8d88234..3740ea8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,6 +8,7 @@ from cmem.cmempy.api import get_token from cmem_plugin_base.dataintegration.context import PluginContext, UserContext +from cmem_plugin_base.dataintegration.types import ParameterType needs_cmem = pytest.mark.skipif( "CMEM_BASE_URI" not in os.environ, reason="Needs CMEM configuration" @@ -33,13 +34,17 @@ class TestPluginContext(PluginContext): def __init__( self, project_id: str = "dummyProject", - user: UserContext | None = TestUserContext(), + user: UserContext | None = None, ): self.project_id = project_id - self.user = user + if user is None: + self.user = TestUserContext() + else: + self.user = user -def get_autocomplete_values(parameter, query_terms, context): +def get_autocomplete_values(parameter: ParameterType, query_terms: list[str], + context: PluginContext) -> list[str]: """Get autocomplete values""" return [ x.value From e8a6341b09ac059b300aedc1ec2000731439e020 Mon Sep 17 00:00:00 2001 From: Robert Isele Date: Tue, 24 Sep 2024 16:14:30 +0200 Subject: [PATCH 08/12] Fixed more mypy issues --- cmem_plugin_base/dataintegration/parameter/choice.py | 2 +- cmem_plugin_base/dataintegration/parameter/dataset.py | 2 +- cmem_plugin_base/dataintegration/parameter/graph.py | 2 +- cmem_plugin_base/dataintegration/types.py | 2 +- cmem_plugin_base/dataintegration/utils/entity_builder.py | 6 +++--- tests/test_output_only_plugin.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmem_plugin_base/dataintegration/parameter/choice.py b/cmem_plugin_base/dataintegration/parameter/choice.py index 8c7b88a..fa41f49 100644 --- a/cmem_plugin_base/dataintegration/parameter/choice.py +++ b/cmem_plugin_base/dataintegration/parameter/choice.py @@ -38,5 +38,5 @@ def autocomplete( for term in query_terms: if term.lower() in label.lower(): result.append(Autocompletion(value=identifier, label=label)) # noqa: PERF401 - result.sort(key=lambda x: x.label) + result.sort(key=lambda x: x.label) # type: ignore[return-value, arg-type] return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/parameter/dataset.py b/cmem_plugin_base/dataintegration/parameter/dataset.py index 46d013e..8133d9c 100644 --- a/cmem_plugin_base/dataintegration/parameter/dataset.py +++ b/cmem_plugin_base/dataintegration/parameter/dataset.py @@ -55,5 +55,5 @@ def autocomplete( if len(query_terms) == 0: # add any dataset to list if no search terms are given result.append(Autocompletion(value=identifier, label=label)) - result.sort(key=lambda x: x.label) + result.sort(key=lambda x: x.label) # type: ignore[return-value, arg-type] return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/parameter/graph.py b/cmem_plugin_base/dataintegration/parameter/graph.py index bc90b10..006119c 100644 --- a/cmem_plugin_base/dataintegration/parameter/graph.py +++ b/cmem_plugin_base/dataintegration/parameter/graph.py @@ -88,5 +88,5 @@ def autocomplete( if term.lower() in label.lower(): result.append(Autocompletion(value=iri, label=label)) continue - result.sort(key=lambda x: x.label) + result.sort(key=lambda x: x.label) # type: ignore[return-value, arg-type] return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/types.py b/cmem_plugin_base/dataintegration/types.py index b5d2917..9f0b266 100644 --- a/cmem_plugin_base/dataintegration/types.py +++ b/cmem_plugin_base/dataintegration/types.py @@ -50,7 +50,7 @@ class ParameterType(Generic[T]): def get_type(self) -> type: """Retrieve the type that is supported by a given instance.""" - return self.__orig_bases__[0].__args__[0] + return self.__orig_bases__[0].__args__[0] # type: ignore[attr-defined, no-any-return] def from_string(self, value: str, context: PluginContext) -> T: """Parse strings into parameter values.""" diff --git a/cmem_plugin_base/dataintegration/utils/entity_builder.py b/cmem_plugin_base/dataintegration/utils/entity_builder.py index 8f25eee..950b032 100644 --- a/cmem_plugin_base/dataintegration/utils/entity_builder.py +++ b/cmem_plugin_base/dataintegration/utils/entity_builder.py @@ -44,7 +44,7 @@ def generate_paths_from_data(data: dict | list, path: str | None = "root") -> di dict: A dictionary representing paths and data types. """ - paths_map = {} + paths_map: dict = {} if isinstance(data, list): for _ in data: paths_map = merge_path_values(paths_map, generate_paths_from_data(_, path=path)) @@ -69,7 +69,7 @@ def generate_paths_from_data(data: dict | list, path: str | None = "root") -> di return paths_map -def _get_schema(data: dict | list) -> dict | None: +def _get_schema(data: dict | list) -> dict[str, EntitySchema] | None: """Get the schema of an entity.""" if not data: return None @@ -122,7 +122,7 @@ def _get_entity( data: dict, ) -> dict: """Get an entity based on the schema and data.""" - path_to_entities = {} + path_to_entities: dict = {} entity_uri = f"urn:x-ulid:{ULID()}" values = [] schema = path_to_schema_map[path_from_root] diff --git a/tests/test_output_only_plugin.py b/tests/test_output_only_plugin.py index fb5cfd1..db776d8 100644 --- a/tests/test_output_only_plugin.py +++ b/tests/test_output_only_plugin.py @@ -34,7 +34,7 @@ class OutputOnlyPlugin(WorkflowPlugin): def __init__(self, param1: str) -> None: self.param1 = param1 - def execute(self, inputs: Sequence[Entities] = (), context: ExecutionContext = ()) -> Entities: + def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Entities: """Execute the workflow plugin on a given collection of entities.""" entity1 = Entity(uri="urn:my:1", values=(["value1"], ["value2"])) entity2 = Entity(uri="urn:my:2", values=(["value3"], ["value4"])) @@ -48,6 +48,6 @@ def execute(self, inputs: Sequence[Entities] = (), context: ExecutionContext = ( def test_output_only_plugin() -> None: """Test example Workflow Plugin.""" output_only = OutputOnlyPlugin(param1="test") - result = output_only.execute() + result = output_only.execute((), ExecutionContext()) for item in result.entities: assert len(item.values) == len(result.schema.paths) From 8b3ef346796fe13c648726dfb0461c9e903ba435 Mon Sep 17 00:00:00 2001 From: Robert Isele Date: Tue, 24 Sep 2024 16:23:03 +0200 Subject: [PATCH 09/12] Ignore some ruff issues: - Plugin methods usually don't use all arguments, which is completely fine and expected. - Magic values are fine to be used in tests IMHO - Specific assert statements should also be fine. --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e87c53f..59aad15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,5 +102,13 @@ ignore = [ "PD", # opinionated linting for pandas code "S101", # use of assert detected "TRY003", # Avoid specifying long messages outside the exception class + "ARG002" # Checks for the presence of unused arguments in instance method definitions. +] + +[tool.ruff.lint.per-file-ignores] +"**/{tests}/*" = [ + "PLR2004", # Checks for the use of unnamed numerical constants ("magic") values in comparisons. + "PT009", # Use a regular `assert` instead of unittest-style `assertListEqual` + "PT027" # Use pytest.raises instead of unittest-style {assertion} ] From 316cd23747ef23f3ebd2358dcfee0df2f284de72 Mon Sep 17 00:00:00 2001 From: saipraneeth <2506664+msaipraneeth@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:33:49 +0100 Subject: [PATCH 10/12] fix ruff and mypy issues --- .../dataintegration/description.py | 4 ++-- .../dataintegration/parameter/choice.py | 7 +++++-- .../dataintegration/utils/__init__.py | 5 ++++- .../dataintegration/utils/entity_builder.py | 4 ++-- tests/conftest.py | 19 ++++++++++--------- tests/parameter_types/test_code.py | 16 ++++++++-------- tests/parameter_types/test_resource.py | 3 ++- tests/test_output_only_plugin.py | 1 + tests/utils.py | 5 +++-- 9 files changed, 37 insertions(+), 27 deletions(-) diff --git a/cmem_plugin_base/dataintegration/description.py b/cmem_plugin_base/dataintegration/description.py index 8654493..f121e89 100644 --- a/cmem_plugin_base/dataintegration/description.py +++ b/cmem_plugin_base/dataintegration/description.py @@ -275,11 +275,11 @@ def __call__(self, func: type): def retrieve_parameters(self, plugin_class: type) -> list[PluginParameter]: """Retrieve parameters from a plugin class and matches them with the user parameter defs""" # Only return parameters for user-defined init methods. - if not hasattr(plugin_class.__init__, "__code__"): # type: ignore[misc] + if not hasattr(plugin_class.__init__, "__code__"): # type: ignore[misc] return [] # Collect parameters from init method params = [] - sig = inspect.signature(plugin_class.__init__) # type: ignore[misc] + sig = inspect.signature(plugin_class.__init__) # type: ignore[misc] for name in sig.parameters: if name != "self": param = next((p for p in self.parameters if p.name == name), None) diff --git a/cmem_plugin_base/dataintegration/parameter/choice.py b/cmem_plugin_base/dataintegration/parameter/choice.py index fa41f49..8fe4638 100644 --- a/cmem_plugin_base/dataintegration/parameter/choice.py +++ b/cmem_plugin_base/dataintegration/parameter/choice.py @@ -26,7 +26,10 @@ def label( return self.choice_list[value] def autocomplete( - self, query_terms: list[str], depend_on_parameter_values: list[Any], context: PluginContext, + self, + query_terms: list[str], + depend_on_parameter_values: list[Any], + context: PluginContext, ) -> list[Autocompletion]: """Autocompletion request - Returns all results that match ALL provided query terms.""" result = [] @@ -38,5 +41,5 @@ def autocomplete( for term in query_terms: if term.lower() in label.lower(): result.append(Autocompletion(value=identifier, label=label)) # noqa: PERF401 - result.sort(key=lambda x: x.label) # type: ignore[return-value, arg-type] + result.sort(key=lambda x: x.label) # type: ignore[return-value, arg-type] return list(set(result)) diff --git a/cmem_plugin_base/dataintegration/utils/__init__.py b/cmem_plugin_base/dataintegration/utils/__init__.py index 665f616..86fdff9 100644 --- a/cmem_plugin_base/dataintegration/utils/__init__.py +++ b/cmem_plugin_base/dataintegration/utils/__init__.py @@ -2,6 +2,7 @@ import os import re +from typing import IO from cmem.cmempy.workspace.projects.datasets.dataset import post_resource @@ -70,7 +71,9 @@ def split_task_id(task_id: str) -> tuple: return project_part, task_part -def write_to_dataset(dataset_id: str, file_resource=None, context: UserContext | None = None): +def write_to_dataset( # noqa: ANN201 + dataset_id: str, file_resource: IO | None = None, context: UserContext | None = None +): """Write to a dataset. Args: diff --git a/cmem_plugin_base/dataintegration/utils/entity_builder.py b/cmem_plugin_base/dataintegration/utils/entity_builder.py index 950b032..cd896ec 100644 --- a/cmem_plugin_base/dataintegration/utils/entity_builder.py +++ b/cmem_plugin_base/dataintegration/utils/entity_builder.py @@ -133,10 +133,10 @@ def _get_entity( values.append( [f"{data.get(_.path)}"] if _.is_single_value - else [f"{_v}" for _v in data.get(_.path)] + else [f"{_v}" for _v in data.get(_.path)] # type: ignore[union-attr] ) else: - _data = [data.get(_.path)] if _.is_single_value else data.get(_.path) + _data: list[dict] = [data.get(_.path)] if _.is_single_value else data.get(_.path) # type: ignore[assignment,list-item] sub_entities_uri = [] for _v in _data: sub_entity_path = f"{path_from_root}/{_.path}" diff --git a/tests/conftest.py b/tests/conftest.py index 89df83c..a1e5b75 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,8 +33,16 @@ def _json_dataset() -> Generator[dict, None, None]: delete_project(PROJECT_NAME) +@dataclass +class JSONResourceFixtureDate: + """fixture dataclass""" + + project_name: str + resource_name: str + + @pytest.fixture(name="json_resource", scope="module") -def _json_resource() -> object: +def _json_resource() -> Generator[JSONResourceFixtureDate, None, None]: """Set up json resource""" _project_name = "resource_test_project" _resource_name = "sample_test.json" @@ -46,13 +54,6 @@ def _json_resource() -> object: replace=True, ) - @dataclass - class FixtureDate: - """fixture dataclass""" - - project_name = _project_name - resource_name = _resource_name - - _ = FixtureDate() + _ = JSONResourceFixtureDate(project_name=_project_name, resource_name=_resource_name) yield _ delete_project(_project_name) diff --git a/tests/parameter_types/test_code.py b/tests/parameter_types/test_code.py index e46211c..accc748 100644 --- a/tests/parameter_types/test_code.py +++ b/tests/parameter_types/test_code.py @@ -32,14 +32,14 @@ class MyTransformPlugin(TransformPlugin): def __init__( # pylint: disable=too-many-arguments # noqa: PLR0913 self, - xml: XmlCode = XmlCode(""), # noqa: B008 - json: JsonCode = JsonCode("{}"), # noqa: B008 - jinja: JinjaCode = JinjaCode(""), # noqa: B008 - sql: SqlCode = SqlCode(""), # noqa: B008 - yaml: YamlCode = YamlCode(""), # noqa: B008 - sparql: SparqlCode = SparqlCode(""), # noqa: B008 - turtle: TurtleCode = TurtleCode(""), # noqa: B008 - python: PythonCode = PythonCode(""), # noqa: B008 + xml: XmlCode = XmlCode(""), # noqa: B008 + json: JsonCode = JsonCode("{}"), # noqa: B008 + jinja: JinjaCode = JinjaCode(""), # noqa: B008 + sql: SqlCode = SqlCode(""), # noqa: B008 + yaml: YamlCode = YamlCode(""), # noqa: B008 + sparql: SparqlCode = SparqlCode(""), # noqa: B008 + turtle: TurtleCode = TurtleCode(""), # noqa: B008 + python: PythonCode = PythonCode(""), # noqa: B008 ) -> None: self.xml = xml self.json = json diff --git a/tests/parameter_types/test_resource.py b/tests/parameter_types/test_resource.py index 277da1a..5f317c6 100644 --- a/tests/parameter_types/test_resource.py +++ b/tests/parameter_types/test_resource.py @@ -1,11 +1,12 @@ """resource parameter type tests""" from cmem_plugin_base.dataintegration.parameter.resource import ResourceParameterType +from tests.conftest import JSONResourceFixtureDate from tests.utils import TestPluginContext, get_autocomplete_values, needs_cmem @needs_cmem -def test_resource_parameter_type_completion(json_resource) -> None: +def test_resource_parameter_type_completion(json_resource: JSONResourceFixtureDate) -> None: """Test resource parameter type completion""" project_name = json_resource.project_name resource_name = json_resource.resource_name diff --git a/tests/test_output_only_plugin.py b/tests/test_output_only_plugin.py index db776d8..7ab09c6 100644 --- a/tests/test_output_only_plugin.py +++ b/tests/test_output_only_plugin.py @@ -1,4 +1,5 @@ """test file.""" + from collections.abc import Sequence from cmem_plugin_base.dataintegration.context import ExecutionContext diff --git a/tests/utils.py b/tests/utils.py index 3740ea8..c087d1c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -43,8 +43,9 @@ def __init__( self.user = user -def get_autocomplete_values(parameter: ParameterType, query_terms: list[str], - context: PluginContext) -> list[str]: +def get_autocomplete_values( + parameter: ParameterType, query_terms: list[str], context: PluginContext +) -> list[str]: """Get autocomplete values""" return [ x.value From b8dbddbd045545d0abb7ca7302616ff7a0eedea1 Mon Sep 17 00:00:00 2001 From: saipraneeth <2506664+msaipraneeth@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:34:58 +0100 Subject: [PATCH 11/12] not fetch token if grant type is prefetched_token --- tests/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index c087d1c..5d29e04 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,6 +6,7 @@ # check for cmem environment and skip if not present from cmem.cmempy.api import get_token +from cmem.cmempy.config import get_oauth_grant_type from cmem_plugin_base.dataintegration.context import PluginContext, UserContext from cmem_plugin_base.dataintegration.types import ParameterType @@ -22,7 +23,10 @@ class TestUserContext(UserContext): def __init__(self): # get access token from default service account - access_token: str = get_token()["access_token"] + if get_oauth_grant_type() == "prefetched_token": + access_token = os.environ.get("OAUTH_ACCESS_TOKEN") + else: + access_token: str = get_token()["access_token"] self.token = lambda: access_token From 89ed175266787262351cdc2314555b951d528959 Mon Sep 17 00:00:00 2001 From: saipraneeth <2506664+msaipraneeth@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:25:57 +0100 Subject: [PATCH 12/12] fix mypy warning --- tests/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 5d29e04..3384c57 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -21,13 +21,17 @@ class TestUserContext(UserContext): __test__ = False - def __init__(self): + def __init__(self) -> None: # get access token from default service account if get_oauth_grant_type() == "prefetched_token": access_token = os.environ.get("OAUTH_ACCESS_TOKEN") else: - access_token: str = get_token()["access_token"] - self.token = lambda: access_token + access_token = get_token()["access_token"] # type : ignore[annotation-unchecked] + self.access_token = str(access_token) + + def token(self) -> str: + """Get access token.""" + return self.access_token class TestPluginContext(PluginContext):