nci-cidc-api-modules 1.2.33__tar.gz → 1.2.39__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.
- {nci_cidc_api_modules-1.2.33/nci_cidc_api_modules.egg-info → nci_cidc_api_modules-1.2.39}/PKG-INFO +22 -13
- nci_cidc_api_modules-1.2.39/cidc_api/__init__.py +1 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/models.py +140 -9
- nci_cidc_api_modules-1.2.39/cidc_api/shared/assay_handling.py +68 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/auth.py +5 -5
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/file_handling.py +3 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/gcloud_client.py +27 -5
- nci_cidc_api_modules-1.2.39/cidc_api/shared/utils.py +19 -0
- nci_cidc_api_modules-1.2.39/cidc_api/telemetry.py +101 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39/nci_cidc_api_modules.egg-info}/PKG-INFO +22 -13
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/nci_cidc_api_modules.egg-info/SOURCES.txt +3 -0
- nci_cidc_api_modules-1.2.39/nci_cidc_api_modules.egg-info/requires.txt +35 -0
- nci_cidc_api_modules-1.2.39/requirements.modules.txt +43 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/setup.py +3 -1
- nci_cidc_api_modules-1.2.33/cidc_api/shared/utils.py +0 -11
- nci_cidc_api_modules-1.2.33/nci_cidc_api_modules.egg-info/requires.txt +0 -26
- nci_cidc_api_modules-1.2.33/requirements.modules.txt +0 -28
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/LICENSE +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/MANIFEST.in +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/README.md +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/db.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/logging.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/secrets.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/settings.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/data.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/base_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/additional_treatment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/administrative_person_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/administrative_role_assignment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/adverse_event_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/arm_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/baseline_clinical_assessment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/cohort_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/comorbidity_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/consent_group_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/contact_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/demographic_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/disease_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/exposure_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/file_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/gvhd_diagnosis_acute_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/gvhd_diagnosis_chronic_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/gvhd_organ_acute_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/gvhd_organ_chronic_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/institution_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/medical_history_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/other_clinical_endpoint_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/other_malignancy_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/participant_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/prior_treatment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/publication_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/radiotherapy_dose_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/response_by_system_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/response_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/shipment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/shipment_specimen_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/specimen_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/stem_cell_transplant_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/surgery_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/therapy_agent_dose_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/treatment_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/trial_orm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/details.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/facets.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/migrations.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/additional_treatment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/administrative_person.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/administrative_role_assignment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/adverse_event.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/arm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/base.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/baseline_clinical_assessment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/cohort.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/comorbidity.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/consent_group.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/contact.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/demographic.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/disease.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/exposure.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/file.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/gvhd_diagnosis_acute.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/gvhd_diagnosis_chronic.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/gvhd_organ_acute.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/gvhd_organ_chronic.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/institution.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/medical_history.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/other_clinical_endpoint.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/other_malignancy.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/participant.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/prior_treatment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/publication.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/radiotherapy_dose.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/response.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/response_by_system.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/shipment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/shipment_specimen.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/specimen.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/stem_cell_transplant.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/surgery.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/therapy_agent_dose.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/treatment.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/trial.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/schemas.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/types.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/ctcae.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/gvhd.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/icd10cm.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/icdo3.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/uberon.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/__init__.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/email_layout.html +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/emails.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/jose.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/rest_utils.py +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/nci_cidc_api_modules.egg-info/dependency_links.txt +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/nci_cidc_api_modules.egg-info/not-zip-safe +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/nci_cidc_api_modules.egg-info/top_level.txt +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/pyproject.toml +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/setup.cfg +0 -0
- {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/tests/test_api.py +0 -0
{nci_cidc_api_modules-1.2.33/nci_cidc_api_modules.egg-info → nci_cidc_api_modules-1.2.39}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nci_cidc_api_modules
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.39
|
|
4
4
|
Summary: SQLAlchemy data models and configuration tools used in the NCI CIDC API
|
|
5
5
|
Home-page: https://github.com/NCI-CIDC/cidc-api-gae
|
|
6
6
|
License: MIT license
|
|
@@ -8,30 +8,39 @@ Requires-Python: >=3.13
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: certifi>=2025.11.12
|
|
11
|
-
Requires-Dist: cloud-sql-python-connector[pg8000]>=1.
|
|
11
|
+
Requires-Dist: cloud-sql-python-connector[pg8000]>=1.19.0
|
|
12
12
|
Requires-Dist: flask>=3.1.2
|
|
13
13
|
Requires-Dist: flask-migrate>=4.1.0
|
|
14
14
|
Requires-Dist: flask-sqlalchemy>=3.1.1
|
|
15
15
|
Requires-Dist: flask-talisman>=0.7.0
|
|
16
|
-
Requires-Dist: google-auth==2.
|
|
17
|
-
Requires-Dist: google-api-python-client>=2.
|
|
18
|
-
Requires-Dist: google-cloud-bigquery>=3.
|
|
19
|
-
Requires-Dist: google-cloud-pubsub>=2.
|
|
20
|
-
Requires-Dist: google-cloud-secret-manager>=2.
|
|
21
|
-
Requires-Dist: google-cloud-storage>=3.
|
|
16
|
+
Requires-Dist: google-auth==2.45.0
|
|
17
|
+
Requires-Dist: google-api-python-client>=2.187.0
|
|
18
|
+
Requires-Dist: google-cloud-bigquery>=3.39.0
|
|
19
|
+
Requires-Dist: google-cloud-pubsub>=2.34.0
|
|
20
|
+
Requires-Dist: google-cloud-secret-manager>=2.26.0
|
|
21
|
+
Requires-Dist: google-cloud-storage>=3.7.0
|
|
22
22
|
Requires-Dist: jinja2>=3.1.6
|
|
23
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: joserfc>=1.6.0
|
|
24
|
+
Requires-Dist: marshmallow>=4.1.2
|
|
24
25
|
Requires-Dist: marshmallow-sqlalchemy>=1.4.2
|
|
25
|
-
Requires-Dist: numpy>=2.
|
|
26
|
+
Requires-Dist: numpy>=2.4.0
|
|
26
27
|
Requires-Dist: packaging>=25.0
|
|
27
28
|
Requires-Dist: pandas>=2.3.3
|
|
28
29
|
Requires-Dist: pyarrow>=22.0.0
|
|
29
|
-
Requires-Dist: pydantic~=2.12.
|
|
30
|
+
Requires-Dist: pydantic~=2.12.5
|
|
30
31
|
Requires-Dist: python-dotenv>=1.2.1
|
|
31
32
|
Requires-Dist: requests>=2.32.5
|
|
32
|
-
Requires-Dist: sqlalchemy>=2.0.
|
|
33
|
+
Requires-Dist: sqlalchemy>=2.0.45
|
|
33
34
|
Requires-Dist: sqlalchemy-mixins~=2.0.5
|
|
34
|
-
Requires-Dist: werkzeug>=3.1.
|
|
35
|
+
Requires-Dist: werkzeug>=3.1.4
|
|
36
|
+
Requires-Dist: opentelemetry-api>=1.39.1
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.39.1
|
|
38
|
+
Requires-Dist: opentelemetry-sdk>=1.39.1
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-flask>=0.59b0
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-requests>=0.59b0
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.59b0
|
|
42
|
+
Requires-Dist: opentelemetry-exporter-gcp-trace>=1.11.0
|
|
43
|
+
Requires-Dist: opentelemetry-propagator-gcp>=1.11.0
|
|
35
44
|
Requires-Dist: nci-cidc-schemas==0.28.10
|
|
36
45
|
Dynamic: description
|
|
37
46
|
Dynamic: description-content-type
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.39"
|
|
@@ -35,7 +35,8 @@ __all__ = [
|
|
|
35
35
|
"ADMIN_FILE_CATEGORIES",
|
|
36
36
|
"FINAL_JOB_STATUS",
|
|
37
37
|
"INGESTION_JOB_STATUSES",
|
|
38
|
-
"
|
|
38
|
+
"ASSAY_JOB_COLORS",
|
|
39
|
+
"CLINICAL_JOB_COLORS",
|
|
39
40
|
]
|
|
40
41
|
|
|
41
42
|
import hashlib
|
|
@@ -3436,7 +3437,7 @@ INGESTION_JOB_STATUSES = [
|
|
|
3436
3437
|
]
|
|
3437
3438
|
|
|
3438
3439
|
# Business decision to pass hex codes from the backend though that should be done by the front end...
|
|
3439
|
-
|
|
3440
|
+
CLINICAL_JOB_COLORS = {
|
|
3440
3441
|
"DRAFT": "",
|
|
3441
3442
|
"INITIAL SUBMISSION": "#ACCAD7",
|
|
3442
3443
|
"VALIDATION REVIEW": "#DABE90",
|
|
@@ -3444,6 +3445,13 @@ INGESTION_JOB_COLORS = {
|
|
|
3444
3445
|
"INGESTION": "#8FCEC7",
|
|
3445
3446
|
"PUBLISHED": "#90D9E6",
|
|
3446
3447
|
}
|
|
3448
|
+
ASSAY_JOB_COLORS = {
|
|
3449
|
+
"INITIAL SUBMISSION": "#43807E",
|
|
3450
|
+
"VALIDATION REVIEW": "#906F3F",
|
|
3451
|
+
"REVISION SUBMISSION": "#95358A",
|
|
3452
|
+
"INGESTION": "#542C88",
|
|
3453
|
+
"PUBLISHED": "#1C81A0",
|
|
3454
|
+
}
|
|
3447
3455
|
# TODO If have "CANCELLED" concept or other final status, add here
|
|
3448
3456
|
FINAL_JOB_STATUS = ["PUBLISHED"]
|
|
3449
3457
|
TRIAL_APPENDIX_A_CELL_THAT_ENDS_THE_HEADER = "Data Category"
|
|
@@ -3465,11 +3473,44 @@ class IngestionJobs(CommonColumns):
|
|
|
3465
3473
|
pending = Column(Boolean, nullable=False, default=False)
|
|
3466
3474
|
start_date = Column(DateTime, nullable=True)
|
|
3467
3475
|
error_status = Column(String, nullable=True)
|
|
3476
|
+
job_type = Column(String, nullable=False, default="clinical")
|
|
3477
|
+
assay_type = Column(String, nullable=True)
|
|
3478
|
+
batch_id = Column(String, nullable=True)
|
|
3479
|
+
submission_id = Column(String, nullable=True)
|
|
3480
|
+
intake_path = Column(String, nullable=True)
|
|
3481
|
+
uploader_email = Column(String, nullable=True)
|
|
3468
3482
|
|
|
3469
3483
|
@staticmethod
|
|
3470
3484
|
@with_default_session
|
|
3471
|
-
def create(
|
|
3472
|
-
|
|
3485
|
+
def create(
|
|
3486
|
+
trial_id: str,
|
|
3487
|
+
status: str,
|
|
3488
|
+
version: int,
|
|
3489
|
+
error_status: str = None,
|
|
3490
|
+
pending: Boolean = False,
|
|
3491
|
+
job_type: str = "clinical",
|
|
3492
|
+
assay_type: str = None,
|
|
3493
|
+
batch_id: str = None,
|
|
3494
|
+
submission_id: str = None,
|
|
3495
|
+
intake_path: str = None,
|
|
3496
|
+
start_date: datetime = None,
|
|
3497
|
+
uploader_email: str = None,
|
|
3498
|
+
session: Session = None,
|
|
3499
|
+
):
|
|
3500
|
+
new_job = IngestionJobs(
|
|
3501
|
+
trial_id=trial_id,
|
|
3502
|
+
status=status,
|
|
3503
|
+
error_status=error_status,
|
|
3504
|
+
version=version,
|
|
3505
|
+
pending=pending,
|
|
3506
|
+
job_type=job_type,
|
|
3507
|
+
assay_type=assay_type,
|
|
3508
|
+
batch_id=batch_id,
|
|
3509
|
+
submission_id=submission_id,
|
|
3510
|
+
intake_path=intake_path,
|
|
3511
|
+
start_date=start_date,
|
|
3512
|
+
uploader_email=uploader_email,
|
|
3513
|
+
)
|
|
3473
3514
|
new_job.insert(session=session)
|
|
3474
3515
|
return new_job
|
|
3475
3516
|
|
|
@@ -3494,29 +3535,43 @@ class IngestionJobs(CommonColumns):
|
|
|
3494
3535
|
|
|
3495
3536
|
@classmethod
|
|
3496
3537
|
@with_default_session
|
|
3497
|
-
def get_jobs_by_trial(
|
|
3498
|
-
|
|
3538
|
+
def get_jobs_by_trial(
|
|
3539
|
+
cls, trial_id: str, job_type: str = "clinical", session: Session = None
|
|
3540
|
+
) -> list["IngestionJobs"]:
|
|
3541
|
+
return (
|
|
3542
|
+
session.query(cls)
|
|
3543
|
+
.filter(cls.trial_id == trial_id, cls.job_type == job_type)
|
|
3544
|
+
.order_by(cls.version.desc())
|
|
3545
|
+
.all()
|
|
3546
|
+
)
|
|
3499
3547
|
|
|
3500
3548
|
@classmethod
|
|
3501
3549
|
@with_default_session
|
|
3502
|
-
def get_open_job_by_trial(
|
|
3550
|
+
def get_open_job_by_trial(
|
|
3551
|
+
cls, trial_id: str, job_type: str = "clinical", session: Session = None
|
|
3552
|
+
) -> Optional["IngestionJobs"]:
|
|
3503
3553
|
"""Return the open job for a given trial if it exists."""
|
|
3504
3554
|
return (
|
|
3505
3555
|
session.query(cls)
|
|
3506
3556
|
.filter(
|
|
3507
3557
|
cls.trial_id == trial_id,
|
|
3558
|
+
cls.job_type == job_type,
|
|
3508
3559
|
cls.status.notin_(FINAL_JOB_STATUS),
|
|
3509
3560
|
)
|
|
3510
3561
|
.order_by(cls._created.desc())
|
|
3511
3562
|
.first()
|
|
3512
3563
|
)
|
|
3513
3564
|
|
|
3565
|
+
@classmethod
|
|
3566
|
+
def get_jobs_for_user(cls, user: Users, job_type: str = None) -> list["IngestionJobs"]:
|
|
3567
|
+
return cls.get_assay_jobs_for_user(user) if job_type == "assay" else cls.get_clinical_jobs_for_user(user)
|
|
3568
|
+
|
|
3514
3569
|
@classmethod
|
|
3515
3570
|
@with_default_session
|
|
3516
|
-
def
|
|
3571
|
+
def get_clinical_jobs_for_user(cls, user: Users, session: Session = None) -> list["IngestionJobs"]:
|
|
3517
3572
|
if user.role not in [CIDCRole.ADMIN.value, CIDCRole.CLINICAL_TRIAL_USER.value]:
|
|
3518
3573
|
return []
|
|
3519
|
-
job_query = session.query(cls).filter(cls.status.notin_(["DRAFT"]))
|
|
3574
|
+
job_query = session.query(cls).filter(cls.status.notin_(["DRAFT"]), cls.job_type == "clinical")
|
|
3520
3575
|
if (
|
|
3521
3576
|
user.role != CIDCRole.ADMIN.value
|
|
3522
3577
|
and not session.query(Permissions)
|
|
@@ -3539,6 +3594,81 @@ class IngestionJobs(CommonColumns):
|
|
|
3539
3594
|
job_query = job_query.filter(cls.trial_id.in_(map(lambda x: x.trial_id, authorized_trials)))
|
|
3540
3595
|
return job_query.order_by(cls._created.desc()).all()
|
|
3541
3596
|
|
|
3597
|
+
@classmethod
|
|
3598
|
+
@with_default_session
|
|
3599
|
+
def get_assay_jobs_for_user(cls, user: Users, session: Session = None) -> list["IngestionJobs"]:
|
|
3600
|
+
# TODO allow more than just Admin role and get authorized trials based on permissions
|
|
3601
|
+
if user.role not in [CIDCRole.ADMIN.value]:
|
|
3602
|
+
return []
|
|
3603
|
+
return session.query(cls).filter(cls.job_type == "assay").order_by(cls._created.desc()).all()
|
|
3604
|
+
|
|
3605
|
+
@classmethod
|
|
3606
|
+
@with_default_session
|
|
3607
|
+
def get_unique_assay_job(
|
|
3608
|
+
cls,
|
|
3609
|
+
trial_id: str,
|
|
3610
|
+
assay_type: str,
|
|
3611
|
+
batch_id: str,
|
|
3612
|
+
session: Session = None,
|
|
3613
|
+
) -> Optional["IngestionJobs"]:
|
|
3614
|
+
"""Look for unique assay job with matching trial_id/assay_type/batch_id combination."""
|
|
3615
|
+
return (
|
|
3616
|
+
session.query(cls)
|
|
3617
|
+
.filter(
|
|
3618
|
+
cls.job_type == "assay",
|
|
3619
|
+
cls.trial_id == trial_id,
|
|
3620
|
+
cls.assay_type == assay_type,
|
|
3621
|
+
cls.batch_id == batch_id,
|
|
3622
|
+
)
|
|
3623
|
+
.first()
|
|
3624
|
+
)
|
|
3625
|
+
|
|
3626
|
+
@classmethod
|
|
3627
|
+
@with_default_session
|
|
3628
|
+
def next_assay_submission_id(cls, trial_id: str, assay_type: str, session: Session = None) -> str:
|
|
3629
|
+
"""
|
|
3630
|
+
Generate the next CIDC Submission ID for an assay job.
|
|
3631
|
+
|
|
3632
|
+
Format:
|
|
3633
|
+
<trial_id>-<assay_type>-<yyyymmdd> (first submission of the day)
|
|
3634
|
+
<trial_id>-<assay_type>-<yyyymmdd>-<#> (subsequent submissions on same day)
|
|
3635
|
+
|
|
3636
|
+
Uses only the most recent matching submission_id to determine the next suffix.
|
|
3637
|
+
"""
|
|
3638
|
+
today_str = datetime.now().strftime("%Y%m%d")
|
|
3639
|
+
base_submission_id = f"{trial_id}-{assay_type}-{today_str}"
|
|
3640
|
+
|
|
3641
|
+
# Get the most recent submission_id matching this prefix
|
|
3642
|
+
latest = (
|
|
3643
|
+
session.query(cls.submission_id)
|
|
3644
|
+
.filter(
|
|
3645
|
+
cls.trial_id == trial_id,
|
|
3646
|
+
cls.assay_type == assay_type,
|
|
3647
|
+
cls.submission_id.like(f"{base_submission_id}%"),
|
|
3648
|
+
)
|
|
3649
|
+
.order_by(cls._created.desc())
|
|
3650
|
+
.first()
|
|
3651
|
+
)
|
|
3652
|
+
|
|
3653
|
+
# No existing submission for this prefix -> start at 1
|
|
3654
|
+
if not latest or not latest[0]:
|
|
3655
|
+
return base_submission_id
|
|
3656
|
+
|
|
3657
|
+
last_id = latest[0]
|
|
3658
|
+
# Case 1: the latest is exactly the prefix (i.e., first submission today)
|
|
3659
|
+
if last_id == base_submission_id:
|
|
3660
|
+
return f"{base_submission_id}-2"
|
|
3661
|
+
|
|
3662
|
+
# Case 2: latest already has a suffix
|
|
3663
|
+
try:
|
|
3664
|
+
_, last_suffix = last_id.rsplit("-", 1)
|
|
3665
|
+
n = int(last_suffix)
|
|
3666
|
+
return f"{base_submission_id}-{n + 1}"
|
|
3667
|
+
except Exception as e:
|
|
3668
|
+
# If malformed, restart numbering for safety
|
|
3669
|
+
logger.error("Unexpected error parsing Submission ID in next_assay_submission_id: %s", e)
|
|
3670
|
+
return f"{base_submission_id}-2"
|
|
3671
|
+
|
|
3542
3672
|
|
|
3543
3673
|
class JobFileCategories(CommonColumns):
|
|
3544
3674
|
__tablename__ = "job_file_categories"
|
|
@@ -3613,6 +3743,7 @@ class CategoryDataElements(CommonColumns):
|
|
|
3613
3743
|
name = Column(String, nullable=False)
|
|
3614
3744
|
is_custom = Column(Boolean, nullable=False, default=False, server_default="false")
|
|
3615
3745
|
element_type = Column(String, nullable=False)
|
|
3746
|
+
data_type = Column(String, nullable=True)
|
|
3616
3747
|
cardinality = Column(String, nullable=True)
|
|
3617
3748
|
|
|
3618
3749
|
@classmethod
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from werkzeug.exceptions import BadRequest
|
|
5
|
+
|
|
6
|
+
from cidc_api.models import IngestionJobs
|
|
7
|
+
from . import gcloud_client
|
|
8
|
+
from ..shared.auth import get_current_user
|
|
9
|
+
|
|
10
|
+
JOB_TYPE_ASSAY = "assay"
|
|
11
|
+
JOB_TYPE_CLINICAL = "clinical"
|
|
12
|
+
ALLOWED_JOB_TYPES = {JOB_TYPE_CLINICAL, JOB_TYPE_ASSAY}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_job_type_and_assay_fields(data: dict) -> tuple[str, str | None, str | None]:
|
|
16
|
+
"""Decide job_type and gather assay_type/batch_id from request JSON."""
|
|
17
|
+
assay_type = data.get("assay_type")
|
|
18
|
+
# If job_type is assay or assay_type is present, treat this as an assay job.
|
|
19
|
+
job_type = data.get("job_type") or (JOB_TYPE_ASSAY if assay_type else JOB_TYPE_CLINICAL)
|
|
20
|
+
|
|
21
|
+
if job_type not in ALLOWED_JOB_TYPES:
|
|
22
|
+
raise BadRequest("Invalid job_type. Allowed values are 'clinical' or 'assay'.")
|
|
23
|
+
|
|
24
|
+
if job_type == JOB_TYPE_ASSAY and (not assay_type or not isinstance(assay_type, str)):
|
|
25
|
+
raise BadRequest("assay_type must be provided for job_type='assay'.")
|
|
26
|
+
|
|
27
|
+
assay_type = assay_type.strip() if assay_type else None
|
|
28
|
+
batch_id = data.get("batch_id").strip() if isinstance(data.get("batch_id"), str) else None
|
|
29
|
+
|
|
30
|
+
return job_type, assay_type, batch_id
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prepare_assay_job(trial_id: str, assay_type: str, batch_id: str) -> tuple[str, str, str, datetime, int, str]:
|
|
34
|
+
"""
|
|
35
|
+
Validate assay job uniqueness and generate submission_id, start_date, version, and the trial’s GCS intake path.
|
|
36
|
+
"""
|
|
37
|
+
if not assay_type:
|
|
38
|
+
raise BadRequest("assay_type must be provided for job_type='assay'.")
|
|
39
|
+
|
|
40
|
+
# Enforce uniqueness of (trial_id, assay_type, batch_id) when batch_id is present.
|
|
41
|
+
if batch_id:
|
|
42
|
+
existing_job = IngestionJobs.get_unique_assay_job(trial_id, assay_type, batch_id)
|
|
43
|
+
if existing_job:
|
|
44
|
+
raise BadRequest(
|
|
45
|
+
f"Assay job {existing_job.id} already exists for this exact trial_id/assay_type/batch_id combination."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
submission_id = IngestionJobs.next_assay_submission_id(trial_id, assay_type)
|
|
49
|
+
job_status = "INITIAL SUBMISSION"
|
|
50
|
+
error_status = "Upload Incomplete" # job starts with 'Incomplete' notifier
|
|
51
|
+
start_date = datetime.now()
|
|
52
|
+
version = 1
|
|
53
|
+
|
|
54
|
+
# Create or retrieve intake bucket corresponding to the trial
|
|
55
|
+
intake_bucket = gcloud_client.create_intake_bucket(get_current_user().email, trial_id=trial_id)
|
|
56
|
+
gcs_path = f"{intake_bucket.name}/{assay_type}/{submission_id}"
|
|
57
|
+
|
|
58
|
+
return submission_id, job_status, error_status, start_date, version, gcs_path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_google_links(intake_path: str) -> tuple[str, str]:
|
|
62
|
+
"""Build the GCS URI and GCS Console URL corresponding to the intake path."""
|
|
63
|
+
gcs_uri = f"gs://{intake_path}"
|
|
64
|
+
# Encode path to ensure link opens correctly
|
|
65
|
+
encoded_path = quote(intake_path)
|
|
66
|
+
console_url = f"https://console.cloud.google.com/storage/browser/{encoded_path}"
|
|
67
|
+
|
|
68
|
+
return gcs_uri, console_url
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
from functools import wraps
|
|
2
2
|
from typing import List
|
|
3
3
|
|
|
4
|
-
from packaging import version
|
|
5
|
-
|
|
6
4
|
from flask import g, request, current_app as app, Flask
|
|
5
|
+
from packaging import version
|
|
7
6
|
from werkzeug.exceptions import Unauthorized, BadRequest, PreconditionFailed
|
|
8
7
|
|
|
9
|
-
from ..models import Users, UserSchema
|
|
10
|
-
|
|
11
8
|
from ..config.logging import get_logger
|
|
12
|
-
|
|
9
|
+
from ..models import Users, UserSchema
|
|
13
10
|
from ..shared.jose import decode_id_token
|
|
11
|
+
from ..telemetry import trace_
|
|
14
12
|
|
|
15
13
|
logger = get_logger(__name__)
|
|
16
14
|
|
|
@@ -144,6 +142,7 @@ def get_current_user() -> Users:
|
|
|
144
142
|
_user_schema = UserSchema()
|
|
145
143
|
|
|
146
144
|
|
|
145
|
+
@trace_()
|
|
147
146
|
def authenticate() -> Users:
|
|
148
147
|
id_token = _extract_token()
|
|
149
148
|
token_payload = decode_id_token(id_token)
|
|
@@ -172,6 +171,7 @@ def _extract_token() -> str:
|
|
|
172
171
|
|
|
173
172
|
|
|
174
173
|
### Authorization logic ###
|
|
174
|
+
@trace_()
|
|
175
175
|
def authorize(user: Users, allowed_roles: List[str], resource: str, method: str) -> bool:
|
|
176
176
|
"""Check if the current user is authorized to act on the current request's resource.
|
|
177
177
|
Raises Unauthorized
|
{nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/file_handling.py
RENAMED
|
@@ -10,10 +10,12 @@ from ..config.settings import GOOGLE_CLINICAL_DATA_BUCKET
|
|
|
10
10
|
from ..models import PreprocessedFiles, TRIAL_APPENDIX_A_CELL_THAT_ENDS_THE_HEADER
|
|
11
11
|
from ..shared.auth import get_current_user
|
|
12
12
|
from ..shared.gcloud_client import upload_file_to_gcs, move_gcs_file
|
|
13
|
+
from ..telemetry import trace_
|
|
13
14
|
|
|
14
15
|
logger = get_logger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
@trace_()
|
|
17
19
|
def set_current_file(
|
|
18
20
|
file: FileStorage, file_category: str, gcs_folder: str, session: Session, uploader_email: str, job_id: int = None
|
|
19
21
|
) -> PreprocessedFiles:
|
|
@@ -26,6 +28,7 @@ def set_current_file(
|
|
|
26
28
|
return latest_file
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
@trace_()
|
|
29
32
|
def create_file(
|
|
30
33
|
file: FileStorage,
|
|
31
34
|
gcs_folder: str,
|
{nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/gcloud_client.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Utilities for interacting with the Google Cloud Platform APIs."""
|
|
2
2
|
|
|
3
|
-
# pylint: disable=logging-fstring-interpolation,too-many-lines
|
|
3
|
+
# pylint: disable=logging-fstring-interpolation,too-many-lines, broad-exception-raised
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
6
|
import datetime
|
|
@@ -8,6 +8,7 @@ import hashlib
|
|
|
8
8
|
import io
|
|
9
9
|
import json
|
|
10
10
|
import os
|
|
11
|
+
import re
|
|
11
12
|
import warnings
|
|
12
13
|
from collections import namedtuple
|
|
13
14
|
from concurrent.futures import Future
|
|
@@ -361,15 +362,34 @@ def get_intake_bucket_name(user_email: str) -> str:
|
|
|
361
362
|
return bucket_name
|
|
362
363
|
|
|
363
364
|
|
|
364
|
-
def
|
|
365
|
+
def get_trial_intake_bucket_name(trial_id: str) -> str:
|
|
365
366
|
"""
|
|
366
|
-
|
|
367
|
+
Return a sanitized GCS bucket name for a given trial_id.
|
|
368
|
+
|
|
369
|
+
Produces: <GOOGLE_INTAKE_BUCKET>-<sanitized_trial_id>
|
|
370
|
+
where the trial_id segment is lowercased and restricted to [a-z0-9-].
|
|
371
|
+
"""
|
|
372
|
+
# Replace non-allowed bucket chars with "-"
|
|
373
|
+
sanitized_id = re.sub(r"[^a-z0-9-]", "-", trial_id.lower())
|
|
374
|
+
# Collapse repeated "-" and trim from both ends
|
|
375
|
+
sanitized_id = re.sub(r"-+", "-", sanitized_id).strip("-")
|
|
376
|
+
|
|
377
|
+
return f"{GOOGLE_INTAKE_BUCKET}-{sanitized_id}"
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def create_intake_bucket(user_email: str, trial_id: str = None) -> storage.Bucket:
|
|
381
|
+
"""
|
|
382
|
+
Create (or retrieve) the appropriate data intake bucket.
|
|
383
|
+
If a trial_id is provided, a trial-specific bucket is used;
|
|
384
|
+
otherwise a user-specific intake bucket is used.
|
|
385
|
+
|
|
367
386
|
Grant the user GCS object admin permissions on the bucket, or refresh those
|
|
368
387
|
permissions if they've already been granted.
|
|
369
388
|
Created with uniform bucket-level IAM access, so expiring permission.
|
|
370
389
|
"""
|
|
371
390
|
storage_client = _get_storage_client()
|
|
372
|
-
|
|
391
|
+
# Get trial-specific bucket name if trial_id is given, otherwise a user-specific bucket name.
|
|
392
|
+
bucket_name = get_trial_intake_bucket_name(trial_id) if trial_id else get_intake_bucket_name(user_email)
|
|
373
393
|
bucket = storage_client.bucket(bucket_name)
|
|
374
394
|
|
|
375
395
|
if not bucket.exists():
|
|
@@ -614,6 +634,7 @@ def _build_trial_upload_prefixes(
|
|
|
614
634
|
trial_set: Set[str] = set()
|
|
615
635
|
upload_set: Set[str] = set()
|
|
616
636
|
if not trial_id:
|
|
637
|
+
# import is here becasue of circular import
|
|
617
638
|
from ..models.models import TrialMetadata
|
|
618
639
|
|
|
619
640
|
trial_set = {str(t.trial_id) for t in session.query(TrialMetadata).add_columns(TrialMetadata.trial_id)}
|
|
@@ -920,7 +941,8 @@ def get_signed_url(
|
|
|
920
941
|
|
|
921
942
|
def _encode_and_publish(content: str, topic: str) -> Future:
|
|
922
943
|
"""Convert `content` to bytes and publish it to `topic`."""
|
|
923
|
-
|
|
944
|
+
publisher_options = pubsub.types.PublisherOptions(enable_open_telemetry_tracing=ENV == "dev-int")
|
|
945
|
+
pubsub_publisher = pubsub.PublisherClient(publisher_options=publisher_options)
|
|
924
946
|
topic = pubsub_publisher.topic_path(GOOGLE_CLOUD_PROJECT, topic)
|
|
925
947
|
data = bytes(content, "utf-8")
|
|
926
948
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from cidc_api.telemetry import trace_
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _stripper(x):
|
|
5
|
+
if x and isinstance(x, str):
|
|
6
|
+
return x.strip()
|
|
7
|
+
else:
|
|
8
|
+
return x
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@trace_("sheet")
|
|
12
|
+
def strip_whitespaces(df, sheet=None):
|
|
13
|
+
if sheet:
|
|
14
|
+
df = df[sheet]
|
|
15
|
+
|
|
16
|
+
df.rename(columns=_stripper, inplace=True)
|
|
17
|
+
df = df.map(_stripper)
|
|
18
|
+
|
|
19
|
+
return df
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# standard modules
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
# external modules
|
|
5
|
+
from opentelemetry import trace
|
|
6
|
+
from opentelemetry.sdk.resources import Resource
|
|
7
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
8
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
9
|
+
|
|
10
|
+
# local modules
|
|
11
|
+
from .config.settings import ENV, TESTING
|
|
12
|
+
|
|
13
|
+
# pylint: disable=import-outside-toplevel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def instrument_flask(app):
|
|
17
|
+
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
|
18
|
+
from opentelemetry.propagate import set_global_textmap
|
|
19
|
+
from opentelemetry.propagators.cloud_trace_propagator import CloudTraceFormatPropagator
|
|
20
|
+
|
|
21
|
+
FlaskInstrumentor().instrument_app(app)
|
|
22
|
+
|
|
23
|
+
# use the X-Cloud-Trace-Context header
|
|
24
|
+
set_global_textmap(CloudTraceFormatPropagator())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def instrument_requests():
|
|
28
|
+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
|
29
|
+
|
|
30
|
+
def _request_hook(span, request_obj):
|
|
31
|
+
span.update_name(f"requests {request_obj.method}")
|
|
32
|
+
|
|
33
|
+
RequestsInstrumentor().instrument(request_hook=_request_hook)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def instrument_sqlachemy(engine):
|
|
37
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
38
|
+
|
|
39
|
+
SQLAlchemyInstrumentor().instrument(engine=engine)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
resource = Resource(attributes={"service.name": f"CIDC-{ENV}"})
|
|
43
|
+
provider = TracerProvider(resource=resource)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if ENV == "dev" and not TESTING:
|
|
47
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
48
|
+
|
|
49
|
+
COLLECTOR_ENDPOINT = "127.0.0.1"
|
|
50
|
+
COLLECTOR_GRPC_PORT = 6004
|
|
51
|
+
|
|
52
|
+
# send spans to local exporter
|
|
53
|
+
# 1. download latest version from https://github.com/open-telemetry/opentelemetry-collector-releases/releases (otelcol-contrib_0.140.1_darwin_arm64)
|
|
54
|
+
# 2. start exporter from otel folder with `./otelcol-contrib --config=config.yaml`
|
|
55
|
+
# 3. download and start Jeager (all-in-one image) - https://www.jaegertracing.io/download/
|
|
56
|
+
exporter = OTLPSpanExporter(endpoint=f"http://{COLLECTOR_ENDPOINT}:{COLLECTOR_GRPC_PORT}", insecure=True)
|
|
57
|
+
processor = BatchSpanProcessor(exporter)
|
|
58
|
+
provider.add_span_processor(processor)
|
|
59
|
+
|
|
60
|
+
if ENV == "dev-int":
|
|
61
|
+
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
|
|
62
|
+
|
|
63
|
+
# send span to Cloud Trace service - https://console.cloud.google.com/traces/explorer
|
|
64
|
+
exporter = CloudTraceSpanExporter()
|
|
65
|
+
processor = BatchSpanProcessor(exporter)
|
|
66
|
+
provider.add_span_processor(processor)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# NOTE: we don't run telemetry in upper tiers; no span processor is noop
|
|
70
|
+
|
|
71
|
+
trace.set_tracer_provider(provider)
|
|
72
|
+
tracer = trace.get_tracer(__name__)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def trace_(*args):
|
|
76
|
+
def decorator_factory(func):
|
|
77
|
+
|
|
78
|
+
@wraps(func)
|
|
79
|
+
def wrapper(*args_, **kwargs_):
|
|
80
|
+
func_name = f"{func.__module__.split(".")[-1]}.{func.__name__}"
|
|
81
|
+
|
|
82
|
+
with tracer.start_as_current_span(func_name) as span:
|
|
83
|
+
for arg in args:
|
|
84
|
+
value = kwargs_.get(arg)
|
|
85
|
+
|
|
86
|
+
# track id of argument if exists
|
|
87
|
+
if hasattr(value, "id"):
|
|
88
|
+
value = getattr(value, "id")
|
|
89
|
+
|
|
90
|
+
span.set_attributes({arg: value})
|
|
91
|
+
|
|
92
|
+
result = func(*args_, **kwargs_)
|
|
93
|
+
|
|
94
|
+
if isinstance(result, (str, int, float, bool)):
|
|
95
|
+
span.set_attribute("result", result)
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
100
|
+
|
|
101
|
+
return decorator_factory
|
{nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39/nci_cidc_api_modules.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nci_cidc_api_modules
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.39
|
|
4
4
|
Summary: SQLAlchemy data models and configuration tools used in the NCI CIDC API
|
|
5
5
|
Home-page: https://github.com/NCI-CIDC/cidc-api-gae
|
|
6
6
|
License: MIT license
|
|
@@ -8,30 +8,39 @@ Requires-Python: >=3.13
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: certifi>=2025.11.12
|
|
11
|
-
Requires-Dist: cloud-sql-python-connector[pg8000]>=1.
|
|
11
|
+
Requires-Dist: cloud-sql-python-connector[pg8000]>=1.19.0
|
|
12
12
|
Requires-Dist: flask>=3.1.2
|
|
13
13
|
Requires-Dist: flask-migrate>=4.1.0
|
|
14
14
|
Requires-Dist: flask-sqlalchemy>=3.1.1
|
|
15
15
|
Requires-Dist: flask-talisman>=0.7.0
|
|
16
|
-
Requires-Dist: google-auth==2.
|
|
17
|
-
Requires-Dist: google-api-python-client>=2.
|
|
18
|
-
Requires-Dist: google-cloud-bigquery>=3.
|
|
19
|
-
Requires-Dist: google-cloud-pubsub>=2.
|
|
20
|
-
Requires-Dist: google-cloud-secret-manager>=2.
|
|
21
|
-
Requires-Dist: google-cloud-storage>=3.
|
|
16
|
+
Requires-Dist: google-auth==2.45.0
|
|
17
|
+
Requires-Dist: google-api-python-client>=2.187.0
|
|
18
|
+
Requires-Dist: google-cloud-bigquery>=3.39.0
|
|
19
|
+
Requires-Dist: google-cloud-pubsub>=2.34.0
|
|
20
|
+
Requires-Dist: google-cloud-secret-manager>=2.26.0
|
|
21
|
+
Requires-Dist: google-cloud-storage>=3.7.0
|
|
22
22
|
Requires-Dist: jinja2>=3.1.6
|
|
23
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: joserfc>=1.6.0
|
|
24
|
+
Requires-Dist: marshmallow>=4.1.2
|
|
24
25
|
Requires-Dist: marshmallow-sqlalchemy>=1.4.2
|
|
25
|
-
Requires-Dist: numpy>=2.
|
|
26
|
+
Requires-Dist: numpy>=2.4.0
|
|
26
27
|
Requires-Dist: packaging>=25.0
|
|
27
28
|
Requires-Dist: pandas>=2.3.3
|
|
28
29
|
Requires-Dist: pyarrow>=22.0.0
|
|
29
|
-
Requires-Dist: pydantic~=2.12.
|
|
30
|
+
Requires-Dist: pydantic~=2.12.5
|
|
30
31
|
Requires-Dist: python-dotenv>=1.2.1
|
|
31
32
|
Requires-Dist: requests>=2.32.5
|
|
32
|
-
Requires-Dist: sqlalchemy>=2.0.
|
|
33
|
+
Requires-Dist: sqlalchemy>=2.0.45
|
|
33
34
|
Requires-Dist: sqlalchemy-mixins~=2.0.5
|
|
34
|
-
Requires-Dist: werkzeug>=3.1.
|
|
35
|
+
Requires-Dist: werkzeug>=3.1.4
|
|
36
|
+
Requires-Dist: opentelemetry-api>=1.39.1
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.39.1
|
|
38
|
+
Requires-Dist: opentelemetry-sdk>=1.39.1
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-flask>=0.59b0
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-requests>=0.59b0
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.59b0
|
|
42
|
+
Requires-Dist: opentelemetry-exporter-gcp-trace>=1.11.0
|
|
43
|
+
Requires-Dist: opentelemetry-propagator-gcp>=1.11.0
|
|
35
44
|
Requires-Dist: nci-cidc-schemas==0.28.10
|
|
36
45
|
Dynamic: description
|
|
37
46
|
Dynamic: description-content-type
|
|
@@ -4,6 +4,8 @@ README.md
|
|
|
4
4
|
pyproject.toml
|
|
5
5
|
requirements.modules.txt
|
|
6
6
|
setup.py
|
|
7
|
+
cidc_api/__init__.py
|
|
8
|
+
cidc_api/telemetry.py
|
|
7
9
|
cidc_api/config/__init__.py
|
|
8
10
|
cidc_api/config/db.py
|
|
9
11
|
cidc_api/config/logging.py
|
|
@@ -100,6 +102,7 @@ cidc_api/reference/icd10cm.py
|
|
|
100
102
|
cidc_api/reference/icdo3.py
|
|
101
103
|
cidc_api/reference/uberon.py
|
|
102
104
|
cidc_api/shared/__init__.py
|
|
105
|
+
cidc_api/shared/assay_handling.py
|
|
103
106
|
cidc_api/shared/auth.py
|
|
104
107
|
cidc_api/shared/email_layout.html
|
|
105
108
|
cidc_api/shared/emails.py
|