flybase-cli 0.1.2__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,4 @@
1
+ from .cli import main
2
+ from .version import __version__
3
+
4
+ __all__ = ["main", "__version__"]
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
flybase_cli/cli.py ADDED
@@ -0,0 +1,667 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ import urllib.error
7
+ import urllib.parse
8
+ from pathlib import Path
9
+
10
+ from .config import (
11
+ BASE_API,
12
+ DEFAULT_DB,
13
+ DEFAULT_MANIFEST,
14
+ DEFAULT_POSTGRES_DIR,
15
+ DEFAULT_RELEASE,
16
+ DEFAULT_ROOT,
17
+ GENOME_SYNC_PRESETS,
18
+ GENOME_ASSET_PATTERNS,
19
+ GENOME_SECTIONS,
20
+ SYNC_PRESETS,
21
+ )
22
+ from .core import (
23
+ build_manifest,
24
+ build_manifest_from_url,
25
+ fetch_bytes,
26
+ filter_manifest,
27
+ ingest_files,
28
+ find_genome,
29
+ genome_asset_pattern,
30
+ genome_section_url,
31
+ list_genomes,
32
+ list_tables,
33
+ load_manifest,
34
+ rebuild_search_index,
35
+ release_base_url,
36
+ search_index,
37
+ sync_manifest,
38
+ sync_preset,
39
+ write_json,
40
+ )
41
+ from .postgres import (
42
+ build_pg_load_plan,
43
+ ensure_dump_file,
44
+ execute_pg_load_script,
45
+ write_pg_load_script,
46
+ )
47
+ from .querying import execute_sql, parse_cli_params, run_query_template
48
+ from .schema import build_query_plan, describe_tables, export_schema_summary
49
+ from .syncing import (
50
+ build_preset_release_diff,
51
+ build_release_diff,
52
+ sync_full_release,
53
+ sync_incremental_preset,
54
+ )
55
+ from .version import __version__
56
+
57
+
58
+ def print_json(payload: object) -> None:
59
+ print(json.dumps(payload, indent=2))
60
+
61
+
62
+ def default_manifest_for_release(root: Path, preset: str, release: str) -> Path:
63
+ return root / "manifests" / release / f"{preset}.json"
64
+
65
+
66
+ def default_db_for_release(root: Path, release: str) -> Path:
67
+ if release == DEFAULT_RELEASE:
68
+ return root / DEFAULT_DB.name
69
+ return root / f"{release}.sqlite"
70
+
71
+
72
+ def default_schema_path(db_path: Path) -> Path:
73
+ if db_path.suffix:
74
+ return db_path.with_suffix(".schema.json")
75
+ return db_path.parent / f"{db_path.name}.schema.json"
76
+
77
+
78
+ def cmd_manifest(args: argparse.Namespace) -> int:
79
+ if args.url:
80
+ manifest = build_manifest_from_url(args.url)
81
+ else:
82
+ manifest = build_manifest(args.prefix, release=args.release)
83
+ filtered = filter_manifest(manifest, args.include, args.exclude)
84
+ write_json(Path(args.output), filtered)
85
+ print(f"{len(filtered)} files -> {args.output}")
86
+ return 0
87
+
88
+
89
+ def cmd_download(args: argparse.Namespace) -> int:
90
+ from .core import download_manifest_entries
91
+
92
+ manifest = load_manifest(Path(args.manifest))
93
+ selected = filter_manifest(manifest, args.include, args.exclude)
94
+ local_paths = download_manifest_entries(selected, Path(args.root), force=args.force)
95
+ for item, (local_path, downloaded) in zip(selected, local_paths, strict=True):
96
+ status = "get " if downloaded else "skip"
97
+ print(f"{status} {item['path']}")
98
+ return 0
99
+
100
+
101
+ def cmd_ingest(args: argparse.Namespace) -> int:
102
+ sources = [Path(item) for item in args.sources]
103
+ results = ingest_files(Path(args.db), sources, no_header=args.no_header)
104
+ for item in results:
105
+ print(f"ingest {item['source_path']} -> {item['table_name']} ({item['row_count']} rows)")
106
+ return 0
107
+
108
+
109
+ def cmd_sql(args: argparse.Namespace) -> int:
110
+ print_json(
111
+ execute_sql(
112
+ Path(args.db),
113
+ args.query,
114
+ limit=args.limit,
115
+ output_format=args.format,
116
+ )
117
+ )
118
+ return 0
119
+
120
+
121
+ def cmd_tables(args: argparse.Namespace) -> int:
122
+ print_json(list_tables(Path(args.db), include_columns=args.columns))
123
+ return 0
124
+
125
+
126
+ def cmd_describe(args: argparse.Namespace) -> int:
127
+ print_json(
128
+ describe_tables(
129
+ Path(args.db),
130
+ table_names=args.tables or None,
131
+ sample_values=args.sample_values,
132
+ )
133
+ )
134
+ return 0
135
+
136
+
137
+ def cmd_schema_export(args: argparse.Namespace) -> int:
138
+ db_path = Path(args.db)
139
+ output_path = Path(args.output) if args.output else default_schema_path(db_path)
140
+ payload = export_schema_summary(
141
+ db_path,
142
+ output_path,
143
+ table_names=args.tables or None,
144
+ sample_values=args.sample_values,
145
+ query_limit=args.query_limit,
146
+ )
147
+ print_json({**payload, "output_path": str(output_path)})
148
+ return 0
149
+
150
+
151
+ def cmd_query_plan(args: argparse.Namespace) -> int:
152
+ print_json(
153
+ build_query_plan(
154
+ Path(args.db),
155
+ table_names=args.tables or None,
156
+ sample_values=args.sample_values,
157
+ limit=args.limit,
158
+ )
159
+ )
160
+ return 0
161
+
162
+
163
+ def cmd_query_run(args: argparse.Namespace) -> int:
164
+ if not any((args.template_id, args.template_name, args.kind, args.table)):
165
+ print("query-run needs at least one selector such as --template-id or --template-name", file=sys.stderr)
166
+ return 1
167
+ try:
168
+ payload = run_query_template(
169
+ Path(args.db),
170
+ template_id=args.template_id,
171
+ template_name=args.template_name,
172
+ kind=args.kind,
173
+ table_name=args.table,
174
+ params=parse_cli_params(args.param),
175
+ sample_values=args.sample_values,
176
+ plan_limit=args.plan_limit,
177
+ result_limit=args.limit,
178
+ output_format=args.format,
179
+ )
180
+ except ValueError as error:
181
+ print(str(error), file=sys.stderr)
182
+ return 1
183
+ print_json(payload)
184
+ return 0
185
+
186
+
187
+ def cmd_presets(_: argparse.Namespace) -> int:
188
+ payload = [
189
+ {
190
+ "name": preset.name,
191
+ "description": preset.description,
192
+ "selections": [
193
+ {
194
+ "prefix": selection.prefix,
195
+ "includes": list(selection.includes),
196
+ "excludes": list(selection.excludes),
197
+ }
198
+ for selection in preset.selections
199
+ ],
200
+ }
201
+ for preset in SYNC_PRESETS.values()
202
+ ]
203
+ print_json(payload)
204
+ return 0
205
+
206
+
207
+ def cmd_genome_presets(_: argparse.Namespace) -> int:
208
+ payload = [
209
+ {
210
+ "name": preset.name,
211
+ "description": preset.description,
212
+ "section": preset.section,
213
+ "asset": preset.asset,
214
+ "includes": list(preset.includes),
215
+ "excludes": list(preset.excludes),
216
+ }
217
+ for preset in GENOME_SYNC_PRESETS.values()
218
+ ]
219
+ print_json(payload)
220
+ return 0
221
+
222
+
223
+ def cmd_sync(args: argparse.Namespace) -> int:
224
+ preset = SYNC_PRESETS[args.preset]
225
+ root = Path(args.root)
226
+ manifest_path = Path(args.manifest or default_manifest_for_release(root, preset.name, args.release))
227
+ db_path = Path(args.db) if args.db else default_db_for_release(root, args.release)
228
+ summary = sync_preset(
229
+ preset=preset,
230
+ root=root,
231
+ db_path=db_path,
232
+ manifest_path=manifest_path,
233
+ release=args.release,
234
+ force=args.force,
235
+ )
236
+ summary["db_path"] = str(db_path)
237
+ print_json(summary)
238
+ return 0
239
+
240
+
241
+ def cmd_full_sync(args: argparse.Namespace) -> int:
242
+ root = Path(args.root)
243
+ db_path = Path(args.db) if args.db else default_db_for_release(root, args.release)
244
+ manifest_path = Path(args.manifest) if args.manifest else root / "manifests" / args.release / "full-sync.json"
245
+ summary = sync_full_release(
246
+ root=root,
247
+ db_path=db_path,
248
+ manifest_path=manifest_path,
249
+ release=args.release,
250
+ prefix=args.prefix,
251
+ include=args.include,
252
+ exclude=args.exclude,
253
+ ingestable_only=not args.all_files,
254
+ force=args.force,
255
+ no_header=args.no_header,
256
+ )
257
+ summary["db_path"] = str(db_path)
258
+ print_json(summary)
259
+ return 0
260
+
261
+
262
+ def cmd_sync_incremental(args: argparse.Namespace) -> int:
263
+ preset = SYNC_PRESETS[args.preset]
264
+ root = Path(args.root)
265
+ db_path = Path(args.db) if args.db else default_db_for_release(root, args.release)
266
+ manifest_path = Path(args.manifest or default_manifest_for_release(root, preset.name, args.release))
267
+ diff_path = (
268
+ Path(args.diff_output)
269
+ if args.diff_output
270
+ else root / "manifests" / args.release / f"{preset.name}-diff-from-{args.from_release}.json"
271
+ )
272
+ summary = sync_incremental_preset(
273
+ preset=preset,
274
+ root=root,
275
+ db_path=db_path,
276
+ manifest_path=manifest_path,
277
+ diff_path=diff_path,
278
+ from_release=args.from_release,
279
+ to_release=args.release,
280
+ force=args.force,
281
+ no_header=args.no_header,
282
+ )
283
+ summary["db_path"] = str(db_path)
284
+ print_json(summary)
285
+ return 0
286
+
287
+
288
+ def cmd_sync_url(args: argparse.Namespace) -> int:
289
+ root = Path(args.root)
290
+ db_path = Path(args.db) if args.db else default_db_for_release(root, args.release)
291
+ manifest = filter_manifest(
292
+ build_manifest_from_url(args.url),
293
+ args.include,
294
+ args.exclude,
295
+ )
296
+ manifest_path = Path(args.manifest) if args.manifest else root / "manifests" / args.release / "url-sync.json"
297
+ summary = sync_manifest(
298
+ manifest,
299
+ root=root,
300
+ db_path=db_path,
301
+ manifest_path=manifest_path,
302
+ force=args.force,
303
+ no_header=args.no_header,
304
+ )
305
+ summary["url"] = args.url
306
+ summary["db_path"] = str(db_path)
307
+ print_json(summary)
308
+ return 0
309
+
310
+
311
+ def cmd_sync_genome(args: argparse.Namespace) -> int:
312
+ root = Path(args.root)
313
+ db_path = Path(args.db) if args.db else default_db_for_release(root, args.release)
314
+ preset = GENOME_SYNC_PRESETS.get(args.preset) if args.preset else None
315
+ genome = find_genome(
316
+ release=args.release,
317
+ genome=args.genome,
318
+ species=args.species,
319
+ )
320
+ section = preset.section if preset else args.section
321
+ asset = preset.asset if preset else args.asset
322
+ include = [*genome_asset_pattern(asset), *(preset.includes if preset else ()), *args.include]
323
+ exclude = [*(preset.excludes if preset else ()), *args.exclude]
324
+ url = genome_section_url(genome["url"], section)
325
+ manifest = filter_manifest(
326
+ build_manifest_from_url(url),
327
+ include,
328
+ exclude,
329
+ )
330
+ default_name = f"{genome['label']}-{section}"
331
+ if preset:
332
+ default_name = f"{default_name}-{preset.name}"
333
+ elif asset:
334
+ default_name = f"{default_name}-{asset}"
335
+ manifest_path = Path(args.manifest) if args.manifest else root / "manifests" / args.release / f"{default_name}.json"
336
+ summary = sync_manifest(
337
+ manifest,
338
+ root=root,
339
+ db_path=db_path,
340
+ manifest_path=manifest_path,
341
+ force=args.force,
342
+ no_header=args.no_header,
343
+ )
344
+ summary["release"] = args.release
345
+ summary["genome"] = genome
346
+ summary["section"] = section
347
+ summary["asset"] = asset
348
+ summary["preset"] = args.preset
349
+ summary["url"] = url
350
+ summary["db_path"] = str(db_path)
351
+ print_json(summary)
352
+ return 0
353
+
354
+
355
+ def cmd_api(args: argparse.Namespace) -> int:
356
+ endpoint = args.endpoint.lstrip("/")
357
+ url = urllib.parse.urljoin(BASE_API, endpoint)
358
+ try:
359
+ payload = fetch_bytes(url)
360
+ except urllib.error.HTTPError as error:
361
+ print(f"HTTP {error.code}: {error.reason}", file=sys.stderr)
362
+ return 1
363
+ if not payload:
364
+ print_json({"url": url, "status": "empty-body"})
365
+ return 0
366
+ try:
367
+ print_json(json.loads(payload.decode("utf-8")))
368
+ except json.JSONDecodeError:
369
+ print(payload.decode("utf-8", errors="replace"))
370
+ return 0
371
+
372
+
373
+ def cmd_release_url(args: argparse.Namespace) -> int:
374
+ print_json({"release": args.release, "base_url": release_base_url(args.release)})
375
+ return 0
376
+
377
+
378
+ def cmd_release_diff(args: argparse.Namespace) -> int:
379
+ preset = SYNC_PRESETS.get(args.preset)
380
+ if preset is None and not args.prefix:
381
+ print("release-diff needs either --preset or --prefix", file=sys.stderr)
382
+ return 1
383
+ if preset is not None:
384
+ payload = build_preset_release_diff(
385
+ preset=preset,
386
+ from_release=args.from_release,
387
+ to_release=args.to_release,
388
+ )
389
+ else:
390
+ payload = build_release_diff(
391
+ prefix=args.prefix,
392
+ from_release=args.from_release,
393
+ to_release=args.to_release,
394
+ include=args.include,
395
+ exclude=args.exclude,
396
+ )
397
+ if args.output:
398
+ write_json(Path(args.output), payload)
399
+ print_json(payload)
400
+ return 0
401
+
402
+
403
+ def cmd_genomes(args: argparse.Namespace) -> int:
404
+ print_json(list_genomes(args.release))
405
+ return 0
406
+
407
+
408
+ def cmd_fts_build(args: argparse.Namespace) -> int:
409
+ indexed = rebuild_search_index(Path(args.db), table_names=args.tables or None)
410
+ print_json(indexed)
411
+ return 0
412
+
413
+
414
+ def cmd_search(args: argparse.Namespace) -> int:
415
+ results = search_index(
416
+ Path(args.db),
417
+ query=args.query,
418
+ limit=args.limit,
419
+ table_name=args.table,
420
+ )
421
+ print_json(results)
422
+ return 0
423
+
424
+
425
+ def cmd_pg_load(args: argparse.Namespace) -> int:
426
+ root = Path(args.root)
427
+ plan = build_pg_load_plan(
428
+ release=args.release,
429
+ root=root,
430
+ db_name=args.db_name,
431
+ dump_path=Path(args.dump_path) if args.dump_path else None,
432
+ script_path=Path(args.script_path) if args.script_path else None,
433
+ drop_existing=args.drop_existing,
434
+ )
435
+ dump_path = Path(plan["dump_path"])
436
+ script_path = Path(plan["script_path"])
437
+ if args.download:
438
+ ensure_dump_file(
439
+ release=args.release,
440
+ dump_path=dump_path,
441
+ force=args.force_download,
442
+ )
443
+ plan["downloaded"] = True
444
+ write_pg_load_script(
445
+ release=args.release,
446
+ dump_path=dump_path,
447
+ db_name=str(plan["db_name"]),
448
+ script_path=script_path,
449
+ drop_existing=args.drop_existing,
450
+ )
451
+ plan["script_written"] = True
452
+ if args.execute:
453
+ missing = [name for name, path in plan["tools"].items() if name in {"createdb", "psql"} and not path]
454
+ if missing:
455
+ print_json({"error": "missing-postgres-tools", "missing": missing, **plan})
456
+ return 1
457
+ execute_pg_load_script(script_path)
458
+ plan["executed"] = True
459
+ print_json(plan)
460
+ return 0
461
+
462
+
463
+ def build_parser() -> argparse.ArgumentParser:
464
+ parser = argparse.ArgumentParser(description="FlyBase sync/query helper for agents.")
465
+ parser.add_argument(
466
+ "--version",
467
+ action="version",
468
+ version=f"%(prog)s {__version__}",
469
+ )
470
+ subparsers = parser.add_subparsers(dest="command", required=True)
471
+
472
+ manifest_parser = subparsers.add_parser("manifest", help="scrape a release prefix or FlyBase directory URL")
473
+ manifest_parser.add_argument("--prefix", default="precomputed_files/genes/")
474
+ manifest_parser.add_argument("--url")
475
+ manifest_parser.add_argument("--release", default=DEFAULT_RELEASE)
476
+ manifest_parser.add_argument("--include", action="append", default=[])
477
+ manifest_parser.add_argument("--exclude", action="append", default=[])
478
+ manifest_parser.add_argument("--output", default=str(DEFAULT_MANIFEST))
479
+ manifest_parser.set_defaults(func=cmd_manifest)
480
+
481
+ download_parser = subparsers.add_parser("download", help="download files from a manifest")
482
+ download_parser.add_argument("--manifest", default=str(DEFAULT_MANIFEST))
483
+ download_parser.add_argument("--root", default=str(DEFAULT_ROOT))
484
+ download_parser.add_argument("--include", action="append", default=[])
485
+ download_parser.add_argument("--exclude", action="append", default=[])
486
+ download_parser.add_argument("--force", action="store_true")
487
+ download_parser.set_defaults(func=cmd_download)
488
+
489
+ ingest_parser = subparsers.add_parser("ingest", help="ingest supported FlyBase files into sqlite")
490
+ ingest_parser.add_argument("sources", nargs="+")
491
+ ingest_parser.add_argument("--db", default=str(DEFAULT_DB))
492
+ ingest_parser.add_argument("--no-header", action="store_true")
493
+ ingest_parser.set_defaults(func=cmd_ingest)
494
+
495
+ sql_parser = subparsers.add_parser("sql", help="run SQL against the local sqlite db")
496
+ sql_parser.add_argument("query")
497
+ sql_parser.add_argument("--db", default=str(DEFAULT_DB))
498
+ sql_parser.add_argument("--limit", type=int, default=20)
499
+ sql_parser.add_argument("--format", choices=("records", "rows"), default="records")
500
+ sql_parser.set_defaults(func=cmd_sql)
501
+
502
+ tables_parser = subparsers.add_parser("tables", help="list ingested tables")
503
+ tables_parser.add_argument("--db", default=str(DEFAULT_DB))
504
+ tables_parser.add_argument("--columns", action="store_true")
505
+ tables_parser.set_defaults(func=cmd_tables)
506
+
507
+ describe_parser = subparsers.add_parser("describe", help="summarize ingested tables for query planning")
508
+ describe_parser.add_argument("--db", default=str(DEFAULT_DB))
509
+ describe_parser.add_argument("--tables", nargs="*")
510
+ describe_parser.add_argument("--sample-values", type=int, default=3)
511
+ describe_parser.set_defaults(func=cmd_describe)
512
+
513
+ schema_parser = subparsers.add_parser("schema-export", help="write machine-readable table metadata")
514
+ schema_parser.add_argument("--db", default=str(DEFAULT_DB))
515
+ schema_parser.add_argument("--tables", nargs="*")
516
+ schema_parser.add_argument("--sample-values", type=int, default=3)
517
+ schema_parser.add_argument("--query-limit", type=int, default=5)
518
+ schema_parser.add_argument("--output")
519
+ schema_parser.set_defaults(func=cmd_schema_export)
520
+
521
+ query_plan_parser = subparsers.add_parser("query-plan", help="suggest starter SQL from schema relationships")
522
+ query_plan_parser.add_argument("--db", default=str(DEFAULT_DB))
523
+ query_plan_parser.add_argument("--tables", nargs="*")
524
+ query_plan_parser.add_argument("--sample-values", type=int, default=1)
525
+ query_plan_parser.add_argument("--limit", type=int, default=5)
526
+ query_plan_parser.set_defaults(func=cmd_query_plan)
527
+
528
+ query_run_parser = subparsers.add_parser("query-run", help="execute a suggested query template")
529
+ query_run_parser.add_argument("--db", default=str(DEFAULT_DB))
530
+ query_run_parser.add_argument("--template-id")
531
+ query_run_parser.add_argument("--template-name")
532
+ query_run_parser.add_argument("--kind")
533
+ query_run_parser.add_argument("--table")
534
+ query_run_parser.add_argument("--param", action="append", default=[])
535
+ query_run_parser.add_argument("--sample-values", type=int, default=1)
536
+ query_run_parser.add_argument("--plan-limit", type=int, default=5)
537
+ query_run_parser.add_argument("--limit", type=int, default=20)
538
+ query_run_parser.add_argument("--format", choices=("records", "rows"), default="records")
539
+ query_run_parser.set_defaults(func=cmd_query_run)
540
+
541
+ presets_parser = subparsers.add_parser("presets", help="list sync presets")
542
+ presets_parser.set_defaults(func=cmd_presets)
543
+
544
+ genome_presets_parser = subparsers.add_parser("genome-presets", help="list genome sync presets")
545
+ genome_presets_parser.set_defaults(func=cmd_genome_presets)
546
+
547
+ sync_parser = subparsers.add_parser("sync", help="manifest + download + ingest a preset")
548
+ sync_parser.add_argument("preset", choices=sorted(SYNC_PRESETS))
549
+ sync_parser.add_argument("--root", default=str(DEFAULT_ROOT))
550
+ sync_parser.add_argument("--db")
551
+ sync_parser.add_argument("--release", default=DEFAULT_RELEASE)
552
+ sync_parser.add_argument("--manifest")
553
+ sync_parser.add_argument("--force", action="store_true")
554
+ sync_parser.set_defaults(func=cmd_sync)
555
+
556
+ full_sync_parser = subparsers.add_parser(
557
+ "full-sync",
558
+ help="crawl and sync all release bulk files under a prefix",
559
+ )
560
+ full_sync_parser.add_argument("--root", default=str(DEFAULT_ROOT))
561
+ full_sync_parser.add_argument("--db")
562
+ full_sync_parser.add_argument("--release", default=DEFAULT_RELEASE)
563
+ full_sync_parser.add_argument("--prefix", default="precomputed_files/")
564
+ full_sync_parser.add_argument("--manifest")
565
+ full_sync_parser.add_argument("--include", action="append", default=[])
566
+ full_sync_parser.add_argument("--exclude", action="append", default=[])
567
+ full_sync_parser.add_argument("--all-files", action="store_true")
568
+ full_sync_parser.add_argument("--force", action="store_true")
569
+ full_sync_parser.add_argument("--no-header", action="store_true")
570
+ full_sync_parser.set_defaults(func=cmd_full_sync)
571
+
572
+ sync_incremental_parser = subparsers.add_parser(
573
+ "sync-incremental",
574
+ help="sync only added or changed preset files between FlyBase releases",
575
+ )
576
+ sync_incremental_parser.add_argument("preset", choices=sorted(SYNC_PRESETS))
577
+ sync_incremental_parser.add_argument("--from-release", required=True)
578
+ sync_incremental_parser.add_argument("--release", required=True)
579
+ sync_incremental_parser.add_argument("--root", default=str(DEFAULT_ROOT))
580
+ sync_incremental_parser.add_argument("--db")
581
+ sync_incremental_parser.add_argument("--manifest")
582
+ sync_incremental_parser.add_argument("--diff-output")
583
+ sync_incremental_parser.add_argument("--force", action="store_true")
584
+ sync_incremental_parser.add_argument("--no-header", action="store_true")
585
+ sync_incremental_parser.set_defaults(func=cmd_sync_incremental)
586
+
587
+ sync_url_parser = subparsers.add_parser("sync-url", help="crawl + download + ingest an arbitrary FlyBase directory URL")
588
+ sync_url_parser.add_argument("--url", required=True)
589
+ sync_url_parser.add_argument("--root", default=str(DEFAULT_ROOT))
590
+ sync_url_parser.add_argument("--db")
591
+ sync_url_parser.add_argument("--release", default=DEFAULT_RELEASE)
592
+ sync_url_parser.add_argument("--manifest")
593
+ sync_url_parser.add_argument("--include", action="append", default=[])
594
+ sync_url_parser.add_argument("--exclude", action="append", default=[])
595
+ sync_url_parser.add_argument("--force", action="store_true")
596
+ sync_url_parser.add_argument("--no-header", action="store_true")
597
+ sync_url_parser.set_defaults(func=cmd_sync_url)
598
+
599
+ sync_genome_parser = subparsers.add_parser("sync-genome", help="discover a genome build and sync one genome asset section")
600
+ sync_genome_parser.add_argument("--release", default=DEFAULT_RELEASE)
601
+ sync_genome_parser.add_argument("--genome")
602
+ sync_genome_parser.add_argument("--species")
603
+ sync_genome_parser.add_argument("--preset", choices=sorted(GENOME_SYNC_PRESETS))
604
+ sync_genome_parser.add_argument("--section", choices=GENOME_SECTIONS, default="fasta")
605
+ sync_genome_parser.add_argument("--asset", choices=sorted(GENOME_ASSET_PATTERNS))
606
+ sync_genome_parser.add_argument("--root", default=str(DEFAULT_ROOT))
607
+ sync_genome_parser.add_argument("--db")
608
+ sync_genome_parser.add_argument("--manifest")
609
+ sync_genome_parser.add_argument("--include", action="append", default=[])
610
+ sync_genome_parser.add_argument("--exclude", action="append", default=[])
611
+ sync_genome_parser.add_argument("--force", action="store_true")
612
+ sync_genome_parser.add_argument("--no-header", action="store_true")
613
+ sync_genome_parser.set_defaults(func=cmd_sync_genome)
614
+
615
+ release_parser = subparsers.add_parser("release-url", help="show the bulk-data base URL for a release")
616
+ release_parser.add_argument("--release", default=DEFAULT_RELEASE)
617
+ release_parser.set_defaults(func=cmd_release_url)
618
+
619
+ release_diff_parser = subparsers.add_parser("release-diff", help="compare FlyBase release manifests")
620
+ release_diff_parser.add_argument("--from-release", required=True)
621
+ release_diff_parser.add_argument("--to-release", required=True)
622
+ release_diff_parser.add_argument("--preset", choices=sorted(SYNC_PRESETS))
623
+ release_diff_parser.add_argument("--prefix")
624
+ release_diff_parser.add_argument("--include", action="append", default=[])
625
+ release_diff_parser.add_argument("--exclude", action="append", default=[])
626
+ release_diff_parser.add_argument("--output")
627
+ release_diff_parser.set_defaults(func=cmd_release_diff)
628
+
629
+ genomes_parser = subparsers.add_parser("genomes", help="list genome builds linked from a FlyBase release")
630
+ genomes_parser.add_argument("--release", default=DEFAULT_RELEASE)
631
+ genomes_parser.set_defaults(func=cmd_genomes)
632
+
633
+ fts_build_parser = subparsers.add_parser("fts-build", help="build a local full-text index")
634
+ fts_build_parser.add_argument("--db", default=str(DEFAULT_DB))
635
+ fts_build_parser.add_argument("--tables", nargs="*")
636
+ fts_build_parser.set_defaults(func=cmd_fts_build)
637
+
638
+ search_parser = subparsers.add_parser("search", help="search the local full-text index")
639
+ search_parser.add_argument("query")
640
+ search_parser.add_argument("--db", default=str(DEFAULT_DB))
641
+ search_parser.add_argument("--table")
642
+ search_parser.add_argument("--limit", type=int, default=20)
643
+ search_parser.set_defaults(func=cmd_search)
644
+
645
+ pg_parser = subparsers.add_parser("pg-load", help="stage or execute a FlyBase Postgres import")
646
+ pg_parser.add_argument("--release", default=DEFAULT_RELEASE)
647
+ pg_parser.add_argument("--root", default=str(DEFAULT_POSTGRES_DIR))
648
+ pg_parser.add_argument("--db-name")
649
+ pg_parser.add_argument("--dump-path")
650
+ pg_parser.add_argument("--script-path")
651
+ pg_parser.add_argument("--download", action="store_true")
652
+ pg_parser.add_argument("--force-download", action="store_true")
653
+ pg_parser.add_argument("--drop-existing", action="store_true")
654
+ pg_parser.add_argument("--execute", action="store_true")
655
+ pg_parser.set_defaults(func=cmd_pg_load)
656
+
657
+ api_parser = subparsers.add_parser("api", help="call the FlyBase HTTP API")
658
+ api_parser.add_argument("endpoint")
659
+ api_parser.set_defaults(func=cmd_api)
660
+
661
+ return parser
662
+
663
+
664
+ def main(argv: list[str] | None = None) -> int:
665
+ parser = build_parser()
666
+ args = parser.parse_args(argv)
667
+ return args.func(args)