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.
- datasluice/__init__.py +103 -0
- datasluice/_uri.py +77 -0
- datasluice/_version.py +15 -0
- datasluice/application.py +658 -0
- datasluice/auth/__init__.py +17 -0
- datasluice/auth/api_key.py +57 -0
- datasluice/auth/base.py +28 -0
- datasluice/auth/basic.py +32 -0
- datasluice/auth/bearer.py +30 -0
- datasluice/auth/headers.py +31 -0
- datasluice/auth/none.py +19 -0
- datasluice/cli/__init__.py +5 -0
- datasluice/cli/_output.py +26 -0
- datasluice/cli/_resolver.py +76 -0
- datasluice/cli/app.py +56 -0
- datasluice/cli/detect.py +82 -0
- datasluice/cli/download.py +52 -0
- datasluice/cli/inspect.py +81 -0
- datasluice/cli/materialize.py +53 -0
- datasluice/cli/open.py +69 -0
- datasluice/cli/scan.py +80 -0
- datasluice/cli/search.py +74 -0
- datasluice/config/__init__.py +21 -0
- datasluice/config/defaults.py +11 -0
- datasluice/connectors/__init__.py +6 -0
- datasluice/connectors/_reject.py +71 -0
- datasluice/connectors/base.py +69 -0
- datasluice/connectors/ckan/__init__.py +9 -0
- datasluice/connectors/ckan/adapter.py +116 -0
- datasluice/connectors/ckan/errors.py +30 -0
- datasluice/connectors/ckan/factory.py +21 -0
- datasluice/connectors/ckan/mapper.py +139 -0
- datasluice/connectors/ckan/pagination.py +24 -0
- datasluice/connectors/datagouv/__init__.py +9 -0
- datasluice/connectors/datagouv/adapter.py +107 -0
- datasluice/connectors/datagouv/errors.py +20 -0
- datasluice/connectors/datagouv/factory.py +21 -0
- datasluice/connectors/datagouv/mapper.py +127 -0
- datasluice/connectors/datagouv/pagination.py +24 -0
- datasluice/connectors/socrata/__init__.py +9 -0
- datasluice/connectors/socrata/adapter.py +134 -0
- datasluice/connectors/socrata/errors.py +20 -0
- datasluice/connectors/socrata/factory.py +21 -0
- datasluice/connectors/socrata/mapper.py +125 -0
- datasluice/connectors/socrata/pagination.py +24 -0
- datasluice/contracts/__init__.py +22 -0
- datasluice/contracts/checks.py +242 -0
- datasluice/contracts/fixtures.py +38 -0
- datasluice/credentials/__init__.py +5 -0
- datasluice/credentials/host_provider.py +139 -0
- datasluice/data/__init__.py +45 -0
- datasluice/data/_byte_source.py +108 -0
- datasluice/data/access.py +404 -0
- datasluice/data/batch_stream.py +208 -0
- datasluice/data/compression.py +336 -0
- datasluice/data/readers/__init__.py +61 -0
- datasluice/data/readers/base.py +51 -0
- datasluice/data/readers/csv.py +92 -0
- datasluice/data/readers/geojson.py +127 -0
- datasluice/data/readers/json.py +106 -0
- datasluice/data/readers/parquet.py +138 -0
- datasluice/data/readers/xlsx.py +97 -0
- datasluice/data/schema.py +139 -0
- datasluice/discovery/__init__.py +15 -0
- datasluice/discovery/detector.py +92 -0
- datasluice/discovery/fingerprints.py +29 -0
- datasluice/discovery/portal_metadata.py +24 -0
- datasluice/domain/__init__.py +38 -0
- datasluice/domain/access.py +97 -0
- datasluice/domain/artifact.py +278 -0
- datasluice/domain/capabilities.py +35 -0
- datasluice/domain/credentials.py +20 -0
- datasluice/domain/dataset.py +50 -0
- datasluice/domain/detection.py +48 -0
- datasluice/domain/license.py +20 -0
- datasluice/domain/organization.py +31 -0
- datasluice/domain/query.py +32 -0
- datasluice/domain/resource.py +67 -0
- datasluice/domain/result.py +35 -0
- datasluice/domain/schema.py +31 -0
- datasluice/domain/sync_state.py +23 -0
- datasluice/exceptions.py +186 -0
- datasluice/integrations/__init__.py +21 -0
- datasluice/integrations/arrow.py +35 -0
- datasluice/integrations/dlt.py +194 -0
- datasluice/integrations/duckdb.py +86 -0
- datasluice/integrations/pandas.py +42 -0
- datasluice/integrations/polars.py +41 -0
- datasluice/io/__init__.py +56 -0
- datasluice/io/cache.py +58 -0
- datasluice/io/checksums.py +53 -0
- datasluice/io/content_cache.py +291 -0
- datasluice/io/downloader.py +113 -0
- datasluice/io/filesystem.py +68 -0
- datasluice/io/fsspec_storage.py +94 -0
- datasluice/io/local.py +45 -0
- datasluice/io/storage.py +64 -0
- datasluice/logging.py +94 -0
- datasluice/ports/__init__.py +29 -0
- datasluice/ports/cache.py +16 -0
- datasluice/ports/catalog.py +33 -0
- datasluice/ports/credentials.py +15 -0
- datasluice/ports/detector.py +14 -0
- datasluice/ports/resource_reader.py +49 -0
- datasluice/ports/state_store.py +91 -0
- datasluice/ports/storage.py +16 -0
- datasluice/ports/transport.py +89 -0
- datasluice/py.typed +1 -0
- datasluice/runtime/__init__.py +10 -0
- datasluice/runtime/context.py +31 -0
- datasluice/runtime/defaults.py +82 -0
- datasluice/runtime/plugin_manager.py +92 -0
- datasluice/runtime/session.py +275 -0
- datasluice/sync/__init__.py +32 -0
- datasluice/sync/_hashing.py +38 -0
- datasluice/sync/_identity.py +135 -0
- datasluice/sync/materialize.py +521 -0
- datasluice/sync/state_store.py +711 -0
- datasluice/sync/sync.py +579 -0
- datasluice/transforms/__init__.py +63 -0
- datasluice/transforms/pipeline.py +108 -0
- datasluice/transforms/protocol.py +60 -0
- datasluice/transforms/steps.py +244 -0
- datasluice/transport/__init__.py +42 -0
- datasluice/transport/http_client.py +164 -0
- datasluice/transport/httpx_transport.py +537 -0
- datasluice/transport/pagination.py +49 -0
- datasluice/transport/rate_limit.py +34 -0
- datasluice/transport/redirect.py +82 -0
- datasluice/transport/retry.py +75 -0
- datasluice/transport/user_agent.py +25 -0
- datasluice-0.2.0.dist-info/METADATA +197 -0
- datasluice-0.2.0.dist-info/RECORD +136 -0
- datasluice-0.2.0.dist-info/WHEEL +4 -0
- datasluice-0.2.0.dist-info/entry_points.txt +7 -0
- 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"
|