rhinestone 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.
Files changed (63) hide show
  1. rhinestone/__init__.py +60 -0
  2. rhinestone/_http.py +152 -0
  3. rhinestone/adapters/__init__.py +33 -0
  4. rhinestone/adapters/execution/__init__.py +17 -0
  5. rhinestone/adapters/execution/_resource.py +14 -0
  6. rhinestone/adapters/execution/base.py +47 -0
  7. rhinestone/adapters/execution/gdal/README.md +10 -0
  8. rhinestone/adapters/execution/gdal/__init__.py +114 -0
  9. rhinestone/adapters/execution/json_service/README.md +10 -0
  10. rhinestone/adapters/execution/json_service/__init__.py +103 -0
  11. rhinestone/adapters/execution/pyogrio/README.md +10 -0
  12. rhinestone/adapters/execution/pyogrio/__init__.py +44 -0
  13. rhinestone/adapters/execution/rasterio/README.md +9 -0
  14. rhinestone/adapters/execution/rasterio/__init__.py +38 -0
  15. rhinestone/adapters/source/__init__.py +29 -0
  16. rhinestone/adapters/source/_knowledge.py +59 -0
  17. rhinestone/adapters/source/_uri.py +20 -0
  18. rhinestone/adapters/source/base.py +234 -0
  19. rhinestone/adapters/source/ckan/README.md +12 -0
  20. rhinestone/adapters/source/ckan/__init__.py +176 -0
  21. rhinestone/adapters/source/ckan/schema.json +11 -0
  22. rhinestone/adapters/source/dcat/README.md +12 -0
  23. rhinestone/adapters/source/dcat/__init__.py +178 -0
  24. rhinestone/adapters/source/dcat/schema.json +13 -0
  25. rhinestone/adapters/source/direct/README.md +10 -0
  26. rhinestone/adapters/source/direct/__init__.py +56 -0
  27. rhinestone/adapters/source/direct/schema.json +17 -0
  28. rhinestone/adapters/source/gsi_fundamental/README.md +11 -0
  29. rhinestone/adapters/source/gsi_fundamental/__init__.py +43 -0
  30. rhinestone/adapters/source/gsi_fundamental/schema.json +32 -0
  31. rhinestone/adapters/source/odpt/README.md +11 -0
  32. rhinestone/adapters/source/odpt/__init__.py +140 -0
  33. rhinestone/adapters/source/odpt/schema.json +15 -0
  34. rhinestone/adapters/source/ogc/README.md +12 -0
  35. rhinestone/adapters/source/ogc/__init__.py +171 -0
  36. rhinestone/adapters/source/ogc/schema.json +12 -0
  37. rhinestone/adapters/source/plateau/README.md +12 -0
  38. rhinestone/adapters/source/plateau/__init__.py +96 -0
  39. rhinestone/adapters/source/plateau/schema.json +24 -0
  40. rhinestone/adapters/source/search_ckan_jp/__init__.py +154 -0
  41. rhinestone/adapters/source/search_ckan_jp/schema.json +9 -0
  42. rhinestone/adapters/source/stac/README.md +11 -0
  43. rhinestone/adapters/source/stac/__init__.py +188 -0
  44. rhinestone/adapters/source/stac/schema.json +13 -0
  45. rhinestone/adapters/source/static/__init__.py +217 -0
  46. rhinestone/adapters/source/static/schema.json +10 -0
  47. rhinestone/api.py +396 -0
  48. rhinestone/catalogs/__init__.py +147 -0
  49. rhinestone/catalogs/sources.json +188 -0
  50. rhinestone/errors.py +69 -0
  51. rhinestone/execution.py +39 -0
  52. rhinestone/models.py +342 -0
  53. rhinestone/pipeline.py +96 -0
  54. rhinestone/py.typed +0 -0
  55. rhinestone/registry.py +99 -0
  56. rhinestone/resolution.py +102 -0
  57. rhinestone/search.py +172 -0
  58. rhinestone/security.py +231 -0
  59. rhinestone/sources.py +31 -0
  60. rhinestone-0.1.0.dist-info/METADATA +140 -0
  61. rhinestone-0.1.0.dist-info/RECORD +63 -0
  62. rhinestone-0.1.0.dist-info/WHEEL +4 -0
  63. rhinestone-0.1.0.dist-info/licenses/LICENSE +21 -0
rhinestone/__init__.py ADDED
@@ -0,0 +1,60 @@
1
+ """Rhinestone public package."""
2
+
3
+ from . import sources
4
+ from .api import Rhinestone, configure
5
+ from .catalogs import Catalog
6
+ from .models import (
7
+ AccessPlan,
8
+ Config,
9
+ Dependencies,
10
+ FileAccessPlan,
11
+ LibraryName,
12
+ Metadata,
13
+ Provenance,
14
+ Provider,
15
+ RemoteDatasetPlan,
16
+ Resource,
17
+ ResourceCandidate,
18
+ Result,
19
+ Runtime,
20
+ RuntimeFactory,
21
+ SearchDiagnostic,
22
+ SearchQuery,
23
+ SearchResult,
24
+ ServiceQueryPlan,
25
+ Source,
26
+ SourceDefinition,
27
+ )
28
+ from .search import SearchResults
29
+ from .security import DestinationPolicy, DestinationRule, NetworkPolicyLevel
30
+
31
+ __all__ = [
32
+ "AccessPlan",
33
+ "Catalog",
34
+ "Config",
35
+ "DestinationPolicy",
36
+ "DestinationRule",
37
+ "Dependencies",
38
+ "FileAccessPlan",
39
+ "LibraryName",
40
+ "Metadata",
41
+ "NetworkPolicyLevel",
42
+ "Provenance",
43
+ "Provider",
44
+ "RemoteDatasetPlan",
45
+ "Rhinestone",
46
+ "Resource",
47
+ "ResourceCandidate",
48
+ "Result",
49
+ "Runtime",
50
+ "RuntimeFactory",
51
+ "SearchDiagnostic",
52
+ "SearchQuery",
53
+ "SearchResult",
54
+ "SearchResults",
55
+ "ServiceQueryPlan",
56
+ "Source",
57
+ "SourceDefinition",
58
+ "configure",
59
+ "sources",
60
+ ]
rhinestone/_http.py ADDED
@@ -0,0 +1,152 @@
1
+ """Built-in standard-library HTTP transport."""
2
+
3
+ import json
4
+ from typing import Any, Mapping, Optional, cast
5
+ from urllib.error import HTTPError
6
+ from urllib.parse import urlencode, urlsplit, urlunsplit
7
+ from urllib.request import HTTPRedirectHandler, Request, build_opener, urlopen
8
+
9
+ from .errors import ProviderResponseError
10
+
11
+ _TIMEOUT_SECONDS = 30
12
+ _DEFAULT_HEADERS = {
13
+ "Accept": "application/json",
14
+ "User-Agent": "rhinestone",
15
+ }
16
+
17
+
18
+ class _JsonIntegerDecodeError(ValueError):
19
+ """JSON integer conversion failed inside the decoder."""
20
+
21
+
22
+ def _parse_json_int(value: str) -> int:
23
+ try:
24
+ return int(value)
25
+ except ValueError as error:
26
+ raise _JsonIntegerDecodeError from error
27
+
28
+
29
+ class JsonDocument(dict[str, Any]):
30
+ """Decoded JSON object together with the URI that supplied it."""
31
+
32
+ def __init__(self, value: Mapping[str, Any], response_uri: str) -> None:
33
+ super().__init__(value)
34
+ self.response_uri = response_uri
35
+
36
+
37
+ def get_json(
38
+ url: str,
39
+ params: Mapping[str, Any],
40
+ headers: Optional[Mapping[str, str]] = None,
41
+ ) -> Any:
42
+ """Fetch and decode JSON over HTTP using the standard library."""
43
+ request = _request(url, params, headers)
44
+ opener = (
45
+ build_opener(_NoRedirectHandler())
46
+ if getattr(headers, "_rhinestone_no_redirects", False)
47
+ else None
48
+ )
49
+ open_request = opener.open if opener is not None else urlopen
50
+ with open_request(request, timeout=_TIMEOUT_SECONDS) as response:
51
+ try:
52
+ decoded = json.load(response, parse_int=_parse_json_int)
53
+ except (
54
+ UnicodeDecodeError,
55
+ json.JSONDecodeError,
56
+ _JsonIntegerDecodeError,
57
+ ):
58
+ raise ProviderResponseError("Provider response is not valid JSON") from None
59
+ if isinstance(decoded, Mapping):
60
+ response_uri = getattr(response, "geturl", lambda: request.full_url)()
61
+ return JsonDocument(
62
+ cast(Mapping[str, Any], decoded), response_uri or request.full_url
63
+ )
64
+ return decoded
65
+
66
+
67
+ def get_text(url: str) -> str:
68
+ """Fetch a text document without following HTTP redirects."""
69
+ request = Request(url, headers={"User-Agent": "rhinestone"})
70
+ opener = build_opener(_NoRedirectHandler())
71
+ with opener.open(request, timeout=_TIMEOUT_SECONDS) as response:
72
+ charset = response.headers.get_content_charset() or "utf-8"
73
+ return response.read().decode(charset)
74
+
75
+
76
+ class JsonServiceRuntime:
77
+ """Small requests-compatible runtime used by ``JsonServiceAdapter``."""
78
+
79
+ def get(
80
+ self,
81
+ url: str,
82
+ *,
83
+ params: Mapping[str, Any],
84
+ headers: Mapping[str, str],
85
+ timeout: int,
86
+ allow_redirects: bool,
87
+ ) -> "JsonResponse":
88
+ request = _request(url, params, headers)
89
+ opener = build_opener(_NoRedirectHandler())
90
+ try:
91
+ response = opener.open(request, timeout=timeout)
92
+ except HTTPError as error:
93
+ response = error
94
+ with response:
95
+ charset = response.headers.get_content_charset() or "utf-8"
96
+ status_code = cast(int, response.getcode())
97
+ return JsonResponse(status_code, response.read(), charset)
98
+
99
+
100
+ class JsonResponse:
101
+ """Subset of the requests response contract used by ``JsonServiceAdapter``."""
102
+
103
+ def __init__(self, status_code: int, body: bytes, charset: str) -> None:
104
+ self.status_code = status_code
105
+ self._body = body
106
+ self._charset = charset
107
+
108
+ def raise_for_status(self) -> None:
109
+ if self.status_code >= 400:
110
+ raise OSError(f"HTTP request failed with status {self.status_code}")
111
+
112
+ def json(self) -> Any:
113
+ return json.loads(self._body.decode(self._charset))
114
+
115
+
116
+ class _NoRedirectHandler(HTTPRedirectHandler):
117
+ def redirect_request(
118
+ self,
119
+ req: Request,
120
+ fp: Any,
121
+ code: int,
122
+ msg: str,
123
+ headers: Any,
124
+ newurl: str,
125
+ ) -> Optional[Request]:
126
+ return None
127
+
128
+
129
+ def _request(
130
+ url: str,
131
+ params: Mapping[str, Any],
132
+ headers: Optional[Mapping[str, str]] = None,
133
+ ) -> Request:
134
+ request_url = _append_query(url, params)
135
+ return Request(
136
+ request_url,
137
+ headers={**_DEFAULT_HEADERS, **dict(headers or {})},
138
+ )
139
+
140
+
141
+ def _append_query(url: str, params: Mapping[str, Any]) -> str:
142
+ """Append parameters without replacing or normalizing an existing query."""
143
+ additional_query = urlencode(params, doseq=True)
144
+ if not additional_query:
145
+ return url
146
+ components = urlsplit(url)
147
+ query = (
148
+ f"{components.query}&{additional_query}"
149
+ if components.query
150
+ else additional_query
151
+ )
152
+ return urlunsplit(components._replace(query=query))
@@ -0,0 +1,33 @@
1
+ """Compatibility exports for built-in source adapters."""
2
+
3
+ from .execution import ExecutionAdapter
4
+ from .source import (
5
+ CkanAdapter,
6
+ DcatAdapter,
7
+ DirectAdapter,
8
+ GsiFundamentalAdapter,
9
+ JsonGetter,
10
+ JsonObject,
11
+ OdptAdapter,
12
+ OgcFeaturesAdapter,
13
+ PlateauAdapter,
14
+ ProviderAdapter,
15
+ StacAdapter,
16
+ StaticAdapter,
17
+ )
18
+
19
+ __all__ = [
20
+ "CkanAdapter",
21
+ "DirectAdapter",
22
+ "DcatAdapter",
23
+ "GsiFundamentalAdapter",
24
+ "JsonGetter",
25
+ "JsonObject",
26
+ "OdptAdapter",
27
+ "OgcFeaturesAdapter",
28
+ "PlateauAdapter",
29
+ "ProviderAdapter",
30
+ "StacAdapter",
31
+ "StaticAdapter",
32
+ "ExecutionAdapter",
33
+ ]
@@ -0,0 +1,17 @@
1
+ """Built-in execution adapters."""
2
+
3
+ from ._resource import resource_attributes
4
+ from .base import ExecutionAdapter
5
+ from .gdal import GdalAdapter
6
+ from .json_service import JsonServiceAdapter
7
+ from .pyogrio import PyogrioAdapter
8
+ from .rasterio import RasterioAdapter
9
+
10
+ __all__ = [
11
+ "ExecutionAdapter",
12
+ "GdalAdapter",
13
+ "JsonServiceAdapter",
14
+ "PyogrioAdapter",
15
+ "RasterioAdapter",
16
+ "resource_attributes",
17
+ ]
@@ -0,0 +1,14 @@
1
+ """Shared helpers for selected execution adapters."""
2
+
3
+ from typing import Any, Mapping
4
+
5
+ from ...models import Resource
6
+
7
+
8
+ def resource_attributes(resource: Resource) -> Mapping[str, Any]:
9
+ for candidate in resource.source.candidates:
10
+ if candidate.uri == resource.uri and candidate.attributes.get(
11
+ "matches_config", True
12
+ ):
13
+ return candidate.attributes
14
+ return {}
@@ -0,0 +1,47 @@
1
+ """Public base class for execution adapters."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, FrozenSet
5
+
6
+ from ...models import Resource
7
+ from ...security import DestinationPolicy
8
+
9
+ __all__ = ["ExecutionAdapter"]
10
+
11
+
12
+ class ExecutionAdapter(ABC):
13
+ """Base for adapters that translate a selected ``Resource`` to an OSS call."""
14
+
15
+ name: str
16
+ """Stable dependency and selection name."""
17
+
18
+ priority: int
19
+ """Deterministic selection priority."""
20
+
21
+ def __init__(self, destination_policy: DestinationPolicy | None = None) -> None:
22
+ self._destination_policy = (
23
+ destination_policy or DestinationPolicy.unrestricted()
24
+ )
25
+
26
+ @abstractmethod
27
+ def supports(self, resource: Resource, dependencies: FrozenSet[str]) -> bool:
28
+ """Whether this adapter can open the already selected resource."""
29
+
30
+ @abstractmethod
31
+ def open(
32
+ self,
33
+ resource: Resource,
34
+ runtime: Any,
35
+ *,
36
+ destination_policy: DestinationPolicy | None = None,
37
+ ) -> Any:
38
+ """Delegate the already selected resource to the supplied runtime."""
39
+
40
+ def authorize(
41
+ self,
42
+ resource: Resource,
43
+ *,
44
+ destination_policy: DestinationPolicy | None = None,
45
+ ) -> None:
46
+ """Authorize a resource before resolving its user-owned runtime."""
47
+ (destination_policy or self._destination_policy).authorize(resource.uri)
@@ -0,0 +1,10 @@
1
+ # GDAL Execution Adapter
2
+
3
+ `GdalAdapter` は選択済み Resource を利用者提供の GDAL runtime へ渡します。
4
+
5
+ - `name`: `gdal`、優先度: `20`
6
+ - 対応形式: Shapefile, GeoTIFF, COG, NetCDF, WMS, GML, CityGML、または XYZ tile
7
+ - 翻訳: ZIP の `/vsizip/` URI、remote ZIP の `/vsicurl/`、encoding の open option、XYZ の GDAL WMS XML
8
+ - runtime: `OpenEx(uri, open_options=...)`
9
+
10
+ Resource の再選択、データ解析、形式・CRS 変換は行いません。
@@ -0,0 +1,114 @@
1
+ """Built-in adapters that delegate selected Resources to user-owned runtimes."""
2
+
3
+ from typing import Any, FrozenSet, List, Mapping, cast
4
+ from xml.etree.ElementTree import Element, SubElement, tostring
5
+
6
+ from ....errors import ResourceAccessError
7
+ from ....models import FileAccessPlan, Resource
8
+ from ....security import DestinationPolicy
9
+ from .._resource import resource_attributes
10
+ from ..base import ExecutionAdapter
11
+
12
+
13
+ class GdalAdapter(ExecutionAdapter):
14
+ """Translate Resources into calls understood by a supplied GDAL module."""
15
+
16
+ name = "gdal"
17
+ priority = 20
18
+ _formats = frozenset(
19
+ {"shapefile", "geotiff", "cog", "netcdf", "wms", "gml", "citygml"}
20
+ )
21
+
22
+ def __init__(self, destination_policy: DestinationPolicy | None = None) -> None:
23
+ super().__init__(destination_policy)
24
+
25
+ def supports(self, resource: Resource, dependencies: FrozenSet[str]) -> bool:
26
+ tile = resource.access_plan.options.get("tile")
27
+ return self.name in dependencies and (
28
+ (resource.format or "").lower() in self._formats
29
+ or (
30
+ isinstance(tile, Mapping)
31
+ and cast(Mapping[str, Any], tile).get("scheme") == "xyz"
32
+ )
33
+ )
34
+
35
+ def open(
36
+ self,
37
+ resource: Resource,
38
+ runtime: Any,
39
+ *,
40
+ destination_policy: DestinationPolicy | None = None,
41
+ ) -> Any:
42
+ uri = self._runtime_uri(resource)
43
+ attributes = resource_attributes(resource)
44
+ options: List[str] = []
45
+ encoding = attributes.get("encoding")
46
+ if isinstance(encoding, str):
47
+ options.append("ENCODING=" + encoding.upper())
48
+ try:
49
+ runtime_options: dict[str, Any] = {"open_options": tuple(options)}
50
+ result = runtime.OpenEx(uri, **runtime_options)
51
+ if result is None:
52
+ raise ResourceAccessError("GDAL returned no dataset")
53
+ return result
54
+ except Exception as error:
55
+ raise ResourceAccessError(
56
+ f"GDAL could not open {resource.uri!r}"
57
+ ) from error
58
+
59
+ @staticmethod
60
+ def _runtime_uri(resource: Resource) -> str:
61
+ attributes = resource_attributes(resource)
62
+ uri = resource.uri
63
+ tile = resource.access_plan.options.get("tile")
64
+ if tile is not None:
65
+ if not isinstance(tile, Mapping):
66
+ raise ResourceAccessError("GDAL tile options must be an object")
67
+ tile = cast(Mapping[str, Any], tile)
68
+ tile_url = tile.get("url")
69
+ if not isinstance(tile_url, str):
70
+ raise ResourceAccessError("GDAL tile URL must be a string")
71
+ uri = GdalAdapter.tile_xml(tile)
72
+ archive = attributes.get("archive")
73
+ if isinstance(resource.access_plan, FileAccessPlan):
74
+ archive = resource.access_plan.archive or archive
75
+ if archive == "zip":
76
+ uri = (
77
+ "/vsizip//vsicurl/" + uri
78
+ if uri.startswith(("http://", "https://"))
79
+ else "/vsizip/" + uri
80
+ )
81
+ member = resource.access_plan.options.get("entry_point")
82
+ if member:
83
+ uri += "/" + member
84
+ return uri
85
+
86
+ @staticmethod
87
+ def tile_xml(tile: Mapping[str, Any]) -> str:
88
+ root = Element("GDAL_WMS")
89
+ service = SubElement(root, "Service", name="TMS")
90
+ template = tile["url"]
91
+ for name in ("x", "y", "z"):
92
+ template = template.replace("{" + name + "}", "${" + name + "}")
93
+ SubElement(service, "ServerUrl").text = template
94
+ window = SubElement(root, "DataWindow")
95
+ for key, value in {
96
+ "UpperLeftX": "-20037508.342789244",
97
+ "UpperLeftY": "20037508.342789244",
98
+ "LowerRightX": "20037508.342789244",
99
+ "LowerRightY": "-20037508.342789244",
100
+ "TileLevel": str(tile["max_zoom"]),
101
+ "TileCountX": "1",
102
+ "TileCountY": "1",
103
+ "YOrigin": "top",
104
+ }.items():
105
+ SubElement(window, key).text = value
106
+ for key, value in {
107
+ "Projection": "EPSG:3857",
108
+ "BlockSizeX": "256",
109
+ "BlockSizeY": "256",
110
+ "BandsCount": "3",
111
+ "OverviewCount": str(tile["max_zoom"] - tile["min_zoom"]),
112
+ }.items():
113
+ SubElement(root, key).text = value
114
+ return tostring(root, encoding="unicode")
@@ -0,0 +1,10 @@
1
+ # JSON Service Execution Adapter
2
+
3
+ `JsonServiceAdapter` は選択済み JSON service query をHTTP runtimeへ渡します。標準の`configure()`経路ではRhinestoneの組み込みruntimeを使用します。
4
+
5
+ - `name`: `json-service`、優先度: `10`
6
+ - 対応条件: `ServiceQueryPlan`、`application/json`、一致する service 名
7
+ - 注入: request を作る callback、service 名、任意の `CredentialRegistry`
8
+ - runtime contract: `get(uri, params=..., headers=..., timeout=30, allow_redirects=False)`
9
+
10
+ redirect は拒否し、secret を含む可能性のある下位例外は公開しません。JSON schema の検証以外の変換は行いません。Adapter単体では同じcontractを満たすruntimeを直接渡せます。
@@ -0,0 +1,103 @@
1
+ """Selected JSON service requests delegated to a requests-compatible runtime."""
2
+
3
+ from typing import Any, Callable, FrozenSet, List, Mapping, Optional, Tuple, cast
4
+
5
+ from ....errors import ProviderResponseError, ResourceAccessError
6
+ from ....models import AccessPlan, Resource, ServiceQueryPlan
7
+ from ....registry import CredentialRegistry
8
+ from ....security import DestinationPolicy
9
+ from ..base import ExecutionAdapter
10
+
11
+ RequestPreparer = Callable[
12
+ [AccessPlan, CredentialRegistry], Tuple[Mapping[str, Any], Mapping[str, str]]
13
+ ]
14
+
15
+
16
+ class JsonServiceAdapter(ExecutionAdapter):
17
+ name = "json-service"
18
+ priority = 10
19
+
20
+ def __init__(
21
+ self,
22
+ prepare_request: RequestPreparer,
23
+ service: str,
24
+ credentials: Optional[CredentialRegistry] = None,
25
+ destination_policy: Optional[DestinationPolicy] = None,
26
+ ) -> None:
27
+ super().__init__(destination_policy)
28
+ self._prepare_request = prepare_request
29
+ self._service = service
30
+ self._credentials = credentials or CredentialRegistry({})
31
+
32
+ def bind_credentials(self, credentials: CredentialRegistry) -> "JsonServiceAdapter":
33
+ return JsonServiceAdapter(
34
+ self._prepare_request,
35
+ self._service,
36
+ credentials,
37
+ self._destination_policy,
38
+ )
39
+
40
+ def supports(self, resource: Resource, dependencies: FrozenSet[str]) -> bool:
41
+ return (
42
+ self.name in dependencies
43
+ and isinstance(resource.access_plan, ServiceQueryPlan)
44
+ and resource.media_type == "application/json"
45
+ and resource.access_plan.options.get("service") == self._service
46
+ )
47
+
48
+ def open(
49
+ self,
50
+ resource: Resource,
51
+ runtime: Any,
52
+ *,
53
+ destination_policy: DestinationPolicy | None = None,
54
+ ) -> Any:
55
+ self._authorize_resource(resource, destination_policy)
56
+ params, headers = self._prepare_request(resource.access_plan, self._credentials)
57
+ try:
58
+ response = runtime.get(
59
+ resource.uri,
60
+ params=params,
61
+ headers=headers,
62
+ timeout=30,
63
+ allow_redirects=False,
64
+ )
65
+ if 300 <= response.status_code < 400:
66
+ raise ResourceAccessError("Service redirects are not supported")
67
+ response.raise_for_status()
68
+ except Exception:
69
+ # requests exceptions may include a URL containing the secret query.
70
+ raise ResourceAccessError("JSON service request failed") from None
71
+ try:
72
+ data = response.json()
73
+ except Exception:
74
+ raise ProviderResponseError("Service returned invalid JSON") from None
75
+ if resource.access_plan.options.get("response_type") == "array" and (
76
+ not isinstance(data, list)
77
+ or any(not isinstance(item, Mapping) for item in cast(List[Any], data))
78
+ ):
79
+ raise ProviderResponseError("Service must return an array of objects")
80
+ return cast(Any, data)
81
+
82
+ def authorize(
83
+ self,
84
+ resource: Resource,
85
+ *,
86
+ destination_policy: DestinationPolicy | None = None,
87
+ ) -> None:
88
+ """Authorize credential release before resolving the service runtime."""
89
+ self._authorize_resource(resource, destination_policy)
90
+
91
+ def _authorize_resource(
92
+ self,
93
+ resource: Resource,
94
+ destination_policy: DestinationPolicy | None,
95
+ ) -> None:
96
+ credential = resource.access_plan.options.get("credential")
97
+ (destination_policy or self._destination_policy).authorize(
98
+ resource.uri,
99
+ credentialed=True,
100
+ provider=resource.provenance.provider,
101
+ service=self._service,
102
+ credential=credential if isinstance(credential, str) else None,
103
+ )
@@ -0,0 +1,10 @@
1
+ # pyogrio Execution Adapter
2
+
3
+ `PyogrioAdapter` は選択済み vector Resource を利用者提供の pyogrio runtime へ渡します。
4
+
5
+ - `name`: `pyogrio`、優先度: `10`
6
+ - 対応形式: Shapefile, GeoJSON, GeoPackage, FlatGeobuf
7
+ - 翻訳: 確定済み `encoding` を `read_dataframe()` option へ渡します。
8
+ - runtime: `read_dataframe(uri, **options)`
9
+
10
+ Resource の再選択、データ解析、形式・CRS 変換は行いません。
@@ -0,0 +1,44 @@
1
+ """Pyogrio execution adapter."""
2
+
3
+ from typing import Any, Dict, FrozenSet
4
+
5
+ from ....errors import ResourceAccessError
6
+ from ....models import Resource
7
+ from ....security import DestinationPolicy
8
+ from .._resource import resource_attributes
9
+ from ..base import ExecutionAdapter
10
+
11
+
12
+ class PyogrioAdapter(ExecutionAdapter):
13
+ """Delegate vector Resources to a supplied pyogrio module."""
14
+
15
+ name = "pyogrio"
16
+ priority = 10
17
+ _formats = frozenset({"shapefile", "geojson", "gpkg", "flatgeobuf"})
18
+
19
+ def __init__(self, destination_policy: DestinationPolicy | None = None) -> None:
20
+ super().__init__(destination_policy)
21
+
22
+ def supports(self, resource: Resource, dependencies: FrozenSet[str]) -> bool:
23
+ return (
24
+ resource.format or ""
25
+ ).lower() in self._formats and self.name in dependencies
26
+
27
+ def open(
28
+ self,
29
+ resource: Resource,
30
+ runtime: Any,
31
+ *,
32
+ destination_policy: DestinationPolicy | None = None,
33
+ ) -> Any:
34
+ attributes = resource_attributes(resource)
35
+ options: Dict[str, Any] = {}
36
+ encoding = attributes.get("encoding")
37
+ if isinstance(encoding, str):
38
+ options["encoding"] = encoding
39
+ try:
40
+ return runtime.read_dataframe(resource.uri, **options)
41
+ except Exception as error:
42
+ raise ResourceAccessError(
43
+ f"pyogrio could not open {resource.uri!r}"
44
+ ) from error
@@ -0,0 +1,9 @@
1
+ # Rasterio Execution Adapter
2
+
3
+ `RasterioAdapter` は選択済み raster Resource を利用者提供の Rasterio runtime へ渡します。
4
+
5
+ - `name`: `rasterio`、優先度: `15`
6
+ - 対応形式: COG, GeoTIFF
7
+ - runtime: `open(uri)`
8
+
9
+ URI は選択済み Resource の値をそのまま使用します。ダウンロード、Resource の再選択、形式・CRS 変換は行いません。
@@ -0,0 +1,38 @@
1
+ """Rasterio execution adapter."""
2
+
3
+ from typing import Any, FrozenSet
4
+
5
+ from ....errors import ResourceAccessError
6
+ from ....models import Resource
7
+ from ....security import DestinationPolicy
8
+ from ..base import ExecutionAdapter
9
+
10
+
11
+ class RasterioAdapter(ExecutionAdapter):
12
+ """Delegate raster Resources to a supplied Rasterio module."""
13
+
14
+ name = "rasterio"
15
+ priority = 15
16
+ _formats = frozenset({"cog", "geotiff"})
17
+
18
+ def __init__(self, destination_policy: DestinationPolicy | None = None) -> None:
19
+ super().__init__(destination_policy)
20
+
21
+ def supports(self, resource: Resource, dependencies: FrozenSet[str]) -> bool:
22
+ return (
23
+ resource.format or ""
24
+ ).lower() in self._formats and self.name in dependencies
25
+
26
+ def open(
27
+ self,
28
+ resource: Resource,
29
+ runtime: Any,
30
+ *,
31
+ destination_policy: DestinationPolicy | None = None,
32
+ ) -> Any:
33
+ try:
34
+ return runtime.open(resource.uri)
35
+ except Exception as error:
36
+ raise ResourceAccessError(
37
+ f"Rasterio could not open {resource.uri!r}"
38
+ ) from error