Skip to content

Commit e496b48

Browse files
committed
add sync client and tests, minor fixups
1 parent d63c1d0 commit e496b48

File tree

5 files changed

+740
-43
lines changed

5 files changed

+740
-43
lines changed

planet/cli/mosaics.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ def _strip_links(resource):
6060
async def _output(result, pretty, include_links=False):
6161
if asyncio.iscoroutine(result):
6262
result = await result
63-
if result is None:
64-
raise click.ClickException("not found")
6563
if not include_links:
6664
_strip_links(result)
6765
echo_json(result, pretty)
@@ -236,11 +234,8 @@ async def list_quads(ctx, name_or_id, bbox, geometry, summary, pretty, links):
236234
planet mosaics search global_monthly_2025_04_mosaic --bbox -100,40,-100,41
237235
"""
238236
async with client(ctx) as cl:
239-
mosaic = await cl.get_mosaic(name_or_id)
240-
if mosaic is None:
241-
raise click.ClickException("No mosaic named " + name_or_id)
242237
await _output(
243-
cl.list_quads(mosaic,
238+
cl.list_quads(name_or_id,
244239
minimal=False,
245240
bbox=bbox,
246241
geometry=geometry,
@@ -268,10 +263,7 @@ async def download(ctx, name_or_id, output_dir, bbox, geometry, **kwargs):
268263
"""
269264
quiet = ctx.obj['QUIET']
270265
async with client(ctx) as cl:
271-
mosaic = await cl.get_mosaic(name_or_id)
272-
if mosaic is None:
273-
raise click.ClickException("No mosaic named " + name_or_id)
274-
await cl.download_quads(mosaic,
266+
await cl.download_quads(name_or_id,
275267
bbox=bbox,
276268
geometry=geometry,
277269
directory=output_dir,

planet/clients/mosaics.py

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
1+
# Copyright 2025 Planet Labs PBC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4+
# use this file except in compliance with the License. You may obtain a copy of
5+
# the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations under
13+
# the License.
14+
115
import asyncio
216
from pathlib import Path
317
from typing import AsyncIterator, Awaitable, Optional, Tuple, Type, TypeVar, Union, cast
18+
from planet.clients.base import _BaseClient
419
from planet.constants import PLANET_BASE_URL
520
from planet.exceptions import MissingResource
621
from planet.http import Session
7-
from planet.models import Mosaic, Paged, Quad, Response, Series, StreamingBody
22+
from planet.models import GeoInterface, Mosaic, Paged, Quad, Response, Series, StreamingBody
823
from uuid import UUID
924

1025
BASE_URL = f'{PLANET_BASE_URL}/basemaps/v1'
@@ -39,7 +54,7 @@ def _is_uuid(val: str) -> bool:
3954
return False
4055

4156

42-
class MosaicsClient:
57+
class MosaicsClient(_BaseClient):
4358
"""High-level asynchronous access to Planet's Mosaics API.
4459
4560
Example:
@@ -49,7 +64,7 @@ class MosaicsClient:
4964
>>>
5065
>>> async def main():
5166
... async with Session() as sess:
52-
... cl = sess.client('data')
67+
... cl = sess.client('mosaics')
5368
... # use client here
5469
...
5570
>>> asyncio.run(main())
@@ -63,18 +78,10 @@ def __init__(self, session: Session, base_url: Optional[str] = None):
6378
base_url: The base URL to use. Defaults to production Mosaics
6479
base url.
6580
"""
66-
self._session = session
67-
68-
self._base_url = base_url or BASE_URL
69-
if self._base_url.endswith('/'):
70-
self._base_url = self._base_url[:-1]
71-
72-
def _call_sync(self, f: Awaitable[T]) -> T:
73-
"""block on an async function call, using the call_sync method of the session"""
74-
return self._session._call_sync(f)
81+
super().__init__(session, base_url or BASE_URL)
7582

7683
def _url(self, path: str) -> str:
77-
return f"{BASE_URL}/{path}"
84+
return f"{self._base_url}/{path}"
7885

7986
async def _get_by_name(self, path: str, pager: Type[Paged],
8087
name: str) -> dict:
@@ -88,7 +95,12 @@ async def _get_by_name(self, path: str, pager: Type[Paged],
8895
listing = response.json()[pager.ITEMS_KEY]
8996
if len(listing):
9097
return listing[0]
91-
raise MissingResource(f"{name} not found")
98+
# mimic the response for 404 when search is empty
99+
resource = "Mosaic"
100+
if path == "series":
101+
resource = "Series"
102+
raise MissingResource('{"message":"%s Not Found: %s"}' %
103+
(resource, name))
92104

93105
async def _get_by_id(self, path: str, id: str) -> dict:
94106
response = await self._session.request(method="GET",
@@ -130,7 +142,7 @@ async def list_series(
130142
name_contains: Optional[str] = None,
131143
interval: Optional[str] = None,
132144
acquired_gt: Optional[str] = None,
133-
acquired_lt: Optional[str] = None) -> AsyncIterator[dict]:
145+
acquired_lt: Optional[str] = None) -> AsyncIterator[Series]:
134146
"""
135147
List the series you have access to.
136148
@@ -157,7 +169,7 @@ async def list_series(
157169
params=params,
158170
)
159171
async for item in _SeriesPage(resp, self._session.request):
160-
yield item
172+
yield Series(item)
161173

162174
async def list_mosaics(
163175
self,
@@ -166,8 +178,7 @@ async def list_mosaics(
166178
interval: Optional[str] = None,
167179
acquired_gt: Optional[str] = None,
168180
acquired_lt: Optional[str] = None,
169-
latest: bool = False,
170-
) -> AsyncIterator[dict]:
181+
) -> AsyncIterator[Mosaic]:
171182
"""
172183
List the mosaics you have access to.
173184
@@ -188,15 +199,13 @@ async def list_mosaics(
188199
params["acquired__gt"] = acquired_gt
189200
if acquired_lt:
190201
params["acquired__lt"] = acquired_lt
191-
if latest:
192-
params["latest"] = "yes"
193202
resp = await self._session.request(
194203
method='GET',
195204
url=self._url("mosaics"),
196205
params=params,
197206
)
198207
async for item in _MosaicsPage(resp, self._session.request):
199-
yield item
208+
yield Mosaic(item)
200209

201210
async def list_series_mosaics(
202211
self,
@@ -206,7 +215,7 @@ async def list_series_mosaics(
206215
acquired_gt: Optional[str] = None,
207216
acquired_lt: Optional[str] = None,
208217
latest: bool = False,
209-
) -> AsyncIterator[dict]:
218+
) -> AsyncIterator[Mosaic]:
210219
"""
211220
List the mosaics in a series.
212221
@@ -218,6 +227,7 @@ async def list_series_mosaics(
218227
print(m)
219228
```
220229
"""
230+
series_id = series
221231
if isinstance(series, Series):
222232
series_id = series["id"]
223233
elif not _is_uuid(series):
@@ -238,15 +248,15 @@ async def list_series_mosaics(
238248
params=params,
239249
)
240250
async for item in _MosaicsPage(resp, self._session.request):
241-
yield item
251+
yield Mosaic(item)
242252

243253
async def list_quads(self,
244254
/,
245255
mosaic: Union[Mosaic, str],
246256
*,
247257
minimal: bool = False,
248258
bbox: Optional[BBox] = None,
249-
geometry: Optional[dict] = None,
259+
geometry: Optional[Union[dict, GeoInterface]] = None,
250260
summary: bool = False) -> AsyncIterator[Quad]:
251261
"""
252262
List the a mosaic's quads.
@@ -262,6 +272,8 @@ async def list_quads(self,
262272
"""
263273
mosaic = await self._resolve_mosaic(mosaic)
264274
if geometry:
275+
if isinstance(geometry, GeoInterface):
276+
geometry = geometry.__geo_interface__
265277
resp = await self._quads_geometry(mosaic,
266278
geometry,
267279
minimal,
@@ -364,14 +376,6 @@ async def download_quad(self,
364376
directory: str = ".",
365377
overwrite: bool = False,
366378
progress_bar: bool = False):
367-
url = quad["_links"]["download"]
368-
Path(directory).mkdir(exist_ok=True, parents=True)
369-
async with self._session.stream(method='GET', url=url) as resp:
370-
body = StreamingBody(resp)
371-
dest = Path(directory, body.name)
372-
await body.write(dest,
373-
overwrite=overwrite,
374-
progress_bar=progress_bar)
375379
"""
376380
Download a quad to a directory.
377381
@@ -382,6 +386,14 @@ async def download_quad(self,
382386
await client.download_quad(quad)
383387
```
384388
"""
389+
url = quad["_links"]["download"]
390+
Path(directory).mkdir(exist_ok=True, parents=True)
391+
async with self._session.stream(method='GET', url=url) as resp:
392+
body = StreamingBody(resp)
393+
dest = Path(directory, body.name)
394+
await body.write(dest,
395+
overwrite=overwrite,
396+
progress_bar=progress_bar)
385397

386398
async def download_quads(self,
387399
/,
@@ -390,7 +402,8 @@ async def download_quads(self,
390402
directory: Optional[str] = None,
391403
overwrite: bool = False,
392404
bbox: Optional[BBox] = None,
393-
geometry: Optional[dict] = None,
405+
geometry: Optional[Union[dict,
406+
GeoInterface]] = None,
394407
progress_bar: bool = False,
395408
concurrency: int = 4):
396409
"""

0 commit comments

Comments
 (0)