Skip to content

Commit 108f421

Browse files
committed
initial commit
1 parent a2b0930 commit 108f421

File tree

12 files changed

+2646
-1
lines changed

12 files changed

+2646
-1
lines changed

planet/cli/analytics.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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+
15+
from contextlib import asynccontextmanager
16+
import json
17+
import click
18+
from click.exceptions import ClickException
19+
20+
from planet.cli.io import echo_json
21+
from planet.clients.analytics import AnalyticsClient
22+
23+
from .cmds import command
24+
from .options import limit
25+
from .session import CliSession
26+
27+
28+
@asynccontextmanager
29+
async def analytics_client(ctx):
30+
async with CliSession() as sess:
31+
cl = AnalyticsClient(sess, base_url=ctx.obj['BASE_URL'])
32+
yield cl
33+
34+
35+
@click.group() # type: ignore
36+
@click.pass_context
37+
@click.option('-u',
38+
'--base-url',
39+
default=None,
40+
help='Assign custom base Analytics API URL.')
41+
def analytics(ctx, base_url):
42+
"""Commands for interacting with the Analytics API"""
43+
ctx.obj['BASE_URL'] = base_url
44+
45+
46+
@analytics.group() # type: ignore
47+
def feeds():
48+
"""Commands for interacting with Analytics feeds"""
49+
pass
50+
51+
52+
@command(feeds, name="list")
53+
@limit
54+
async def feeds_list(ctx, limit, pretty):
55+
"""List available analytics feeds.
56+
57+
Example:
58+
59+
\b
60+
planet analytics feeds list
61+
planet analytics feeds list --limit 10
62+
"""
63+
async with analytics_client(ctx) as cl:
64+
feeds_iter = cl.list_feeds(limit=limit)
65+
async for feed in feeds_iter:
66+
echo_json(feed, pretty)
67+
68+
69+
@command(feeds, name="get")
70+
@click.argument('feed_id', required=True)
71+
async def feeds_get(ctx, feed_id, pretty):
72+
"""Get details of a specific analytics feed.
73+
74+
Parameters:
75+
FEED_ID: The ID of the analytics feed
76+
77+
Example:
78+
79+
\b
80+
planet analytics feeds get my-feed-id
81+
"""
82+
async with analytics_client(ctx) as cl:
83+
feed = await cl.get_feed(feed_id)
84+
echo_json(feed, pretty)
85+
86+
87+
@command(feeds, name="stats")
88+
@click.argument('feed_id', required=True)
89+
@click.option('--subscription-id',
90+
help='Get stats for a specific subscription.')
91+
@click.option('--start-time',
92+
help='Start time for temporal filtering (ISO 8601 format).')
93+
@click.option('--end-time',
94+
help='End time for temporal filtering (ISO 8601 format).')
95+
async def feeds_stats(ctx,
96+
feed_id,
97+
subscription_id,
98+
start_time,
99+
end_time,
100+
pretty):
101+
"""Get statistics for an analytics feed.
102+
103+
Parameters:
104+
FEED_ID: The ID of the analytics feed
105+
106+
Example:
107+
108+
\b
109+
planet analytics feeds stats my-feed-id
110+
planet analytics feeds stats my-feed-id --start-time 2023-01-01T00:00:00Z
111+
"""
112+
async with analytics_client(ctx) as cl:
113+
stats = await cl.get_feed_stats(feed_id=feed_id,
114+
subscription_id=subscription_id,
115+
start_time=start_time,
116+
end_time=end_time)
117+
echo_json(stats, pretty)
118+
119+
120+
@analytics.group() # type: ignore
121+
def subscriptions():
122+
"""Commands for interacting with Analytics subscriptions"""
123+
pass
124+
125+
126+
@command(subscriptions, name="list")
127+
@click.option('--feed-id', help='Filter subscriptions by feed ID.')
128+
@limit
129+
async def subscriptions_list(ctx, feed_id, limit, pretty):
130+
"""List analytics subscriptions.
131+
132+
Example:
133+
134+
\b
135+
planet analytics subscriptions list
136+
planet analytics subscriptions list --feed-id my-feed-id
137+
"""
138+
async with analytics_client(ctx) as cl:
139+
subs_iter = cl.list_subscriptions(feed_id=feed_id, limit=limit)
140+
async for subscription in subs_iter:
141+
echo_json(subscription, pretty)
142+
143+
144+
@command(subscriptions, name="get")
145+
@click.argument('subscription_id', required=True)
146+
async def subscriptions_get(ctx, subscription_id, pretty):
147+
"""Get details of a specific analytics subscription.
148+
149+
Parameters:
150+
SUBSCRIPTION_ID: The ID of the analytics subscription
151+
152+
Example:
153+
154+
\b
155+
planet analytics subscriptions get my-subscription-id
156+
"""
157+
async with analytics_client(ctx) as cl:
158+
subscription = await cl.get_subscription(subscription_id)
159+
echo_json(subscription, pretty)
160+
161+
162+
@analytics.group() # type: ignore
163+
def results():
164+
"""Commands for interacting with Analytics results"""
165+
pass
166+
167+
168+
@command(results, name="search")
169+
@click.argument('feed_id', required=True)
170+
@click.option('--subscription-id', help='Filter results by subscription ID.')
171+
@click.option('--start-time',
172+
help='Start time for temporal filtering (ISO 8601 format).')
173+
@click.option('--end-time',
174+
help='End time for temporal filtering (ISO 8601 format).')
175+
@click.option('--bbox', help='Bounding box as west,south,east,north.')
176+
@click.option('--geometry', help='GeoJSON geometry for spatial filtering.')
177+
@limit
178+
async def results_search(ctx,
179+
feed_id,
180+
subscription_id,
181+
start_time,
182+
end_time,
183+
bbox,
184+
geometry,
185+
limit,
186+
pretty):
187+
"""Search for analytics results.
188+
189+
Parameters:
190+
FEED_ID: The ID of the analytics feed to search
191+
192+
Example:
193+
194+
\b
195+
planet analytics results search my-feed-id
196+
planet analytics results search my-feed-id --start-time 2023-01-01T00:00:00Z
197+
planet analytics results search my-feed-id --bbox -122.5,37.7,-122.3,37.8
198+
"""
199+
# Parse bbox if provided
200+
bbox_list = None
201+
if bbox:
202+
try:
203+
bbox_list = [float(x.strip()) for x in bbox.split(',')]
204+
if len(bbox_list) != 4:
205+
raise ValueError("bbox must contain exactly 4 values")
206+
except (ValueError, TypeError) as e:
207+
raise ClickException(f"Invalid bbox format: {e}")
208+
209+
# Parse geometry if provided
210+
geometry_dict = None
211+
if geometry:
212+
try:
213+
geometry_dict = json.loads(geometry)
214+
except json.JSONDecodeError as e:
215+
raise ClickException(f"Invalid geometry JSON: {e}")
216+
217+
async with analytics_client(ctx) as cl:
218+
results_iter = cl.search_results(feed_id=feed_id,
219+
subscription_id=subscription_id,
220+
start_time=start_time,
221+
end_time=end_time,
222+
bbox=bbox_list,
223+
geometry=geometry_dict,
224+
limit=limit)
225+
async for result in results_iter:
226+
echo_json(result, pretty)
227+
228+
229+
@command(results, name="get")
230+
@click.argument('result_id', required=True)
231+
async def results_get(ctx, result_id, pretty):
232+
"""Get details of a specific analytics result.
233+
234+
Parameters:
235+
RESULT_ID: The ID of the analytics result
236+
237+
Example:
238+
239+
\b
240+
planet analytics results get my-result-id
241+
"""
242+
async with analytics_client(ctx) as cl:
243+
result = await cl.get_result(result_id)
244+
echo_json(result, pretty)
245+
246+
247+
@command(results, name="download")
248+
@click.argument('result_id', required=True)
249+
@click.option('--format',
250+
default='json',
251+
type=click.Choice(['json', 'geojson', 'csv']),
252+
help='Download format (default: json).')
253+
async def results_download(ctx, result_id, format, pretty):
254+
"""Download analytics result data.
255+
256+
Parameters:
257+
RESULT_ID: The ID of the analytics result
258+
259+
Example:
260+
261+
\b
262+
planet analytics results download my-result-id
263+
planet analytics results download my-result-id --format geojson
264+
"""
265+
async with analytics_client(ctx) as cl:
266+
data = await cl.download_result(result_id, format)
267+
268+
if format.lower() in ['json', 'geojson']:
269+
echo_json(data, pretty)
270+
else:
271+
# For CSV or other text formats
272+
click.echo(data)

planet/cli/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import planet
2323
from planet.cli import mosaics
2424

25-
from . import auth, cmds, collect, data, destinations, orders, subscriptions, features
25+
from . import analytics, auth, cmds, collect, data, destinations, orders, subscriptions, features
2626

2727
LOGGER = logging.getLogger(__name__)
2828

@@ -123,6 +123,7 @@ def _configure_logging(verbosity):
123123
main.add_command(cmd=planet_auth_utils.cmd_plauth_embedded,
124124
name="plauth") # type: ignore
125125

126+
main.add_command(analytics.analytics) # type: ignore
126127
main.add_command(auth.cmd_auth) # type: ignore
127128
main.add_command(data.data) # type: ignore
128129
main.add_command(orders.orders) # type: ignore

planet/clients/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15+
from .analytics import AnalyticsClient
1516
from .data import DataClient
1617
from .destinations import DestinationsClient
1718
from .features import FeaturesClient
@@ -20,6 +21,7 @@
2021
from .subscriptions import SubscriptionsClient
2122

2223
__all__ = [
24+
'AnalyticsClient',
2325
'DataClient',
2426
'DestinationsClient',
2527
'FeaturesClient',
@@ -30,6 +32,7 @@
3032

3133
# Organize client classes by their module name to allow lookup.
3234
_client_directory = {
35+
'analytics': AnalyticsClient,
3336
'data': DataClient,
3437
'destinations': DestinationsClient,
3538
'features': FeaturesClient,

0 commit comments

Comments
 (0)