chile-open-data-sdk 0.1.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.
@@ -0,0 +1,8 @@
1
+ """Unofficial Chile-first CKAN Action API SDK."""
2
+
3
+ from chile_open_data_sdk.client import ChileOpenDataClient, CKANClient
4
+ from chile_open_data_sdk.config import ClientConfig
5
+ from chile_open_data_sdk.types import JSONValue
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = ["CKANClient", "ChileOpenDataClient", "ClientConfig", "JSONValue", "__version__"]
@@ -0,0 +1 @@
1
+ """Private network-independent implementation helpers."""
@@ -0,0 +1,111 @@
1
+ """Parse CKAN envelopes and redact diagnostic payloads."""
2
+
3
+ import json
4
+ from typing import cast
5
+
6
+ import httpx
7
+ from pydantic import BaseModel, ConfigDict, JsonValue, StrictBool, ValidationError
8
+
9
+ from chile_open_data_sdk.errors import (
10
+ CKANAPIError,
11
+ CKANAuthenticationError,
12
+ CKANAuthorizationError,
13
+ CKANError,
14
+ CKANHTTPError,
15
+ CKANNotFoundError,
16
+ CKANProtocolError,
17
+ CKANRateLimitError,
18
+ CKANValidationError,
19
+ )
20
+ from chile_open_data_sdk.types import JSONValue
21
+
22
+
23
+ class ActionResponse(BaseModel):
24
+ """Internal envelope; extension metadata is tolerated."""
25
+
26
+ model_config = ConfigDict(extra="allow")
27
+ success: StrictBool
28
+ result: JsonValue = None
29
+ error: dict[str, JsonValue] | None = None
30
+
31
+
32
+ def redact(value: JSONValue, token: str | None) -> JSONValue:
33
+ """Remove configured tokens and values of credential-like diagnostic keys."""
34
+ if isinstance(value, str):
35
+ return value.replace(token, "[REDACTED]") if token else value
36
+ if isinstance(value, list):
37
+ return [redact(item, token) for item in value]
38
+ if isinstance(value, dict):
39
+ return {
40
+ str(redact(key, token)): (
41
+ "[REDACTED]"
42
+ if any(
43
+ part in key.lower()
44
+ for part in (
45
+ "token",
46
+ "authorization",
47
+ "password",
48
+ "secret",
49
+ "api_key",
50
+ "apikey",
51
+ )
52
+ )
53
+ else redact(item, token)
54
+ )
55
+ for key, item in value.items()
56
+ }
57
+ return value
58
+
59
+
60
+ _STATUS_ERRORS: dict[int, type[CKANError]] = {
61
+ 400: CKANValidationError,
62
+ 401: CKANAuthenticationError,
63
+ 403: CKANAuthorizationError,
64
+ 404: CKANNotFoundError,
65
+ 409: CKANValidationError,
66
+ 422: CKANValidationError,
67
+ 429: CKANRateLimitError,
68
+ }
69
+ _TYPE_ERRORS: dict[str, type[CKANError]] = {
70
+ "Authentication Error": CKANAuthenticationError,
71
+ "Authorization Error": CKANAuthorizationError,
72
+ "Not Found Error": CKANNotFoundError,
73
+ "Validation Error": CKANValidationError,
74
+ }
75
+
76
+
77
+ def parse_response(response: httpx.Response, action: str, token: str | None) -> JSONValue:
78
+ """Return only the result; convert HTTP, CKAN, and malformed response failures."""
79
+ envelope = None
80
+ try:
81
+ envelope = ActionResponse.model_validate_json(response.content)
82
+ except (ValidationError, ValueError):
83
+ pass
84
+ status = response.status_code
85
+ if not response.is_success or (envelope is not None and not envelope.success):
86
+ details = redact(cast(JSONValue, envelope.error), token) if envelope else None
87
+ error_type = details.get("__type") if isinstance(details, dict) else None
88
+ error_type = error_type if isinstance(error_type, str) else None
89
+ error_class = _STATUS_ERRORS.get(status)
90
+ if error_class is None:
91
+ error_class = _TYPE_ERRORS.get(error_type or "")
92
+ if error_class is None:
93
+ error_class = CKANHTTPError if not response.is_success else CKANAPIError
94
+ raise error_class(
95
+ "CKAN action failed",
96
+ action=action,
97
+ status_code=status,
98
+ error_type=error_type,
99
+ details=details,
100
+ )
101
+ if envelope is None or "result" not in envelope.model_fields_set:
102
+ raise CKANProtocolError("Invalid CKAN response envelope", action=action, status_code=status)
103
+ # The JSON parser must reject non-standard NaN/Infinity accepted by some decoders.
104
+ result: JSONValue = envelope.result
105
+ try:
106
+ json.dumps(result, allow_nan=False)
107
+ except ValueError:
108
+ raise CKANProtocolError(
109
+ "Non-finite JSON result", action=action, status_code=status
110
+ ) from None
111
+ return result
@@ -0,0 +1,142 @@
1
+ """Synchronous CKAN clients and generic JSON Action API access."""
2
+
3
+ import json
4
+ import re
5
+ from collections.abc import Mapping
6
+ from types import TracebackType
7
+ from typing import Self
8
+
9
+ import httpx
10
+
11
+ from chile_open_data_sdk._internal.responses import parse_response, redact
12
+ from chile_open_data_sdk.config import ClientConfig
13
+ from chile_open_data_sdk.errors import CKANConnectionError, CKANError, CKANTimeoutError
14
+ from chile_open_data_sdk.types import JSONValue
15
+
16
+
17
+ class ActionService:
18
+ """Generic CKAN JSON actions, with no automatic retries or redirects."""
19
+
20
+ def __init__(self, http: httpx.Client, config: ClientConfig) -> None:
21
+ self._http = http
22
+ self._config = config
23
+
24
+ def call(self, action: str, data: Mapping[str, JSONValue] | None = None) -> JSONValue:
25
+ """POST action parameters as JSON and return the unwrapped result.
26
+
27
+ Action names must be single alphanumeric/underscore identifiers. Calls
28
+ may mutate server data: choose actions and credentials deliberately.
29
+ Unsupported payload values raise ValueError before any network request.
30
+ """
31
+ if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", action):
32
+ raise ValueError("action must be a CKAN action identifier")
33
+ safe_action = str(redact(action, self._config.api_token))
34
+ if self._http.is_closed:
35
+ raise CKANError("Client is closed", action=safe_action)
36
+ try:
37
+ payload = json.dumps(dict(data) if data is not None else {}, allow_nan=False)
38
+ except (TypeError, ValueError):
39
+ raise ValueError("data must be a JSON-compatible mapping with finite numbers") from None
40
+ # Suppress raw HTTPX exception messages and chains, which can contain credentials.
41
+ failure: CKANError
42
+ try:
43
+ response = self._http.post(
44
+ self._config.action_url + "/" + action, content=payload.encode("utf-8")
45
+ )
46
+ except httpx.TimeoutException:
47
+ failure = CKANTimeoutError("CKAN request timed out", action=safe_action)
48
+ except httpx.RequestError:
49
+ failure = CKANConnectionError("CKAN request could not complete", action=safe_action)
50
+ else:
51
+ return parse_response(response, safe_action, self._config.api_token)
52
+ raise failure
53
+
54
+
55
+ class CKANClient:
56
+ """Reusable synchronous client for an explicitly configured CKAN site.
57
+
58
+ Supply either ``site_url`` (and optionally ``api_token``) or ``config``.
59
+ An injected HTTPX transport is owned and closed by this client.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ site_url: str | None = None,
65
+ *,
66
+ api_token: str | None = None,
67
+ config: ClientConfig | None = None,
68
+ transport: httpx.BaseTransport | None = None,
69
+ ) -> None:
70
+ if config is not None and (site_url is not None or api_token is not None):
71
+ raise ValueError("Pass config or site_url/api_token, not both")
72
+ if config is None:
73
+ if site_url is None:
74
+ raise ValueError("CKANClient requires site_url or config")
75
+ config = ClientConfig(site_url=site_url, api_token=api_token)
76
+ self.config = config
77
+ headers = {
78
+ "User-Agent": config.user_agent,
79
+ "Accept": "application/json",
80
+ "Content-Type": "application/json",
81
+ }
82
+ if config.api_token is not None:
83
+ headers["Authorization"] = config.api_token
84
+ self._http = httpx.Client(
85
+ headers=headers,
86
+ timeout=httpx.Timeout(
87
+ connect=config.connect_timeout,
88
+ read=config.read_timeout,
89
+ write=config.write_timeout,
90
+ pool=config.pool_timeout,
91
+ ),
92
+ limits=httpx.Limits(
93
+ max_connections=config.max_connections,
94
+ max_keepalive_connections=config.max_keepalive_connections,
95
+ ),
96
+ verify=config.verify,
97
+ follow_redirects=False,
98
+ trust_env=False,
99
+ transport=transport,
100
+ )
101
+ self.actions = ActionService(self._http, config)
102
+
103
+ @property
104
+ def is_closed(self) -> bool:
105
+ """Whether the owned HTTP client has been closed."""
106
+ return self._http.is_closed
107
+
108
+ def close(self) -> None:
109
+ """Release the connection pool; safe to call more than once."""
110
+ self._http.close()
111
+
112
+ def __enter__(self) -> Self:
113
+ if self.is_closed:
114
+ raise CKANError("Client is closed", action="")
115
+ return self
116
+
117
+ def __exit__(
118
+ self,
119
+ exc_type: type[BaseException] | None,
120
+ exc: BaseException | None,
121
+ traceback: TracebackType | None,
122
+ ) -> None:
123
+ self.close()
124
+
125
+
126
+ class ChileOpenDataClient(CKANClient):
127
+ """CKAN client defaulting to https://datos.gob.cl/api/3/action."""
128
+
129
+ def __init__(
130
+ self,
131
+ site_url: str | None = None,
132
+ *,
133
+ api_token: str | None = None,
134
+ config: ClientConfig | None = None,
135
+ transport: httpx.BaseTransport | None = None,
136
+ ) -> None:
137
+ super().__init__(
138
+ site_url="https://datos.gob.cl" if config is None and site_url is None else site_url,
139
+ api_token=api_token,
140
+ config=config,
141
+ transport=transport,
142
+ )
@@ -0,0 +1,93 @@
1
+ """Validated, immutable connection settings; constructors never read credentials implicitly."""
2
+
3
+ import re
4
+ from dataclasses import dataclass, field
5
+ from math import isfinite
6
+ from typing import TypeGuard
7
+ from urllib.parse import urlsplit
8
+
9
+
10
+ def _is_connection_limit(value: object) -> TypeGuard[int]:
11
+ """Narrow untrusted runtime input to an integer, excluding booleans."""
12
+ return isinstance(value, int) and not isinstance(value, bool)
13
+
14
+
15
+ def _validate_tls_verification(value: object) -> None:
16
+ """Reject untyped inputs that could accidentally disable TLS verification."""
17
+ if not isinstance(value, bool):
18
+ raise ValueError("verify must be a boolean")
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class ClientConfig:
23
+ """Configure a CKAN endpoint, timeouts in seconds, and connection pool limits.
24
+
25
+ ``site_url`` can include a deployment prefix. TLS verification is enabled and
26
+ redirects are deliberately disabled. ``api_token`` is excluded from repr.
27
+ """
28
+
29
+ site_url: str = "https://datos.gob.cl"
30
+ action_path: str = "/api/3/action"
31
+ api_token: str | None = field(default=None, repr=False)
32
+ user_agent: str = "chile-open-data-sdk/0.1.0"
33
+ connect_timeout: float = 5.0
34
+ read_timeout: float = 30.0
35
+ write_timeout: float = 30.0
36
+ pool_timeout: float = 5.0
37
+ max_connections: int = 100
38
+ max_keepalive_connections: int = 20
39
+ verify: bool = True
40
+
41
+ def __post_init__(self) -> None:
42
+ """Reject ambiguous endpoints and invalid settings without echoing credentials."""
43
+ try:
44
+ url = urlsplit(self.site_url)
45
+ valid = (
46
+ url.scheme in {"http", "https"}
47
+ and bool(url.hostname)
48
+ and url.username is None
49
+ and url.password is None
50
+ and not url.query
51
+ and not url.fragment
52
+ and url.port != 0
53
+ and not any(c.isspace() or ord(c) < 32 for c in self.site_url)
54
+ and "\\" not in self.site_url
55
+ and all(p not in {".", ".."} for p in url.path.split("/"))
56
+ and "%" not in url.path
57
+ )
58
+ except ValueError:
59
+ valid = False
60
+ if not valid:
61
+ raise ValueError(
62
+ "site_url must be an HTTP(S) URL without credentials, query or fragment"
63
+ )
64
+ if not re.fullmatch(r"/(?:[A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+/?", self.action_path):
65
+ raise ValueError("action_path must contain only absolute path segments")
66
+ object.__setattr__(self, "site_url", self.site_url.rstrip("/"))
67
+ object.__setattr__(self, "action_path", self.action_path.rstrip("/"))
68
+ for value in (
69
+ self.connect_timeout,
70
+ self.read_timeout,
71
+ self.write_timeout,
72
+ self.pool_timeout,
73
+ ):
74
+ if isinstance(value, bool) or not isfinite(value) or value <= 0:
75
+ raise ValueError("Timeouts must be finite positive seconds")
76
+ if (
77
+ not _is_connection_limit(self.max_connections)
78
+ or self.max_connections < 1
79
+ or not _is_connection_limit(self.max_keepalive_connections)
80
+ or not 0 <= self.max_keepalive_connections <= self.max_connections
81
+ ):
82
+ raise ValueError("Connection limits must be integers with 0 <= keepalive <= maximum")
83
+ for header_value in (self.user_agent, self.api_token):
84
+ if header_value is not None and (
85
+ not header_value or any(ord(c) < 32 or ord(c) > 126 for c in header_value)
86
+ ):
87
+ raise ValueError("Header values must be nonempty printable ASCII")
88
+ _validate_tls_verification(self.verify)
89
+
90
+ @property
91
+ def action_url(self) -> str:
92
+ """Return the normalized Action API base URL."""
93
+ return self.site_url + self.action_path
@@ -0,0 +1,66 @@
1
+ """SDK exceptions with safe action context; raw requests and responses are not retained."""
2
+
3
+ from chile_open_data_sdk.types import JSONValue
4
+
5
+
6
+ class ChileOpenDataError(Exception):
7
+ """Base exception for SDK runtime failures."""
8
+
9
+
10
+ class CKANError(ChileOpenDataError):
11
+ """CKAN failure with sanitized diagnostic context."""
12
+
13
+ def __init__(
14
+ self,
15
+ message: str,
16
+ *,
17
+ action: str,
18
+ status_code: int | None = None,
19
+ error_type: str | None = None,
20
+ details: JSONValue = None,
21
+ ) -> None:
22
+ super().__init__(message)
23
+ self.action = action
24
+ self.status_code = status_code
25
+ self.error_type = error_type
26
+ self.details = details
27
+
28
+
29
+ class CKANAPIError(CKANError):
30
+ """CKAN returned success=false."""
31
+
32
+
33
+ class CKANProtocolError(CKANError):
34
+ """The response was not a valid CKAN JSON envelope."""
35
+
36
+
37
+ class CKANHTTPError(CKANError):
38
+ """An HTTP response was unsuccessful, including redirects."""
39
+
40
+
41
+ class CKANAuthenticationError(CKANAPIError, CKANHTTPError):
42
+ """Authentication is required or failed."""
43
+
44
+
45
+ class CKANAuthorizationError(CKANAPIError, CKANHTTPError):
46
+ """The server denied permission."""
47
+
48
+
49
+ class CKANNotFoundError(CKANAPIError, CKANHTTPError):
50
+ """The requested action or entity was not found."""
51
+
52
+
53
+ class CKANValidationError(CKANAPIError, CKANHTTPError):
54
+ """The server rejected the action parameters."""
55
+
56
+
57
+ class CKANRateLimitError(CKANAPIError, CKANHTTPError):
58
+ """The server rate limited the request; it was not retried."""
59
+
60
+
61
+ class CKANTimeoutError(CKANError):
62
+ """A connect, read, write, or pool timeout occurred."""
63
+
64
+
65
+ class CKANConnectionError(CKANError):
66
+ """HTTPX could not complete the request."""
File without changes
@@ -0,0 +1,5 @@
1
+ """Shared JSON types for schema-independent CKAN action data."""
2
+
3
+ from typing import TypeAlias
4
+
5
+ JSONValue: TypeAlias = bool | int | float | str | list["JSONValue"] | dict[str, "JSONValue"] | None
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: chile-open-data-sdk
3
+ Version: 0.1.0
4
+ Summary: An unofficial, typed Python SDK for Chile's datos.gob.cl and CKAN Action API
5
+ Keywords: chile,open-data,ckan,sdk
6
+ Author: Eli-ezer Reuven Ramirez Ruiz
7
+ Author-email: Eli-ezer Reuven Ramirez Ruiz <ramirez.ruiz.eliezer.reuven@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE.md
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: httpx>=0.28.1
18
+ Requires-Dist: pydantic>=2.12.0,<3
19
+ Requires-Python: >=3.11
20
+ Project-URL: Repository, https://github.com/ezer-mackenzie/chile-open-data-sdk
21
+ Project-URL: Issues, https://github.com/ezer-mackenzie/chile-open-data-sdk/issues
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Chile Open Data SDK
25
+
26
+ An unofficial, typed Python SDK for Chile's **datos.gob.cl**, with a reusable
27
+ CKAN Action API core. This community project is **not an official SDK of the
28
+ Government of Chile or the maintainers of datos.gob.cl**.
29
+
30
+ ## v0.1.0 scope
31
+
32
+ This first release provides a synchronous client, validated configuration,
33
+ connection pooling, JSON action calls, token authentication, and structured errors.
34
+ It is an alpha foundation with a deliberately small public API.
35
+
36
+ Typed dataset services, DataStore wrappers, pagination, async clients, downloads,
37
+ and pandas/Polars integrations are planned for later milestones; they are not
38
+ available in v0.1.0. Generic actions can already query catalog and DataStore
39
+ endpoints when the server supports them.
40
+
41
+ ## Installation
42
+
43
+ Python 3.11 or later is required. Before publication, install from a local checkout:
44
+
45
+ ```console
46
+ uv sync --locked
47
+ ```
48
+
49
+ Or build and install the wheel with a standard Python installer:
50
+
51
+ ```console
52
+ uv build
53
+ python -m pip install dist/chile_open_data_sdk-0.1.0-py3-none-any.whl
54
+ ```
55
+
56
+ After v0.1.0 is published, installation will be:
57
+
58
+ ```console
59
+ python -m pip install chile-open-data-sdk==0.1.0
60
+ ```
61
+
62
+ The distribution is `chile-open-data-sdk`; the import is `chile_open_data_sdk`.
63
+
64
+ ## Quick start
65
+
66
+ ```python
67
+ from chile_open_data_sdk import ChileOpenDataClient
68
+
69
+ with ChileOpenDataClient() as client:
70
+ result = client.actions.call("package_search", {"q": "transport", "rows": 5})
71
+ datasets = result.get("results") if isinstance(result, dict) else None
72
+ if isinstance(datasets, list):
73
+ for dataset in datasets:
74
+ if isinstance(dataset, dict):
75
+ print(dataset.get("title"))
76
+ ```
77
+
78
+ The default endpoint is `https://datos.gob.cl/api/3/action`. Calls return the
79
+ unwrapped JSON `result`, preserving additional server fields. Availability and
80
+ metadata quality depend on the upstream portal.
81
+
82
+ ## Another CKAN site
83
+
84
+ ```python
85
+ from chile_open_data_sdk import CKANClient
86
+
87
+ with CKANClient(site_url="https://demo.ckan.org") as client:
88
+ print(client.actions.call("package_list", {"limit": 5}))
89
+ ```
90
+
91
+ ## Authentication and errors
92
+
93
+ Credentials are explicit and are sent only in the `Authorization` header. The
94
+ constructor does not read environment credentials. Your application can do so:
95
+
96
+ ```python
97
+ import os
98
+
99
+ from chile_open_data_sdk import ChileOpenDataClient
100
+ from chile_open_data_sdk.errors import CKANError
101
+
102
+ try:
103
+ with ChileOpenDataClient(api_token=os.environ.get("CHILE_OPEN_DATA_API_TOKEN")) as client:
104
+ result = client.actions.call("package_list", {"limit": 5})
105
+ except CKANError as error:
106
+ print(error.action, error.status_code, error.error_type)
107
+ ```
108
+
109
+ Generic calls use JSON POST and **never retry automatically**, including read
110
+ operations. They can execute writes: the caller chooses the action and the server
111
+ enforces permissions. Redirects are not followed. Configuration and SDK error
112
+ representations exclude the configured token; do not log raw payloads or credentials.
113
+
114
+ ## Documentation
115
+
116
+ - [Getting started](docs/getting-started.md)
117
+ - [Generic actions and querying examples](docs/actions.md)
118
+ - [Configuration and authentication](docs/configuration.md)
119
+ - [Error handling](docs/errors.md)
120
+ - [Public API reference](docs/api.md)
121
+ - [Development tools and benchmarks](docs/development.md)
122
+ - [Architecture](docs/architecture.md)
123
+ - [Compatibility and limitations](docs/compatibility.md)
124
+ - [Roadmap](docs/roadmap.md)
125
+ - [Release process](docs/releasing.md)
126
+ - [v0.1.0 release notes](docs/release-notes.md)
127
+ - [Changelog](CHANGELOG.md)
128
+
129
+ Build the documentation locally with `uv run --group docs mkdocs build --strict`,
130
+ or preview with `uv run --group docs mkdocs serve`. No hosted documentation URL
131
+ is claimed until deployment is configured.
132
+
133
+ ## Development
134
+
135
+ ```console
136
+ uv sync --locked --group docs
137
+ uv run ruff check .
138
+ uv run ruff format --check .
139
+ uv run mypy src
140
+ uv run basedpyright
141
+ uv run pytest
142
+ uv run --group docs mkdocs build --strict
143
+ uv build
144
+ uv run python scripts/check_artifacts.py
145
+ ```
146
+
147
+ Normal tests use mocked HTTP; live portal tests are opt-in. See
148
+ [Contributing](CONTRIBUTING.md) and [Security](SECURITY.md).
149
+
150
+ Existing CKAN clients such as [ckanapi](https://github.com/ckan/ckanapi) serve the
151
+ community already. This project's direction is Chile-first defaults, modern
152
+ Python typing, and progressively richer sync/async workflows.
153
+
154
+ ## License
155
+
156
+ [MIT](LICENSE.md). Portal datasets may have their own licenses; the SDK's license
157
+ does not determine permission to use upstream data.
@@ -0,0 +1,12 @@
1
+ chile_open_data_sdk/__init__.py,sha256=cZM8N04lEAF5LKohv6Y4gncfJHxvkxaQPN-GSv_xYCo,337
2
+ chile_open_data_sdk/_internal/__init__.py,sha256=-aV_bdzUpMEuUpWP6np8AckdhNg8nVPLRcpaYpCryMo,58
3
+ chile_open_data_sdk/_internal/responses.py,sha256=hqlm6ZmJ7VNJfRK6eUZ5taS35TEXEik_2uF028aWjYY,3826
4
+ chile_open_data_sdk/client.py,sha256=V192S_Eddr2aaA7WHVTr2Sbo_37X3DsbKQ_P6zUbitM,5284
5
+ chile_open_data_sdk/config.py,sha256=2RCatEv9bIZt2PR72SpYo4PJm-DqDDvGsAYTVd7Ad7I,3845
6
+ chile_open_data_sdk/errors.py,sha256=MESVxHjzupP6x6jyj_4YoYBk5AgzUO-CCyZhIVnRaL0,1726
7
+ chile_open_data_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ chile_open_data_sdk/types.py,sha256=VIkGbsOPuQVQWvUHuDVExFMUNfxgnZmmmTsG9YX2_0g,196
9
+ chile_open_data_sdk-0.1.0.dist-info/licenses/LICENSE.md,sha256=_52YphtXeFMUcoxi_K3XL3uasy7QqRl_0nbFfBfTHrM,1084
10
+ chile_open_data_sdk-0.1.0.dist-info/WHEEL,sha256=-i9oRNYVXXZJUIYl5zclLIg6onEb0NLibTX34uln84w,81
11
+ chile_open_data_sdk-0.1.0.dist-info/METADATA,sha256=05V1qBuynEMfh9-ZGCs22-wC8AukImFx0sAXQNvDWHs,5462
12
+ chile_open_data_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.13
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eli-ezer Reuven Ramirez Ruiz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.