From 0588755877f8a6c86c070c2f8dabce916fcd299d Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Thu, 20 Jun 2024 13:16:55 -0400 Subject: [PATCH 1/5] chore: add boilerplate files close #1 --- .github/ISSUE_TEMPLATE/bug-report.yaml | 85 ++++++++++ .github/ISSUE_TEMPLATE/feature-request.yaml | 60 ++++++++ .github/workflows/python-cqa.yaml | 43 ++++++ .github/workflows/release.yaml | 63 ++++++++ .gitignore | 162 ++++++++++++++++++++ .pre-commit-config.yaml | 17 ++ Makefile | 120 +++++++++++++++ README.md | 86 +++++++++++ pyproject.toml | 145 ++++++++++++++++++ src/ga4gh/va_spec/__init__.py | 1 + 10 files changed, 782 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml create mode 100644 .github/workflows/python-cqa.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 Makefile create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 src/ga4gh/va_spec/__init__.py diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 0000000..5b3f7c8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,85 @@ +name: Bug Report +description: Report a bug. +labels: ["bug"] +body: + - type: textarea + attributes: + label: Describe the bug + description: Provide a clear and concise description of what the bug is. + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce + description: Provide detailed steps to replicate the bug. + placeholder: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + validations: + required: true + - type: textarea + attributes: + label: Expected behavior + description: What did you expect to happen? + validations: + required: true + - type: textarea + attributes: + label: Current behavior + description: | + What actually happened? + + Include full errors, stack traces, and/or relevant logs. + validations: + required: true + - type: textarea + attributes: + label: Possible reason(s) + description: Provide any insights into what might be causing the issue. + validations: + required: false + - type: textarea + attributes: + label: Suggested fix + description: Provide any suggestions on how to resolve the bug. + validations: + required: false + - type: textarea + attributes: + label: Branch, commit, and/or version + description: Provide the branch, commit, and/or version you're using. + placeholder: | + branch: issue-1 + commit: abc123d + validations: + required: true + - type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots with descriptions to help explain your problem. + validations: + required: false + - type: textarea + attributes: + label: Environment details + description: Provide environment details (OS name and version, etc). + validations: + required: true + - type: textarea + attributes: + label: Additional details + description: Provide any other additional details about the problem. + validations: + required: false + - type: dropdown + attributes: + label: Contribution + description: Can you contribute to the development of this feature? + options: + - "Yes, I can create a PR for this fix." + - "Yes, but I can only provide ideas and feedback." + - "No, I cannot contribute." + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 0000000..2e1ae64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,60 @@ +name: Feature Request +description: Suggest an idea for this project. +labels: ["enhancement"] +body: + - type: textarea + attributes: + label: Feature description + description: Provide a clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + attributes: + label: Use case + description: | + Why do you need this feature? For example: "I'm always frustrated when..." + validations: + required: true + - type: textarea + attributes: + label: Proposed solution + description: Provide proposed solution. + validations: + required: false + - type: textarea + attributes: + label: Alternatives considered + description: Describe any alternative solutions you've considered. + validations: + required: false + - type: textarea + attributes: + label: Implementation details + description: Provide any technical details on how the feature might be implemented. + validations: + required: false + - type: textarea + attributes: + label: Potential Impact + description: | + Discuss any potential impacts of this feature on existing functionality or performance, if known. + Will this feature cause breaking changes? + What challenges might arise? + validations: + required: false + - type: textarea + attributes: + label: Additional context + description: Provide any other context or screenshots about the feature. + validations: + required: false + - type: dropdown + attributes: + label: Contribution + description: Can you contribute to the development of this feature? + options: + - "Yes, I can create a PR for this feature." + - "Yes, but I can only provide ideas and feedback." + - "No, I cannot contribute." + validations: + required: false diff --git a/.github/workflows/python-cqa.yaml b/.github/workflows/python-cqa.yaml new file mode 100644 index 0000000..9df1355 --- /dev/null +++ b/.github/workflows/python-cqa.yaml @@ -0,0 +1,43 @@ +name: Python CQA + +on: [push, pull_request] + +jobs: + test: + name: test py${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python3 -m pip install ".[tests]" + + - name: Run tests + run: python3 -m pytest + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install dependencies + run: python3 -m pip install ".[dev]" + + - name: Check style + run: python3 -m ruff check && python3 -m ruff format --check diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..27c12ed --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,63 @@ +name: Upload tagged commit to PyPI +on: + push: + tags: + - "*.*.**" +jobs: + get_branch: + runs-on: ubuntu-latest + outputs: + branch_name: ${{ steps.get_branch_name.outputs.name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get branch name + id: get_branch_name + run: | + raw=$(git branch -r --contains ${{ github.ref }}) + branch=$(echo "$raw" | grep "origin/main" | sed "s|origin/||" | xargs) + echo "name=$branch" >> "$GITHUB_OUTPUT" + build: + name: Build distribution + runs-on: ubuntu-latest + needs: get_branch + if: needs.get_branch.outputs.branch_name == 'main' + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v3 + with: + name: python-package-distributions + path: dist/ + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/ga4gh-va-spec + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82f9275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..56efe9d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-added-large-files + - id: detect-private-key + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-merge-conflict + - id: detect-aws-credentials + args: [--allow-missing-credentials] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.9 # ruff version + hooks: + - id: ruff-format + - id: ruff + args: [ --fix, --exit-non-zero-on-fix ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..67ae087 --- /dev/null +++ b/Makefile @@ -0,0 +1,120 @@ +# Makefile for Python project + +.DELETE_ON_ERROR: +.PHONY: FORCE +.PRECIOUS: +.SUFFIXES: + +SHELL:=/bin/bash -e -o pipefail +SELF:=$(firstword $(MAKEFILE_LIST)) + +UNAME = $(shell uname) +ifeq (${UNAME},Darwin) + _XRM_R:= +else + _XRM_R:=r +endif +XRM=xargs -0${_XRM_R} rm + +PKG=ga4gh.va_spec +PKGD=$(subst .,/,${PKG}) +PYV:=3.12 +VEDIR=venv/${PYV} + + +############################################################################ +#= BASIC USAGE +default: help + +#=> help -- display this help message +help: + @sbin/makefile-extract-documentation "${SELF}" + + +############################################################################ +#= SETUP, INSTALLATION, PACKAGING + +#=> venv: make a Python 3 virtual environment +.PHONY: venv/% +venv/%: + python$* -m venv $@; \ + source $@/bin/activate; \ + python -m ensurepip --upgrade; \ + pip install --upgrade pip setuptools + +#=> develop: install package in develop mode +.PHONY: develop setup +develop setup: + pip install -e .[dev,tests] + +#=> devready: create venv, install prerequisites, install pkg in develop mode +.PHONY: devready +devready: + make ${VEDIR} && source ${VEDIR}/bin/activate && make develop + @echo '#################################################################################' + @echo '### Do not forget to `source ${VEDIR}/bin/activate` to use this environment ###' + @echo '#################################################################################' + +############################################################################ +#= TESTING +# see test configuration in pyproject.toml + +#=> test: execute tests +.PHONY: test +test: + pytest + +#=> doctest: execute documentation tests (requires extra data) +.PHONY: doctest +doctest: + pytest --doctest-modules + +############################################################################ +#= UTILITY TARGETS + +#=> format: reformat code with ruff +.PHONY: format +format: + ruff format + +#=> lint: static analysis check +.PHONY: lint +lint: + ruff check --fix --exit-zero + +############################################################################ +#= CLEANUP + +#=> clean: remove temporary and backup files +.PHONY: clean +clean: + find . \( -name \*~ -o -name \*.bak \) -print0 | ${XRM} + +#=> cleaner: remove files and directories that are easily rebuilt +.PHONY: cleaner +cleaner: clean + rm -fr .cache *.egg-info .pytest_cache build dist doc/_build htmlcov + find . \( -name \*.pyc -o -name \*.orig -o -name \*.rej \) -print0 | ${XRM} + find . -name __pycache__ -print0 | ${XRM} -fr + +#=> cleanest: remove files and directories that require more time/network fetches to rebuild +.PHONY: cleanest +cleanest: cleaner + rm -fr .eggs venv + + +## +## Copyright 2016 Source Code Committers +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## diff --git a/README.md b/README.md new file mode 100644 index 0000000..70561a8 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# va-spec-python + +GA4GH Variant Annotation (VA) Python Implementation + +## Information + +[![license](https://img.shields.io/badge/license-Apache-green)](https://github.com/ga4gh/va-spec-python/blob/main/LICENSE) + +## Releases + +[![gitHub tag](https://img.shields.io/github/v/tag/ga4gh/va-spec-python.svg)](https://github.com/ga4gh/va-spec-python/releases) [![pypi](https://img.shields.io/pypi/v/ga4gh.va_spec.svg)](https://pypi.org/project/ga4gh.va_spec/) + +## Development + +[![action status](https://github.com/ga4gh/va-spec-python/actions/workflows/python-cqa.yaml/badge.svg)](https://github.com/ga4gh/va-spec-python/actions/workflows/python-cqa.yaml) [![issues](https://img.shields.io/github/issues-raw/ga4gh/va-spec-python.svg)](https://github.com/ga4gh/va-spec-python/issues) +[![GitHub Open Pull Requests](https://img.shields.io/github/issues-pr/ga4gh/va-spec-python.svg)](https://github.com/ga4gh/va-spec-python/pull/) [![GitHub license](https://img.shields.io/github/contributors/ga4gh/va-spec-python.svg)](https://github.com/ga4gh/va-spec-python/graphs/contributors/) [![GitHub stars](https://img.shields.io/github/stars/ga4gh/va-spec-python.svg?style=social&label=Stars)](https://github.com/ga4gh/va-spec-python/stargazers) [![GitHub forks](https://img.shields.io/github/forks/ga4gh/va-spec-python.svg?style=social&label=Forks)](https://github.com/ga4gh/va-spec-python/network) + +## Features + +- Pydantic implementation of VA-Spec models + +## Known Issues + +**You are encouraged to** [browse issues](https://github.com/ga4gh/va-spec-python/issues). +All known issues are listed there. Please report any issues you find. + +## Developers + +This section is intended for developers who contribute to VA-Spec-Python. + +### Installing for development + +#### Prerequisites + +- Python >= 3.10 + - _Note: Python 3.12 is required for developers contributing to VA-Spec-Python_ + +Fork the repo at . + +Install development dependencies and `pre-commit`: + +```shell +git clone --recurse-submodules git@github.com:YOUR_GITHUB_ID/va-spec-python.git +cd va-spec-python +make devready +source venv/3.12/bin/activate +pre-commit install +``` + +Check style with `ruff`: + +```shell +make format; make lint +``` + +#### Submodules + +va-spec-python embeds va-spec as a submodule, only for testing purposes. When checking +out va-spec-python and switching branches, it is important to make sure that the +submodule tracks va-spec-python correctly. The recommended way to do this is +`git config --global submodule.recurse true`. **If you don't set submodule.recurse, +developers and reviewers must be extremely careful to not accidentally upgrade or +downgrade schemas with respect to va-spec-python.** + +If you already cloned the repo, but forgot to include `--recurse-submodules` you can run: + +```shell +git submodule update --init --recursive +``` + +### Testing + +To run tests: + +```shell +make test +``` + +## Security Note (from the GA4GH Security Team) + +A stand-alone security review has been performed on the specification itself. +This implementation is offered as-is, and without any security guarantees. It +will need an independent security review before it can be considered ready for +use in security-critical applications. If you integrate this code into your +application it is AT YOUR OWN RISK AND RESPONSIBILITY to arrange for a security +audit. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5cbda40 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,145 @@ +[project] +name = "ga4gh.va_spec" +authors = [ + {name = "Matt Brush"}, + {name = "Javier Lopez"}, +] +readme = "README.md" +description = "GA4GH Variant Annotation (VA) reference implementation" +license = {file = "LICENSE"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Healthcare Industry", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "Topic :: Scientific/Engineering :: Medical Science Apps.", +] +keywords = [ + "bioinformatics", + "ga4gh", + "genomics", + "variation" +] +requires-python = ">=3.10" +dynamic = ["version"] +dependencies = [] + +[project.optional-dependencies] +dev = [ + "pre-commit", + "ruff==0.4.9", +] +tests = [ + "pytest", + "pytest-cov", +] + +[project.urls] +Homepage = "https://github.com/ga4gh/va-spec-python" +Documentation = "https://github.com/ga4gh/va-spec-python" +Changelog = "https://github.com/ga4gh/va-spec-python/releases" +Source = "https://github.com/ga4gh/va-spec-python" +"Bug Tracker" = "https://github.com/ga4gh/va-spec-python/issues" + +[build-system] +requires = ["setuptools>=65.3", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + +[tool.ruff] +exclude = [ + "submodules" +] + +[tool.ruff.lint] +select = [ + "F", # https://docs.astral.sh/ruff/rules/#pyflakes-f + "E", "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "I", # https://docs.astral.sh/ruff/rules/#isort-i + "N", # https://docs.astral.sh/ruff/rules/#pep8-naming-n + "D", # https://docs.astral.sh/ruff/rules/#pydocstyle-d + "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up + "ANN", # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + "ASYNC", # https://docs.astral.sh/ruff/rules/#flake8-async-async + "S", # https://docs.astral.sh/ruff/rules/#flake8-bandit-s + "B", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "DTZ", # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + "T10", # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + "EM", # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + "G", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g + "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20 + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q + "RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse + "RET", # https://docs.astral.sh/ruff/rules/#flake8-return-ret + "SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + "PGH", # https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf +] +fixable = [ + "I", + "F401", + "D", + "UP", + "ANN", + "B", + "C4", + "G", + "PIE", + "PT", + "RSE", + "SIM", + "RUF" +] +# ANN101 - missing-type-self +# ANN003 - missing-type-kwargs +# D203 - one-blank-line-before-class +# D205 - blank-line-after-summary +# D206 - indent-with-spaces* +# D213 - multi-line-summary-second-line +# D300 - triple-single-quotes* +# D400 - ends-in-period +# D415 - ends-in-punctuation +# E111 - indentation-with-invalid-multiple* +# E114 - indentation-with-invalid-multiple-comment* +# E117 - over-indented* +# E501 - line-too-long* +# W191 - tab-indentation* +# S321 - suspicious-ftp-lib-usage +# *ignored for compatibility with formatter +ignore = [ + "ANN101", "ANN003", + "D203", "D205", "D206", "D213", "D300", "D400", "D415", + "E111", "E114", "E117", "E501", + "W191", + "S321", +] + +[tool.ruff.lint.per-file-ignores] +# ANN001 - missing-type-function-argument +# ANN2 - missing-return-type +# ANN102 - missing-type-cls +# S101 - assert +# B011 - assert-false +"tests/*" = ["ANN001", "ANN2", "ANN102", "S101", "B011"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools_scm] + +[tool.pytest.ini_options] +addopts = "--cov-report=term-missing --cov=ga4gh" +testpaths = ["tests", "src"] +doctest_optionflags = "ALLOW_UNICODE ALLOW_BYTES ELLIPSIS IGNORE_EXCEPTION_DETAIL NORMALIZE_WHITESPACE" diff --git a/src/ga4gh/va_spec/__init__.py b/src/ga4gh/va_spec/__init__.py new file mode 100644 index 0000000..97c18cf --- /dev/null +++ b/src/ga4gh/va_spec/__init__.py @@ -0,0 +1 @@ +"""Package for VA-Spec Python implementation""" From b65568053c5327e4621ee1b66d034f4e61ee0932 Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Mon, 30 Sep 2024 21:25:04 -0400 Subject: [PATCH 2/5] wip --- .gitmodules | 4 + pyproject.toml | 5 +- src/ga4gh/va_spec/profiles/__init__.py | 44 ++++ .../va_spec/profiles/assay_var_effect.py | 118 +++++++++++ .../va_spec/profiles/caf_study_result.py | 46 ++++ src/ga4gh/va_spec/profiles/var_path_stmt.py | 51 +++++ src/ga4gh/va_spec/profiles/var_study_stmt.py | 196 ++++++++++++++++++ submodules/va_spec | 1 + tests/validation/test_va_spec_schema.py | 70 +++++++ 9 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 src/ga4gh/va_spec/profiles/__init__.py create mode 100644 src/ga4gh/va_spec/profiles/assay_var_effect.py create mode 100644 src/ga4gh/va_spec/profiles/caf_study_result.py create mode 100644 src/ga4gh/va_spec/profiles/var_path_stmt.py create mode 100644 src/ga4gh/va_spec/profiles/var_study_stmt.py create mode 160000 submodules/va_spec create mode 100644 tests/validation/test_va_spec_schema.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9d51a2b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "submodules/va_spec"] + path = submodules/va_spec + url = https://github.com/ga4gh/va-spec + branch = 1.x diff --git a/pyproject.toml b/pyproject.toml index 5cbda40..f50a2d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,9 @@ keywords = [ ] requires-python = ">=3.10" dynamic = ["version"] -dependencies = [] +dependencies = [ + "pydantic==2.*" +] [project.optional-dependencies] dev = [ @@ -133,6 +135,7 @@ ignore = [ # S101 - assert # B011 - assert-false "tests/*" = ["ANN001", "ANN2", "ANN102", "S101", "B011"] +"src/ga4gh/cat_vrs/*models.py" = ["ANN102"] [tool.setuptools.packages.find] where = ["src"] diff --git a/src/ga4gh/va_spec/profiles/__init__.py b/src/ga4gh/va_spec/profiles/__init__.py new file mode 100644 index 0000000..558a390 --- /dev/null +++ b/src/ga4gh/va_spec/profiles/__init__.py @@ -0,0 +1,44 @@ +"""Package for VA-Spec Python implementation""" + +from .assay_var_effect import ( + AssayVariantEffectClinicalClassificationStatement, + AssayVariantEffectFunctionalClassificationStatement, + AssayVariantEffectMeasurementStudyResult, + AveClinicalClassification, + AveFunctionalClassification, +) +from .caf_study_result import CohortAlleleFrequencyStudyResult +from .var_path_stmt import PenetranceQualifier, VariantPathogenicityStatement +from .var_study_stmt import ( + AlleleOriginQualifier, + AllelePrevalenceQualifier, + DiagnosticPredicate, + OncogenicPredicate, + PrognosticPredicate, + TherapeuticResponsePredicate, + VariantDiagnosticStudyStatement, + VariantOncogenicityStudyStatement, + VariantPrognosticStudyStatement, + VariantTherapeuticResponseStudyStatement, +) + +__all__ = [ + "AveFunctionalClassification", + "AveClinicalClassification", + "AssayVariantEffectFunctionalClassificationStatement", + "AssayVariantEffectClinicalClassificationStatement", + "AssayVariantEffectMeasurementStudyResult", + "CohortAlleleFrequencyStudyResult", + "PenetranceQualifier", + "VariantPathogenicityStatement", + "AlleleOriginQualifier", + "DiagnosticPredicate", + "OncogenicPredicate", + "PrognosticPredicate", + "TherapeuticResponsePredicate", + "AllelePrevalenceQualifier", + "VariantDiagnosticStudyStatement", + "VariantOncogenicityStudyStatement", + "VariantPrognosticStudyStatement", + "VariantTherapeuticResponseStudyStatement", +] diff --git a/src/ga4gh/va_spec/profiles/assay_var_effect.py b/src/ga4gh/va_spec/profiles/assay_var_effect.py new file mode 100644 index 0000000..29f5271 --- /dev/null +++ b/src/ga4gh/va_spec/profiles/assay_var_effect.py @@ -0,0 +1,118 @@ +"""VA Spec Assay Variant Effect statement and study result Profiles""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal + +from ga4gh.cat_vrs.core_models import CategoricalVariant +from ga4gh.core.entity_models import ( + IRI, + Coding, + DataSet, + Method, + StatementBase, + StudyResultBase, +) +from ga4gh.vrs.models import MolecularVariation +from pydantic import ConfigDict, Field + + +class AveFunctionalClassification(str, Enum): + """The functional classification of the variant effect in the assay.""" + + NORMAL = "normal" + INDETERMINATE = "indeterminate" + ABNORMAL = "abnormal" + + +class AveClinicalClassification(str, Enum): + """The clinical strength of evidence of the variant effect in the assay.""" + + PS3_STRONG = "PS3_Strong" + PS3_MODERATE = "PS3_Moderate" + PS3_SUPPORTING = "PS3_Supporting" + BS3_STRONG = "BS3_Strong" + BS3_MODERATE = "BS3_Moderate" + BS3_SUPPORTING = "BS3_Supporting" + + +class AssayVariantEffectFunctionalClassificationStatement(StatementBase): + """A statement that assigns a functional classification to a variant effect from a functional assay.""" + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["AssayVariantEffectFunctionalClassificationStatement"] = Field( + "AssayVariantEffectFunctionalClassificationStatement", + description="MUST be 'AssayVariantEffectFunctionalClassificationStatement'.", + ) + subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., + description="A protein or genomic contextual or canonical molecular variant.", + ) + predicate: Literal["hasAssayVariantEffectFor"] = "hasAssayVariantEffectFor" + objectAssay: IRI | Coding = Field( # noqa: N815 + ..., + description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", + ) + classification: AveFunctionalClassification = Field( + ..., + description="The functional classification of the variant effect in the assay.", + ) + specifiedBy: Method | IRI | None = Field( # noqa: N815 + None, + description="The method that specifies the functional classification of the variant effect in the assay.", + ) + + +class AssayVariantEffectClinicalClassificationStatement(StatementBase): + """A statement that assigns a clinical strength of evidence to a variant effect from a functional assay.""" + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["AssayVariantEffectClinicalClassificationStatement"] = Field( + "AssayVariantEffectClinicalClassificationStatement", + description="MUST be 'AssayVariantEffectClinicalClassificationStatement'.", + ) + subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., + description="A protein or genomic contextual or canonical molecular variant.", + ) + predicate: Literal["hasAssayVariantEffectFor"] = "hasAssayVariantEffectFor" + objectAssay: IRI | Coding = Field( # noqa: N815 + ..., + description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", + ) + classification: AveClinicalClassification = Field( + ..., + description="he clinical strength of evidence of the variant effect in the assay.", + ) + specifiedBy: Method | IRI | None = Field( # noqa: N815 + None, + description="The method that specifies the clinical strength of evidence of the variant effect in the assay.", + ) + + +class AssayVariantEffectMeasurementStudyResult(StudyResultBase): + """A StudyResult that reports a variant effect score from a functional assay.""" + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["AssayVariantEffectMeasurementStudyResult"] = Field( + "AssayVariantEffectMeasurementStudyResult", + description="MUST be 'AssayVariantEffectMeasurementStudyResult'.", + ) + focusVariant: MolecularVariation | IRI | None = Field( # noqa: N815 + None, + description="The human mapped representation of the variant that is the subject of the Statement.", + ) + score: float | None = Field( + None, description="The score of the variant effect in the assay." + ) + specifiedBy: Method | IRI | None = Field( # noqa: N815 + None, + description="The assay that was used to measure the variant effect with all the various properties", + ) + sourceDataSet: list[DataSet] | None = Field( # noqa: N815 + None, description="The full data set that this measurement is a part of" + ) diff --git a/src/ga4gh/va_spec/profiles/caf_study_result.py b/src/ga4gh/va_spec/profiles/caf_study_result.py new file mode 100644 index 0000000..8e8939d --- /dev/null +++ b/src/ga4gh/va_spec/profiles/caf_study_result.py @@ -0,0 +1,46 @@ +"""VA Spec Cohort Allele Frequency (population frequency) Study Result Standard Profile""" + +from __future__ import annotations + +from typing import Literal + +from ga4gh.core.entity_models import ( + DataSet, + StudyResult, + StudyResultBase, +) +from ga4gh.vrs.models import Allele +from pydantic import ConfigDict, Field + + +class CohortAlleleFrequencyStudyResult(StudyResultBase): + """A StudyResult that reports measures related to the frequency of an Allele in a cohort""" + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["CohortAlleleFrequencyStudyResult"] = Field( + "CohortAlleleFrequencyStudyResult", + description="MUST be 'CohortAlleleFrequencyStudyResult'.", + ) + sourceDataSet: list[DataSet] | None = Field( # noqa: N815 + None, + description="The dataset from which the CohortAlleleFrequencyStudyResult was reported.", + ) + focusAllele: Allele | str # noqa: N815 + focusAlleleCount: int = Field( # noqa: N815 + ..., description="The number of occurrences of the focusAllele in the cohort." + ) + locusAlleleCount: int = Field( # noqa: N815 + ..., + description="The number of occurrences of all alleles at the locus in the cohort (sometimes referred to as 'allele number')", + ) + focusAlleleFrequency: float = Field( # noqa: N815 + ..., description="The frequency of the focusAllele in the cohort." + ) + cohort: list[StudyResult] = Field( + ..., description="The cohort from which the frequency was derived." + ) + subCohortFrequency: list[CohortAlleleFrequencyStudyResult] | None = Field( # noqa: N815 + None, + description="A list of CohortAlleleFrequency objects describing subcohorts of the cohort currently being described. This creates a recursive relationship and subcohorts can be further subdivided into more subcohorts. This enables, for example, the description of different ancestry groups and sexes among those ancestry groups.", + ) diff --git a/src/ga4gh/va_spec/profiles/var_path_stmt.py b/src/ga4gh/va_spec/profiles/var_path_stmt.py new file mode 100644 index 0000000..1c24aca --- /dev/null +++ b/src/ga4gh/va_spec/profiles/var_path_stmt.py @@ -0,0 +1,51 @@ +"""VA Spec Variant Pathogenicity Statement Standard Profile""" + +from enum import Enum +from typing import Literal + +from ga4gh.cat_vrs.core_models import CategoricalVariant +from ga4gh.core.domain_models import Condition, Gene +from ga4gh.core.entity_models import IRI, Coding, StatementBase +from ga4gh.vrs.models import Variation +from pydantic import ConfigDict, Field + + +class PenetranceQualifier(str, Enum): + """Reports the penetrance of the pathogenic effect - i.e. the extent to which the + variant impact is expressed by individuals carrying it as a measure of the + proportion of carriers exhibiting the condition. + """ + + HIGH = "high" + LOW = "low" + RISK_ALLELE = "risk allele" + + +class VariantPathogenicityStatement(StatementBase): + """A Statement describing the role of a variant in causing an inherited condition.""" + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["VariantPathogenicityStatement"] = Field( + "VariantPathogenicityStatement", + description="MUST be 'VariantPathogenicityStatement'.", + ) + subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., description="A variant that is the subject of the Statement." + ) + predicate: Literal["isCausalFor"] = "isCausalFor" + objectCondition: Condition | IRI = Field( # noqa: N815 + ..., description="The `Condition` for which the variant impact is stated." + ) + penetranceQualifier: PenetranceQualifier | None = Field( # noqa: N815 + None, + description="Reports the penetrance of the pathogenic effect - i.e. the extent to which the variant impact is expressed by individuals carrying it as a measure of the proportion of carriers exhibiting the condition.", + ) + modeOfInheritanceQualifier: list[Coding] | None = Field( # noqa: N815 + None, + description="Reports a pattern of inheritance expected for the pathogenic effect of the variant. Use HPO terms within the hierarchy of 'HP:0000005' (mode of inheritance) to specify.", + ) + geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + None, + description="Reports the gene through which the pathogenic effect asserted for the variant is mediated (i.e. it is the variant's impact on this gene that is responsible for causing the condition).", + ) diff --git a/src/ga4gh/va_spec/profiles/var_study_stmt.py b/src/ga4gh/va_spec/profiles/var_study_stmt.py new file mode 100644 index 0000000..c35bcb9 --- /dev/null +++ b/src/ga4gh/va_spec/profiles/var_study_stmt.py @@ -0,0 +1,196 @@ +"""VA Spec Variant Study Statement Standard Profiles""" + +from enum import Enum +from typing import Literal + +from ga4gh.cat_vrs.core_models import CategoricalVariant +from ga4gh.core.domain_models import Condition, Gene, TherapeuticProcedure +from ga4gh.core.entity_models import IRI, StatementBase +from ga4gh.vrs.models import Variation +from pydantic import ConfigDict, Field + + +class AlleleOriginQualifier(str, Enum): + """Reports whether the statement should be interpreted in the context of an + inherited (germline) variant, an acquired (somatic) mutation, or both (combined). + """ + + GERMLINE = "germline" + SOMATIC = "somatic" + COMBINED = "combined" + + +class DiagnosticPredicate(str, Enum): + """Define constraints for diagnostic predicate""" + + INCLUSIVE = "isDiagnosticInclusionCriterionFor" + EXCLUSIVE = "isDiagnosticExclusionCriterionFor" + + +class OncogenicPredicate(str, Enum): + """Define constraints for oncogenic predicate""" + + ONCOGENIC = "isOncogenicFor" + PROTECTIVE = "isProtectiveFor" + PREDISPOSING = "isPredisposingFor" + + +class PrognosticPredicate(str, Enum): + """Define constraints for prognostic predicate""" + + BETTER_OUTCOME = "associatedWithBetterOutcomeFor" + WORSE_OUTCOME = "associatedWithWorseOutcomeFor" + + +class TherapeuticResponsePredicate(str, Enum): + """Define constraints for therapeutic response predicate""" + + SENSITIVITY = "predictsSensitivityTo" + RESISTANCE = "predictsResistanceTo" + + +class AllelePrevalenceQualifier(str, Enum): + """Reports whether the statement should be interpreted in the context of the variant + being rare or common. + """ + + RARE = "rare" + COMMON = "common" + + +class VariantDiagnosticStudyStatement(StatementBase): + """A Statement reporting a conclusion from a single study about whether a variant is + associated with a disease (a diagnostic inclusion criterion), or absence of a + disease (diagnostic exclusion criterion) - based on interpretation of the study's + results. + """ + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["VariantDiagnosticStudyStatement"] = Field( + "VariantDiagnosticStudyStatement", + description="MUST be 'VariantDiagnosticStudyStatement'.", + ) + subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., description="A variant that is the subject of the Statement." + ) + predicate: DiagnosticPredicate + objectCondition: Condition | IRI = Field( # noqa: N815 + ..., description="The `Condition` for which the variant impact is stated." + ) + alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", + ) + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", + ) + geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + None, + description="Reports a gene impacted by the variant, which may contribute to the diagnostic association in the Statement.", + ) + + +class VariantOncogenicityStudyStatement(StatementBase): + """A Statement reporting a conclusion from a single study that supports or refutes a + variant's effect on oncogenesis for a specific tumor type - based on interpretation + of the study's results. + """ + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["VariantOncogenicityStudyStatement"] = Field( + "VariantOncogenicityStudyStatement", + description="MUST be 'VariantOncogenicityStudyStatement'.", + ) + subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., description="A variant that is the subject of the Statement." + ) + predicate: OncogenicPredicate + objectTumorType: Condition | IRI = Field( # noqa: N815 + ..., description="The tumor type for which the variant impact is evaluated." + ) + alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", + ) + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", + ) + geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + None, + description="Reports a gene impacted by the variant, which may contribute to the oncogenic role in the Statement.", + ) + + +class VariantPrognosticStudyStatement(StatementBase): + """A Statement reporting a conclusion from a single study about whether a variant is + associated with an improved or worse outcome for a disease - based on interpretation + of the study's results. + """ + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["VariantPrognosticStudyStatement"] = Field( + "VariantPrognosticStudyStatement", + description="MUST be 'VariantPrognosticStudyStatement'.", + ) + subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., description="A variant that is the subject of the Statement." + ) + predicate: PrognosticPredicate + objectCondition: Condition | IRI = Field( # noqa: N815 + ..., description="The disease that is evaluated for outcome." + ) + alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", + ) + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", + ) + geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + None, + description="Reports a gene impacted by the variant, which may contribute to the prognostic association in the Statement.", + ) + + +class VariantTherapeuticResponseStudyStatement(StatementBase): + """A Statement reporting a conclusion from a single study about the role of a + variant in modulating the response of a neoplasm to drug administration or other + therapeutic procedures - based on interpretation of the study's results. + """ + + model_config = ConfigDict(use_enum_values=True) + + type: Literal["VariantTherapeuticResponseStudyStatement"] = Field( + "VariantTherapeuticResponseStudyStatement", + description="MUST be 'VariantTherapeuticResponseStudyStatement'.", + ) + subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + ..., description="A variant that is the subject of the Statement." + ) + predicate: TherapeuticResponsePredicate + objectTherapeutic: TherapeuticProcedure | IRI = Field( # noqa: N815 + ..., + description="A drug administration or other therapeutic procedure that the neoplasm is intended to respond to.", + ) + conditionQualifier: Condition | IRI = Field( # noqa: N815 + ..., + description="Reports the disease context in which the variant's association with therapeutic sensitivity or resistance is evaluated. Note that this is a required qualifier in therapeutic response statements.", + ) + alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", + ) + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + None, + description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", + ) + geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + None, + description="Reports a gene impacted by the variant, which may contribute to the therapeutic sensitivity or resistance reported in the Statement.", + ) diff --git a/submodules/va_spec b/submodules/va_spec new file mode 160000 index 0000000..dc10690 --- /dev/null +++ b/submodules/va_spec @@ -0,0 +1 @@ +Subproject commit dc106908f36326126273721f9db90788fbdf7ce5 diff --git a/tests/validation/test_va_spec_schema.py b/tests/validation/test_va_spec_schema.py new file mode 100644 index 0000000..3011ac9 --- /dev/null +++ b/tests/validation/test_va_spec_schema.py @@ -0,0 +1,70 @@ +"""Test that VA-Spec Python model structures match VA-Spec Schema""" + +import json +from pathlib import Path + +import ga4gh.va_spec.profiles as va_spec_profiles + +ROOT_DIR = Path(__file__).parents[2] +VA_SPEC_SCHEMA_DIR = ROOT_DIR / "submodules" / "va_spec" / "schema" +VA_SPEC_SCHEMA = {} + +VA_SPEC_CONCRETE_CLASSES = set() +VA_SPEC_PRIMITIVES = set() + + +def _update_classes_and_primitives(f_path: Path): + with f_path.open() as rf: + cls_def = json.load(rf) + + va_spec_class = cls_def["title"] + VA_SPEC_SCHEMA[va_spec_class] = cls_def + + if "properties" in cls_def: + VA_SPEC_CONCRETE_CLASSES.add(va_spec_class) + elif cls_def.get("type") in {"array", "int", "str"}: + VA_SPEC_PRIMITIVES.add(va_spec_class) + + +# Get profile classes +for child in (VA_SPEC_SCHEMA_DIR / "profiles").iterdir(): + for f in (child / "json").glob("*"): + _update_classes_and_primitives(f) + + +def test_schema_models_exist(): + """Test that VA-Spec Python covers the models defined by VA-Spec""" + for va_spec_class in VA_SPEC_CONCRETE_CLASSES | VA_SPEC_PRIMITIVES: + assert getattr(va_spec_profiles, va_spec_class, False) + + +def test_schema_class_fields_are_valid(): + """Test that VA-Spec Python model fields match the VA-Spec specification""" + for va_spec_class in VA_SPEC_CONCRETE_CLASSES: + schema_fields = set(VA_SPEC_SCHEMA[va_spec_class]["properties"]) + pydantic_model = getattr(va_spec_profiles, va_spec_class) + assert set(pydantic_model.model_fields) == schema_fields, va_spec_class + + +def test_model_keys_are_valid(): + """Test that digest keys on objects are valid and sorted""" + for va_spec_class in VA_SPEC_CONCRETE_CLASSES: + if ( + VA_SPEC_SCHEMA[va_spec_class].get("ga4ghDigest", {}).get("keys", None) + is None + ): + continue + + pydantic_model = getattr(va_spec_profiles, va_spec_class) + + try: + pydantic_model_digest_keys = pydantic_model.ga4gh.keys + except AttributeError as e: + raise AttributeError(va_spec_class) from e + + assert set(pydantic_model_digest_keys) == set( + VA_SPEC_SCHEMA[va_spec_class]["ga4ghDigest"]["keys"] + ), va_spec_class + assert pydantic_model_digest_keys == sorted( + pydantic_model.ga4gh.keys + ), va_spec_class From e849aa01ccd0994ee489f1a56559eb73c03d52fa Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Tue, 12 Nov 2024 10:51:22 -0500 Subject: [PATCH 3/5] updates --- .github/workflows/release.yaml | 8 +- pyproject.toml | 2 + .../va_spec/profiles/assay_var_effect.py | 22 ++++- .../va_spec/profiles/caf_study_result.py | 5 +- src/ga4gh/va_spec/profiles/var_path_stmt.py | 7 +- src/ga4gh/va_spec/profiles/var_study_stmt.py | 24 +++-- submodules/va_spec | 2 +- tests/validation/test_va_spec_schema.py | 93 ++++++++++--------- 8 files changed, 104 insertions(+), 59 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 27c12ed..7e1a18e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install pypa/build @@ -38,7 +38,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ @@ -50,12 +50,12 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/ga4gh-va-spec + url: https://pypi.org/p/ga4gh.va_spec permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ diff --git a/pyproject.toml b/pyproject.toml index f50a2d3..d20f1cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ keywords = [ requires-python = ">=3.10" dynamic = ["version"] dependencies = [ + "ga4gh.vrs~=2.0.0a12", + "ga4gh.cat_vrs~=0.1.0", "pydantic==2.*" ] diff --git a/src/ga4gh/va_spec/profiles/assay_var_effect.py b/src/ga4gh/va_spec/profiles/assay_var_effect.py index 29f5271..9be7d44 100644 --- a/src/ga4gh/va_spec/profiles/assay_var_effect.py +++ b/src/ga4gh/va_spec/profiles/assay_var_effect.py @@ -12,6 +12,8 @@ DataSet, Method, StatementBase, + StudyGroup, + StudyResult, StudyResultBase, ) from ga4gh.vrs.models import MolecularVariation @@ -50,7 +52,10 @@ class AssayVariantEffectFunctionalClassificationStatement(StatementBase): ..., description="A protein or genomic contextual or canonical molecular variant.", ) - predicate: Literal["hasAssayVariantEffectFor"] = "hasAssayVariantEffectFor" + predicate: Literal["hasAssayVariantEffectFor"] = Field( + "hasAssayVariantEffectFor", + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectAssay: IRI | Coding = Field( # noqa: N815 ..., description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", @@ -78,14 +83,17 @@ class AssayVariantEffectClinicalClassificationStatement(StatementBase): ..., description="A protein or genomic contextual or canonical molecular variant.", ) - predicate: Literal["hasAssayVariantEffectFor"] = "hasAssayVariantEffectFor" + predicate: Literal["hasAssayVariantEffectFor"] = Field( + "hasAssayVariantEffectFor", + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectAssay: IRI | Coding = Field( # noqa: N815 ..., description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", ) classification: AveClinicalClassification = Field( ..., - description="he clinical strength of evidence of the variant effect in the assay.", + description="The clinical strength of evidence of the variant effect in the assay.", ) specifiedBy: Method | IRI | None = Field( # noqa: N815 None, @@ -102,6 +110,14 @@ class AssayVariantEffectMeasurementStudyResult(StudyResultBase): "AssayVariantEffectMeasurementStudyResult", description="MUST be 'AssayVariantEffectMeasurementStudyResult'.", ) + componentResult: list[StudyResult] | None = Field( # noqa: N815 + None, + description="Another StudyResult comprised of data items about the same focus as its parent Result, but based on a more narrowly scoped analysis of the foundational data (e.g. an analysis based on data about a subset of the parent Results full study population) .", + ) + studyGroup: StudyGroup | None = Field( # noqa: N815 + None, + description="A description of a specific group or population of subjects interrogated in the ResearchStudy that produced the data captured in the StudyResult.", + ) focusVariant: MolecularVariation | IRI | None = Field( # noqa: N815 None, description="The human mapped representation of the variant that is the subject of the Statement.", diff --git a/src/ga4gh/va_spec/profiles/caf_study_result.py b/src/ga4gh/va_spec/profiles/caf_study_result.py index 8e8939d..d5f1aff 100644 --- a/src/ga4gh/va_spec/profiles/caf_study_result.py +++ b/src/ga4gh/va_spec/profiles/caf_study_result.py @@ -26,7 +26,10 @@ class CohortAlleleFrequencyStudyResult(StudyResultBase): None, description="The dataset from which the CohortAlleleFrequencyStudyResult was reported.", ) - focusAllele: Allele | str # noqa: N815 + focusAllele: Allele | str = Field( # noqa: N815 + ..., + description="The specific subject or experimental unit in a Study that data in the StudyResult object is about - e.g. a particular variant in a population allele frequency dataset like ExAC or gnomAD.", + ) focusAlleleCount: int = Field( # noqa: N815 ..., description="The number of occurrences of the focusAllele in the cohort." ) diff --git a/src/ga4gh/va_spec/profiles/var_path_stmt.py b/src/ga4gh/va_spec/profiles/var_path_stmt.py index 1c24aca..b7a31e1 100644 --- a/src/ga4gh/va_spec/profiles/var_path_stmt.py +++ b/src/ga4gh/va_spec/profiles/var_path_stmt.py @@ -33,9 +33,12 @@ class VariantPathogenicityStatement(StatementBase): subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 ..., description="A variant that is the subject of the Statement." ) - predicate: Literal["isCausalFor"] = "isCausalFor" + predicate: Literal["isCausalFor"] = Field( + "isCausalFor", + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectCondition: Condition | IRI = Field( # noqa: N815 - ..., description="The `Condition` for which the variant impact is stated." + ..., description="The Condition for which the variant impact is stated." ) penetranceQualifier: PenetranceQualifier | None = Field( # noqa: N815 None, diff --git a/src/ga4gh/va_spec/profiles/var_study_stmt.py b/src/ga4gh/va_spec/profiles/var_study_stmt.py index c35bcb9..4376332 100644 --- a/src/ga4gh/va_spec/profiles/var_study_stmt.py +++ b/src/ga4gh/va_spec/profiles/var_study_stmt.py @@ -74,9 +74,12 @@ class VariantDiagnosticStudyStatement(StatementBase): subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 ..., description="A variant that is the subject of the Statement." ) - predicate: DiagnosticPredicate + predicate: DiagnosticPredicate = Field( + ..., + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectCondition: Condition | IRI = Field( # noqa: N815 - ..., description="The `Condition` for which the variant impact is stated." + ..., description="The disease that is evaluated for diagnosis." ) alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 None, @@ -107,7 +110,10 @@ class VariantOncogenicityStudyStatement(StatementBase): subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 ..., description="A variant that is the subject of the Statement." ) - predicate: OncogenicPredicate + predicate: OncogenicPredicate = Field( + ..., + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectTumorType: Condition | IRI = Field( # noqa: N815 ..., description="The tumor type for which the variant impact is evaluated." ) @@ -140,7 +146,10 @@ class VariantPrognosticStudyStatement(StatementBase): subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 ..., description="A variant that is the subject of the Statement." ) - predicate: PrognosticPredicate + predicate: PrognosticPredicate = Field( + ..., + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectCondition: Condition | IRI = Field( # noqa: N815 ..., description="The disease that is evaluated for outcome." ) @@ -173,7 +182,10 @@ class VariantTherapeuticResponseStudyStatement(StatementBase): subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 ..., description="A variant that is the subject of the Statement." ) - predicate: TherapeuticResponsePredicate + predicate: TherapeuticResponsePredicate = Field( + ..., + description="The relationship declared to hold between the subject and the object of the Statement.", + ) objectTherapeutic: TherapeuticProcedure | IRI = Field( # noqa: N815 ..., description="A drug administration or other therapeutic procedure that the neoplasm is intended to respond to.", @@ -192,5 +204,5 @@ class VariantTherapeuticResponseStudyStatement(StatementBase): ) geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 None, - description="Reports a gene impacted by the variant, which may contribute to the therapeutic sensitivity or resistance reported in the Statement.", + description="Reports a gene impacted by the variant, which may contribute to the therapeutic sensitivity or resistance reported in the Statement. ", ) diff --git a/submodules/va_spec b/submodules/va_spec index dc10690..3261ad7 160000 --- a/submodules/va_spec +++ b/submodules/va_spec @@ -1 +1 @@ -Subproject commit dc106908f36326126273721f9db90788fbdf7ce5 +Subproject commit 3261ad79b0c6d03786ae8f14287f1926dc5b45bd diff --git a/tests/validation/test_va_spec_schema.py b/tests/validation/test_va_spec_schema.py index 3011ac9..1d99d76 100644 --- a/tests/validation/test_va_spec_schema.py +++ b/tests/validation/test_va_spec_schema.py @@ -2,19 +2,24 @@ import json from pathlib import Path +from typing import Literal, get_args, get_origin import ga4gh.va_spec.profiles as va_spec_profiles ROOT_DIR = Path(__file__).parents[2] -VA_SPEC_SCHEMA_DIR = ROOT_DIR / "submodules" / "va_spec" / "schema" +VA_SPEC_SCHEMA_DIR = ( + ROOT_DIR / "submodules" / "va_spec" / "schema" / "profiles" / "json" +) VA_SPEC_SCHEMA = {} +VA_SPEC_BASE_CLASSES = set() VA_SPEC_CONCRETE_CLASSES = set() VA_SPEC_PRIMITIVES = set() -def _update_classes_and_primitives(f_path: Path): - with f_path.open() as rf: +# Get profile classes +for f in VA_SPEC_SCHEMA_DIR.glob("*"): + with f.open() as rf: cls_def = json.load(rf) va_spec_class = cls_def["title"] @@ -22,49 +27,53 @@ def _update_classes_and_primitives(f_path: Path): if "properties" in cls_def: VA_SPEC_CONCRETE_CLASSES.add(va_spec_class) - elif cls_def.get("type") in {"array", "int", "str"}: + elif cls_def.get("type") in {"array", "integer", "string"}: VA_SPEC_PRIMITIVES.add(va_spec_class) + else: + VA_SPEC_BASE_CLASSES.add(va_spec_class) -# Get profile classes -for child in (VA_SPEC_SCHEMA_DIR / "profiles").iterdir(): - for f in (child / "json").glob("*"): - _update_classes_and_primitives(f) - - -def test_schema_models_exist(): - """Test that VA-Spec Python covers the models defined by VA-Spec""" - for va_spec_class in VA_SPEC_CONCRETE_CLASSES | VA_SPEC_PRIMITIVES: - assert getattr(va_spec_profiles, va_spec_class, False) - - -def test_schema_class_fields_are_valid(): - """Test that VA-Spec Python model fields match the VA-Spec specification""" - for va_spec_class in VA_SPEC_CONCRETE_CLASSES: - schema_fields = set(VA_SPEC_SCHEMA[va_spec_class]["properties"]) - pydantic_model = getattr(va_spec_profiles, va_spec_class) - assert set(pydantic_model.model_fields) == schema_fields, va_spec_class +def test_schema_models_in_pydantic(): + """Ensure that each schema model has corresponding Pydantic model""" + for va_spec_class in ( + VA_SPEC_BASE_CLASSES | VA_SPEC_CONCRETE_CLASSES | VA_SPEC_PRIMITIVES + ): + assert getattr(va_spec_profiles, va_spec_class, False), va_spec_class -def test_model_keys_are_valid(): - """Test that digest keys on objects are valid and sorted""" +def test_schema_class_fields(): + """Check that each schema model properties exist and are required in corresponding + Pydantic model, and validate required properties + """ for va_spec_class in VA_SPEC_CONCRETE_CLASSES: - if ( - VA_SPEC_SCHEMA[va_spec_class].get("ga4ghDigest", {}).get("keys", None) - is None - ): - continue - + schema_properties = VA_SPEC_SCHEMA[va_spec_class]["properties"] pydantic_model = getattr(va_spec_profiles, va_spec_class) - - try: - pydantic_model_digest_keys = pydantic_model.ga4gh.keys - except AttributeError as e: - raise AttributeError(va_spec_class) from e - - assert set(pydantic_model_digest_keys) == set( - VA_SPEC_SCHEMA[va_spec_class]["ga4ghDigest"]["keys"] - ), va_spec_class - assert pydantic_model_digest_keys == sorted( - pydantic_model.ga4gh.keys - ), va_spec_class + assert set(pydantic_model.model_fields) == set(schema_properties), va_spec_class + + required_schema_fields = set(VA_SPEC_SCHEMA[va_spec_class]["required"]) + + for prop, property_def in schema_properties.items(): + pydantic_model_field_info = pydantic_model.model_fields[prop] + pydantic_field_required = pydantic_model_field_info.is_required() + + if prop in required_schema_fields: + if prop != "type": + if get_origin(pydantic_model_field_info.annotation) is Literal: + assert ( + get_args(pydantic_model_field_info.annotation)[0] + == pydantic_model_field_info.default + ) + else: + assert pydantic_field_required, f"{pydantic_model}.{prop}" + else: + assert not pydantic_field_required, f"{pydantic_model}.{prop}" + + if property_def.get("description") is not None: + field_descr = pydantic_model_field_info.description or "" + assert property_def["description"].replace( + "'", '"' + ) == field_descr.replace("'", '"'), f"{pydantic_model}.{prop}" + else: + assert ( + pydantic_model_field_info.description is None + ), f"{pydantic_model}.{prop}" From 32e6ad117a32a36ab0fbcb15ff02b886a0d553a4 Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Wed, 13 Nov 2024 09:18:57 -0500 Subject: [PATCH 4/5] fix copy/paste --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d20f1cb..c6137af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,7 +137,7 @@ ignore = [ # S101 - assert # B011 - assert-false "tests/*" = ["ANN001", "ANN2", "ANN102", "S101", "B011"] -"src/ga4gh/cat_vrs/*models.py" = ["ANN102"] +"src/ga4gh/va_spec/profiles/*" = ["ANN102"] [tool.setuptools.packages.find] where = ["src"] From b250b4a305f409ba1863775dfe49c14a3a3577f5 Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Wed, 13 Nov 2024 09:57:09 -0500 Subject: [PATCH 5/5] ignore n815 --- pyproject.toml | 3 +- .../va_spec/profiles/assay_var_effect.py | 22 +++++----- .../va_spec/profiles/caf_study_result.py | 12 +++--- src/ga4gh/va_spec/profiles/var_path_stmt.py | 10 ++--- src/ga4gh/va_spec/profiles/var_study_stmt.py | 42 +++++++++---------- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c6137af..5374c81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,8 +136,9 @@ ignore = [ # ANN102 - missing-type-cls # S101 - assert # B011 - assert-false +# N815 - mixed-case-variable-in-class-scope "tests/*" = ["ANN001", "ANN2", "ANN102", "S101", "B011"] -"src/ga4gh/va_spec/profiles/*" = ["ANN102"] +"src/ga4gh/va_spec/profiles/*" = ["ANN102", "N815"] [tool.setuptools.packages.find] where = ["src"] diff --git a/src/ga4gh/va_spec/profiles/assay_var_effect.py b/src/ga4gh/va_spec/profiles/assay_var_effect.py index 9be7d44..5225f98 100644 --- a/src/ga4gh/va_spec/profiles/assay_var_effect.py +++ b/src/ga4gh/va_spec/profiles/assay_var_effect.py @@ -48,7 +48,7 @@ class AssayVariantEffectFunctionalClassificationStatement(StatementBase): "AssayVariantEffectFunctionalClassificationStatement", description="MUST be 'AssayVariantEffectFunctionalClassificationStatement'.", ) - subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( ..., description="A protein or genomic contextual or canonical molecular variant.", ) @@ -56,7 +56,7 @@ class AssayVariantEffectFunctionalClassificationStatement(StatementBase): "hasAssayVariantEffectFor", description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectAssay: IRI | Coding = Field( # noqa: N815 + objectAssay: IRI | Coding = Field( ..., description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", ) @@ -64,7 +64,7 @@ class AssayVariantEffectFunctionalClassificationStatement(StatementBase): ..., description="The functional classification of the variant effect in the assay.", ) - specifiedBy: Method | IRI | None = Field( # noqa: N815 + specifiedBy: Method | IRI | None = Field( None, description="The method that specifies the functional classification of the variant effect in the assay.", ) @@ -79,7 +79,7 @@ class AssayVariantEffectClinicalClassificationStatement(StatementBase): "AssayVariantEffectClinicalClassificationStatement", description="MUST be 'AssayVariantEffectClinicalClassificationStatement'.", ) - subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: MolecularVariation | CategoricalVariant | IRI = Field( ..., description="A protein or genomic contextual or canonical molecular variant.", ) @@ -87,7 +87,7 @@ class AssayVariantEffectClinicalClassificationStatement(StatementBase): "hasAssayVariantEffectFor", description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectAssay: IRI | Coding = Field( # noqa: N815 + objectAssay: IRI | Coding = Field( ..., description="The assay that is evaluated for the variant effect. (e.g growth in haploid cell culture protein stability in fluorescence assay)", ) @@ -95,7 +95,7 @@ class AssayVariantEffectClinicalClassificationStatement(StatementBase): ..., description="The clinical strength of evidence of the variant effect in the assay.", ) - specifiedBy: Method | IRI | None = Field( # noqa: N815 + specifiedBy: Method | IRI | None = Field( None, description="The method that specifies the clinical strength of evidence of the variant effect in the assay.", ) @@ -110,25 +110,25 @@ class AssayVariantEffectMeasurementStudyResult(StudyResultBase): "AssayVariantEffectMeasurementStudyResult", description="MUST be 'AssayVariantEffectMeasurementStudyResult'.", ) - componentResult: list[StudyResult] | None = Field( # noqa: N815 + componentResult: list[StudyResult] | None = Field( None, description="Another StudyResult comprised of data items about the same focus as its parent Result, but based on a more narrowly scoped analysis of the foundational data (e.g. an analysis based on data about a subset of the parent Results full study population) .", ) - studyGroup: StudyGroup | None = Field( # noqa: N815 + studyGroup: StudyGroup | None = Field( None, description="A description of a specific group or population of subjects interrogated in the ResearchStudy that produced the data captured in the StudyResult.", ) - focusVariant: MolecularVariation | IRI | None = Field( # noqa: N815 + focusVariant: MolecularVariation | IRI | None = Field( None, description="The human mapped representation of the variant that is the subject of the Statement.", ) score: float | None = Field( None, description="The score of the variant effect in the assay." ) - specifiedBy: Method | IRI | None = Field( # noqa: N815 + specifiedBy: Method | IRI | None = Field( None, description="The assay that was used to measure the variant effect with all the various properties", ) - sourceDataSet: list[DataSet] | None = Field( # noqa: N815 + sourceDataSet: list[DataSet] | None = Field( None, description="The full data set that this measurement is a part of" ) diff --git a/src/ga4gh/va_spec/profiles/caf_study_result.py b/src/ga4gh/va_spec/profiles/caf_study_result.py index d5f1aff..ecd348d 100644 --- a/src/ga4gh/va_spec/profiles/caf_study_result.py +++ b/src/ga4gh/va_spec/profiles/caf_study_result.py @@ -22,28 +22,28 @@ class CohortAlleleFrequencyStudyResult(StudyResultBase): "CohortAlleleFrequencyStudyResult", description="MUST be 'CohortAlleleFrequencyStudyResult'.", ) - sourceDataSet: list[DataSet] | None = Field( # noqa: N815 + sourceDataSet: list[DataSet] | None = Field( None, description="The dataset from which the CohortAlleleFrequencyStudyResult was reported.", ) - focusAllele: Allele | str = Field( # noqa: N815 + focusAllele: Allele | str = Field( ..., description="The specific subject or experimental unit in a Study that data in the StudyResult object is about - e.g. a particular variant in a population allele frequency dataset like ExAC or gnomAD.", ) - focusAlleleCount: int = Field( # noqa: N815 + focusAlleleCount: int = Field( ..., description="The number of occurrences of the focusAllele in the cohort." ) - locusAlleleCount: int = Field( # noqa: N815 + locusAlleleCount: int = Field( ..., description="The number of occurrences of all alleles at the locus in the cohort (sometimes referred to as 'allele number')", ) - focusAlleleFrequency: float = Field( # noqa: N815 + focusAlleleFrequency: float = Field( ..., description="The frequency of the focusAllele in the cohort." ) cohort: list[StudyResult] = Field( ..., description="The cohort from which the frequency was derived." ) - subCohortFrequency: list[CohortAlleleFrequencyStudyResult] | None = Field( # noqa: N815 + subCohortFrequency: list[CohortAlleleFrequencyStudyResult] | None = Field( None, description="A list of CohortAlleleFrequency objects describing subcohorts of the cohort currently being described. This creates a recursive relationship and subcohorts can be further subdivided into more subcohorts. This enables, for example, the description of different ancestry groups and sexes among those ancestry groups.", ) diff --git a/src/ga4gh/va_spec/profiles/var_path_stmt.py b/src/ga4gh/va_spec/profiles/var_path_stmt.py index b7a31e1..6bf536d 100644 --- a/src/ga4gh/va_spec/profiles/var_path_stmt.py +++ b/src/ga4gh/va_spec/profiles/var_path_stmt.py @@ -30,25 +30,25 @@ class VariantPathogenicityStatement(StatementBase): "VariantPathogenicityStatement", description="MUST be 'VariantPathogenicityStatement'.", ) - subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: Variation | CategoricalVariant | IRI = Field( ..., description="A variant that is the subject of the Statement." ) predicate: Literal["isCausalFor"] = Field( "isCausalFor", description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectCondition: Condition | IRI = Field( # noqa: N815 + objectCondition: Condition | IRI = Field( ..., description="The Condition for which the variant impact is stated." ) - penetranceQualifier: PenetranceQualifier | None = Field( # noqa: N815 + penetranceQualifier: PenetranceQualifier | None = Field( None, description="Reports the penetrance of the pathogenic effect - i.e. the extent to which the variant impact is expressed by individuals carrying it as a measure of the proportion of carriers exhibiting the condition.", ) - modeOfInheritanceQualifier: list[Coding] | None = Field( # noqa: N815 + modeOfInheritanceQualifier: list[Coding] | None = Field( None, description="Reports a pattern of inheritance expected for the pathogenic effect of the variant. Use HPO terms within the hierarchy of 'HP:0000005' (mode of inheritance) to specify.", ) - geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + geneContextQualifier: Gene | IRI | None = Field( None, description="Reports the gene through which the pathogenic effect asserted for the variant is mediated (i.e. it is the variant's impact on this gene that is responsible for causing the condition).", ) diff --git a/src/ga4gh/va_spec/profiles/var_study_stmt.py b/src/ga4gh/va_spec/profiles/var_study_stmt.py index 4376332..bb7c7f9 100644 --- a/src/ga4gh/va_spec/profiles/var_study_stmt.py +++ b/src/ga4gh/va_spec/profiles/var_study_stmt.py @@ -71,25 +71,25 @@ class VariantDiagnosticStudyStatement(StatementBase): "VariantDiagnosticStudyStatement", description="MUST be 'VariantDiagnosticStudyStatement'.", ) - subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: Variation | CategoricalVariant | IRI = Field( ..., description="A variant that is the subject of the Statement." ) predicate: DiagnosticPredicate = Field( ..., description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectCondition: Condition | IRI = Field( # noqa: N815 + objectCondition: Condition | IRI = Field( ..., description="The disease that is evaluated for diagnosis." ) - alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + alleleOriginQualifier: AlleleOriginQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", ) - allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", ) - geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + geneContextQualifier: Gene | IRI | None = Field( None, description="Reports a gene impacted by the variant, which may contribute to the diagnostic association in the Statement.", ) @@ -107,25 +107,25 @@ class VariantOncogenicityStudyStatement(StatementBase): "VariantOncogenicityStudyStatement", description="MUST be 'VariantOncogenicityStudyStatement'.", ) - subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: Variation | CategoricalVariant | IRI = Field( ..., description="A variant that is the subject of the Statement." ) predicate: OncogenicPredicate = Field( ..., description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectTumorType: Condition | IRI = Field( # noqa: N815 + objectTumorType: Condition | IRI = Field( ..., description="The tumor type for which the variant impact is evaluated." ) - alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + alleleOriginQualifier: AlleleOriginQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", ) - allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", ) - geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + geneContextQualifier: Gene | IRI | None = Field( None, description="Reports a gene impacted by the variant, which may contribute to the oncogenic role in the Statement.", ) @@ -143,25 +143,25 @@ class VariantPrognosticStudyStatement(StatementBase): "VariantPrognosticStudyStatement", description="MUST be 'VariantPrognosticStudyStatement'.", ) - subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: Variation | CategoricalVariant | IRI = Field( ..., description="A variant that is the subject of the Statement." ) predicate: PrognosticPredicate = Field( ..., description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectCondition: Condition | IRI = Field( # noqa: N815 + objectCondition: Condition | IRI = Field( ..., description="The disease that is evaluated for outcome." ) - alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + alleleOriginQualifier: AlleleOriginQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", ) - allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", ) - geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + geneContextQualifier: Gene | IRI | None = Field( None, description="Reports a gene impacted by the variant, which may contribute to the prognostic association in the Statement.", ) @@ -179,30 +179,30 @@ class VariantTherapeuticResponseStudyStatement(StatementBase): "VariantTherapeuticResponseStudyStatement", description="MUST be 'VariantTherapeuticResponseStudyStatement'.", ) - subjectVariant: Variation | CategoricalVariant | IRI = Field( # noqa: N815 + subjectVariant: Variation | CategoricalVariant | IRI = Field( ..., description="A variant that is the subject of the Statement." ) predicate: TherapeuticResponsePredicate = Field( ..., description="The relationship declared to hold between the subject and the object of the Statement.", ) - objectTherapeutic: TherapeuticProcedure | IRI = Field( # noqa: N815 + objectTherapeutic: TherapeuticProcedure | IRI = Field( ..., description="A drug administration or other therapeutic procedure that the neoplasm is intended to respond to.", ) - conditionQualifier: Condition | IRI = Field( # noqa: N815 + conditionQualifier: Condition | IRI = Field( ..., description="Reports the disease context in which the variant's association with therapeutic sensitivity or resistance is evaluated. Note that this is a required qualifier in therapeutic response statements.", ) - alleleOriginQualifier: AlleleOriginQualifier | None = Field( # noqa: N815 + alleleOriginQualifier: AlleleOriginQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of an inherited (germline) variant, an acquired (somatic) mutation, or both (combined).", ) - allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( # noqa: N815 + allelePrevalenceQualifier: AllelePrevalenceQualifier | None = Field( None, description="Reports whether the statement should be interpreted in the context of the variant being rare or common.", ) - geneContextQualifier: Gene | IRI | None = Field( # noqa: N815 + geneContextQualifier: Gene | IRI | None = Field( None, description="Reports a gene impacted by the variant, which may contribute to the therapeutic sensitivity or resistance reported in the Statement. ", )