feat(cli): optional kerykeion[cli] β the whole library from the terminal (6.0.0a85) - #249
Open
g-battaglia wants to merge 11 commits into
Open
feat(cli): optional kerykeion[cli] β the whole library from the terminal (6.0.0a85)#249g-battaglia wants to merge 11 commits into
g-battaglia wants to merge 11 commits into
Conversation
``_resolve_model`` extracted the kind/chart_type tuple with ``next()`` over a generator expression. Pyright (1.1.408, the declared floor) widens the unpacked literal to ``str`` inside a genexpr, so the assignment to ``self._model_kind`` failed type-checking β a pre-existing regression the no-CI policy let slip. A plain for-loop with unpacking keeps the ``LiteralReportKind`` narrowing. Verified: ``0 errors, 0 warnings, 0 informations``. Co-Authored-By: Claude <noreply@anthropic.com>
Phase 1 of the staged CLI build-out β metadata only, no command logic yet. * pyproject: new ``[cli]`` extra (typer>=0.26.4, rich>=13.8.0); ``[all]`` now includes it (an "all" that omits an extra lies in its name); a ``[project.scripts] kerykeion`` entry point installed for every user, even a bare ``pip install kerykeion``; a ``cli`` pytest marker; typer+rich also in the dev group because pyright's reportMissingImports is an ERROR in basic mode and the include covers the whole ``kerykeion`` tree. * kerykeion/cli/__init__.py: ``main()`` is the entry point. It imports the Typer app lazily and degrades to an install hint (exit 3) when [cli] is absent, so the bare command never crashes with a traceback. The module imports NO typer and NO kerykeion symbol at module level. * kerykeion/cli/app.py: placeholder ``run()`` so the command is exercisable end-to-end before any command is wired. typer 0.27.1 vendorizes click (it added no click to the graph β click 8.3 was already present as a transitive of libephemeris), which is the floor's reason. Verified: ``uv run kerykeion`` exits 0; ``import kerykeion`` and ``import kerykeion.cli`` keep typer out of sys.modules; pyright/ruff/mypy clean; fresh-imports OK (132 checked); public-API surface walker green. Co-Authored-By: Claude <noreply@anthropic.com>
Phase 2 of the CLI build-out. * kerykeion/__main__.py and kerykeion/cli/__main__.py enable ``python -m kerykeion`` and ``python -m kerykeion.cli``. A plain ``import kerykeion`` still never runs them, so typer stays out of the library import path; the cold-import gate already skips every ``__main__``. * tests/core/test_public_api_surface.py: the ``pkgutil.walk_packages`` walk now skips the whole ``kerykeion.cli`` subtree, whose leaves import the optional [cli] extra. Exclusion is exact / dot-prefixed-subtree / dot-suffixed-leaf, never a bare substring β ``"kerykeion.cli" in "kerykeion.client"`` is True, so a substring test would swallow a future client package too. Verified: both -m forms exit 0; walker green (23 passed); the exclusion predicate keeps ``kerykeion.client`` importable while skipping ``kerykeion.cli`` and its leaves. Co-Authored-By: Claude <noreply@anthropic.com>
Phase 3 of the CLI build-out β the gate before any domain command exists. * kerykeion/cli/app.py: a real (minimal) Typer app with --version/-V and --help. Imports typer at module level ON PURPOSE: that import is what lets kerykeion.cli.main detect a missing [cli] extra and print the install hint. No kerykeion symbol at module level (cold-import gate stays green). A plain ``kerykeion`` shows help and exits 0 (not click's exit 2): handled in the callback rather than via no_args_is_help. * scripts/cli_smoke_check.py: from the dev checkout, proves ``import kerykeion`` stays typer-free, the console_scripts entry point is wired, and --version / --help / bare all behave. Run by ``poe cli:smoke``. * scripts/build_smoke_check_cli.py: against the BUILT WHEEL in a clean room, proves the two install paths β wheel alone degrades to exit 3 + install hint with no traceback; wheel + [cli] runs --version/--help. The only place the install-hint guard is exercised for real. * pyproject: new ``test:cli`` and ``cli:smoke`` tasks; ``check`` now ends with cli:smoke; ``build:smoke`` runs both isolated environments (wheel-only, and wheel+[cli]) so the degration path ships verified. * scripts/quality_check.py: adds the cli smoke step. Verified: poe check green (5670 passed + cli:smoke); poe build:smoke green across both isolated envs; mypy/pyright/ruff clean on cli/. Co-Authored-By: Claude <noreply@anthropic.com>
β¦` command Fase 4 del build-out kerykeion[cli]. Aggiunge il nucleo che ogni comando chart riusera: - subject_resolver: merge profilo + flag inline; parsing date/time via regex (non date.fromisoformat, che rifiuta anno < 1 e quindi le date BCE); rename degli alias (--houses placidus -> P, --points all -> costante, --fixed-stars royal -> lista); --with/--without -> calculate_*; --set key=value whitelisted contro la firma di from_birth_data (rifiuta le chiavi _private); default online inferito da lat+lng+tz; dispatch from_birth_data/from_iso_utc_time. Separato in merge_inputs (ricetta ProfileInput) + materialize (chiamata factory), cosi' save persiste la ricetta e natal/verify la materializzano. - profiles: store JSON in $XDG_CONFIG_HOME/kerykeion/subjects/, file 0600 (birth data = PII) via os.open+fdopen; ProfileInput extra=forbid; resolve_path (file path -> nome nello store -> difflib suggestions). - rendering: render()/emit()/write_output() funnel unico (payload sempre su stdout via sys.stdout.write, mai rich.Console.print); dispatcher text/json/ xml/svg con import lazy (svg_out e' stub); text via probe ReportGenerator + fallback generico (list[str] un-per-riga, list[BaseModel] a blocchi, JSON); formats.resolve (explicit -f -> suffisso -o -> $KERYKEION_CLI_FORMAT -> TTY). - commands/subject: save/show/list/path/verify, registrato in app.py. - profiles.make_meta: rimosso refuso `backend = None` inutilizzato (F841). - rendering/text: consolidato il probe ReportGenerator in _try_report con un solo type: ignore[arg-type] documentato (mypy vede l'Union ristretta; il probe passa un BaseModel qualunque di proposito). verify materializza via effemeridi: Ada Lovelace 1815-12-10 18:00 London -> Sole in Sagittario, Luna Ariete, Asc Cancro. Gate: lint/analyze/typecheck verdi; imports:fresh OK (147); test:core 5670 passed / 144 skipped; cli:smoke OK; check end-to-end save->show->list->path-> verify con permessi 0600 e JSON valido in pipe. Co-Authored-By: Claude <noreply@anthropic.com>
Fase 5 del build-out kerykeion[cli]. - errors: ExitCode (0 ok, 1 unexpected, 3 extra mancante, 4 input, 5 KerykeionException, 6 ephemeris, 7 network, 8 sampling, 9 warnings-as-errors, 130 interrupted); classify() ordina i tipi backend PRIMA di ValueError (lo EphemerisRangeError di Skyfield e` ValueError subclass); _backend_error_types best-effort (cattura Skyfield; TODO BACKEND_ERROR_TYPES in MANDATORY_EVOLUTIONS Β§2 per i tipi nativi libephemeris/swisseph); error_boundary decorator + handle_uncaught (messaggio pulito di default, traceback con --traceback o per UNEXPECTED). FileNotFoundError -> exit 4 (copre ProfileNotFound). - typer_app: KerykeionTyper(typer.Typer) il cui command() wrappa ogni comando (top-level e sottocomandi delle subapp) con error_boundary. Separato da app.py perche' app esegue _register_commands come side-effect modulo-livello: se un comando importasse KerykeionTyper da app, l'ordine di cold-import del gate import-graph genererebbe un circular import partially-initialized (subject_app letto prima della definizione). - warnings: collector ricorsivo di ephemeris_warnings + polar_house_fallbacks che attraversa .subject / .first_subject / .second_subject (chart data model non li hanno diretti) e ricorre nei CompositeSubjectModel annidati, con cycle protection per id e dedup per contenuto. output_with_warnings emette il payload su stdout, i warning su stderr (sempre, anche con --format json, cosi' jq resta pulito) ed esce 9 con --warnings-as-errors (dopo aver emesso il payload). - commands/charts: natal (resolve + output_with_warnings, 4 formati). - app: opzioni globali --traceback / --warnings-as-errors sul root callback; registrazione di natal; run() avvolge app() con handle_uncaught come backstop. - subject_resolver: build_flags() spostato qui (condiviso da subject save e natal); options: SubjectProfile (-s). - svg_out: lo stub ora solleva KerykeionException (exit 5, clean) invece di NotImplementedError (exit 1 + traceback); sara` cablato con SVG reale in Fase 6. Verifiche: natal text/json/xml exit 0 (XML via to_context funziona sul subject); svg exit 5 clean; date malformate e profilo inesistente -> exit 4 clean senza traceback; --traceback mostra lo stack. Ada Lovelace 1815-12-10 -> Sole Sag. Gate: lint/analyze/typecheck verdi; imports:fresh OK (151); test:core 5670 passed / 144 skipped; cli:smoke OK. Co-Authored-By: Claude <noreply@anthropic.com>
Adds the rest of the chart commands on top of `natal`:
- `now` β a subject for the current moment (from_current_time), with the
same inline flag set as `natal`.
- `synastry` / `composite` β dual/midpoint charts of two stored profiles
(`-s` + `-S`).
- `transit` β natal vs a transit moment (now, or `--to-date`/`--to-time`).
- `return` β Solar/Lunar return dual wheel; the return-chart location
defaults to the natal birthplace (offline) and can be relocated with
`--lat/--lng/--tz` or `--city --online`. PlanetaryReturnFactory does its
own geocoding, unlike the other factories, so it needs the location
passed explicitly.
- `progression` β secondary progression dual wheel for `--target-year`.
SVG is now real: `svg_out.render_svg` goes through
`ChartDrawer.generate_svg_string()` (never `save_svg()`, which writes into
`Path.home()` and prints to stdout). `natal` emits the subject for
text/json/xml and builds a natal chart-data wrapper only for the svg path.
subject_resolver gains a `mode_override` ("current") so `now` and transit
without `--to-date` dispatch to `from_current_time`.
Co-Authored-By: Claude <noreply@anthropic.com>
Adds the three remaining command groups on top of the chart family. `technique <sub>` (subject-based, -s <profile>): profections, firdaria, zr (zodiacal releasing), receptions, horary, midpoints, directions (primary), acg (astro-cartography), stars (heliacal), nodes (planetary), relocate. `sky <sub>` (date/location-based, inline --lat/--lng/--tz or -s): sun-times, hours (planetary), voc (instant or --to range), eclipses (located or global), lunations, ingresses, stations. `ephemeris` / `transits` (top-level time series): ephemeris is a planet position series; transits runs it against a natal chart as per-sample aspects (--events collapses to discrete applyingβexactβseparating). Sampling guard: EphemerisDataFactory materialises one subject per sample, so a long range at a fine step is an OOM risk. The library's own max_days/max_hours/max_minutes defaults (730/8760/525600) raise a bare ValueError AFTER starting and map to the wrong exit. We read the ceilings off the factory signature, count samples BEFORE constructing anything, and raise SamplingLimitError (exit 8) if too large. --no-limit skips the pre-check and passes max_*=None through. errors.py gains SamplingLimitError (a ValueError subclass, so a missed classify branch still falls through sanely) mapped to exit 8. Co-Authored-By: Claude <noreply@anthropic.com>
`kerykeion call Factory.method` reaches any factory method (or bare
function) in kerykeion.__all__ without writing Python:
kerykeion call ProfectionsFactory.from_subject -s ada
kerykeion call DominantsFactory.from_subject -s ada --strategy modern
kerykeion call --list | --explain Factory.method
kerykeion call PlanetaryReturnFactory.next_return_from_date -s ada \
--param year=2000 --param return_type=Solar
Three modules:
* registry.py β the security layer. Only __all__ names, one split on '.',
no private members, no models/exceptions/protocols, getattr_static so no
descriptor fires. `call os.system` fails because 'os' is not in __all__ β
that is the test this command exists to pass.
* introspect.py β type coercion (bool/int/float/str/Literal/list/tuple/
datetime, multi-type Unions) and parameter classification (cli | subject
| json-only | unsupported) for --explain. Annotations are resolved with
get_type_hints so the factories' string forward-refs match.
* commands/call.py β --list/--explain and the invocation. Subject params
bind from -s/-S; everything else from --param key=value. classmethod/
static go through the owner so Python binds cls; instance factories
(PlanetaryReturnFactory, HeliacalFactory, ...) split the namespace into
__init__ vs method kwargs.
errors.py: TypeError now maps to exit 4 (a missing/wrong argument to a
dispatched factory is bad input, not a crash) alongside ValueError.
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 9 of the kerykeion[cli] feature. tests/core/test_cli.py β 25 cases, marker cli, identity (not snapshot): the CLI payload is asserted to equal the library model built from the same inputs (json.loads(stdout) == json.loads(model.model_dump_json())), so the suite is immune to ephemeris drift and there is no regenerate:cli task. Covers version/help/exit codes, clean error boundaries (no traceback), stdout-vs-stderr discipline, ANSI-free pipes, the 4 formats, the sampling limit (exit 8) and its --no-limit bypass, the call dispatcher security guarantee (os.system refused), entry-point registration, import-kerykeion stays typer-free, and the TTY-detection seam. Runs fully offline on base-tier dates; avoids the all_points token (skipped on Swiss Ephemeris). Documentation: README section + ToC entry, new site/docs/cli.md (order 64) linked from the index, DEVELOPMENT (cli/ in the tree, test:cli/cli:smoke tasks, why typer/rich live in both the extra and the dev group), TEST principle #7 (identity not snapshots), llms.txt CLI section, CHANGELOG [Unreleased], and a NOTICE block for the optional CLI dependencies. docs:snippets 400/400, docs:check 100%, ruff clean, cli:smoke OK. Co-Authored-By: Claude <noreply@anthropic.com>
Optional command-line interface (kerykeion[cli]): charts, techniques, sky events, time series, subject profiles and a guarded call dispatcher over __all__. Local engine, backend-agnostic; text on a TTY, JSON in a pipe; classified exit codes 0-9/130; import kerykeion stays free of typer. No calculation changed and no public name moved: __all__ is exactly a84, every Pydantic schema byte-identical. Adds typer+rich (MIT); no Apache-2.0 dependency added (NOTICE carries the new CLI license block). Co-Authored-By: Claude <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the βοΈ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an optional command-line interface,
kerykeion[cli], that exposes the whole library from the terminal β every chart type, analytical technique, sky event, factory and time series β without writing any Python.No calculation changed, and no public name moved.
kerykeion.__all__is exactly whata84exported, every Pydantic schema is byte-identical, andimport kerykeionis unchanged. The CLI is anextra:pip install kerykeionwithout it behaves exactly as before.Release:
6.0.0a85.What you can do from the terminal
natal,synastry,transit,composite,return,progression,nowtechnique(profections, firdaria, zodiacal releasing, receptions, horary, midpoints, directions, acg, stars, relocate)sky(eclipses, lunations, ingresses, stations, mundane, phenomena, nodes, sun-times, hours, voc)ephemeris,transits(with a pre-flight sampling guard β exit8)subject save|show|list|path|verify(perms0600β birth data is PII), reused via-s <name>call: a guarded dispatcher reaching any publicFactory.methodinkerykeion.__all__Design invariants
import kerykeionstays free oftyper. The console script is static metadata, so the command is installed even without the extra: it then prints an install hint and exits3β never a traceback. Enforced incli/__init__.py(lazy import),kerykeion/__init__.py(untouched β never importskerykeion.cli), thewalk_packagesexclusion intest_public_api_surface.py, and a dedicated subprocess test.sys.stdout/Path.write_text, neverrich.Console.print. Format is chosen for you: text on a TTY, JSON in a pipe (-f text|json|xml|svgforces). Warnings always go to stderr, even under-f json, so a piped payload stays clean.0ok Β·1unexpected Β·2usage Β·3extra-missing Β·4invalid-input Β·5KerykeionException Β·6ephemeris Β·7network Β·8sampling-limit Β·9warnings-as-errors Β·130interrupted.callresolves only names inkerykeion.__all__, split on a single., no private members, no activated descriptors.kerykeion call os.systemis refused by an explicit test.test_cli.pyis green underKERYKEION_BACKEND=libephemerisand=swisseph.Tests
tests/core/test_cli.py(25 cases,climarker) asserts identity, not snapshots: a command's payload== json.loads(model.model_dump_json())built from the same inputs. Identity is immune to ephemeris drift β if the Sun moves, both sides move together. There is deliberately noregenerate:clitask. The suite avoids theall_pointstoken (silently skipped on swisseph byconftest.py). Lives undertests/core/sopoe checkruns it; mostly in-process viatyper.testing.CliRunner, subprocess only where CliRunner can't prove the point (entry point,python -m kerykeion, real TTY).Dependencies & licensing
The extra adds typer and rich β both MIT. No Apache-2.0 dependency was added (the repo's standing rule).
NOTICEcarries a new "Optional command-line interface" block (typer, rich, shellingham ISC, annotated-doc, markdown-it-py, mdurl, pygments BSD-2, colorama BSD-3; Click is vendored by Typer under its MIT).typer>=0.26.4vendors Click β important becauseclick 8.3.1is already in the graph as a transitive oflibephemeris; vendoring eliminates a silent correctness dependence on whicheverclickanother dependency resolves.typer/richare declared in both the[cli]extra and the[dev]group β intentional, not redundancy:pyrightconfig.jsonruns inbasicmode wherereportMissingImportsis an error across the wholekerykeion/tree, so without them in dev every contributor'spoe typecheckfails onkerykeion/cli/. Documented inDEVELOPMENT.mdso it doesn't get "cleaned up".The local-gates-only policy is unchanged: no CI added,
.github/workflows/verified empty. New local taskspoe test:cliandpoe cli:smoke;poe build:smokegrew a third isolated environment that verifies the no-extra degradation.Gate results
poe qualityβ 5/5 (lint, analyze, typecheck, test, cli)poe test:extendedβ 10929 passed, 1390 skippedpoe test:libβ 5665 passed (libephemeris backend)tests/core/test_cli.pyβ 25 passed under bothKERYKEION_BACKEND=libephemerisand=swissephpoe build:smokeβ 3 environments green;--version (6.0.0a85), wheel-only degrades to exit3with the hint, wheel+[cli]functionalScope notes
engine=local), so a future--remoteis an addition, not a refactor.requestsis already a runtime dependency, so an HTTP client costs zero new deps. A future importable Python SDK for the API would be MIT and a separate package β out of scope here.1.48scost ofimport kerykeion(eager__init__.py) is not addressed here; making it lazy via PEP 562 touches every user of the library and is tracked as a separate issue.Commits
10 phases, one commit each (plus the release): metadata β lazy-import guard & entry points β smoke gates β subject & rendering β error boundary &
natalβ chart family & SVG β techniques/sky/series βcalldispatcher β tests & docs β release6.0.0a85.π€ Generated with Claude Code