Skip to content

feat(ogar-from-ruff): Python/Odoo lift consuming ruff_python_spo#131

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/odoo-rs-transcode-lf8ya5
Jun 29, 2026
Merged

feat(ogar-from-ruff): Python/Odoo lift consuming ruff_python_spo#131
AdaWorldAPI merged 1 commit into
mainfrom
claude/odoo-rs-transcode-lf8ya5

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

Adds the Python-aware lift path to ogar-from-ruff, consuming the now-merged ruff_python_spo frontend (ruff #34, merged to main at 80d7f2cb). This is the OGAR half of the odoo-rs transcode track: ruff produces the Odoo/Python SPO corpus, OGAR lifts it into ogar_vocab::Class with the correct producer-language discriminant.

Before this change, the lift hard-coded Language::Ruby, so an Odoo ModelGraph (namespace odoo) lifted as if produced by ruff_ruby_spo (Rails). The projection was otherwise correct — only the language stamp was wrong.

What changed

  • Refactor lift_model / lift_model_graph to delegate to private lift_model_with_language / lift_model_graph_with_language.
  • Add public lift_model_python / lift_model_graph_python that stamp Language::Python.
  • The odoo namespace already routes to the erp source domain and odoo curator via the existing classify_domain path — the Python wrapper changes only the language discriminant, nothing else in the projection.
  • Language is set explicitly per producer rather than guessed from ModelGraph::namespace, because the namespace (openproject, odoo, …) does not bind one-to-one to the producer language.

Tests

Two regression tests added:

  • lift_model_python_stamps_python_language
  • lift_model_graph_python_stamps_python_and_keeps_erp_domain (asserts Python language and erp domain / odoo curator preserved)

Verification

Verified via an isolated probe workspace (path-dep real ogar-vocab + git-dep ruff main @ 80d7f2cb): 28 tests pass, clippy -D warnings clean.

The OGAR workspace itself cannot resolve in-sandbox (surrealdb git dep returns 403 via ogar-adapter-surrealql), so the probe is the verification path. Cargo.toml already pins ruff branch = "main"; the lift compiles against both pre- and post-merge ruff. The OGAR Cargo.lock could not be regenerated in-sandbox for the same surrealdb resolution reason.

Notes

  • Pre-existing rustfmt debt in lib.rs (the crate is not fmt-gated, no rustfmt.toml) was left untouched; the added code matches the file's existing style.

🤖 Generated with Claude Code


Generated by Claude Code

…odel_graph_python)

The ruff_python_spo frontend (ruff #34, merged to main at 80d7f2cb)
emits the Odoo/Python SPO corpus under the `odoo` namespace. OGAR's
lift previously hard-coded `Language::Ruby`, so an Odoo ModelGraph
lifted with the wrong producer-language discriminant.

Refactor `lift_model` / `lift_model_graph` to delegate to private
`lift_model_with_language` / `lift_model_graph_with_language` and add
public `lift_model_python` / `lift_model_graph_python` that stamp
`Language::Python`. The projection is otherwise identical: the `odoo`
namespace already routes to the `erp` source domain and `odoo` curator
via the existing `classify_domain` path, so the Python wrapper changes
only the language discriminant, nothing else.

Language is set explicitly (per producer) rather than guessed from
`ModelGraph::namespace`, because the namespace (openproject, odoo, …)
does not bind one-to-one to the producer language.

Two regression tests:
- lift_model_python_stamps_python_language
- lift_model_graph_python_stamps_python_and_keeps_erp_domain

Verified via an isolated probe workspace (path-dep real ogar-vocab +
git-dep ruff main @ 80d7f2cb): 28 tests pass, clippy -D warnings clean.
The OGAR workspace itself cannot resolve offline (surrealdb git dep
403 via ogar-adapter-surrealql), so the probe is the verification path.

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7495afba1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/// projection to [`lift_model`]; only the language discriminant differs.
#[must_use]
pub fn lift_model_python(model: &Model) -> Class {
lift_model_with_language(model, Language::Python)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Map Python fields before exposing Odoo path

For ruff_python_spo/Odoo graphs with fields.X declarations, the ruff IR records those as Model::fields (the core SPO shape), while lift_model_with_language only reads the Rails-side model.attributes/model.associations vectors and never populates Class.attributes, Class.associations, or Class.computed_fields from fields. Delegating the new Python API directly to that projection means a normal Odoo model can be stamped Python/erp but lose its schema, so downstream consumers receive empty classes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Codex's P1 is correct: stamping Language::Python is necessary but not sufficient. An Odoo ModelGraph carries its schema in the core-7 Model::fields vector (the fields.X(...) declarations), while lift_model_with_language only reads the Rails-side model.attributes / model.associations / model.callbacks / model.validations (all empty for Odoo). So the Python lift currently produces a class stamped Python/erp with an empty schema.

Fix in progress — a Python-only project_odoo_fields pass that maps Model::fields onto the existing Class columns:

  • non-relational field → Attribute
  • compute field (emitted_by set) → ComputedField (method + @api.depends)
  • relational field (target set) → Association, with the kind chosen from the field's cardinality:
    • many2oneBelongsTo, one2manyHasMany, many2manyHasAndBelongsToMany

That last mapping needs a disambiguator: target + inverse_name alone cannot tell a Many2one from a Many2many (both are comodel-only, no inverse). I've filed the companion ruff change that surfaces the cardinality — AdaWorldAPI/ruff#35 (relation_kind predicate). Once #35 lands on ruff main (which this crate pins via branch = "main"), I'll push the project_odoo_fields commit here and CI will pick up relation_kind. The projection is already implemented and probe-verified against that ruff branch.


Generated by Claude Code

@AdaWorldAPI AdaWorldAPI merged commit e915bda into main Jun 29, 2026
1 check passed
AdaWorldAPI pushed a commit that referenced this pull request Jun 29, 2026
…odex P1 #131)

The Python lift previously only set the language discriminant; it read the
Rails-side AR-DSL vectors (model.attributes / model.associations / …),
which are empty for an Odoo model. An Odoo model carries its schema in the
core-7 `Model::fields` vector, so the lifted class came out with empty
attributes / associations / computed_fields — downstream consumers got a
schema-less class (Codex P1 on #131).

Add a Python-only `project_odoo_fields` pass mapping `Model::fields` onto
the existing Class columns:
- relational field (target set) -> Association; kind from `relation_kind`
  (many2one->BelongsTo, one2many->HasMany, many2many->HasAndBelongsToMany),
  class_name = raw comodel, inverse_of = One2many inverse.
- non-relational field -> Attribute.
- compute field (emitted_by set) -> ComputedField (method + @api.depends),
  in addition to its Attribute/Association.

Gated on Language::Python: Rails ALSO populates Model::fields (DB columns),
so projecting them for Rails would double-count its AR-DSL surface.

Adds `ComputedField::new(field, compute_method)` to ogar-vocab (it lacked a
constructor; #[non_exhaustive] makes a struct literal non-constructible
cross-crate — parallels Association::new / Attribute::new).

Consumes ruff's `relation_kind` predicate (AdaWorldAPI/ruff#35): target +
inverse_name alone can't separate a Many2one from a Many2many (both
comodel-only, no inverse), so the kind is required to pick the right
AssociationKind.

Tests: lift_model_python_projects_odoo_fields (scalar/relational/compute,
incl. the M2O-vs-M2M case) + lift_model_ruby_does_not_project_fields
(Ruby gating). Verified via probe (local ruff w/ relation_kind + real
ogar-vocab): 30 tests pass, clippy -D warnings clean.

NOTE: not yet pushed — gated on ruff#35 landing on ruff main + an
OGAR Cargo.lock ruff-commit bump.

Co-Authored-By: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Owner Author

The Codex P1 from this PR (the Python lift dropping Odoo schema because it never projected Model::fields) is fixed in the follow-up #132, now that the companion ruff relation_kind predicate (AdaWorldAPI/ruff#35) has merged. #132 adds the Python-only project_odoo_fields pass (attributes / associations / computed_fields).


Generated by Claude Code

AdaWorldAPI pushed a commit that referenced this pull request Jun 30, 2026
…sembler

Adds the keystone gap named in E-KEEP-AR-REMOVE-ORM / the OP convergence
assessment: a Rails-correct sibling of compile_graph_python. Identical
shape (mint_graph::<P> + per-class facet resolution) but routes through
the existing lift_model_graph (Language::Ruby) instead of
lift_model_graph_python — pure operator-reuse, no new lift, and
project_odoo_fields is correctly never invoked for Rails (it would
double-count; lift_model_graph_python's own doc-comment says so).

Proves the convergence claim in code: compile_graph_ruby::<OpenProjectPort>
on a WorkPackage graph and compile_graph_ruby::<RedminePort> on an Issue
graph mint to the SAME low-u16 concept (0x0102 project_work_item) and
DIFFERENT high-u16 render prefixes (0x0001 vs 0x0007) — one canonical
concept, two render skins, machine-checked rather than asserted.

Drive-by fix: 3 pre-existing Function{...} literal constructions
(emit.rs, mint.rs's account_move fixture, lib.rs) broke against the
already-merged ruff#38 (writes/calls fields) because this crate's
ruff_spo_triplet dep floats on branch=main. Added ..Default::default()
to each — no behavior change, restores compilation.

Verification: standalone probe workspace (path-dep ogar-vocab +
ogar-from-ruff, git-dep ruff branch=main) — the OGAR workspace itself
can't resolve in-sandbox (ogar-adapter-surrealql's surrealdb-ast git dep
403s), the same pattern prior PRs (#131/#132/#136/#138/#141) used.
44/44 tests pass (3 new + 41 pre-existing unbroken); clippy --no-deps
-D warnings clean at the pinned 1.95.0 toolchain (ogar-vocab itself has
pre-existing unrelated clippy debt from never being --workspace-gated,
out of scope here); doc-links resolve.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants