Skip to content

Tests - #45

Merged
r-fedorov merged 15 commits into
mainfrom
tests
Jul 22, 2026
Merged

Tests#45
r-fedorov merged 15 commits into
mainfrom
tests

Conversation

@r-fedorov

@r-fedorov r-fedorov commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Introduce shared recursive naming protocols and tree-node builders while improving stereochemical substituent formatting and updating packaging and CI configuration.

New Features:

  • Add helpers to build naming tree nodes and shortcut tree nodes with a shared, schema-complete structure.
  • Introduce a RecursiveSubgraphNamer protocol to standardize recursive branch/subgraph naming contracts.
  • Add RenderedSubstituentName to carry metadata about optional outer parentheses around substituent names.

Enhancements:

  • Refactor component and substituent shortcut tree construction to use the shared tree-node builders and metadata merging.
  • Tighten handling of optional outer parentheses for complex substituent prefixes to avoid unnecessary parentheses while preserving semantic boundaries.
  • Prevent tree metadata from overwriting invariant fields when assembling naming tree nodes.

Build:

  • Bump project version from 0.1.3 to 0.1.4 and configure Hatch build to exclude examples and evaluation directories from distributions.

CI:

  • Adjust the paper-evaluations GitHub Actions job to run on pull requests targeting the tests branch instead of direct pushes.

Documentation:

  • Update README development instructions to reference the correct openclatura repository and directory name.

Tests:

  • Add tests ensuring naming tree builders share a common schema and avoid mutable default pitfalls.
  • Add regression tests covering stereochemical substituent naming and the omission or retention of optional parentheses around substituents.

Summary by CodeRabbit

  • New Features
    • More robust stereochemical substituent naming with smarter optional parenthesis handling.
    • Improved naming/trace output fidelity for assembled structures.
  • Bug Fixes
    • Fixed nested and reused stereochemical substituents to preserve correct semantic boundaries, including locanted disambiguation.
    • Improved consistency in naming tree generation.
  • Documentation
    • Updated development instructions to point to the correct repository.
  • Chores
    • Bumped version to 0.1.4 and refined build packaging; adjusted CI to run evaluations for the right pull-request branch.

@sourcery-ai

sourcery-ai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduce shared naming tree builders and a common RecursiveSubgraphNamer protocol, refactor callers to use them, and add stereochemical substituent boundary metadata plus tests and build/config tweaks.

File-Level Changes

Change Details Files
Add reusable helpers for constructing naming/tree nodes and refactor existing tree construction to use them.
  • Introduce build_naming_tree_node to construct the invariant portion of component/substituent tree nodes with safe defaults and optional metadata merging.
  • Introduce build_shortcut_tree_node to build schema-complete shortcut tree nodes, delegating invariant fields to build_naming_tree_node.
  • Refactor assembly_substituent_tree to return a fragment node via build_naming_tree_node with invariant fields and move stereo/hydrogen/hydro-operation/charge data into metadata.
  • Refactor component shortcut tree construction in name_component to use a new _component_shortcut_tree helper backed by build_shortcut_tree_node.
  • Refactor _shortcut_substituent_tree in namer to use build_shortcut_tree_node for substituent shortcuts and keep functional_prefix as an additional field when present.
src/openclatura/trace_helpers.py
src/openclatura/component_namer.py
src/openclatura/namer.py
Centralize the recursive subgraph naming callable contract and update all callers to use the shared protocol.
  • Remove locally defined SubgraphNamer and BranchNamer Protocols and related typing imports from several modules.
  • Add new naming_protocols.py defining the RecursiveSubgraphNamer Protocol with overloads covering trace and tree-return modes.
  • Update function signatures and type hints in component_namer, component_modifiers, special_cases, heteroatom_subgraphs, functional_prefixes, and substituent_tokens to use RecursiveSubgraphNamer instead of per-module protocols or generic callables.
src/openclatura/component_namer.py
src/openclatura/component_modifiers.py
src/openclatura/special_cases.py
src/openclatura/heteroatom_subgraphs.py
src/openclatura/functional_prefixes.py
src/openclatura/substituent_tokens.py
src/openclatura/naming_protocols.py
Track and exploit metadata about optional outer parentheses on stereochemical substituent names to improve formatting and semantics.
  • Add RenderedSubstituentName subclass of str that carries an outer_parentheses_optional flag in assembly_parts.
  • Change various sites that construct AssemblyParts/SubstituentItem names to import and use RenderedSubstituentName where stereochemical boundary metadata is needed.
  • Add _mark_optional_substituent_boundary helper in namer to wrap finalized substituent names (or NameAssemblyResult.text) in RenderedSubstituentName when stereo features and finalize_subgraph conditions are met.
  • Add _omit_optional_outer_parentheses helper in assembly_prefixes that unwraps a RenderedSubstituentName’s outer parentheses when the substituent is simple, singly counted, unlocanted, and uniquely grouped, leaving complex or disambiguating parentheses intact.
  • Wire _omit_optional_outer_parentheses into format_substituent_prefixes so that optional parentheses may be dropped while still re-added when needed for complex/multiple cases.
src/openclatura/assembly_parts.py
src/openclatura/namer.py
src/openclatura/assembly_prefixes.py
Extend tests to cover the new tree construction helpers and stereochemical substituent parentheses behavior.
  • Import new helpers and types (RenderedSubstituentName, build_naming_tree_node, build_shortcut_tree_node) into the analysis test module.
  • Add test ensuring build_naming_tree_node and build_shortcut_tree_node share the same core schema, sort atom ids, avoid mutable default sharing, and respect nested_decisions and substituents isolation.
  • Add tests verifying that unlocanted stereochemical substituent names can omit optional outer parentheses, while locanted or nested stereochemical substituents keep parentheses to preserve disambiguation and semantic boundaries.
  • Add regression tests confirming methane naming and hydrogen cyanide nitrile name behavior remain correct under new changes.
src/openclatura/tests/test_analysis.py
Adjust packaging, documentation, and CI behavior for this project rename and evaluation workflow.
  • Bump project version from 0.1.3 to 0.1.4 in pyproject.toml.
  • Add Hatch build exclude list to omit examples, evaluations, and eval_failures from built distributions.
  • Update README development instructions to clone and cd into the openclatura repo instead of the previous iupac-name-generator name.
  • Change paper-evaluations CI job condition to run on pull requests targeting the tests branch instead of pushes to tests.
pyproject.toml
README.md
.github/workflows/ci.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f250dc59-3328-4cae-bbd8-7be637c92b8e

📥 Commits

Reviewing files that changed from the base of the PR and between ade7a0c and 0239393.

📒 Files selected for processing (11)
  • src/openclatura/assembly_parts.py
  • src/openclatura/assembly_prefixes.py
  • src/openclatura/component_modifiers.py
  • src/openclatura/component_namer.py
  • src/openclatura/formatting.py
  • src/openclatura/functional_prefixes.py
  • src/openclatura/heteroatom_subgraphs.py
  • src/openclatura/namer.py
  • src/openclatura/naming_protocols.py
  • src/openclatura/tests/test_analysis.py
  • src/openclatura/trace_helpers.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openclatura/naming_protocols.py
  • src/openclatura/heteroatom_subgraphs.py

📝 Walkthrough

Walkthrough

The PR adds a shared recursive naming protocol, standardizes naming-tree construction and shortcut metadata, preserves stereochemical substituent boundaries during rendering, adds regression tests, and updates package versioning, build exclusions, development instructions, and CI conditions.

Changes

Naming and metadata flow

Layer / File(s) Summary
Shared recursive naming contract
src/openclatura/naming_protocols.py, src/openclatura/*namer*.py, src/openclatura/*subgraphs.py, src/openclatura/*prefixes.py, src/openclatura/*tokens.py, src/openclatura/component_modifiers.py, src/openclatura/special_cases.py
RecursiveSubgraphNamer defines overloaded return shapes for names, traces, and trees, replacing local branch-namer aliases and protocols across naming helpers.
Naming-tree schema and shortcut integration
src/openclatura/trace_helpers.py, src/openclatura/component_namer.py, src/openclatura/namer.py, src/openclatura/tests/test_analysis.py
Shared builders normalize naming-tree fields and metadata; component and substituent shortcut paths use them, with regression coverage for schema, metadata validation, and mutable defaults.
Optional stereochemical parentheses
src/openclatura/assembly_parts.py, src/openclatura/assembly_prefixes.py, src/openclatura/formatting.py, src/openclatura/namer.py, src/openclatura/component_modifiers.py, src/openclatura/component_namer.py, src/openclatura/trace_helpers.py, src/openclatura/tests/test_analysis.py
RenderedSubstituentName carries optional-boundary metadata, allowing formatting to omit parentheses for eligible unlocanted substituents while retaining disambiguating and nested boundaries.
Project delivery configuration
pyproject.toml, README.md, .github/workflows/ci.yml
The package version is raised to 0.1.4, build exclusions are added, setup instructions target openclatura, and paper evaluations run for pull requests targeting tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NamingAssembly
  participant RenderedSubstituentName
  participant PrefixFormatting
  NamingAssembly->>RenderedSubstituentName: attach optional outer-parentheses metadata
  RenderedSubstituentName->>PrefixFormatting: provide rendered substituent name
  PrefixFormatting->>PrefixFormatting: conditionally unwrap or preserve parentheses
Loading

Suggested reviewers: adrianm0

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic and does not describe the actual changes in the pull request. Replace it with a concise title that names the main change, such as recursive naming protocol and tree-builder updates.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai 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.

Hey - I've found 2 issues, and left some high level feedback:

  • RenderedSubstituentName stores metadata on a str subclass instance attribute, which may be surprising or fragile when values are copied or transformed; consider using a lightweight wrapper/dataclass that holds both text and boundary metadata explicitly instead of extending str.
  • build_naming_tree_node rejects overlapping metadata keys at runtime, but callers currently pass ad‑hoc dicts; it may be safer to centralize the allowed metadata fields or use a typed structure so additions/renames are caught at type-check time rather than via ValueError.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- RenderedSubstituentName stores metadata on a str subclass instance attribute, which may be surprising or fragile when values are copied or transformed; consider using a lightweight wrapper/dataclass that holds both text and boundary metadata explicitly instead of extending str.
- build_naming_tree_node rejects overlapping metadata keys at runtime, but callers currently pass ad‑hoc dicts; it may be safer to centralize the allowed metadata fields or use a typed structure so additions/renames are caught at type-check time rather than via ValueError.

## Individual Comments

### Comment 1
<location path="src/openclatura/naming_protocols.py" line_range="8-17" />
<code_context>
+class RecursiveSubgraphNamer(Protocol):
</code_context>
<issue_to_address>
**suggestion:** RecursiveSubgraphNamer’s positional upstream_atom parameter may be slightly mismatched with existing implementations’ keyword-only signatures.

The protocol currently declares `upstream_atom` as positional-or-keyword before `*`, while concrete implementations make it keyword-only (`*, upstream_atom=None, ...`). Although runtime behavior is unaffected, type checkers may flag this mismatch. Please update the protocol overloads to place `upstream_atom` after `*` so it is explicitly keyword-only and consistent with the implementations.

Suggested implementation:

```python
    @overload
    def __call__(
        self,
        mol: Molecule,
        start_idx: int,
        exclude_atoms: set[int],
        *,
        upstream_atom: int | None = None,

```

The protocol likely has additional overloads, and possibly a non-overload `__call__` signature, that also accept `upstream_atom`. To fully align the protocol with the concrete implementations’ keyword-only signatures, you should:
1. Update every `__call__` overload in `RecursiveSubgraphNamer` to move `upstream_atom` after `*` (making it keyword-only) in the same way as shown in the edit.
2. Ensure the non-overload `__call__` declaration (if present) also declares `*, upstream_atom: int | None = None, ...` rather than taking `upstream_atom` positionally.
This will keep type checker expectations consistent with implementations using `*, upstream_atom=None, ...`.
</issue_to_address>

### Comment 2
<location path="src/openclatura/tests/test_analysis.py" line_range="319" />
<code_context>
+    assert name_smiles(smiles) == "(3R)-3-cyclohexylcyclohexylbenzene"
+
+
+def test_locanted_stereochemical_substituent_keeps_disambiguating_parentheses():
+    parts = AssemblyParts(
+        parent_length=6,
+        is_ring=True,
+        retained_name="benzene",
+        parent_atom_symbols_by_locant={str(locant): "C" for locant in range(1, 7)},
+        substituents=[
+            SubstituentItem(
+                name=RenderedSubstituentName(
+                    "((3R)-3-cyclohexylcyclohexyl)",
+                    outer_parentheses_optional=True,
+                ),
+                locants=["1"],
+            ),
+            SubstituentItem(name="methyl", locants=["4"]),
+        ],
+    )
+
+    assert assemble_name(parts) == "1-((3R)-3-cyclohexylcyclohexyl)-4-methylbenzene"
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a case where a stereochemical substituent is itself used as a substituent to verify boundary preservation

These tests cover optional outer parentheses on stereochemical substituents attached to the parent. `_omit_optional_outer_parentheses` also handles the case where `parts.is_substituent` is `True`: when a `RenderedSubstituentName` is itself reused as a substituent, its outer parentheses must be kept. Please add a test that builds such an `AssemblyParts` (with `is_substituent=True` and a `RenderedSubstituentName` having `outer_parentheses_optional=True`) and asserts that the parentheses are preserved in this nested substituent scenario.

```suggestion
    assert name_smiles(smiles) == "(3R)-3-cyclohexylcyclohexylbenzene"


def test_stereochemical_substituent_reused_as_substituent_preserves_optional_outer_parentheses():
    parts = AssemblyParts(
        parent_length=2,
        is_ring=False,
        is_substituent=True,
        retained_name="ethyl",
        parent_atom_symbols_by_locant={str(locant): "C" for locant in range(1, 3)},
        substituents=[
            SubstituentItem(
                name=RenderedSubstituentName(
                    "((3R)-3-cyclohexylcyclohexyl)",
                    outer_parentheses_optional=True,
                ),
                locants=["1"],
            )
        ],
    )

    assert assemble_name(parts) == "1-((3R)-3-cyclohexylcyclohexyl)ethyl"
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/openclatura/naming_protocols.py Outdated
def test_single_unlocanted_stereochemical_substituent_omits_optional_outer_parentheses():
smiles = "C1(CCCCC1)[C@H]1CC(CCC1)C1=CC=CC=C1"

assert name_smiles(smiles) == "(3R)-3-cyclohexylcyclohexylbenzene"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add a case where a stereochemical substituent is itself used as a substituent to verify boundary preservation

These tests cover optional outer parentheses on stereochemical substituents attached to the parent. _omit_optional_outer_parentheses also handles the case where parts.is_substituent is True: when a RenderedSubstituentName is itself reused as a substituent, its outer parentheses must be kept. Please add a test that builds such an AssemblyParts (with is_substituent=True and a RenderedSubstituentName having outer_parentheses_optional=True) and asserts that the parentheses are preserved in this nested substituent scenario.

Suggested change
assert name_smiles(smiles) == "(3R)-3-cyclohexylcyclohexylbenzene"
assert name_smiles(smiles) == "(3R)-3-cyclohexylcyclohexylbenzene"
def test_stereochemical_substituent_reused_as_substituent_preserves_optional_outer_parentheses():
parts = AssemblyParts(
parent_length=2,
is_ring=False,
is_substituent=True,
retained_name="ethyl",
parent_atom_symbols_by_locant={str(locant): "C" for locant in range(1, 3)},
substituents=[
SubstituentItem(
name=RenderedSubstituentName(
"((3R)-3-cyclohexylcyclohexyl)",
outer_parentheses_optional=True,
),
locants=["1"],
)
],
)
assert assemble_name(parts) == "1-((3R)-3-cyclohexylcyclohexyl)ethyl"

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openclatura/component_namer.py`:
- Around line 601-612: Update _component_shortcut_tree to accept mol as its
first argument and pass _bond_ids_within(mol, component_atoms) to
build_shortcut_tree_node as bond_ids. Update all three component shortcut call
sites, including the single-atom, structural-replacement-parent, and
anhydride-component paths, to pass mol first while preserving their existing
component, binding, and token-span arguments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e01092c-4844-4ea6-97ae-67e26dd0687c

📥 Commits

Reviewing files that changed from the base of the PR and between 08d0a73 and ade7a0c.

📒 Files selected for processing (15)
  • .github/workflows/ci.yml
  • README.md
  • pyproject.toml
  • src/openclatura/assembly_parts.py
  • src/openclatura/assembly_prefixes.py
  • src/openclatura/component_modifiers.py
  • src/openclatura/component_namer.py
  • src/openclatura/functional_prefixes.py
  • src/openclatura/heteroatom_subgraphs.py
  • src/openclatura/namer.py
  • src/openclatura/naming_protocols.py
  • src/openclatura/special_cases.py
  • src/openclatura/substituent_tokens.py
  • src/openclatura/tests/test_analysis.py
  • src/openclatura/trace_helpers.py

Comment on lines +601 to +612
def _component_shortcut_tree(
name: str, component_atoms: set[int], bindings: list[dict], token_spans: list[dict]
) -> dict:
"""Return a minimal component tree for shortcut component names."""

return {
"kind": "component",
"name": name,
"atoms": sorted(component_atoms),
"bonds": [],
"parent": None,
"principal_group": None,
"substituents": [],
"replacement_prefixes": [],
"unsaturations": [],
"trace_segments": [],
"nested_decisions": [],
"name_atom_bindings": bindings,
"name_token_spans": token_spans,
}
return build_shortcut_tree_node(
kind="component",
name=name,
atom_ids=component_atoms,
name_atom_bindings=bindings,
name_token_spans=token_spans,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

_component_shortcut_tree never populates bond_ids, unlike its substituent-side sibling.

namer.py's _shortcut_substituent_tree passes bond_ids=_bond_ids_within(mol, component) into build_shortcut_tree_node, but this component-side counterpart omits bond_ids entirely, so the resulting tree node's "bonds" field is always []. This is harmless for the single-atom shortcut path (line 279) but drops real bond data for the structural-replacement-parent (line 323) and anhydride-component (line 378) shortcuts, which can span multiple bonded atoms (e.g. biphenyl, anhydride halves).

🔧 Proposed fix
-def _component_shortcut_tree(
-    name: str, component_atoms: set[int], bindings: list[dict], token_spans: list[dict]
+def _component_shortcut_tree(
+    mol: Molecule, name: str, component_atoms: set[int], bindings: list[dict], token_spans: list[dict]
 ) -> dict:
     """Return a minimal component tree for shortcut component names."""
 
     return build_shortcut_tree_node(
         kind="component",
         name=name,
         atom_ids=component_atoms,
+        bond_ids=bond_ids_within(mol, component_atoms),
         name_atom_bindings=bindings,
         name_token_spans=token_spans,
     )

And update the three call sites (lines 279, 283, 323, 327, 378, 382) to pass mol as the first argument.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openclatura/component_namer.py` around lines 601 - 612, Update
_component_shortcut_tree to accept mol as its first argument and pass
_bond_ids_within(mol, component_atoms) to build_shortcut_tree_node as bond_ids.
Update all three component shortcut call sites, including the single-atom,
structural-replacement-parent, and anhydride-component paths, to pass mol first
while preserving their existing component, binding, and token-span arguments.

@r-fedorov
r-fedorov merged commit 1e43668 into main Jul 22, 2026
22 checks passed
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