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.
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/Cargo.lock +2 -2
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/Cargo.toml +1 -1
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/PKG-INFO +9 -4
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/README.md +8 -3
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/pyproject.toml +1 -1
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/cli.py +97 -2
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/LICENSE +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/Cargo.toml +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/benches/load_test.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/bin/pyreverseetl_dashboard.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/activation.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/alert_compat.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/error.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/http_client.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/hubspot.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/mapping.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/marketo.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/oauth_manager.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/retry_policy.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/salesforce.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/schema_detect.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/adapters/webhook.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/change_detector.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/changelog.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/checkpoint.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/cdc/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/config.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/connectors_db.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/database_advanced.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/destination.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/error.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/hdfs.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/mysql.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/object_storage.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/postgres.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/rate_limiting.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/registry.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/saas.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/source.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/connectors/web.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/destination.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/dlq.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/entity.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/error.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/executor.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/cached_gate.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/compliance_rules.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/credentials.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/quality_gate.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/retry_policy.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/schema_evolution.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/governance/statguardian_client.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/idempotency.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/lib.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/lineage.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/dashboard_launcher.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/logs.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/metrics.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/metrics_server.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/observability/traces.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/activation_pipeline.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/backpressure.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/latency_tracker.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/pipeline/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/kafka.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/polling.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sources/sync_config.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/statguardian.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/repository.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/storage/schema.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/event_processor.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/event_schema.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streaming/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streampdf.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/streamxl.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/sync.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/connector_test.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/harness.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/metrics.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/mock_http.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/testing/test_data.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/transformers/mod.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/transformers/spark.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/core/src/workflow.rs +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/Cargo.toml +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/__init__.py +0 -0
- {pyreverseetl-3.2.0 → pyreverseetl-3.3.0}/python/pyreverseetl/server.py +0 -0
- {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.
|
|
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.
|
|
3213
|
+
version = "3.3.0"
|
|
3214
3214
|
dependencies = [
|
|
3215
3215
|
"async-trait",
|
|
3216
3216
|
"aws-config",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyReverseETL
|
|
3
|
-
Version: 3.
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
|
@@ -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
|
|
230
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|