gen3-dataops-toolkit 3.2.0__tar.gz → 3.3.0__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 (56) hide show
  1. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/PKG-INFO +2 -2
  2. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/pyproject.toml +2 -2
  3. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/validate/validate.py +86 -21
  4. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/README.md +0 -0
  5. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/__init__.py +0 -0
  6. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/__init__.py +0 -0
  7. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/__init__.py +0 -0
  8. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/dispatch.py +0 -0
  9. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/registry.py +0 -0
  10. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/resolve.py +0 -0
  11. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/runner.py +0 -0
  12. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/_internal/safety.py +0 -0
  13. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/config_cmds.py +0 -0
  14. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/delete_cmds.py +0 -0
  15. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/dict_cmds.py +0 -0
  16. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/ec2_cmds.py +0 -0
  17. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/indexd_cmds.py +0 -0
  18. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/jobs.py +0 -0
  19. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/k8s.py +0 -0
  20. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/main.py +0 -0
  21. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/metadata.py +0 -0
  22. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/pipeline_cmds.py +0 -0
  23. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/release_cmds.py +0 -0
  24. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/cli/synth.py +0 -0
  25. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/config.py +0 -0
  26. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/indexd/__init__.py +0 -0
  27. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/indexd/file_access.py +0 -0
  28. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/indexd/indexd_registrar.py +0 -0
  29. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/ingest/ingest.py +0 -0
  30. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/resolver.py +0 -0
  31. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/delete/delete_all_metadata_for_project.py +0 -0
  32. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/delete/delete_metadata.sh +0 -0
  33. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/delete/delete_metadata_by_guid.py +0 -0
  34. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/dictionary/deploy_dd.sh +0 -0
  35. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/dictionary/pull_dict.sh +0 -0
  36. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/dictionary/upload_dictionary.py +0 -0
  37. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/indexd/register_indexd.py +0 -0
  38. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/indexd/verify_file_access.py +0 -0
  39. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/k8s_ops/argocd_restart_etl.sh +0 -0
  40. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/k8s_ops/argocd_restart_ms.sh +0 -0
  41. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/k8s_ops/argocd_restart_schema.sh +0 -0
  42. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/k8s_ops/login_to_pod.sh +0 -0
  43. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/k8s_ops/restart_etl_and_ms.sh +0 -0
  44. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/synthetic_data/delete_synth_metadata_sheepdog.py +0 -0
  45. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/synthetic_data/full_deploy_dd_and_synth.sh +0 -0
  46. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/synthetic_data/generate_synth_metadata.sh +0 -0
  47. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/synthetic_data/upload_synth_metadata_sheepdog.py +0 -0
  48. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/upload/metadata/upload_all_studies.sh +0 -0
  49. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/services/upload/metadata/upload_metadata.py +0 -0
  50. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/upload/__init__.py +0 -0
  51. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/upload/metadata_deleter.py +0 -0
  52. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/upload/metadata_submitter.py +0 -0
  53. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/upload/upload_synthdata_s3.py +0 -0
  54. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/utils/athena_utils.py +0 -0
  55. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/utils/dbt_utils.py +0 -0
  56. {gen3_dataops_toolkit-3.2.0 → gen3_dataops_toolkit-3.3.0}/src/g3dt/utils/release_writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gen3-dataops-toolkit
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments
5
5
  License: Apache-2.0
6
6
  Author: JoshuaHarris391
@@ -17,7 +17,7 @@ Requires-Dist: awswrangler (>=3.14.0,<4.0.0)
17
17
  Requires-Dist: boto3
18
18
  Requires-Dist: gen3 (>=4.27.4,<5.0.0)
19
19
  Requires-Dist: gen3-metadata (>=1.4.0,<2.0.0)
20
- Requires-Dist: gen3_validator (>=2.0.0,<3.0.0)
20
+ Requires-Dist: gen3_validator (>=2.3.0,<3.0.0)
21
21
  Requires-Dist: numpy (<2.0.0)
22
22
  Requires-Dist: openpyxl (>=3.1.0)
23
23
  Requires-Dist: pyarrow (>=14.0.0,<19.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "gen3-dataops-toolkit"
3
- version = "3.2.0"
3
+ version = "3.3.0"
4
4
  description = "Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments"
5
5
  authors = ["JoshuaHarris391 <harjo391@gmail.com>"]
6
6
  readme = "README.md"
@@ -19,7 +19,7 @@ numpy = "<2.0.0"
19
19
  pyyaml = ">=6.0.2,<7.0.0"
20
20
  tzlocal = ">=5.3.1,<6.0.0"
21
21
  gen3 = ">=4.27.4, <5.0.0"
22
- gen3_validator = ">=2.0.0,<3.0.0"
22
+ gen3_validator = ">=2.3.0,<3.0.0"
23
23
  pytz = ">=2025.2,<2026.0"
24
24
  s3fs = "2025.10.0"
25
25
  pyjwt = ">=2.10.1,<3.0.0"
@@ -479,6 +479,49 @@ def truncate_linkage_results(linkage_results_dict: dict) -> dict:
479
479
  return linkage_results_dict
480
480
 
481
481
 
482
+ #: The eight fields every result row carries, as emitted by
483
+ #: ``gen3_validator.validate.validate_object`` (FAIL rows) and
484
+ #: ``gen3_validator.validate.error_record`` (ERROR rows).
485
+ VALIDATION_RESULT_BASE_COLUMNS = [
486
+ "node",
487
+ "index",
488
+ "validation_result",
489
+ "invalid_key",
490
+ "schema_path",
491
+ "validator",
492
+ "validator_value",
493
+ "validation_error",
494
+ ]
495
+
496
+ #: Full column contract of the results table, in write order.
497
+ VALIDATION_RESULT_COLUMNS = [
498
+ "validation_id",
499
+ "index",
500
+ "node",
501
+ "study_id",
502
+ "validation_result",
503
+ "invalid_key",
504
+ "schema_path",
505
+ "validator",
506
+ "validator_value",
507
+ "validation_error",
508
+ "schema_version",
509
+ ]
510
+
511
+ #: Written when a study validates clean, so that every run leaves exactly one
512
+ #: gradeable row. Excluded by the validation gate — see
513
+ #: :func:`run_validation_gate`.
514
+ PASS_MARKER_ROW = {
515
+ "node": None,
516
+ "index": None,
517
+ "validation_result": "PASS",
518
+ "invalid_key": None,
519
+ "schema_path": None,
520
+ "validator": None,
521
+ "validator_value": None,
522
+ "validation_error": None,
523
+ }
524
+
482
525
  #: validation_error patterns treated as known noise by the validation gate —
483
526
  #: structural artefacts of the export (null-typed fields, extra export-side
484
527
  #: properties, the Gen3 'programs' root requirement), not data quality issues.
@@ -497,9 +540,14 @@ def run_validation_gate(glue_database: str, athena_s3_output: str,
497
540
 
498
541
  The gate looks at the most recent ``validation_id`` in the results table,
499
542
  filters out the known-noise error patterns
500
- (:data:`VALIDATION_GATE_IGNORED_ERRORS`) and synthetic studies, and
501
- aggregates what remains. An empty result means the latest validation run
502
- is clean; any rows are genuine schema failures an operator must fix.
543
+ (:data:`VALIDATION_GATE_IGNORED_ERRORS`), PASS markers and synthetic
544
+ studies, and aggregates what remains. An empty result means the latest
545
+ validation run is clean; any rows are genuine schema failures an operator
546
+ must fix.
547
+
548
+ ERROR rows are deliberately NOT filtered out. A record whose node is absent
549
+ from the dictionary could not be checked at all, which is a failure to
550
+ validate rather than a clean result, and must hold the gate closed.
503
551
 
504
552
  Callers (the validator Glue job) FAIL the run when rows come back, so the
505
553
  validation Step Function goes red until the data is fixed and validation
@@ -523,6 +571,7 @@ WHERE validation_id = (
523
571
  FROM "{glue_database}"."{results_table}"
524
572
  )
525
573
  {ignore_clauses}
574
+ AND validation_result <> 'PASS'
526
575
  AND study_id NOT LIKE '%synthetic%'
527
576
  GROUP BY node, study_id, invalid_key, validator_value, validation_error
528
577
  ORDER BY node, study_id
@@ -567,6 +616,7 @@ def validate_pipeline(
567
616
  resolver=None,
568
617
  metadata_table: pd.DataFrame = None,
569
618
  write_iceberg: bool = True,
619
+ results_table: str = "full_validation_results",
570
620
  ) -> pd.DataFrame:
571
621
  """
572
622
  Orchestrate the validation workflow for a study: load + resolve schema, find the latest
@@ -592,9 +642,14 @@ def validate_pipeline(
592
642
  write_iceberg: When False, skip the per-study Iceberg write and leave it
593
643
  to the caller (who can concatenate several studies' results into a
594
644
  single write). The CSV write-back always happens.
645
+ results_table: Table to write into when ``write_iceberg`` is True. Callers
646
+ validating an isolated warehouse (e.g. the CI databases) MUST pass
647
+ their own — the real and CI results must never share a table, since
648
+ the gate grades whichever run has the greatest validation_id.
595
649
 
596
650
  Returns:
597
- The study's validation results DataFrame.
651
+ The study's validation results DataFrame. Never empty: a study with no
652
+ failures yields a single PASS marker row.
598
653
 
599
654
  Raises:
600
655
  RuntimeError: When expected inputs are missing or a pipeline step fails.
@@ -663,24 +718,34 @@ def validate_pipeline(
663
718
  logger.info("Getting Schema Version")
664
719
  schema_version = resolver.get_schema_version(schema = schema)
665
720
 
666
- full_validation_results_df = pd.DataFrame(results)
721
+ if not results:
722
+ # No failures. Record the run anyway: the validation gate grades
723
+ # the greatest validation_id in the results table, so a clean run
724
+ # that writes nothing would leave the previous FAILING run as the
725
+ # latest and the gate could never go green no matter how many
726
+ # times the data was fixed. A PASS marker also guarantees the
727
+ # table exists after the first run, which is what stops a fresh
728
+ # environment reporting TABLE_NOT_FOUND.
729
+ logger.info("No validation failures for study '%s'; writing PASS marker.", study_id)
730
+ results = [PASS_MARKER_ROW.copy()]
731
+
732
+ # Build with an explicit column list rather than inferring it from the
733
+ # rows: pd.DataFrame([]) has NO columns, so the reindex below used to
734
+ # raise KeyError and surface as "Validation failed." — a clean study
735
+ # failing with the same message as a dirty one.
736
+ full_validation_results_df = pd.DataFrame(results, columns=VALIDATION_RESULT_BASE_COLUMNS)
667
737
  full_validation_results_df["validation_id"] = latest_validation_id
668
738
  full_validation_results_df["study_id"] = study_id
669
739
  full_validation_results_df["schema_version"] = schema_version
670
-
671
- full_validation_results_df = full_validation_results_df[[
672
- "validation_id",
673
- "index",
674
- "node",
675
- "study_id",
676
- "validation_result",
677
- "invalid_key",
678
- "schema_path",
679
- "validator",
680
- "validator_value",
681
- "validation_error",
682
- "schema_version"
683
- ]]
740
+
741
+ full_validation_results_df = full_validation_results_df[VALIDATION_RESULT_COLUMNS]
742
+
743
+ # 'index' is the only numeric column. A PASS-only frame leaves it
744
+ # entirely null, which would infer a different Iceberg column type
745
+ # than a frame containing failures — and full_validation_results
746
+ # already exists in deployed environments. Pin it so both shapes
747
+ # write the same schema.
748
+ full_validation_results_df = full_validation_results_df.astype({"index": "Int64"})
684
749
 
685
750
  logger.info("Validation completed (%s rows).", len(full_validation_results_df))
686
751
  except Exception as e:
@@ -703,12 +768,12 @@ def validate_pipeline(
703
768
  logger.info(
704
769
  "Writing Parquet to Glue DB '%s', table '%s'.",
705
770
  glue_database,
706
- "full_validation_results",
771
+ results_table,
707
772
  )
708
773
  write_iceberg_to_db(
709
774
  df=full_validation_results_df,
710
775
  database=glue_database,
711
- table="full_validation_results",
776
+ table=results_table,
712
777
  athena_s3_output=athena_s3_output,
713
778
  workgroup=workgroup,
714
779
  )