datasluice 0.2.0__py3-none-any.whl

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 (136) hide show
  1. datasluice/__init__.py +103 -0
  2. datasluice/_uri.py +77 -0
  3. datasluice/_version.py +15 -0
  4. datasluice/application.py +658 -0
  5. datasluice/auth/__init__.py +17 -0
  6. datasluice/auth/api_key.py +57 -0
  7. datasluice/auth/base.py +28 -0
  8. datasluice/auth/basic.py +32 -0
  9. datasluice/auth/bearer.py +30 -0
  10. datasluice/auth/headers.py +31 -0
  11. datasluice/auth/none.py +19 -0
  12. datasluice/cli/__init__.py +5 -0
  13. datasluice/cli/_output.py +26 -0
  14. datasluice/cli/_resolver.py +76 -0
  15. datasluice/cli/app.py +56 -0
  16. datasluice/cli/detect.py +82 -0
  17. datasluice/cli/download.py +52 -0
  18. datasluice/cli/inspect.py +81 -0
  19. datasluice/cli/materialize.py +53 -0
  20. datasluice/cli/open.py +69 -0
  21. datasluice/cli/scan.py +80 -0
  22. datasluice/cli/search.py +74 -0
  23. datasluice/config/__init__.py +21 -0
  24. datasluice/config/defaults.py +11 -0
  25. datasluice/connectors/__init__.py +6 -0
  26. datasluice/connectors/_reject.py +71 -0
  27. datasluice/connectors/base.py +69 -0
  28. datasluice/connectors/ckan/__init__.py +9 -0
  29. datasluice/connectors/ckan/adapter.py +116 -0
  30. datasluice/connectors/ckan/errors.py +30 -0
  31. datasluice/connectors/ckan/factory.py +21 -0
  32. datasluice/connectors/ckan/mapper.py +139 -0
  33. datasluice/connectors/ckan/pagination.py +24 -0
  34. datasluice/connectors/datagouv/__init__.py +9 -0
  35. datasluice/connectors/datagouv/adapter.py +107 -0
  36. datasluice/connectors/datagouv/errors.py +20 -0
  37. datasluice/connectors/datagouv/factory.py +21 -0
  38. datasluice/connectors/datagouv/mapper.py +127 -0
  39. datasluice/connectors/datagouv/pagination.py +24 -0
  40. datasluice/connectors/socrata/__init__.py +9 -0
  41. datasluice/connectors/socrata/adapter.py +134 -0
  42. datasluice/connectors/socrata/errors.py +20 -0
  43. datasluice/connectors/socrata/factory.py +21 -0
  44. datasluice/connectors/socrata/mapper.py +125 -0
  45. datasluice/connectors/socrata/pagination.py +24 -0
  46. datasluice/contracts/__init__.py +22 -0
  47. datasluice/contracts/checks.py +242 -0
  48. datasluice/contracts/fixtures.py +38 -0
  49. datasluice/credentials/__init__.py +5 -0
  50. datasluice/credentials/host_provider.py +139 -0
  51. datasluice/data/__init__.py +45 -0
  52. datasluice/data/_byte_source.py +108 -0
  53. datasluice/data/access.py +404 -0
  54. datasluice/data/batch_stream.py +208 -0
  55. datasluice/data/compression.py +336 -0
  56. datasluice/data/readers/__init__.py +61 -0
  57. datasluice/data/readers/base.py +51 -0
  58. datasluice/data/readers/csv.py +92 -0
  59. datasluice/data/readers/geojson.py +127 -0
  60. datasluice/data/readers/json.py +106 -0
  61. datasluice/data/readers/parquet.py +138 -0
  62. datasluice/data/readers/xlsx.py +97 -0
  63. datasluice/data/schema.py +139 -0
  64. datasluice/discovery/__init__.py +15 -0
  65. datasluice/discovery/detector.py +92 -0
  66. datasluice/discovery/fingerprints.py +29 -0
  67. datasluice/discovery/portal_metadata.py +24 -0
  68. datasluice/domain/__init__.py +38 -0
  69. datasluice/domain/access.py +97 -0
  70. datasluice/domain/artifact.py +278 -0
  71. datasluice/domain/capabilities.py +35 -0
  72. datasluice/domain/credentials.py +20 -0
  73. datasluice/domain/dataset.py +50 -0
  74. datasluice/domain/detection.py +48 -0
  75. datasluice/domain/license.py +20 -0
  76. datasluice/domain/organization.py +31 -0
  77. datasluice/domain/query.py +32 -0
  78. datasluice/domain/resource.py +67 -0
  79. datasluice/domain/result.py +35 -0
  80. datasluice/domain/schema.py +31 -0
  81. datasluice/domain/sync_state.py +23 -0
  82. datasluice/exceptions.py +186 -0
  83. datasluice/integrations/__init__.py +21 -0
  84. datasluice/integrations/arrow.py +35 -0
  85. datasluice/integrations/dlt.py +194 -0
  86. datasluice/integrations/duckdb.py +86 -0
  87. datasluice/integrations/pandas.py +42 -0
  88. datasluice/integrations/polars.py +41 -0
  89. datasluice/io/__init__.py +56 -0
  90. datasluice/io/cache.py +58 -0
  91. datasluice/io/checksums.py +53 -0
  92. datasluice/io/content_cache.py +291 -0
  93. datasluice/io/downloader.py +113 -0
  94. datasluice/io/filesystem.py +68 -0
  95. datasluice/io/fsspec_storage.py +94 -0
  96. datasluice/io/local.py +45 -0
  97. datasluice/io/storage.py +64 -0
  98. datasluice/logging.py +94 -0
  99. datasluice/ports/__init__.py +29 -0
  100. datasluice/ports/cache.py +16 -0
  101. datasluice/ports/catalog.py +33 -0
  102. datasluice/ports/credentials.py +15 -0
  103. datasluice/ports/detector.py +14 -0
  104. datasluice/ports/resource_reader.py +49 -0
  105. datasluice/ports/state_store.py +91 -0
  106. datasluice/ports/storage.py +16 -0
  107. datasluice/ports/transport.py +89 -0
  108. datasluice/py.typed +1 -0
  109. datasluice/runtime/__init__.py +10 -0
  110. datasluice/runtime/context.py +31 -0
  111. datasluice/runtime/defaults.py +82 -0
  112. datasluice/runtime/plugin_manager.py +92 -0
  113. datasluice/runtime/session.py +275 -0
  114. datasluice/sync/__init__.py +32 -0
  115. datasluice/sync/_hashing.py +38 -0
  116. datasluice/sync/_identity.py +135 -0
  117. datasluice/sync/materialize.py +521 -0
  118. datasluice/sync/state_store.py +711 -0
  119. datasluice/sync/sync.py +579 -0
  120. datasluice/transforms/__init__.py +63 -0
  121. datasluice/transforms/pipeline.py +108 -0
  122. datasluice/transforms/protocol.py +60 -0
  123. datasluice/transforms/steps.py +244 -0
  124. datasluice/transport/__init__.py +42 -0
  125. datasluice/transport/http_client.py +164 -0
  126. datasluice/transport/httpx_transport.py +537 -0
  127. datasluice/transport/pagination.py +49 -0
  128. datasluice/transport/rate_limit.py +34 -0
  129. datasluice/transport/redirect.py +82 -0
  130. datasluice/transport/retry.py +75 -0
  131. datasluice/transport/user_agent.py +25 -0
  132. datasluice-0.2.0.dist-info/METADATA +197 -0
  133. datasluice-0.2.0.dist-info/RECORD +136 -0
  134. datasluice-0.2.0.dist-info/WHEEL +4 -0
  135. datasluice-0.2.0.dist-info/entry_points.txt +7 -0
  136. datasluice-0.2.0.dist-info/licenses/LICENSE +21 -0
datasluice/__init__.py ADDED
@@ -0,0 +1,103 @@
1
+ """DataSluice — one Python interface for open-data discovery, extraction,
2
+ format normalization, and pipeline integration.
3
+ """
4
+
5
+ from datasluice._version import __version__
6
+ from datasluice.application import (
7
+ CatalogResourceLocator,
8
+ DataSluice,
9
+ DirectResourceLocator,
10
+ OpenedResource,
11
+ Portal,
12
+ ResourceLocator,
13
+ resource_locator_from_dict,
14
+ )
15
+ from datasluice.domain import (
16
+ Artifact,
17
+ ArtifactProvenance,
18
+ CatalogCapabilities,
19
+ CredentialScope,
20
+ Dataset,
21
+ DetectionResult,
22
+ Digest,
23
+ HttpDownload,
24
+ License,
25
+ LocalFile,
26
+ ObjectStorage,
27
+ Organization,
28
+ Query,
29
+ QueryAccess,
30
+ Resource,
31
+ ResourceAccess,
32
+ Schema,
33
+ SearchResult,
34
+ StreamAccess,
35
+ SyncState,
36
+ )
37
+ from datasluice.exceptions import (
38
+ AdapterError,
39
+ AdapterNotFoundError,
40
+ AuthenticationError,
41
+ ChecksumMismatchError,
42
+ ConfigError,
43
+ DataSluiceError,
44
+ DownloadError,
45
+ FormatError,
46
+ NotFoundError,
47
+ OpenedResourceConsumedError,
48
+ PortalDetectionError,
49
+ PortalError,
50
+ RateLimitError,
51
+ ResourceResolutionError,
52
+ StateStoreError,
53
+ SyncStateConflictError,
54
+ )
55
+
56
+ __all__ = [
57
+ "__version__",
58
+ "DataSluice",
59
+ "Portal",
60
+ "OpenedResource",
61
+ "DirectResourceLocator",
62
+ "CatalogResourceLocator",
63
+ "ResourceLocator",
64
+ "resource_locator_from_dict",
65
+ # Domain models
66
+ "Dataset",
67
+ "Resource",
68
+ "Organization",
69
+ "License",
70
+ "Query",
71
+ "SearchResult",
72
+ "Artifact",
73
+ "ArtifactProvenance",
74
+ "CatalogCapabilities",
75
+ "CredentialScope",
76
+ "DetectionResult",
77
+ "Digest",
78
+ "HttpDownload",
79
+ "LocalFile",
80
+ "ObjectStorage",
81
+ "QueryAccess",
82
+ "ResourceAccess",
83
+ "Schema",
84
+ "StreamAccess",
85
+ "SyncState",
86
+ # Exceptions
87
+ "DataSluiceError",
88
+ "PortalError",
89
+ "AdapterError",
90
+ "AdapterNotFoundError",
91
+ "PortalDetectionError",
92
+ "AuthenticationError",
93
+ "RateLimitError",
94
+ "StateStoreError",
95
+ "SyncStateConflictError",
96
+ "NotFoundError",
97
+ "DownloadError",
98
+ "ChecksumMismatchError",
99
+ "FormatError",
100
+ "ConfigError",
101
+ "ResourceResolutionError",
102
+ "OpenedResourceConsumedError",
103
+ ]
datasluice/_uri.py ADDED
@@ -0,0 +1,77 @@
1
+ """URI display sanitizer: redact userinfo and sensitive query values (CR-07).
2
+
3
+ Query-position API keys, bearer tokens, signed-URL signatures, and
4
+ ``user:password@`` userinfo routinely end up embedded inside URI strings
5
+ that flow into log records and exception messages. The package
6
+ :class:`datasluice.logging.RedactingFilter` only inspects named dict
7
+ keys, so it cannot redact a secret buried inside a positional URL string.
8
+ This module provides :func:`sanitize_uri` for every call site that needs
9
+ a URI for human-facing display. The unsanitized URI is still used for
10
+ actual I/O.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
16
+
17
+ _SENSITIVE_QUERY_KEYS = frozenset(
18
+ {
19
+ "api_key",
20
+ "apikey",
21
+ "token",
22
+ "access_token",
23
+ "refresh_token",
24
+ "secret",
25
+ "signature",
26
+ "credential",
27
+ "password",
28
+ "x-api-key",
29
+ "x-api-token",
30
+ "x-auth-token",
31
+ }
32
+ )
33
+
34
+ _REDACTED = "***"
35
+
36
+
37
+ def sanitize_uri(uri: str) -> str:
38
+ """Return *uri* with userinfo and sensitive query values redacted for safe display.
39
+
40
+ Strips ``user:password@`` from the netloc (keeping host and port) and
41
+ replaces the value of every sensitive query parameter (``api_key``,
42
+ ``token``, ``signature``, …) with ``"***"``. The returned value is
43
+ suitable only for logs, exception text, and other human-facing display;
44
+ never reuse it for actual I/O, because the redaction discards
45
+ credentials the destination filesystem still needs.
46
+
47
+ Strings without a scheme are returned unchanged so this helper is safe to
48
+ call on path-like state keys, bare paths, or opaque identifiers.
49
+ """
50
+ try:
51
+ parts = urlsplit(uri)
52
+ except ValueError:
53
+ return uri
54
+ if not parts.scheme:
55
+ return uri
56
+ if parts.hostname is None:
57
+ netloc = parts.netloc.rsplit("@", 1)[-1]
58
+ else:
59
+ try:
60
+ port = parts.port
61
+ except ValueError:
62
+ port = None
63
+ hostname = parts.hostname
64
+ if ":" in hostname:
65
+ hostname = f"[{hostname}]"
66
+ netloc = hostname if port is None else f"{hostname}:{port}"
67
+ redacted: list[tuple[str, str]] = []
68
+ if parts.query:
69
+ try:
70
+ redacted = [
71
+ (name, _REDACTED if name.lower() in _SENSITIVE_QUERY_KEYS else value)
72
+ for name, value in parse_qsl(parts.query, keep_blank_values=True)
73
+ ]
74
+ except ValueError:
75
+ redacted = []
76
+ query = urlencode(redacted, doseq=True, safe="*")
77
+ return urlunsplit((parts.scheme, netloc, parts.path, query, parts.fragment))
datasluice/_version.py ADDED
@@ -0,0 +1,15 @@
1
+ """Version information for DataSluice, read from installed package metadata.
2
+
3
+ Kept in a separate module to break a circular import with
4
+ ``transport/user_agent.py``. The value is derived from ``pyproject.toml``
5
+ (the single source of truth, kept in sync with release tags by
6
+ **release-please**) through ``importlib.metadata`` — there is no second
7
+ copy of the version to keep in sync. Do NOT move it into ``__init__.py``.
8
+ """
9
+
10
+ from importlib.metadata import PackageNotFoundError, version
11
+
12
+ try:
13
+ __version__ = version("datasluice")
14
+ except PackageNotFoundError: # pragma: no cover
15
+ __version__ = "0.0.0"