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.
Files changed (125) hide show
  1. {nci_cidc_api_modules-1.2.33/nci_cidc_api_modules.egg-info → nci_cidc_api_modules-1.2.39}/PKG-INFO +22 -13
  2. nci_cidc_api_modules-1.2.39/cidc_api/__init__.py +1 -0
  3. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/models.py +140 -9
  4. nci_cidc_api_modules-1.2.39/cidc_api/shared/assay_handling.py +68 -0
  5. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/auth.py +5 -5
  6. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/file_handling.py +3 -0
  7. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/gcloud_client.py +27 -5
  8. nci_cidc_api_modules-1.2.39/cidc_api/shared/utils.py +19 -0
  9. nci_cidc_api_modules-1.2.39/cidc_api/telemetry.py +101 -0
  10. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39/nci_cidc_api_modules.egg-info}/PKG-INFO +22 -13
  11. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/nci_cidc_api_modules.egg-info/SOURCES.txt +3 -0
  12. nci_cidc_api_modules-1.2.39/nci_cidc_api_modules.egg-info/requires.txt +35 -0
  13. nci_cidc_api_modules-1.2.39/requirements.modules.txt +43 -0
  14. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/setup.py +3 -1
  15. nci_cidc_api_modules-1.2.33/cidc_api/shared/utils.py +0 -11
  16. nci_cidc_api_modules-1.2.33/nci_cidc_api_modules.egg-info/requires.txt +0 -26
  17. nci_cidc_api_modules-1.2.33/requirements.modules.txt +0 -28
  18. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/LICENSE +0 -0
  19. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/MANIFEST.in +0 -0
  20. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/README.md +0 -0
  21. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/__init__.py +0 -0
  22. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/db.py +0 -0
  23. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/logging.py +0 -0
  24. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/secrets.py +0 -0
  25. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/config/settings.py +0 -0
  26. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/__init__.py +0 -0
  27. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/data.py +0 -0
  28. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/base_orm.py +0 -0
  29. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/__init__.py +0 -0
  30. {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
  31. {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
  32. {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
  33. {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
  34. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/arm_orm.py +0 -0
  35. {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
  36. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/cohort_orm.py +0 -0
  37. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/comorbidity_orm.py +0 -0
  38. {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
  39. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/contact_orm.py +0 -0
  40. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/demographic_orm.py +0 -0
  41. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/disease_orm.py +0 -0
  42. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/exposure_orm.py +0 -0
  43. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/file_orm.py +0 -0
  44. {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
  45. {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
  46. {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
  47. {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
  48. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/institution_orm.py +0 -0
  49. {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
  50. {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
  51. {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
  52. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/participant_orm.py +0 -0
  53. {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
  54. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/publication_orm.py +0 -0
  55. {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
  56. {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
  57. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/response_orm.py +0 -0
  58. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/shipment_orm.py +0 -0
  59. {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
  60. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/specimen_orm.py +0 -0
  61. {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
  62. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/surgery_orm.py +0 -0
  63. {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
  64. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/treatment_orm.py +0 -0
  65. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/db/stage2/trial_orm.py +0 -0
  66. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/__init__.py +0 -0
  67. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/details.py +0 -0
  68. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/files/facets.py +0 -0
  69. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/migrations.py +0 -0
  70. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/__init__.py +0 -0
  71. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/additional_treatment.py +0 -0
  72. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/administrative_person.py +0 -0
  73. {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
  74. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/adverse_event.py +0 -0
  75. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/arm.py +0 -0
  76. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/base.py +0 -0
  77. {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
  78. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/cohort.py +0 -0
  79. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/comorbidity.py +0 -0
  80. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/consent_group.py +0 -0
  81. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/contact.py +0 -0
  82. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/demographic.py +0 -0
  83. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/disease.py +0 -0
  84. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/exposure.py +0 -0
  85. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/file.py +0 -0
  86. {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
  87. {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
  88. {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
  89. {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
  90. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/institution.py +0 -0
  91. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/medical_history.py +0 -0
  92. {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
  93. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/other_malignancy.py +0 -0
  94. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/participant.py +0 -0
  95. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/prior_treatment.py +0 -0
  96. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/publication.py +0 -0
  97. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/radiotherapy_dose.py +0 -0
  98. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/response.py +0 -0
  99. {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
  100. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/shipment.py +0 -0
  101. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/shipment_specimen.py +0 -0
  102. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/specimen.py +0 -0
  103. {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
  104. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/surgery.py +0 -0
  105. {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
  106. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/treatment.py +0 -0
  107. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/pydantic/stage2/trial.py +0 -0
  108. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/schemas.py +0 -0
  109. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/models/types.py +0 -0
  110. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/ctcae.py +0 -0
  111. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/gvhd.py +0 -0
  112. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/icd10cm.py +0 -0
  113. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/icdo3.py +0 -0
  114. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/reference/uberon.py +0 -0
  115. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/__init__.py +0 -0
  116. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/email_layout.html +0 -0
  117. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/emails.py +0 -0
  118. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/jose.py +0 -0
  119. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/cidc_api/shared/rest_utils.py +0 -0
  120. {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
  121. {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
  122. {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
  123. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/pyproject.toml +0 -0
  124. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/setup.cfg +0 -0
  125. {nci_cidc_api_modules-1.2.33 → nci_cidc_api_modules-1.2.39}/tests/test_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nci_cidc_api_modules
3
- Version: 1.2.33
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.18.5
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.43.0
17
- Requires-Dist: google-api-python-client>=2.185.0
18
- Requires-Dist: google-cloud-bigquery>=3.38.0
19
- Requires-Dist: google-cloud-pubsub>=2.33.0
20
- Requires-Dist: google-cloud-secret-manager>=2.25.0
21
- Requires-Dist: google-cloud-storage>=3.6.0
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: marshmallow>=4.1.0
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.3.5
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.4
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.44
33
+ Requires-Dist: sqlalchemy>=2.0.45
33
34
  Requires-Dist: sqlalchemy-mixins~=2.0.5
34
- Requires-Dist: werkzeug>=3.1.3
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
- "INGESTION_JOB_COLORS",
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
- INGESTION_JOB_COLORS = {
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(trial_id: str, status: str, version: int, pending: Boolean = False, session: Session = None):
3472
- new_job = IngestionJobs(trial_id=trial_id, status=status, version=version, pending=pending)
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(cls, trial_id: str, session: Session = None) -> list["IngestionJobs"]:
3498
- return session.query(cls).filter(cls.trial_id == trial_id).order_by(cls.version.desc()).all()
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(cls, trial_id: str, session: Session = None) -> Optional["IngestionJobs"]:
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 get_open_jobs_for_user(cls, user: Users, session: Session = None) -> list["IngestionJobs"]:
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
@@ -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,
@@ -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 create_intake_bucket(user_email: str) -> storage.Bucket:
365
+ def get_trial_intake_bucket_name(trial_id: str) -> str:
365
366
  """
366
- Create a new data intake bucket for this user, or get the existing one.
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
- bucket_name = get_intake_bucket_name(user_email)
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
- pubsub_publisher = pubsub.PublisherClient()
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nci_cidc_api_modules
3
- Version: 1.2.33
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.18.5
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.43.0
17
- Requires-Dist: google-api-python-client>=2.185.0
18
- Requires-Dist: google-cloud-bigquery>=3.38.0
19
- Requires-Dist: google-cloud-pubsub>=2.33.0
20
- Requires-Dist: google-cloud-secret-manager>=2.25.0
21
- Requires-Dist: google-cloud-storage>=3.6.0
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: marshmallow>=4.1.0
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.3.5
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.4
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.44
33
+ Requires-Dist: sqlalchemy>=2.0.45
33
34
  Requires-Dist: sqlalchemy-mixins~=2.0.5
34
- Requires-Dist: werkzeug>=3.1.3
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