indico-cli 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.
- indico_cli/__init__.py +3 -0
- indico_cli/cli.py +326 -0
- indico_cli/indico_api.py +1766 -0
- indico_cli/utils.py +145 -0
- indico_cli-0.1.0.dist-info/METADATA +118 -0
- indico_cli-0.1.0.dist-info/RECORD +9 -0
- indico_cli-0.1.0.dist-info/WHEEL +4 -0
- indico_cli-0.1.0.dist-info/entry_points.txt +2 -0
- indico_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
indico_cli/__init__.py
ADDED
indico_cli/cli.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI entry point for interacting with Indico event management systems."""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from indico_cli.indico_api import IndicoAPI, IndicoClient
|
|
14
|
+
|
|
15
|
+
CONFIG_DIR = Path.home() / ".config" / "indico-cli"
|
|
16
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_config() -> dict:
|
|
20
|
+
"""Load saved configuration from disk, returning empty dict on failure."""
|
|
21
|
+
if CONFIG_FILE.exists():
|
|
22
|
+
try:
|
|
23
|
+
return json.loads(CONFIG_FILE.read_text())
|
|
24
|
+
except (json.JSONDecodeError, OSError):
|
|
25
|
+
return {}
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def save_config(base_url: str, token: str) -> None:
|
|
30
|
+
"""Persist configuration to ~/.config/indico-cli/config.json."""
|
|
31
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
CONFIG_FILE.write_text(json.dumps({"base_url": base_url, "token": token}, indent=2))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_client(base_url: Optional[str], token: Optional[str]) -> IndicoClient:
|
|
36
|
+
"""Create an IndicoClient from CLI args, env vars, or saved config."""
|
|
37
|
+
saved_config = load_config()
|
|
38
|
+
resolved_url = base_url or os.getenv("INDICO_BASE_URL") or saved_config.get("base_url", "")
|
|
39
|
+
resolved_token = token or os.getenv("INDICO_API_TOKEN") or saved_config.get("token", "")
|
|
40
|
+
|
|
41
|
+
if not resolved_url:
|
|
42
|
+
click.echo("Error: --base-url, INDICO_BASE_URL, or saved config required", err=True)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
api = IndicoAPI(resolved_url, bearer_token=resolved_token if resolved_token else None)
|
|
46
|
+
return IndicoClient(api)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_async(coro):
|
|
50
|
+
"""Run an async coroutine and return its result."""
|
|
51
|
+
return asyncio.run(coro)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_speakers_json(speakers_string: Optional[str]):
|
|
55
|
+
"""Parse a JSON string of speakers into a list of dicts."""
|
|
56
|
+
if not speakers_string:
|
|
57
|
+
return None
|
|
58
|
+
return json.loads(speakers_string)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.group()
|
|
62
|
+
@click.option("--base-url", envvar="INDICO_BASE_URL", help="Indico instance URL")
|
|
63
|
+
@click.option("--token", envvar="INDICO_API_TOKEN", help="API bearer token")
|
|
64
|
+
@click.pass_context
|
|
65
|
+
def cli(ctx, base_url, token):
|
|
66
|
+
"""CLI for interacting with Indico event management systems."""
|
|
67
|
+
ctx.ensure_object(dict)
|
|
68
|
+
ctx.obj["base_url"] = base_url
|
|
69
|
+
ctx.obj["token"] = token
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@cli.command()
|
|
73
|
+
@click.option("--base-url", required=True, help="Indico instance URL")
|
|
74
|
+
@click.option("--token", default="", help="API bearer token")
|
|
75
|
+
def configure(base_url, token):
|
|
76
|
+
"""Configure connection to an Indico instance and test it."""
|
|
77
|
+
api = IndicoAPI(base_url, bearer_token=token if token else None)
|
|
78
|
+
client = IndicoClient(api)
|
|
79
|
+
try:
|
|
80
|
+
result = run_async(client.get_user_info())
|
|
81
|
+
click.echo("Connection successful!")
|
|
82
|
+
click.echo(result)
|
|
83
|
+
save_config(base_url, token)
|
|
84
|
+
click.echo(f"Configuration saved to {CONFIG_FILE}")
|
|
85
|
+
except Exception as error:
|
|
86
|
+
click.echo(f"Connection test failed: {error}", err=True)
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@cli.command("user-info")
|
|
91
|
+
@click.pass_context
|
|
92
|
+
def user_info(ctx):
|
|
93
|
+
"""Get current user information."""
|
|
94
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
95
|
+
result = run_async(client.get_user_info())
|
|
96
|
+
click.echo(result)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@cli.command("search-events")
|
|
100
|
+
@click.option("--category-id", required=True, help="Category ID to search in")
|
|
101
|
+
@click.option("--from-date", default=None, help="Start date filter (e.g. 2024-01-01)")
|
|
102
|
+
@click.option("--to-date", default=None, help="End date filter (e.g. 2024-12-31)")
|
|
103
|
+
@click.option("--limit", type=int, default=None, help="Maximum number of results")
|
|
104
|
+
@click.option("--only-public", is_flag=True, default=False, help="Only show public events")
|
|
105
|
+
@click.pass_context
|
|
106
|
+
def search_events(ctx, category_id, from_date, to_date, limit, only_public):
|
|
107
|
+
"""Search for events in a category."""
|
|
108
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
109
|
+
result = run_async(client.search_events(category_id, from_date, to_date, limit, only_public))
|
|
110
|
+
click.echo(result)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@cli.command("search-by-term")
|
|
114
|
+
@click.option("--term", required=True, help="Search term")
|
|
115
|
+
@click.option("--limit", type=int, default=50, help="Maximum number of results")
|
|
116
|
+
@click.pass_context
|
|
117
|
+
def search_by_term(ctx, term, limit):
|
|
118
|
+
"""Search for events by keyword/term."""
|
|
119
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
120
|
+
result = run_async(client.search_events_by_term(term, limit))
|
|
121
|
+
click.echo(result)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@cli.command("search-categories")
|
|
125
|
+
@click.option("--category-id", default="0", help="Parent category ID (default: root)")
|
|
126
|
+
@click.option("--limit", type=int, default=None, help="Maximum number of results")
|
|
127
|
+
@click.pass_context
|
|
128
|
+
def search_categories(ctx, category_id, limit):
|
|
129
|
+
"""Search for event categories."""
|
|
130
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
131
|
+
result = run_async(client.search_categories(category_id, limit))
|
|
132
|
+
click.echo(result)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@cli.command("event-details")
|
|
136
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
137
|
+
@click.option("--contributions", is_flag=True, default=False, help="Include contributions")
|
|
138
|
+
@click.option("--sessions", is_flag=True, default=False, help="Include sessions")
|
|
139
|
+
@click.pass_context
|
|
140
|
+
def event_details(ctx, event_id, contributions, sessions):
|
|
141
|
+
"""Get detailed information about a specific event."""
|
|
142
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
143
|
+
result = run_async(client.get_event_details(event_id, contributions, sessions))
|
|
144
|
+
click.echo(result)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@cli.command()
|
|
148
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
149
|
+
@click.option("--subcontributions", is_flag=True, default=False, help="Include subcontributions")
|
|
150
|
+
@click.option("--limit", type=int, default=None, help="Maximum number of results")
|
|
151
|
+
@click.pass_context
|
|
152
|
+
def contributions(ctx, event_id, subcontributions, limit):
|
|
153
|
+
"""Get contributions for a specific event."""
|
|
154
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
155
|
+
result = run_async(client.get_event_contributions(event_id, subcontributions, limit))
|
|
156
|
+
click.echo(result)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@cli.command()
|
|
160
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
161
|
+
@click.option("--download/--no-download", default=True, help="Download files (default: yes)")
|
|
162
|
+
@click.option("--download-dir", default=None, help="Directory to save downloaded files")
|
|
163
|
+
@click.pass_context
|
|
164
|
+
def files(ctx, event_id, download, download_dir):
|
|
165
|
+
"""Get and optionally download files for an event."""
|
|
166
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
167
|
+
result = run_async(client.get_files(event_id, download_files=download, download_dir=download_dir))
|
|
168
|
+
click.echo(result)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@cli.command()
|
|
172
|
+
@click.option("--url", required=True, help="Download URL for the attachment")
|
|
173
|
+
@click.option("--filename", default=None, help="Save as this filename")
|
|
174
|
+
@click.pass_context
|
|
175
|
+
def download(ctx, url, filename):
|
|
176
|
+
"""Download a single file attachment."""
|
|
177
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
178
|
+
result = run_async(client.download_single_attachment(url, filename))
|
|
179
|
+
click.echo(result)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@cli.command("create-event")
|
|
183
|
+
@click.option("--title", required=True, help="Event title")
|
|
184
|
+
@click.option("--category-id", required=True, help="Category ID")
|
|
185
|
+
@click.option("--type", "event_type", required=True, type=click.Choice(["lecture", "meeting", "conference"]), help="Event type")
|
|
186
|
+
@click.option("--start", default=None, help="Start date/time (ISO format)")
|
|
187
|
+
@click.option("--end", default=None, help="End date/time (ISO format)")
|
|
188
|
+
@click.option("--timezone", default=None, help="Timezone (e.g. Europe/Zurich)")
|
|
189
|
+
@click.option("--description", default=None, help="Event description")
|
|
190
|
+
@click.option("--location", default=None, help="Event location")
|
|
191
|
+
@click.option("--room", default=None, help="Room name")
|
|
192
|
+
@click.option("--speakers", default=None, help="Speakers as JSON string")
|
|
193
|
+
@click.pass_context
|
|
194
|
+
def create_event(ctx, title, category_id, event_type, start, end, timezone, description, location, room, speakers):
|
|
195
|
+
"""Create a new event."""
|
|
196
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
197
|
+
parsed_speakers = parse_speakers_json(speakers)
|
|
198
|
+
result = run_async(client.create_event(
|
|
199
|
+
title=title,
|
|
200
|
+
category_id=category_id,
|
|
201
|
+
event_type=event_type,
|
|
202
|
+
start_date=start,
|
|
203
|
+
end_date=end,
|
|
204
|
+
timezone=timezone,
|
|
205
|
+
description=description,
|
|
206
|
+
location=location,
|
|
207
|
+
room=room,
|
|
208
|
+
speakers=parsed_speakers,
|
|
209
|
+
))
|
|
210
|
+
click.echo(result)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@cli.command("create-session")
|
|
214
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
215
|
+
@click.option("--title", required=True, help="Session title")
|
|
216
|
+
@click.option("--description", default=None, help="Session description")
|
|
217
|
+
@click.pass_context
|
|
218
|
+
def create_session(ctx, event_id, title, description):
|
|
219
|
+
"""Create a session in an event."""
|
|
220
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
221
|
+
result = run_async(client.create_session(
|
|
222
|
+
event_id=event_id,
|
|
223
|
+
title=title,
|
|
224
|
+
description=description,
|
|
225
|
+
))
|
|
226
|
+
click.echo(result)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@cli.command("create-contribution")
|
|
230
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
231
|
+
@click.option("--title", required=True, help="Contribution title")
|
|
232
|
+
@click.option("--description", default=None, help="Contribution description")
|
|
233
|
+
@click.option("--duration", type=int, default=None, help="Duration in minutes")
|
|
234
|
+
@click.option("--speakers", default=None, help="Speakers as JSON string")
|
|
235
|
+
@click.option("--session-id", default=None, help="Session ID to add contribution to")
|
|
236
|
+
@click.pass_context
|
|
237
|
+
def create_contribution(ctx, event_id, title, description, duration, speakers, session_id):
|
|
238
|
+
"""Create a contribution in an event."""
|
|
239
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
240
|
+
parsed_speakers = parse_speakers_json(speakers)
|
|
241
|
+
result = run_async(client.create_contribution(
|
|
242
|
+
event_id=event_id,
|
|
243
|
+
title=title,
|
|
244
|
+
description=description,
|
|
245
|
+
duration_minutes=duration,
|
|
246
|
+
speakers=parsed_speakers,
|
|
247
|
+
session_id=session_id,
|
|
248
|
+
))
|
|
249
|
+
click.echo(result)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@cli.command("add-timetable-entry")
|
|
253
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
254
|
+
@click.option("--type", "entry_type", required=True, type=click.Choice(["session_block", "contribution"]), help="Entry type")
|
|
255
|
+
@click.option("--object-id", required=True, help="Object ID (session or contribution)")
|
|
256
|
+
@click.option("--start", required=True, help="Start date/time (ISO format)")
|
|
257
|
+
@click.option("--duration", type=int, default=None, help="Duration in minutes")
|
|
258
|
+
@click.pass_context
|
|
259
|
+
def add_timetable_entry(ctx, event_id, entry_type, object_id, start, duration):
|
|
260
|
+
"""Add an entry to the event timetable."""
|
|
261
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
262
|
+
result = run_async(client.add_timetable_entry(
|
|
263
|
+
event_id=event_id,
|
|
264
|
+
entry_type=entry_type,
|
|
265
|
+
object_id=object_id,
|
|
266
|
+
start_date=start,
|
|
267
|
+
duration_minutes=duration,
|
|
268
|
+
))
|
|
269
|
+
click.echo(result)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@cli.command("delete-timetable-entry")
|
|
273
|
+
@click.option("--event-id", required=True, help="Event ID")
|
|
274
|
+
@click.option("--entry-id", required=True, help="Timetable entry ID")
|
|
275
|
+
@click.pass_context
|
|
276
|
+
def delete_timetable_entry(ctx, event_id, entry_id):
|
|
277
|
+
"""Delete a timetable entry from an event."""
|
|
278
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
279
|
+
result = run_async(client.delete_timetable_entry(
|
|
280
|
+
event_id=event_id,
|
|
281
|
+
entry_id=entry_id,
|
|
282
|
+
))
|
|
283
|
+
click.echo(result)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@cli.command("create-event-with-agenda")
|
|
287
|
+
@click.option("--title", required=True, help="Event title")
|
|
288
|
+
@click.option("--category-id", required=True, help="Category ID")
|
|
289
|
+
@click.option("--type", "event_type", required=True, type=click.Choice(["lecture", "meeting", "conference"]), help="Event type")
|
|
290
|
+
@click.option("--start", required=True, help="Start date/time (ISO format)")
|
|
291
|
+
@click.option("--end", required=True, help="End date/time (ISO format)")
|
|
292
|
+
@click.option("--timezone", default=None, help="Timezone (e.g. Europe/Zurich)")
|
|
293
|
+
@click.option("--description", default=None, help="Event description")
|
|
294
|
+
@click.option("--location", default=None, help="Event location")
|
|
295
|
+
@click.option("--room", default=None, help="Room name")
|
|
296
|
+
@click.option("--speakers", default=None, help="Speakers as JSON string")
|
|
297
|
+
@click.option("--agenda-file", type=click.Path(exists=True), default=None, help="Path to JSON file with agenda array")
|
|
298
|
+
@click.pass_context
|
|
299
|
+
def create_event_with_agenda(ctx, title, category_id, event_type, start, end, timezone, description, location, room, speakers, agenda_file):
|
|
300
|
+
"""Create an event with a full agenda from a JSON file."""
|
|
301
|
+
client = get_client(ctx.obj["base_url"], ctx.obj["token"])
|
|
302
|
+
parsed_speakers = parse_speakers_json(speakers)
|
|
303
|
+
|
|
304
|
+
agenda_data = None
|
|
305
|
+
if agenda_file:
|
|
306
|
+
with open(agenda_file) as agenda_fh:
|
|
307
|
+
agenda_data = json.load(agenda_fh)
|
|
308
|
+
|
|
309
|
+
result = run_async(client.create_event_with_agenda(
|
|
310
|
+
title=title,
|
|
311
|
+
category_id=category_id,
|
|
312
|
+
event_type=event_type,
|
|
313
|
+
start_date=start,
|
|
314
|
+
end_date=end,
|
|
315
|
+
timezone=timezone,
|
|
316
|
+
description=description,
|
|
317
|
+
location=location,
|
|
318
|
+
room=room,
|
|
319
|
+
speakers=parsed_speakers,
|
|
320
|
+
agenda=agenda_data,
|
|
321
|
+
))
|
|
322
|
+
click.echo(result)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
cli()
|