PyReverseETL 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 (95) hide show
  1. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/Cargo.lock +2 -2
  2. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/Cargo.toml +1 -1
  3. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/PKG-INFO +9 -4
  4. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/README.md +8 -3
  5. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/pyproject.toml +1 -1
  6. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/cli.py +97 -2
  7. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/LICENSE +0 -0
  8. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/Cargo.toml +0 -0
  9. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/benches/load_test.rs +0 -0
  10. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/bin/pyreverseetl_dashboard.rs +0 -0
  11. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/activation.rs +0 -0
  12. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/alert_compat.rs +0 -0
  13. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/error.rs +0 -0
  14. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/http_client.rs +0 -0
  15. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/hubspot.rs +0 -0
  16. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/mapping.rs +0 -0
  17. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/marketo.rs +0 -0
  18. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/mod.rs +0 -0
  19. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/oauth_manager.rs +0 -0
  20. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/retry_policy.rs +0 -0
  21. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/salesforce.rs +0 -0
  22. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/schema_detect.rs +0 -0
  23. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/webhook.rs +0 -0
  24. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/change_detector.rs +0 -0
  25. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/changelog.rs +0 -0
  26. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/checkpoint.rs +0 -0
  27. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/mod.rs +0 -0
  28. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/config.rs +0 -0
  29. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/connectors_db.rs +0 -0
  30. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/database_advanced.rs +0 -0
  31. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/destination.rs +0 -0
  32. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/error.rs +0 -0
  33. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/hdfs.rs +0 -0
  34. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/mod.rs +0 -0
  35. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/mysql.rs +0 -0
  36. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/object_storage.rs +0 -0
  37. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/postgres.rs +0 -0
  38. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/rate_limiting.rs +0 -0
  39. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/registry.rs +0 -0
  40. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/saas.rs +0 -0
  41. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/source.rs +0 -0
  42. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/web.rs +0 -0
  43. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/destination.rs +0 -0
  44. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/dlq.rs +0 -0
  45. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/entity.rs +0 -0
  46. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/error.rs +0 -0
  47. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/executor.rs +0 -0
  48. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/cached_gate.rs +0 -0
  49. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/compliance_rules.rs +0 -0
  50. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/credentials.rs +0 -0
  51. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/mod.rs +0 -0
  52. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/quality_gate.rs +0 -0
  53. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/retry_policy.rs +0 -0
  54. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/schema_evolution.rs +0 -0
  55. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/statguardian_client.rs +0 -0
  56. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/idempotency.rs +0 -0
  57. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/lib.rs +0 -0
  58. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/lineage.rs +0 -0
  59. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/dashboard_launcher.rs +0 -0
  60. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/logs.rs +0 -0
  61. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/metrics.rs +0 -0
  62. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/metrics_server.rs +0 -0
  63. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/mod.rs +0 -0
  64. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/traces.rs +0 -0
  65. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/activation_pipeline.rs +0 -0
  66. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/backpressure.rs +0 -0
  67. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/latency_tracker.rs +0 -0
  68. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/mod.rs +0 -0
  69. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/kafka.rs +0 -0
  70. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/mod.rs +0 -0
  71. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/polling.rs +0 -0
  72. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/sync_config.rs +0 -0
  73. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/statguardian.rs +0 -0
  74. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/mod.rs +0 -0
  75. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/repository.rs +0 -0
  76. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/schema.rs +0 -0
  77. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/event_processor.rs +0 -0
  78. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/event_schema.rs +0 -0
  79. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/mod.rs +0 -0
  80. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streampdf.rs +0 -0
  81. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streamxl.rs +0 -0
  82. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sync.rs +0 -0
  83. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/connector_test.rs +0 -0
  84. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/harness.rs +0 -0
  85. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/metrics.rs +0 -0
  86. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/mock_http.rs +0 -0
  87. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/mod.rs +0 -0
  88. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/test_data.rs +0 -0
  89. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/transformers/mod.rs +0 -0
  90. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/transformers/spark.rs +0 -0
  91. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/workflow.rs +0 -0
  92. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/Cargo.toml +0 -0
  93. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/__init__.py +0 -0
  94. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/server.py +0 -0
  95. {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/src/lib.rs +0 -0
@@ -3199,7 +3199,7 @@ dependencies = [
3199
3199
 
3200
3200
  [[package]]
3201
3201
  name = "pyreverseetl"
3202
- version = "3.2.0"
3202
+ version = "3.3.0"
3203
3203
  dependencies = [
3204
3204
  "chrono",
3205
3205
  "pyo3",
@@ -3210,7 +3210,7 @@ dependencies = [
3210
3210
 
3211
3211
  [[package]]
3212
3212
  name = "pyreverseetl-core"
3213
- version = "3.2.0"
3213
+ version = "3.3.0"
3214
3214
  dependencies = [
3215
3215
  "async-trait",
3216
3216
  "aws-config",
@@ -3,7 +3,7 @@ members = ["core", "python"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "3.2.0"
6
+ version = "3.3.0"
7
7
  authors = ["Georgi Mammen Mullassery <mullassery@gmail.com>"]
8
8
  license = "Apache-2.0"
9
9
  repository = "https://github.com/Mullassery/PyReverseETL"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyReverseETL
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3
@@ -252,12 +252,17 @@ Python CLI / API → pyreverseetl._core (PyO3 bindings) → pyreverseetl_cor
252
252
  ## Validation gate
253
253
 
254
254
  ```bash
255
+ # File-based report
255
256
  pyreverseetl execute-activation my-activation --validation-report statguardian-report.json
257
+
258
+ # Live StatGuardian service: validate a sample before the real sync
259
+ pyreverseetl execute-activation my-activation --statguardian-url http://127.0.0.1:8000
256
260
  ```
257
261
 
258
- The report must be a JSON object with `"passed": true`; anything else blocks
259
- the sync before reading or writing data. This is a file-based gate until a live
260
- StatGuardian service endpoint is available.
262
+ A file report must have `"passed": true`; anything else blocks the sync. The
263
+ live `--statguardian-url` gate dry-runs a sample and calls the service's
264
+ `/validate`; a failure or network error blocks the real sync. It is a sample
265
+ gate, not a full-table scan.
261
266
 
262
267
  ## Documentation
263
268
 
@@ -229,12 +229,17 @@ Python CLI / API → pyreverseetl._core (PyO3 bindings) → pyreverseetl_cor
229
229
  ## Validation gate
230
230
 
231
231
  ```bash
232
+ # File-based report
232
233
  pyreverseetl execute-activation my-activation --validation-report statguardian-report.json
234
+
235
+ # Live StatGuardian service: validate a sample before the real sync
236
+ pyreverseetl execute-activation my-activation --statguardian-url http://127.0.0.1:8000
233
237
  ```
234
238
 
235
- The report must be a JSON object with `"passed": true`; anything else blocks
236
- the sync before reading or writing data. This is a file-based gate until a live
237
- StatGuardian service endpoint is available.
239
+ A file report must have `"passed": true`; anything else blocks the sync. The
240
+ live `--statguardian-url` gate dry-runs a sample and calls the service's
241
+ `/validate`; a failure or network error blocks the real sync. It is a sample
242
+ gate, not a full-table scan.
238
243
 
239
244
  ## Documentation
240
245
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "PyReverseETL"
7
- version = "3.2.0"
7
+ version = "3.3.0"
8
8
  description = "The Open Operational Data Activation Runtime"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -27,10 +27,49 @@ from __future__ import annotations
27
27
  import json
28
28
  import os
29
29
  import sys
30
+ import urllib.error
31
+ import urllib.request
30
32
 
31
33
  DEFAULT_STATE_PATH = os.path.join(os.path.expanduser("~"), ".pyreverseetl", "state.json")
32
34
 
33
35
 
36
+ def validate_activation_rows(
37
+ rows: list[dict],
38
+ service_url: str,
39
+ *,
40
+ limit: int = 100,
41
+ timeout_seconds: float = 10.0,
42
+ ) -> tuple[bool, dict]:
43
+ """POST sample rows to StatGuardian's ``/validate`` endpoint.
44
+
45
+ Fails closed on network/HTTP/parse errors. Only the first ``limit`` rows
46
+ are checked; this is a pre-activation sample gate, not a full-table scan.
47
+ """
48
+ url = service_url.rstrip("/") + "/validate"
49
+ checked = 0
50
+ for row in rows[: max(1, limit)]:
51
+ checked += 1
52
+ body = json.dumps({"entity": {"attributes": row}}).encode("utf-8")
53
+ request = urllib.request.Request(
54
+ url,
55
+ data=body,
56
+ headers={"Content-Type": "application/json"},
57
+ method="POST",
58
+ )
59
+ try:
60
+ with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
61
+ payload = json.loads(response.read().decode("utf-8"))
62
+ except (urllib.error.URLError, OSError, ValueError) as exc:
63
+ return False, {"reason": f"StatGuardian request failed: {exc}", "checked": checked}
64
+ if payload.get("passed") is not True:
65
+ return False, {
66
+ "reason": "StatGuardian rejected a sample row",
67
+ "checked": checked,
68
+ "issues": payload.get("issues", []),
69
+ }
70
+ return True, {"checked": checked}
71
+
72
+
34
73
  def validation_report_allows(path: str) -> tuple[bool, dict]:
35
74
  """Read a StatGuardian-style JSON report and decide whether sync may run.
36
75
 
@@ -185,6 +224,8 @@ class CLIInterface:
185
224
  idempotency_store_path: str | None = None,
186
225
  dlq_store_path: str | None = None,
187
226
  validation_report_path: str | None = None,
227
+ statguardian_url: str | None = None,
228
+ statguardian_limit: int = 100,
188
229
  ) -> dict:
189
230
  """Execute an activation: run a real sync through the Rust engine.
190
231
 
@@ -226,8 +267,13 @@ class CLIInterface:
226
267
  validation_report_path: Optional path to a JSON validation report
227
268
  (``{"passed": bool, ...}``). If supplied and not passing, the
228
269
  sync is blocked before any source read or destination write.
229
- This is a file-based StatGuardian gate; it does not call the
230
- StatGuardian service.
270
+ This is a file-based StatGuardian gate.
271
+ statguardian_url: Optional base URL of a running StatGuardian
272
+ service. A dry-run reads a sample of source rows and POSTs them
273
+ to ``/validate``; any failure (or network error) blocks the real
274
+ sync. This doubles the source read for the sample pass.
275
+ statguardian_limit: Maximum sample rows sent to StatGuardian
276
+ (default 100).
231
277
  dlq_store_path: Optional path to a SQLite file used as a
232
278
  dead-letter queue (quarantine) for records the destination
233
279
  rejects. Each failed record is stored with its run id, the
@@ -276,6 +322,38 @@ class CLIInterface:
276
322
  source_config.setdefault("table", workflow.get("table"))
277
323
  destination_config = activation.get("destination_config") or {}
278
324
 
325
+ if statguardian_url:
326
+ try:
327
+ preview = _core.run_sync(
328
+ source_type=workflow["source"],
329
+ source_config=json.dumps(source_config),
330
+ destination_type=activation["destination"],
331
+ destination_config=json.dumps(destination_config),
332
+ limit=limit,
333
+ compliance_rules=json.dumps(compliance_rules) if compliance_rules else None,
334
+ dry_run=True,
335
+ schema_store_path=None,
336
+ idempotency_store_path=None,
337
+ dlq_store_path=None,
338
+ )
339
+ except Exception as exc: # noqa: BLE001
340
+ return {
341
+ "status": "error",
342
+ "activation_id": activation_id,
343
+ "message": f"StatGuardian sample read failed: {exc}",
344
+ }
345
+ sample_rows = [json.loads(row) for row in preview.dry_run_preview]
346
+ allowed, detail = validate_activation_rows(
347
+ sample_rows, statguardian_url, limit=statguardian_limit
348
+ )
349
+ if not allowed:
350
+ return {
351
+ "status": "error",
352
+ "activation_id": activation_id,
353
+ "message": "Blocked by StatGuardian sample validation",
354
+ "validation": detail,
355
+ }
356
+
279
357
  try:
280
358
  result = _core.run_sync(
281
359
  source_type=workflow["source"],
@@ -546,6 +624,21 @@ def main():
546
624
  sys.exit(1)
547
625
  validation_report_path = sys.argv[idx + 1]
548
626
 
627
+ statguardian_url = None
628
+ if "--statguardian-url" in sys.argv:
629
+ idx = sys.argv.index("--statguardian-url")
630
+ if idx + 1 >= len(sys.argv):
631
+ print(json.dumps({"error": "--statguardian-url requires a URL"}))
632
+ sys.exit(1)
633
+ statguardian_url = sys.argv[idx + 1]
634
+ statguardian_limit = 100
635
+ if "--statguardian-limit" in sys.argv:
636
+ idx = sys.argv.index("--statguardian-limit")
637
+ if idx + 1 >= len(sys.argv):
638
+ print(json.dumps({"error": "--statguardian-limit requires a number"}))
639
+ sys.exit(1)
640
+ statguardian_limit = int(sys.argv[idx + 1])
641
+
549
642
  result = cli.execute_activation(
550
643
  activation_id,
551
644
  limit,
@@ -555,6 +648,8 @@ def main():
555
648
  idempotency_store_path,
556
649
  dlq_store_path,
557
650
  validation_report_path,
651
+ statguardian_url,
652
+ statguardian_limit,
558
653
  )
559
654
  print(json.dumps(result))
560
655
 
File without changes