feat: Gemara v1 schemas as Pydantic v2 models - #1
Conversation
Replaces complytime-labs/gemara-py as a new distribution: `py-gemara` on the index, imported as `gemara.v1`. `gemara` is a PEP 420 namespace package, so a future `gemara.v2` can ship separately and install side by side. Pipeline. `poe sync-schema` exports every #Definition as JSON Schema and vendors the merged result, its provenance, and the upstream good-*/bad-* corpus. `poe generate` is hermetic -- no cue, no network -- reads that schema, and emits _models.py and _registry.py, both committed behind a `git diff --exit-code` drift gate that CI runs on PRs and now on the release path too. JSON Schema rather than upstream's OpenAPI 3.0.3 projection, which loses integer types, flattens date-time to date, and leaks a hidden _uniqueTermIds field as required. Three repair passes from the old generator are gone. Two new ones exist: recovering an array `allOf`'s element type (EnforcementLog.actions was landing as list[Any], hiding two structurally-detectable bad fixtures), and reopening CUE's closed structs so unknown properties are ignored rather than rejected -- without which every additive minor upstream breaks every installed reader until it re-syncs. Unknown properties are dropped, not retained, matching go-gemara and keeping the models honest as a typed surface. API. `load`/`loads` dispatch on metadata.type through the generated registry and return a GemaraDocument union, so consumers narrow with `match` and no isinstance ladder. Every failure raises from one hierarchy: GemaraError, or UnknownDocumentTypeError naming the offending value and the 13 valid ones. pyyaml sits behind a `yaml` extra; the core needs only pydantic. Known limitations, stated in the README rather than discovered later: these are structural validators, not full Gemara validators -- 12 of 17 bad-* fixtures parse, because CUE enforces cross-field semantics that cannot survive projection into JSON Schema, and `cue vet` remains the source of truth. Reading a document written against a newer minor and writing it back does not preserve its unknown fields. Assisted-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
…dabot The workflows tripped every high-severity zizmor audit. Fixed at the source rather than suppressed: - unpinned-uses: pin every action to a commit SHA, keeping the current major (checkout v5.1.0, setup-uv v7.6.0, upload-artifact v4.6.2, download-artifact v4.3.0, gh-action-pypi-publish v1.14.2). Major bumps are left for dependabot to propose so they get reviewed. - excessive-permissions: default CI to contents: read. - artipacked: persist-credentials: false on every checkout, so the job token is not left behind in .git/config for later steps to pick up. - cache-poisoning: enable-cache: false for setup-uv on the release path, which builds the artifacts that get published and must not restore a cache a pull request run could have written. Add a CI job running zizmor so this cannot regress. It reports via inline annotations instead of SARIF, which keeps it working without code scanning enabled on the repository. Add dependabot for github-actions and uv. SHA pins only stay safe if they also stay current, and the 7-day cooldown avoids opening a PR for a release fresh enough that a malicious version likely has not been yanked yet. Assisted-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
…lows
Four changes from review feedback.
pyyaml moves from a `[yaml]` extra into `dependencies`. The extra made the
default install unable to read the format the ecosystem actually uses: 35 of the
36 vendored fixtures are YAML, so `pip install py-gemara` followed by
`load("catalog.yaml")` -- the most obvious first thing anyone does -- failed with
an install-the-extra message. It also bought a branching `_parse` with a bespoke
fallback error and an exception contract that had to promise behaviour
"regardless of whether the optional yaml extra is installed". go-gemara depends
on goccy/go-yaml outright. `_parse` is now four lines.
README is trimmed to what a consumer needs: install, usage, the newer-minor
reading behaviour, and the known limitations. The usage example now shows `match`
narrowing over the GemaraDocument union, which typechecks under `mypy --strict`
without the `isinstance` assert the old example carried. Development
instructions, the sync-schema/generate pipeline, the never-edit-generated-files
rule, and the release procedure move to a new CONTRIBUTING.md.
tests/test_readme.py is deleted. It policed prose placement by slicing the file
between `##` headings, which is brittle -- rewording that improves the prose
breaks CI -- and a substring cannot verify that something is "stated plainly"
anyway. SEMANTIC_GAPS already fails if the validation strength it describes
actually changes, which is the property worth guarding.
TestPyPI publishing splits into its own workflow_dispatch workflow. Chaining it
ahead of PyPI on a tag meant you could not rehearse without burning a real
version, and a TestPyPI hiccup -- most often "version already exists" -- would
fail the job and block a PyPI release that was otherwise fine. It now runs
manually against any ref with skip-existing, and release.yml goes straight from
build to PyPI. Verified clean under zizmor.
Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
…groups Three fixes from review. Codegen no longer shells out. datamodel_code_generator.generate() takes the schema as a mapping and returns the source, so the temp file, the JSON round-trip, the cwd juggling and the stderr parsing all go away, and the CLI need not be on PATH. The header's recorded filename is now set explicitly via input_filename rather than relying on the CLI deriving it from the input's basename -- which is what the temp-dir dance existed to control. Verified byte-identical to the previous CLI invocation, and the drift gate confirms the committed output is unchanged. A side benefit worth having: the API types `preset` as a Literal of valid names, so CODEGEN_PRESET is now `Final` and a typo in it is a mypy error rather than a runtime failure partway through generation. Dependency groups are split by purpose -- test, lint, codegen -- with dev including all three. Everything was previously lumped into dev, so running the test suite pulled datamodel-code-generator, which is only needed to regenerate models. CI jobs and contributors can now install just what they use. Project URLs pointed at jpower432/py-gemara, which is a fork; corrected to gemaraproj/py-gemara. Assisted-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Adds a concurrency group so a push supersedes the in-flight run for the same ref, which is the largest practical saving on an active pull request: the older run's result is already irrelevant. `main` is excluded so pushes there always produce a complete record. Narrows installs per job now that dependency groups are scoped. The test matrix takes `--group test` alone: 12 packages instead of 34, across four parallel jobs, since it needs neither the linters nor the code generator. The drift job takes codegen and lint -- generate.py formats its own output with ruff -- and calls tools/generate.py directly, poe being a dev-group tool. Quality keeps the full sync: mypy typechecks tools/generate.py and the test suite, so it needs essentially everything anyway, and narrowing it would save only the task runner. Also enables the uv cache on lower-bounds, the one uv job that lacked it. Fixes a regression from the previous commit while doing so: moving codegen in-process put `from datamodel_code_generator import ...` at module scope in tools/generate.py, which coupled the whole test suite to that dependency at collection time -- test_generate.py imports the module for its pure functions. The import is now inside run_codegen, so the module stays importable without the generator installed. That coupling is exactly what surfaced when the test job stopped installing it. Each job's install and commands were run locally as CI will run them. Assisted-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
|
Keeping this in draft pending a final review and more exhaustive local testing. |
Add from_text and from_file constructors to generated document models. Fold transparent hidden schema definitions during generation and reorganize associated tests by package domain. Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Update the package metadata, repository links, and lockfile for the gemara-python distribution name. Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Document typed document loading, distribution installation, schema update steps, and the TestPyPI rehearsal tag convention. Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Run workflow security checks independently and install the published TestPyPI package before executing the fixture suite against it. Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Remove redundant comments while preserving the existing update policy. Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
merge_exports() previously only checked nested-def-vs-nested-def agreement, letting a root export silently clobber a stored nested def of the same name. Also drop a misleading `or []` on Lexicon.terms, which is a required field and can never be falsy. Assisted-by: Claude (Anthropic, Opus 5) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
eddie-knight
left a comment
There was a problem hiding this comment.
🤖 Multi-model review: three independent reviewers (haiku, sonnet, opus) worked from a local worktree at cb89523, then every claim was re-verified against source, the pinned toolchain, and GitHub's docs before posting. Local run on Python 3.11: 117 tests pass, ruff check and mypy --strict clean, poe generate produces no drift, ruff format --check . fails. GitHub CI has not executed on this PR at all yet, so none of the workflows have been exercised. Inline comments are limited to findings with a drop-in suggestion; everything else is here.
Questions for the author
- What does the
Catalog/Logruntime tier (commit 532865c, no body) give a consumer? At runtime every subclass redeclares every base field, so nothing is inherited, and the README never mentions either name. If it isisinstancenarrowing across catalogs, a sentence in the README and a test that fails without the tier would settle it; otherwise it looks removable. Details under Major below. - The description says
py-gemaraand_registery.py; the package isgemara-pythonsince commit 6290126 and the file is_registry.py. Worth fixing before merge so the merged description is not the first stale doc.
Focus questions
- PEP 8 / idiom / typing / packaging: substantively clean. PEP 420 namespace + src layout,
py.typed,Self, pydantic v2 idiom, mypy strict all check out. One deviation the repo's own formatter catches:_loader.py:1-2(inline). - Linters in CI, CI vs local: everything is wired, but three seams: the two workflows cancelling each other (inline on
zizmor.yml:16andci.yml:9), a CONTRIBUTING instruction that cannot run becausepoethepoetis in the wrong dependency group (Major below), and the format check that CI runs but no local task or doc mentions, which is how the docstring failure got through. Poe is used for some commands and bypassed for others; pick one. - DRY: docs duplicate the sync procedure in two files and one copy already contradicts the test file it describes (Major below). Four copies of the skip-guard shell block, two of the drift gate, two
sys.path.insert(inline onpyproject.toml:77), one duplicated decode branch, and two suppression mechanisms for one lint line that neither fires (inline on__init__.py:6andpyproject.toml:59-61). - YAGNI / readability for new maintainers: the
Catalog/Logtier above; two.gitignoreentries for AI-tooling scratch dirs that do not exist in the repo (inline); a temp-file round trip inrun_codegenthat its own docstring says does not exist; and prose that points at a commit, a file path, and four numbered "Defects" that exist nowhere in this repo.
Blocking
The format failure on _loader.py:1-2 (CI quality job is red as submitted, inline). The concurrency-group collision (inline) will start biting on the first PR that touches a workflow.
Major (no drop-in fix, so not inline)
CONTRIBUTING.md:13-14vspyproject.toml:32,36— "uv sync --only-group lintis enough to run the linters" cannot work:poethepoetis declared only in thecodegengroup, so the documenteduv run poe lint/poe typecheckfail with nopoeon the path. CI hits the same seam: the test job runsuv run pytestraw, the quality job runsruff format --check .raw, and the drift job can callpoe generateonly because codegen happens to carry it. Two clean options: putpoethepoetin a group every documented caller includes (or plaindev) and drop the--only-group lintclaim; or drop poe entirely, since the six tasks are each a one-word command that CONTRIBUTING can list directly, as CI already does for two of them.schemas/README.md:48-53— states thatSEMANTIC_GAPSentries "are known CUE cross-field rules that JSON Schema cannot currently express", whichtests/test_fixtures.py:30-40explicitly denies: not every entry is that, two former entries turned out to be codegen fidelity losses, and "still in this set" means "not yet proven recoverable". A maintainer reading the README stops investigating exactly the entries the test file asks them to investigate. Lines 20-40 are also a second copy ofCONTRIBUTING.md:16-53, same three-line bash block verbatim. Pick one owner for the procedure and link from the other; for the policy, point at the comment intest_fixtures.pyrather than restating it.tools/generate.py:102-110(prioritize_category_bases) — nothing in the PR says what theCatalog/Logtier buys. Commit 532865c has no body,README.mdnever mentions either name, and at runtime the sharing is empty: all ten Catalog/Log subclasses redeclare every base field, so the set of fields inherited-without-redeclaring is empty for each (checked viamodel_fieldsagainst each class's own annotations). The tier costs this pass (no reference to it undertests/), thereplaceat lines 297-298, andtest_registry.py:49-58, which asserts the inheritance exists but not that it does anything. Either state the consumer benefit and add a test that fails without the tier, or delete this function, the tworeplacelines, and the two registry tests; every document model then sits onGemaraDocumentModelwith identical fields and behaviour.
Minor (no drop-in fix, so not inline)
tools/generate.py:297-298— same root cause as the tier above. If it stays, thisreplaceneeds thecount() != 1guard that lines 300-302 give the document models; today a changed declaration shape silently no-ops and surfaces as a confusingtest_registryfailure instead of aGenerateError.tools/generate.py:272-285(run_codegen) — the docstring says nothing is serialised to disk, but the body writes to aTemporaryDirectoryand reads it back.datamodel_code_generator.generatereturns the source asstrwhenoutputis omitted (its own docstring: "When output is None and single module: str"). Dropping the temp dir,output=,read_textand thetempfileimport passes mypy strict and ruff and regenerates byte-identical_models.py/_registry.py(checked in the worktree). The return type isstr | GeneratedModules | None, so keep oneisinstance(source, str)guard that raisesGenerateErrorotherwise.src/gemara/v1/_loader.py:104-110— a copy of_loads_aslines 57-60, comment included. One_text(text) -> strhelper (return text if isinstance(text, str) else _decode(text)) collapses both:loadsbecomes_dispatch(_parse(_text(text)))and_loads_asbecomesmodel.model_validate(_parse(_text(text)))..github/workflows/ci.yml:32-40— the skip-guard shell block appears four times (release.yml:33-42,publish-testpypi.yml:26-33and:64-73), the drift gate twice (release.yml:43-48), and the tomllib version one-liner twice with different interpreters (release.yml:28viauv run python,publish-testpypi.yml:55barepython). The skip guard belongs in the test suite, not in shell around it. Atests/conftest.pypytest_sessionfinishhook that setssession.exitstatus = pytest.ExitCode.TESTS_FAILEDwhenterminalreporter.stats.get("skipped")is non-empty does it in four lines (verified: exit 1 with a skip, 0 without; mypy strict and ruff clean). Every job then runs plainpytest, andsummary.txtand its.gitignoreentry go away.tests/tools/test_sync_schema.py:11andtest_generate.py:12— bothsys.path.insertlines and their# noqa: E402go away with thepythonpathchange suggested inline onpyproject.toml:77.CONTRIBUTING.md:47-49— points at a "known limitation documented onrecover_array_allof_element_type" that commit 3347a49 deleted in this same PR;tools/generate.py:213-218is now a five-line docstring with no limitation text. Restore the one sentence there or drop the pointer.tests/tools/test_schema.py:7-8— commita21d87eis not in this repository's history (this PR is the initial import), andtests/test_sync_schema.pydoes not exist (the file istests/tools/test_sync_schema.py). Same pattern with "Defect 1", "Defect 3" and "Defect 4" atci.yml:34,release.yml:26,35,test_generate.py:79andtest_registry.py:34, cited against a numbered list that exists nowhere in the repo. Replace each with the fact it stands for or delete it.tools/sync_schema.py:119-120— the root-vs-nested conflict guard is the head commit's behavioural change and has no test;test_merge_exports_rejects_conflicting_nested_defs(tests/tools/test_sync_schema.py:51-57) exercises only the nested-vs-nested branch at 113-114. One mirror test with a root export that disagrees with a stored nested def covers it.tools/generate.py:359andtools/sync_schema.py:180—main() -> intplussys.exit(main())promises an exit-code contract, but every failure path raises the tool's own error uncaught, so the return value is always0and a maintainer gets a traceback. Either catchGenerateError/SyncErrorin__main__and return 1 with the message, or drop the wrapper and let the exception be the interface.
Recorded so they don't get re-raised
- Tooling security:
subprocess.runwith argument lists, noshell=True;yaml.safe_load. Fine. - Workflow supply chain: every action SHA-pinned,
persist-credentials: falseeverywhere,contents: readdefault, uv cache off on the publish path. Fine. - Drift gate: regeneration in the worktree is byte-identical.
- Error surface: no
yaml.YAMLErrororUnicodeDecodeErrorescapesloads; tests cover tab-indented JSON and undecodable bytes. from_text/from_filego beyond "mimicgemara.Load" but are documented and tested; keep.- The 218-line
.gitignoretemplate predates this PR (only 9 lines were added); trimming it is a follow-up, not a PR finding. - Two reviewers rated the docstring format failure Critical; downgraded here because it is a one-line, zero-runtime-effect fix.
- One reviewer counted the sync pipeline as documented three times including the README's Known limitations section; that section does not describe it. Two copies.
sync_schema.py:41-47has the same multi-line-summary docstring shape as_loader.pybut ruff does not flag it; left out under "no nitpicking".
| [tool.pytest.ini_options] | ||
| testpaths = ["tests"] | ||
| addopts = "-ra" | ||
| pythonpath = ["tests"] |
There was a problem hiding this comment.
🤖 Minor — tests/tools/test_generate.py:12 and test_sync_schema.py:11 each hand-roll sys.path.insert(0, .../"tools") plus a # noqa: E402 on the import that follows, while this setting already does the job for tests. Adding tools here and deleting both sys.path lines keeps all 28 tool tests and mypy strict green (checked; mypy_path already includes tools).
| pythonpath = ["tests"] | |
| pythonpath = ["tests", "tools"] |
sonupreetam
left a comment
There was a problem hiding this comment.
Complementing @eddie-knight's review. I tested the library end-to-end against every fixture and API path, and runtime behavior is solid.
| return source.read() | ||
|
|
||
|
|
||
| def _dispatch(raw: Any) -> GemaraDocument: |
There was a problem hiding this comment.
Minor — Every other function in this module has a docstring. _dispatch is the core routing logic and would benefit from one for consistency:
| def _dispatch(raw: Any) -> GemaraDocument: | |
| def _dispatch(raw: Any) -> GemaraDocument: | |
| """Route a parsed mapping to the model selected by metadata.type.""" |
| from gemara.v1._models import __all__ as _MODEL_NAMES | ||
| from gemara.v1._registry import DOCUMENT_TYPES, SCHEMA_VERSION, GemaraDocument | ||
|
|
||
| __all__ = [ |
There was a problem hiding this comment.
Minor — The package exposes SCHEMA_VERSION (upstream schema version) but not its own package version. For a PyPI-distributed library, gemara.v1.__version__ is a common expectation for troubleshooting and CI version checks.
from importlib.metadata import version as _pkg_version
__version__ = _pkg_version("gemara-python")| with: | ||
| name: dist | ||
| path: dist/ | ||
| - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 |
There was a problem hiding this comment.
Minor — publish-testpypi.yml has a verify-published job that installs from the index and runs tests against the published artifact. The production release.yml has no equivalent — a packaging misconfiguration (wrong module path, missing py.typed) would ship to real users undetected. Consider adding a mirrored verification step with a brief delay for PyPI index propagation.
There was a problem hiding this comment.
Interesting. Certainly something to consider. The purpose of the testpypi workflow is to uncovered hidden issues with the published packaged. Maybe we could create an internal action that does validation from a package from a given index.
| if not source.is_dir(): | ||
| raise SyncError(f"{ref} has no test/test-data directory") | ||
|
|
||
| if FIXTURE_DIR.exists(): |
There was a problem hiding this comment.
Minor — vendor_fixtures unconditionally deletes schemas/fixtures/ via shutil.rmtree before re-populating. If the subsequent clone or copy fails (network error, disk full, tag missing test/test-data), the working tree is left with an empty directory and poe test fails with no clear recovery path.
Consider writing to a temp directory first, then swapping:
with tempfile.TemporaryDirectory(dir=SCHEMA_DIR) as staging:
# copy fixtures into staging/
if FIXTURE_DIR.exists():
shutil.rmtree(FIXTURE_DIR)
Path(staging).rename(FIXTURE_DIR)Or at minimum, mention git checkout -- schemas/fixtures/ in the error output.
| assert len(BAD) == 17 | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("path", GOOD, ids=lambda p: p.stem) |
There was a problem hiding this comment.
Minor — This test calls load(path) but makes no assertion on the result — "no exception" is the entire pass criterion. A minimal assertion would make the test self-documenting:
def test_good_fixture_validates(path: Path) -> None:
doc = load(path)
assert doc.metadata.type in DOCUMENT_TYPES| assert name in message | ||
|
|
||
|
|
||
| def test_missing_type_raises_unknown_document_type_with_none() -> None: |
There was a problem hiding this comment.
Minor — There's a test for {"metadata": {}} (missing type) but none for a mapping with no metadata key at all (e.g., {}), which exercises the isinstance(metadata, dict) → False branch in _dispatch. Consider adding:
def test_missing_metadata_key_raises_unknown_document_type() -> None:
with pytest.raises(UnknownDocumentTypeError) as excinfo:
loads(json.dumps({}))
assert excinfo.value.value is None|
|
||
| ## Reference | ||
|
|
||
| - `DOCUMENT_TYPES` contains the supported document models. |
There was a problem hiding this comment.
Nit — GemaraDocument is the return type of load/loads and is exported in __all__, but isn't mentioned here. Users who type-annotate their code need to know about it:
- `GemaraDocument` is the union type of all document models — the return type of `load` and `loads`.|
Thanks @eddie-knight and @sonupreetam. You are awesome! Working through the review feedback now. |
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> Co-authored-by: Eddie Knight <21176439+eddie-knight@users.noreply.github.com> Signed-off-by: Jennifer Power <jpower@redhat.com>
Signed-off-by: Jennifer Power <jpower@redhat.com> Co-authored-by: Eddie Knight <21176439+eddie-knight@users.noreply.github.com>
Yep. This was for
Fixed! @eddie-knight after our discussion on ecosystem convention for package names, I looked a bit more and found the |
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> # Conflicts: # pyproject.toml
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Assisted-by: OpenCode (OpenAI, GPT 5.6 Terra) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
sonupreetam
left a comment
There was a problem hiding this comment.
Re-reviewed at 8fdb9b7. All CI gates pass locally (tests, lint, format, typecheck, drift). Ran E2E tests and integration tests against the updated code. Everything is solid.
Summary
This PR adapts
complytime-labs/gemara-py(unreleased) as a new distribution.gemara-pythonon the index, imported asgemara.v1.gemarais a PEP 420 namespace package, so afuture
gemara.v2can ship separately and install side by side.Approach
How Types are generated
There is only one package for
v1so the tooling is designed to be forward compatible. Unknown fields (additive, optional) are ignored instead of generating an error. Pydantic v2 types are used to do structural and syntactic validation.datamode-codegenis used to create the Pydantic types from JSON Schemas.Basic loading capabilities are included to mimic the behavior of
gemara.Loadingo-gemara. Conversion capabilities are not included on this first iteration.Related Issues
Closes gemaraproj/community#12