qpx 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qpx-1.0.1/.codacy.yml +7 -0
- qpx-1.0.1/.github/workflows/deploy-docs.yml +47 -0
- qpx-1.0.1/.github/workflows/integration-tests.yml +114 -0
- qpx-1.0.1/.github/workflows/python-app.yml +58 -0
- qpx-1.0.1/.github/workflows/python-package.yml +59 -0
- qpx-1.0.1/.github/workflows/python-publish.yml +36 -0
- qpx-1.0.1/.gitignore +116 -0
- qpx-1.0.1/.markdownlint.json +8 -0
- qpx-1.0.1/.pre-commit-config.yaml +15 -0
- qpx-1.0.1/CHANGELOG.md +55 -0
- qpx-1.0.1/CONTRIBUTING.md +92 -0
- qpx-1.0.1/LICENSE +81 -0
- qpx-1.0.1/PKG-INFO +334 -0
- qpx-1.0.1/README.md +275 -0
- qpx-1.0.1/data/ontology/pride_cv.parquet +0 -0
- qpx-1.0.1/data/ontology/psi_ms.parquet +0 -0
- qpx-1.0.1/data/ontology/versions.yaml +13 -0
- qpx-1.0.1/docs/.gitignore +1 -0
- qpx-1.0.1/docs/CNAME +1 -0
- qpx-1.0.1/docs/community.md +46 -0
- qpx-1.0.1/docs/customstyle.css +10 -0
- qpx-1.0.1/docs/examples/index.md +41 -0
- qpx-1.0.1/docs/examples/integration.md +153 -0
- qpx-1.0.1/docs/examples/qc.md +95 -0
- qpx-1.0.1/docs/examples/quickstart.md +80 -0
- qpx-1.0.1/docs/examples/workflows.md +82 -0
- qpx-1.0.1/docs/guide/convert.md +576 -0
- qpx-1.0.1/docs/guide/index.md +183 -0
- qpx-1.0.1/docs/guide/info.md +188 -0
- qpx-1.0.1/docs/guide/ontology.md +185 -0
- qpx-1.0.1/docs/guide/project.md +369 -0
- qpx-1.0.1/docs/guide/query.md +189 -0
- qpx-1.0.1/docs/guide/stats.md +467 -0
- qpx-1.0.1/docs/guide/transform.md +312 -0
- qpx-1.0.1/docs/guide/validate.md +147 -0
- qpx-1.0.1/docs/guide/visualize.md +566 -0
- qpx-1.0.1/docs/images/anndata.png +0 -0
- qpx-1.0.1/docs/images/architecture.png +0 -0
- qpx-1.0.1/docs/images/data_conversion.png +0 -0
- qpx-1.0.1/docs/images/favicon.ico +0 -0
- qpx-1.0.1/docs/images/favicon.svg +10 -0
- qpx-1.0.1/docs/images/file-relation.png +0 -0
- qpx-1.0.1/docs/images/format_conversion.png +0 -0
- qpx-1.0.1/docs/images/map.png +0 -0
- qpx-1.0.1/docs/images/mudata-architecture.svg +233 -0
- qpx-1.0.1/docs/images/qpx-architecture.svg +231 -0
- qpx-1.0.1/docs/images/qpx-benchmark.svg +166 -0
- qpx-1.0.1/docs/images/query_time.png +0 -0
- qpx-1.0.1/docs/images/workflow.png +0 -0
- qpx-1.0.1/docs/images/workflow2.png +0 -0
- qpx-1.0.1/docs/index.md +63 -0
- qpx-1.0.1/docs/quickstart.md +156 -0
- qpx-1.0.1/docs/spec/absolute.md +171 -0
- qpx-1.0.1/docs/spec/anndata.md +114 -0
- qpx-1.0.1/docs/spec/converter-coverage.md +42 -0
- qpx-1.0.1/docs/spec/dataset.md +131 -0
- qpx-1.0.1/docs/spec/differential.md +219 -0
- qpx-1.0.1/docs/spec/feature.md +220 -0
- qpx-1.0.1/docs/spec/file-naming.md +125 -0
- qpx-1.0.1/docs/spec/index.md +119 -0
- qpx-1.0.1/docs/spec/intensities.md +131 -0
- qpx-1.0.1/docs/spec/modifications.md +209 -0
- qpx-1.0.1/docs/spec/mz.md +133 -0
- qpx-1.0.1/docs/spec/ontology.md +382 -0
- qpx-1.0.1/docs/spec/pepmap.md +91 -0
- qpx-1.0.1/docs/spec/peptidoform.md +81 -0
- qpx-1.0.1/docs/spec/pg.md +328 -0
- qpx-1.0.1/docs/spec/provenance.md +284 -0
- qpx-1.0.1/docs/spec/psm.md +209 -0
- qpx-1.0.1/docs/spec/run.md +219 -0
- qpx-1.0.1/docs/spec/sample.md +120 -0
- qpx-1.0.1/docs/spec/scan.md +112 -0
- qpx-1.0.1/docs/spec/schemas/dataset.yaml +46 -0
- qpx-1.0.1/docs/spec/schemas/feature.yaml +123 -0
- qpx-1.0.1/docs/spec/schemas/mz.yaml +56 -0
- qpx-1.0.1/docs/spec/schemas/ontology.yaml +35 -0
- qpx-1.0.1/docs/spec/schemas/pepmap.yaml +20 -0
- qpx-1.0.1/docs/spec/schemas/pg.yaml +87 -0
- qpx-1.0.1/docs/spec/schemas/provenance.yaml +37 -0
- qpx-1.0.1/docs/spec/schemas/psm.yaml +93 -0
- qpx-1.0.1/docs/spec/schemas/run.yaml +44 -0
- qpx-1.0.1/docs/spec/schemas/sample.yaml +55 -0
- qpx-1.0.1/docs/spec/schemas/types.yaml +121 -0
- qpx-1.0.1/docs/spec/schemas.md +96 -0
- qpx-1.0.1/docs/spec/scores.md +106 -0
- qpx-1.0.1/docs/spec/sdrf-mapping.md +127 -0
- qpx-1.0.1/docs/spec/serialization.md +226 -0
- qpx-1.0.1/docs/spec/tool-mappings.md +80 -0
- qpx-1.0.1/docs/spec/versioning.md +77 -0
- qpx-1.0.1/docs/spec/views.md +184 -0
- qpx-1.0.1/docs/stylesheets/quantms-theme.css +523 -0
- qpx-1.0.1/docs/troubleshooting.md +331 -0
- qpx-1.0.1/environment.yml +21 -0
- qpx-1.0.1/mkdocs.yml +196 -0
- qpx-1.0.1/pyproject.toml +88 -0
- qpx-1.0.1/qpx/__init__.py +77 -0
- qpx-1.0.1/qpx/_version.py +6 -0
- qpx-1.0.1/qpx/cli/__init__.py +5 -0
- qpx-1.0.1/qpx/cli/convert.py +845 -0
- qpx-1.0.1/qpx/cli/info.py +419 -0
- qpx-1.0.1/qpx/cli/main.py +55 -0
- qpx-1.0.1/qpx/cli/ontology.py +158 -0
- qpx-1.0.1/qpx/cli/query.py +393 -0
- qpx-1.0.1/qpx/cli/transform.py +600 -0
- qpx-1.0.1/qpx/cli/validate.py +136 -0
- qpx-1.0.1/qpx/collection.py +119 -0
- qpx-1.0.1/qpx/config/__init__.py +26 -0
- qpx-1.0.1/qpx/config/default_filters.yaml +23 -0
- qpx-1.0.1/qpx/converters/__init__.py +58 -0
- qpx-1.0.1/qpx/converters/base.py +325 -0
- qpx-1.0.1/qpx/converters/column_mappings.yaml +195 -0
- qpx-1.0.1/qpx/converters/diann/__init__.py +13 -0
- qpx-1.0.1/qpx/converters/diann/base_adapter.py +49 -0
- qpx-1.0.1/qpx/converters/diann/constants.py +70 -0
- qpx-1.0.1/qpx/converters/diann/converter.py +179 -0
- qpx-1.0.1/qpx/converters/diann/feature_adapter.py +591 -0
- qpx-1.0.1/qpx/converters/diann/pg_adapter.py +315 -0
- qpx-1.0.1/qpx/converters/fragpipe/__init__.py +13 -0
- qpx-1.0.1/qpx/converters/fragpipe/constants.py +84 -0
- qpx-1.0.1/qpx/converters/fragpipe/converter.py +143 -0
- qpx-1.0.1/qpx/converters/fragpipe/feature_adapter.py +450 -0
- qpx-1.0.1/qpx/converters/fragpipe/pg_adapter.py +262 -0
- qpx-1.0.1/qpx/converters/fragpipe/psm_adapter.py +269 -0
- qpx-1.0.1/qpx/converters/mappings.py +45 -0
- qpx-1.0.1/qpx/converters/maxquant/__init__.py +15 -0
- qpx-1.0.1/qpx/converters/maxquant/base_adapter.py +73 -0
- qpx-1.0.1/qpx/converters/maxquant/constants.py +231 -0
- qpx-1.0.1/qpx/converters/maxquant/converter.py +177 -0
- qpx-1.0.1/qpx/converters/maxquant/feature_adapter.py +475 -0
- qpx-1.0.1/qpx/converters/maxquant/pg_adapter.py +375 -0
- qpx-1.0.1/qpx/converters/maxquant/psm_adapter.py +307 -0
- qpx-1.0.1/qpx/converters/mzidentml/__init__.py +19 -0
- qpx-1.0.1/qpx/converters/mzidentml/converter.py +346 -0
- qpx-1.0.1/qpx/converters/mzidentml/mgf_parser.py +222 -0
- qpx-1.0.1/qpx/converters/mzidentml/pg_adapter.py +181 -0
- qpx-1.0.1/qpx/converters/mzidentml/psm_adapter.py +845 -0
- qpx-1.0.1/qpx/converters/mztab.py +395 -0
- qpx-1.0.1/qpx/converters/orchestrator.py +119 -0
- qpx-1.0.1/qpx/converters/ptm.py +354 -0
- qpx-1.0.1/qpx/converters/quantms/__init__.py +13 -0
- qpx-1.0.1/qpx/converters/quantms/constants.py +47 -0
- qpx-1.0.1/qpx/converters/quantms/converter.py +256 -0
- qpx-1.0.1/qpx/converters/quantms/feature_adapter.py +1106 -0
- qpx-1.0.1/qpx/converters/quantms/pg_adapter.py +605 -0
- qpx-1.0.1/qpx/converters/quantms/psm_adapter.py +421 -0
- qpx-1.0.1/qpx/converters/sdrf.py +456 -0
- qpx-1.0.1/qpx/converters/utils.py +104 -0
- qpx-1.0.1/qpx/core/__init__.py +1 -0
- qpx-1.0.1/qpx/core/cleavage.py +76 -0
- qpx-1.0.1/qpx/core/constants.py +22 -0
- qpx-1.0.1/qpx/core/convert.py +45 -0
- qpx-1.0.1/qpx/core/cv_terms.py +103 -0
- qpx-1.0.1/qpx/core/data/__init__.py +49 -0
- qpx-1.0.1/qpx/core/data/base.py +135 -0
- qpx-1.0.1/qpx/core/data/dataset.py +12 -0
- qpx-1.0.1/qpx/core/data/feature.py +89 -0
- qpx-1.0.1/qpx/core/data/loader.py +146 -0
- qpx-1.0.1/qpx/core/data/mz.py +24 -0
- qpx-1.0.1/qpx/core/data/ontology.py +17 -0
- qpx-1.0.1/qpx/core/data/pepmap.py +26 -0
- qpx-1.0.1/qpx/core/data/pg.py +51 -0
- qpx-1.0.1/qpx/core/data/provenance.py +12 -0
- qpx-1.0.1/qpx/core/data/psm.py +25 -0
- qpx-1.0.1/qpx/core/data/run.py +12 -0
- qpx-1.0.1/qpx/core/data/sample.py +21 -0
- qpx-1.0.1/qpx/core/data/schema.py +285 -0
- qpx-1.0.1/qpx/core/data/schemas/dataset.yaml +46 -0
- qpx-1.0.1/qpx/core/data/schemas/feature.yaml +123 -0
- qpx-1.0.1/qpx/core/data/schemas/mz.yaml +56 -0
- qpx-1.0.1/qpx/core/data/schemas/ontology.yaml +35 -0
- qpx-1.0.1/qpx/core/data/schemas/pepmap.yaml +20 -0
- qpx-1.0.1/qpx/core/data/schemas/pg.yaml +87 -0
- qpx-1.0.1/qpx/core/data/schemas/provenance.yaml +37 -0
- qpx-1.0.1/qpx/core/data/schemas/psm.yaml +93 -0
- qpx-1.0.1/qpx/core/data/schemas/run.yaml +44 -0
- qpx-1.0.1/qpx/core/data/schemas/sample.yaml +55 -0
- qpx-1.0.1/qpx/core/data/schemas/types.yaml +121 -0
- qpx-1.0.1/qpx/core/engine.py +115 -0
- qpx-1.0.1/qpx/core/obo.py +258 -0
- qpx-1.0.1/qpx/core/ontology/__init__.py +18 -0
- qpx-1.0.1/qpx/core/ontology/build.py +219 -0
- qpx-1.0.1/qpx/core/ontology/data/pride_cv.parquet +0 -0
- qpx-1.0.1/qpx/core/ontology/data/psi_ms.parquet +0 -0
- qpx-1.0.1/qpx/core/ontology/registry.py +491 -0
- qpx-1.0.1/qpx/core/ontology/sources.yaml +33 -0
- qpx-1.0.1/qpx/core/parquet_io.py +27 -0
- qpx-1.0.1/qpx/core/pride.py +74 -0
- qpx-1.0.1/qpx/core/quantms/pg.py +667 -0
- qpx-1.0.1/qpx/core/query.py +90 -0
- qpx-1.0.1/qpx/core/scores.py +643 -0
- qpx-1.0.1/qpx/core/sdrf.py +446 -0
- qpx-1.0.1/qpx/dataset.py +780 -0
- qpx-1.0.1/qpx/mudata.py +470 -0
- qpx-1.0.1/qpx/transforms/__init__.py +13 -0
- qpx-1.0.1/qpx/transforms/accession_normalizer.py +204 -0
- qpx-1.0.1/qpx/transforms/gene_mapping.py +515 -0
- qpx-1.0.1/qpx/transforms/sdrf_updater.py +229 -0
- qpx-1.0.1/qpx/transforms/spectra_mapping.py +477 -0
- qpx-1.0.1/qpx/views/__init__.py +23 -0
- qpx-1.0.1/qpx/views/api.py +365 -0
- qpx-1.0.1/qpx/views/base.py +85 -0
- qpx-1.0.1/qpx/views/plotting.py +87 -0
- qpx-1.0.1/qpx/writers/__init__.py +27 -0
- qpx-1.0.1/qpx/writers/base.py +165 -0
- qpx-1.0.1/qpx/writers/dataset.py +8 -0
- qpx-1.0.1/qpx/writers/feature.py +8 -0
- qpx-1.0.1/qpx/writers/mz.py +8 -0
- qpx-1.0.1/qpx/writers/ontology.py +8 -0
- qpx-1.0.1/qpx/writers/pepmap.py +8 -0
- qpx-1.0.1/qpx/writers/pg.py +8 -0
- qpx-1.0.1/qpx/writers/provenance.py +8 -0
- qpx-1.0.1/qpx/writers/psm.py +8 -0
- qpx-1.0.1/qpx/writers/run.py +8 -0
- qpx-1.0.1/qpx/writers/sample.py +8 -0
- qpx-1.0.1/scripts/convert_msnet.py +1179 -0
- qpx-1.0.1/tests/__init__.py +6 -0
- qpx-1.0.1/tests/cli/__init__.py +0 -0
- qpx-1.0.1/tests/cli/test_cli.py +200 -0
- qpx-1.0.1/tests/conftest.py +474 -0
- qpx-1.0.1/tests/converters/__init__.py +0 -0
- qpx-1.0.1/tests/converters/test_converter_utils.py +185 -0
- qpx-1.0.1/tests/converters/test_converters.py +1014 -0
- qpx-1.0.1/tests/converters/test_diann_converter.py +269 -0
- qpx-1.0.1/tests/converters/test_maxquant_converter.py +332 -0
- qpx-1.0.1/tests/converters/test_mgf_parser.py +414 -0
- qpx-1.0.1/tests/converters/test_mzidentml_converter.py +514 -0
- qpx-1.0.1/tests/converters/test_mzidentml_full_converter.py +307 -0
- qpx-1.0.1/tests/converters/test_ptm.py +193 -0
- qpx-1.0.1/tests/converters/test_quantms_converter.py +340 -0
- qpx-1.0.1/tests/core/quantms/test_full_conversion_suite.py +445 -0
- qpx-1.0.1/tests/integration/__init__.py +0 -0
- qpx-1.0.1/tests/integration/test_collection.py +145 -0
- qpx-1.0.1/tests/integration/test_dataset.py +702 -0
- qpx-1.0.1/tests/integration/test_integrity.py +112 -0
- qpx-1.0.1/tests/integration/test_partitioning.py +123 -0
- qpx-1.0.1/tests/integration/test_s3_support.py +87 -0
- qpx-1.0.1/tests/integration/test_view_plots.py +47 -0
- qpx-1.0.1/tests/integration/test_views.py +296 -0
- qpx-1.0.1/tests/integration/test_views_enhanced.py +154 -0
- qpx-1.0.1/tests/real_data/README.md +13 -0
- qpx-1.0.1/tests/real_data/__init__.py +0 -0
- qpx-1.0.1/tests/real_data/test_diann_small.py +360 -0
- qpx-1.0.1/tests/real_data/test_maxquant_simple.py +303 -0
- qpx-1.0.1/tests/real_data/test_pxd054720_dataset.py +325 -0
- qpx-1.0.1/tests/real_data/test_pxd054720_mzidentml.py +146 -0
- qpx-1.0.1/tests/unit/__init__.py +0 -0
- qpx-1.0.1/tests/unit/test_ae_view.py +68 -0
- qpx-1.0.1/tests/unit/test_core_duckdbengine.py +148 -0
- qpx-1.0.1/tests/unit/test_cross_links.py +121 -0
- qpx-1.0.1/tests/unit/test_data.py +175 -0
- qpx-1.0.1/tests/unit/test_diann_constants.py +22 -0
- qpx-1.0.1/tests/unit/test_field_ontology.py +41 -0
- qpx-1.0.1/tests/unit/test_mappings.py +99 -0
- qpx-1.0.1/tests/unit/test_ontology_registry.py +233 -0
- qpx-1.0.1/tests/unit/test_pepmap.py +145 -0
- qpx-1.0.1/tests/unit/test_pride.py +101 -0
- qpx-1.0.1/tests/unit/test_resolve_columns.py +35 -0
- qpx-1.0.1/tests/unit/test_sample_summary_view.py +55 -0
- qpx-1.0.1/tests/unit/test_schema.py +176 -0
- qpx-1.0.1/tests/unit/test_validate.py +180 -0
- qpx-1.0.1/tests/unit/test_writers.py +137 -0
qpx-1.0.1/.codacy.yml
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# This workflow will build and deploy documentation to GitHub Pages
|
|
2
|
+
# For more information see: https://www.mkdocs.org/user-guide/deploying-your-docs/
|
|
3
|
+
|
|
4
|
+
name: Deploy Documentation
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
# TODO: re-enable after mkdocs config is fixed for next release
|
|
8
|
+
# push:
|
|
9
|
+
# branches: ["main", "dev"]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy-docs:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Set up Python 3.10
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.10"
|
|
27
|
+
|
|
28
|
+
- name: Cache pip dependencies
|
|
29
|
+
uses: actions/cache@v4
|
|
30
|
+
with:
|
|
31
|
+
path: ~/.cache/pip
|
|
32
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
|
|
33
|
+
restore-keys: |
|
|
34
|
+
${{ runner.os }}-pip-
|
|
35
|
+
|
|
36
|
+
- name: Install Python dependencies
|
|
37
|
+
run: |
|
|
38
|
+
python -m pip install --upgrade pip
|
|
39
|
+
python -m pip install -e ".[dev]"
|
|
40
|
+
|
|
41
|
+
- name: Build MkDocs documentation
|
|
42
|
+
run: |
|
|
43
|
+
mkdocs build --strict
|
|
44
|
+
|
|
45
|
+
- name: Deploy to GitHub Pages
|
|
46
|
+
run: |
|
|
47
|
+
mkdocs gh-deploy --force --message "docs: deploy documentation [skip ci]"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
name: Integration Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
test_type:
|
|
9
|
+
description: "Type of integration test to run"
|
|
10
|
+
required: true
|
|
11
|
+
default: "all"
|
|
12
|
+
type: choice
|
|
13
|
+
options:
|
|
14
|
+
- all
|
|
15
|
+
- full-conversion-suite
|
|
16
|
+
- lfq-only
|
|
17
|
+
- tmt-only
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
integration-tests:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 60 # 1 hour timeout for the entire job
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Set up Python 3.10
|
|
28
|
+
uses: actions/setup-python@v4
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.10"
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@v5
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
|
|
37
|
+
- name: Sync workspace
|
|
38
|
+
run: uv sync --extra all --extra dev
|
|
39
|
+
|
|
40
|
+
- name: Run Full Conversion Suite Tests
|
|
41
|
+
if: ${{ github.event.inputs.test_type == 'all' || github.event.inputs.test_type == 'full-conversion-suite' || github.event_name == 'release' }}
|
|
42
|
+
run: uv run pytest -vv tests/core/quantms/test_full_conversion_suite.py --timeout=3000
|
|
43
|
+
env:
|
|
44
|
+
PYTHONIOENCODING: utf-8
|
|
45
|
+
|
|
46
|
+
- name: Run LFQ Tests Only
|
|
47
|
+
if: ${{ github.event.inputs.test_type == 'lfq-only' }}
|
|
48
|
+
run: uv run pytest -vv tests/core/quantms/test_full_conversion_suite.py -k "lfq" --timeout=3000
|
|
49
|
+
env:
|
|
50
|
+
PYTHONIOENCODING: utf-8
|
|
51
|
+
|
|
52
|
+
- name: Run TMT Tests Only
|
|
53
|
+
if: ${{ github.event.inputs.test_type == 'tmt-only' }}
|
|
54
|
+
run: uv run pytest -vv tests/core/quantms/test_full_conversion_suite.py -k "tmt" --timeout=3000
|
|
55
|
+
env:
|
|
56
|
+
PYTHONIOENCODING: utf-8
|
|
57
|
+
|
|
58
|
+
- name: Run All Integration Tests
|
|
59
|
+
if: ${{ github.event.inputs.test_type == 'all' || github.event_name == 'release' }}
|
|
60
|
+
run: uv run pytest -vv -m "integration" --timeout=3000
|
|
61
|
+
env:
|
|
62
|
+
PYTHONIOENCODING: utf-8
|
|
63
|
+
|
|
64
|
+
- name: Generate Example Outputs
|
|
65
|
+
if: ${{ github.event_name == 'release' }}
|
|
66
|
+
run: |
|
|
67
|
+
mkdir -p examples/output
|
|
68
|
+
|
|
69
|
+
echo "=== QuantMS LFQ Examples ==="
|
|
70
|
+
uv run qpxc convert quantms \
|
|
71
|
+
--mztab-path tests/examples/quantms/dda-lfq-full/PXD007683-LFQ.sdrf_openms_design_openms.mzTab.gz \
|
|
72
|
+
--sdrf-file tests/examples/quantms/dda-lfq-full/PXD007683-LFQ.sdrf.tsv \
|
|
73
|
+
--msstats-file tests/examples/quantms/dda-lfq-full/PXD007683-LFQ.sdrf_openms_design_msstats_in.csv.gz \
|
|
74
|
+
--output-folder examples/output \
|
|
75
|
+
--output-prefix quantms-lfq \
|
|
76
|
+
--structures psm,feature,pg \
|
|
77
|
+
--verbose
|
|
78
|
+
|
|
79
|
+
echo "=== DIA-NN Examples ==="
|
|
80
|
+
uv run qpxc convert diann \
|
|
81
|
+
--report-path tests/examples/diann/small/diann_report.tsv \
|
|
82
|
+
--qvalue-threshold 0.05 \
|
|
83
|
+
--mzml-info-folder tests/examples/diann/small/mzml \
|
|
84
|
+
--sdrf-file tests/examples/diann/small/PXD019909-DIA.sdrf.tsv \
|
|
85
|
+
--pg-matrix-path tests/examples/diann/small/diann_report.pg_matrix.tsv \
|
|
86
|
+
--output-folder examples/output \
|
|
87
|
+
--output-prefix diann \
|
|
88
|
+
--duckdb-max-memory 4GB \
|
|
89
|
+
--duckdb-threads 4 \
|
|
90
|
+
--verbose
|
|
91
|
+
|
|
92
|
+
echo "=== MaxQuant Examples ==="
|
|
93
|
+
uv run qpxc convert maxquant \
|
|
94
|
+
--msms-file tests/examples/maxquant/maxquant_simple/msms.txt \
|
|
95
|
+
--evidence-file tests/examples/maxquant/maxquant_full/evidence.txt.gz \
|
|
96
|
+
--sdrf-file tests/examples/maxquant/maxquant_full/PXD001819.sdrf.tsv \
|
|
97
|
+
--protein-groups-file tests/examples/maxquant/maxquant_full/proteinGroups.txt \
|
|
98
|
+
--output-folder examples/output \
|
|
99
|
+
--output-prefix maxquant \
|
|
100
|
+
--structures psm,feature,pg \
|
|
101
|
+
--verbose
|
|
102
|
+
|
|
103
|
+
echo "Generated example files:"
|
|
104
|
+
ls -la examples/output/
|
|
105
|
+
env:
|
|
106
|
+
PYTHONIOENCODING: utf-8
|
|
107
|
+
|
|
108
|
+
- name: Upload Example Outputs
|
|
109
|
+
if: ${{ github.event_name == 'release' }}
|
|
110
|
+
uses: actions/upload-artifact@v4
|
|
111
|
+
with:
|
|
112
|
+
name: qpx-example-outputs-${{ github.ref_name }}
|
|
113
|
+
path: examples/output/
|
|
114
|
+
retention-days: 90
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# This workflow installs dependencies with uv, runs pre-commit, and tests.
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: ["main", "dev"]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: ["main", "dev"]
|
|
11
|
+
schedule:
|
|
12
|
+
# Run full test suite daily at 2 AM UTC
|
|
13
|
+
- cron: "0 2 * * *"
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
pre-commit:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
submodules: true
|
|
27
|
+
- name: Set up Python 3.10
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.10"
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
- name: Sync workspace
|
|
36
|
+
run: uv sync --extra dev
|
|
37
|
+
- name: Run pre-commit hooks
|
|
38
|
+
run: uv run pre-commit run --all-files
|
|
39
|
+
|
|
40
|
+
unit-tests:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
needs: pre-commit
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
- name: Set up Python 3.10
|
|
48
|
+
uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.10"
|
|
51
|
+
- name: Install uv
|
|
52
|
+
uses: astral-sh/setup-uv@v5
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
- name: Sync workspace
|
|
56
|
+
run: uv sync --extra dev
|
|
57
|
+
- name: Run unit tests
|
|
58
|
+
run: uv run pytest -vv -m "not integration and not large_data"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: Python package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: ["dev", "main"]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: ["dev", "main"]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
unit-tests:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.10", "3.11"]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
uses: actions/setup-python@v4
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
- name: Lint with ruff
|
|
31
|
+
run: |
|
|
32
|
+
ruff check .
|
|
33
|
+
- name: Test with pytest (Unit Tests Only)
|
|
34
|
+
run: |
|
|
35
|
+
pytest -vv -m "not integration and not large_data"
|
|
36
|
+
|
|
37
|
+
integration-tests:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
needs: unit-tests # Only run if unit tests pass
|
|
40
|
+
strategy:
|
|
41
|
+
fail-fast: false
|
|
42
|
+
matrix:
|
|
43
|
+
python-version: ["3.10", "3.11"]
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
48
|
+
uses: actions/setup-python@v4
|
|
49
|
+
with:
|
|
50
|
+
python-version: ${{ matrix.python-version }}
|
|
51
|
+
- name: Install dependencies
|
|
52
|
+
run: |
|
|
53
|
+
python -m pip install --upgrade pip
|
|
54
|
+
pip install -e ".[dev]"
|
|
55
|
+
- name: Test with pytest (Integration Tests)
|
|
56
|
+
run: |
|
|
57
|
+
pytest -vv -m "integration" --timeout=3000
|
|
58
|
+
env:
|
|
59
|
+
PYTHONIOENCODING: utf-8
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Publish to PyPI via Trusted Publishing (OIDC) when a GitHub Release is created.
|
|
2
|
+
# Prerequisites: configure a "Trusted Publisher" on PyPI for this repository.
|
|
3
|
+
# See https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/
|
|
4
|
+
|
|
5
|
+
name: Upload Python Package
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write # Required for OIDC Trusted Publishing
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
environment: pypi
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
fetch-tags: true
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.10"
|
|
29
|
+
- name: Install build dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
pip install build
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: python -m build
|
|
35
|
+
- name: Publish package to PyPI
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
qpx-1.0.1/.gitignore
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
qpx.egg-info
|
|
2
|
+
dist
|
|
3
|
+
build
|
|
4
|
+
.idea
|
|
5
|
+
/data/*
|
|
6
|
+
!/data/ontology/
|
|
7
|
+
docs/.vscode
|
|
8
|
+
__pycache__
|
|
9
|
+
.vscode
|
|
10
|
+
|
|
11
|
+
# DuckDB database files
|
|
12
|
+
*duckdb*.db
|
|
13
|
+
*.db
|
|
14
|
+
*.db.wal
|
|
15
|
+
*.db-shm
|
|
16
|
+
|
|
17
|
+
# Temporary files
|
|
18
|
+
*.tmp
|
|
19
|
+
*.temp
|
|
20
|
+
temp/
|
|
21
|
+
tmp/
|
|
22
|
+
.tmp/
|
|
23
|
+
.temp/
|
|
24
|
+
|
|
25
|
+
# Temporary mzTab files (created during processing)
|
|
26
|
+
mztab_temp*.mzTab
|
|
27
|
+
mztab_temp*.mzTab.gz
|
|
28
|
+
mztab_temp*.mzTab.bz2
|
|
29
|
+
|
|
30
|
+
# Test output and temporary data
|
|
31
|
+
test_output*/
|
|
32
|
+
output*/
|
|
33
|
+
results*/
|
|
34
|
+
*.parquet
|
|
35
|
+
!examples/psm/*.parquet
|
|
36
|
+
!qpx/core/ontology/data/*.parquet
|
|
37
|
+
!data/ontology/*.parquet
|
|
38
|
+
.hypothesis/
|
|
39
|
+
|
|
40
|
+
# Log files
|
|
41
|
+
*.log
|
|
42
|
+
logs/
|
|
43
|
+
|
|
44
|
+
# OS generated files
|
|
45
|
+
.DS_Store
|
|
46
|
+
.DS_Store?
|
|
47
|
+
._*
|
|
48
|
+
.Spotlight-V100
|
|
49
|
+
.Trashes
|
|
50
|
+
ehthumbs.db
|
|
51
|
+
Thumbs.db
|
|
52
|
+
|
|
53
|
+
# Python cache and compiled files
|
|
54
|
+
*.pyc
|
|
55
|
+
*.pyo
|
|
56
|
+
*.pyd
|
|
57
|
+
.Python
|
|
58
|
+
*.so
|
|
59
|
+
__pycache__/
|
|
60
|
+
*.egg-info/
|
|
61
|
+
.pytest_cache/
|
|
62
|
+
.coverage
|
|
63
|
+
htmlcov/
|
|
64
|
+
|
|
65
|
+
# Jupyter Notebook checkpoints
|
|
66
|
+
.ipynb_checkpoints/
|
|
67
|
+
|
|
68
|
+
# Environment files
|
|
69
|
+
.env
|
|
70
|
+
.venv
|
|
71
|
+
env/
|
|
72
|
+
venv/
|
|
73
|
+
ENV/
|
|
74
|
+
env.bak/
|
|
75
|
+
venv.bak/
|
|
76
|
+
|
|
77
|
+
# Cursor
|
|
78
|
+
.cursorrules
|
|
79
|
+
.cursor/
|
|
80
|
+
|
|
81
|
+
# MkDocs build output
|
|
82
|
+
site/
|
|
83
|
+
|
|
84
|
+
# Big examples data
|
|
85
|
+
tissues/
|
|
86
|
+
msnet_qpx/
|
|
87
|
+
|
|
88
|
+
#Ignore cursor AI rules
|
|
89
|
+
.cursor/rules/codacy.mdc
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Claude Code
|
|
93
|
+
.claude/
|
|
94
|
+
|
|
95
|
+
# Kiro
|
|
96
|
+
.kiro/
|
|
97
|
+
workflow.md
|
|
98
|
+
docs/metadata.avsc
|
|
99
|
+
.claude/
|
|
100
|
+
.codacy/
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
#Ignore vscode AI rules
|
|
104
|
+
.github/instructions/codacy.instructions.md
|
|
105
|
+
|
|
106
|
+
.ruff*
|
|
107
|
+
# Ruff cache lock
|
|
108
|
+
.ruff_cache.lock
|
|
109
|
+
|
|
110
|
+
uv.lock
|
|
111
|
+
absexpr*
|
|
112
|
+
scripts/convert_absexpr.py
|
|
113
|
+
scripts/plasma_infiltration_model.py
|
|
114
|
+
scripts/plot_absexpr_analysis.py
|
|
115
|
+
scripts/plot_blood_biology.py
|
|
116
|
+
docs/superpowers/*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: end-of-file-fixer
|
|
6
|
+
exclude: '\.svg$'
|
|
7
|
+
- id: trailing-whitespace
|
|
8
|
+
exclude: '\.svg$'
|
|
9
|
+
- id: detect-private-key
|
|
10
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
+
rev: v0.15.8
|
|
12
|
+
hooks:
|
|
13
|
+
- id: ruff
|
|
14
|
+
args: [--fix]
|
|
15
|
+
- id: ruff-format
|
qpx-1.0.1/CHANGELOG.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-03-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **CLI command groups**: `convert`, `transform`, `query`, `info`, `validate`, `ontology`
|
|
13
|
+
- **Converters**: DIA-NN, MaxQuant, QuantMS (mzTab LFQ & TMT), FragPipe, mzIdentML, SDRF
|
|
14
|
+
- **Transform commands**:
|
|
15
|
+
- `gene-map` — map gene names from FASTA to QPX parquet data
|
|
16
|
+
- `quantify` — protein-level quantification via mokume (DirectLFQ, MaxLFQ, iBAQ, TopN, Sum)
|
|
17
|
+
- `normalize-accessions` — normalize protein accession formats (full ↔ bare UniProt)
|
|
18
|
+
- `update-metadata` — update sample/run metadata from a revised SDRF
|
|
19
|
+
- **Query commands**: `sql`, `filter`, `head` for interactive dataset exploration
|
|
20
|
+
- **Info commands**: dataset summary, Arrow schema display, Parquet metadata inspection
|
|
21
|
+
- **Validate command**: schema validation with column presence, type matching, null checks, and PK uniqueness
|
|
22
|
+
- **Ontology management**: `info`, `update`, `build`, `search` for PSI-MS and PRIDE CV terms
|
|
23
|
+
- **Python API**: `qpx.open()`, `qpx.read_feature()`, `qpx.read_psm()`, `qpx.read_pg()`, etc.
|
|
24
|
+
- **Dataset class**: unified access to all QPX structures with DuckDB-backed SQL queries
|
|
25
|
+
- **DatasetCollection**: work with multiple datasets simultaneously
|
|
26
|
+
- **Writers**: Parquet writers for all QPX structures (Feature, PSM, PG, Sample, Run, MzSpectra, PepMap, Ontology, Provenance)
|
|
27
|
+
- **Views**: analytical views for protein, peptide, QC, and sample summaries
|
|
28
|
+
- **Schema validation**: YAML-defined canonical schemas for all data structures
|
|
29
|
+
- **Score normalization**: automatic PSI-MS ontology mapping for search engine scores
|
|
30
|
+
- **PRIDE API integration**: `--enrich-pride` flag for automatic project metadata enrichment
|
|
31
|
+
- **S3 support**: read QPX datasets from S3-compatible storage
|
|
32
|
+
- **MkDocs documentation**: full CLI reference with auto-generated parameter tables and usage examples
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Replaced per-converter `_safe_float_val` with shared `safe_float` utility
|
|
37
|
+
- Optimized `_table_exists` to use parameterized `information_schema` query
|
|
38
|
+
- Increased DIA-NN `file_num` default from 50 to 100 for larger cohort support
|
|
39
|
+
- Defensive handling of None/NaN in gene mapping (`_resolve_gene_names`, `annotate_dataframe`)
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- `.gitignore` duplicate entry removal
|
|
44
|
+
- SQL injection safety in `_table_exists` (parameterized query)
|
|
45
|
+
- Gene mapping crash on None/NaN protein accessions
|
|
46
|
+
- Gene mapping `list(dict)` producing keys instead of preserving struct
|
|
47
|
+
|
|
48
|
+
## [0.0.4] - 2025-03-01
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- Initial PyPI release
|
|
53
|
+
- Basic converter framework for QuantMS, DIA-NN, MaxQuant
|
|
54
|
+
- QPX Parquet schema definition
|
|
55
|
+
- CLI entry point (`qpxc`)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Contributing to QPX
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to QPX!
|
|
4
|
+
|
|
5
|
+
## Code of Conduct
|
|
6
|
+
|
|
7
|
+
We follow the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/).
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
### Setup
|
|
12
|
+
|
|
13
|
+
1. Fork and clone the repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/your-username/qpx.git
|
|
17
|
+
cd qpx
|
|
18
|
+
git remote add upstream https://github.com/bigbio/qpx.git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Install dependencies (Python 3.10+):
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Development Workflow
|
|
27
|
+
|
|
28
|
+
Create a feature branch:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git checkout -b feature/your-feature-name
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Code Standards
|
|
35
|
+
|
|
36
|
+
Format code with ruff:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ruff check .
|
|
40
|
+
ruff format .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
|
|
45
|
+
Run tests (excludes integration tests by default):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pytest -vv -m "not integration"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Test markers:
|
|
52
|
+
|
|
53
|
+
- `integration`: Integration tests requiring external resources
|
|
54
|
+
- `large_data`: Tests requiring large data files
|
|
55
|
+
|
|
56
|
+
## Submitting Changes
|
|
57
|
+
|
|
58
|
+
1. Commit and push your changes:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git add .
|
|
62
|
+
git commit -m "Brief description of changes"
|
|
63
|
+
git push origin feature/your-feature-name
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. Create a pull request on GitHub with:
|
|
67
|
+
- Clear description of changes
|
|
68
|
+
- Related issue numbers (e.g., "Fixes #123")
|
|
69
|
+
- Test results
|
|
70
|
+
|
|
71
|
+
## Reporting Issues
|
|
72
|
+
|
|
73
|
+
**Bugs**: Include title, description, steps to reproduce, environment (QPX version, Python version, OS), and error messages.
|
|
74
|
+
|
|
75
|
+
**Features**: Describe the use case, proposed solution, and alternatives considered.
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
Build and preview documentation locally:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mkdocs serve # Available at http://127.0.0.1:8000/
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Contact
|
|
86
|
+
|
|
87
|
+
- **Maintainers**: Yasset Perez-Riverol (PRIDE Team, EMBL-EBI), Ping Zheng (Chongqing University)
|
|
88
|
+
- **Issues**: [GitHub Issues](https://github.com/bigbio/qpx/issues)
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
Contributions are licensed under [Apache License 2.0](LICENSE). Please add your name to the contributors list in README.md.
|