Skip to content

openconceptlab 0.2.0: the API client as an importable core package - #14

Merged
paynejd merged 10 commits into
mainfrom
feat/507-openconceptlab-core
Oct 21, 2026
Merged

paynejd merged 10 commits into
mainfrom
feat/507-openconceptlab-core

Conversation

@paynejd

@paynejd paynejd commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Linked Issue

Closes OpenConceptLab/ocl_online#507

Summary

The API client that ocl uses becomes an importable package, openconceptlab, so other tools can use it without the CLI. It ships in the same PyPI distribution, openconceptlab. The ocl command and its output don't change. Version 0.2.0.

  • Lint and CI. ruff check src tests was failing, with 16 errors. They're fixed: import sorting and unused imports, plus one unused variable. A new workflow runs ruff and pytest on every PR, on Python 3.10 and 3.13, with a read-only token.
  • The core package.
    • The client moves from src/ocl_cli/api_client.py to src/openconceptlab/client.py, which doesn't import Click.
    • openconceptlab exports OCLAPIClient, APIError and __version__.
    • ocl_cli.api_client is now a two-name shim, so the command modules and their tests don't change.
  • One version. pyproject.toml is the only source. Before, ocl_cli/__init__.py hardcoded 0.1.0 while the package was 0.1.1.
  • Constructor options: OCLAPIClient(base_url=None, token=None, *, auth_scheme="Token", client_name=None, headers=None, timeout=30.0).
    • client_name sets X-OCL-CLIENT and the User-Agent. It defaults to openconceptlab/<version>, and the CLI passes ocl-cli/<version>, as before.
    • auth_scheme="Bearer" sends an OIDC access token instead of an API token.
    • headers are added to every request and override the defaults, whatever the case of the header names.
  • Requests stay on the API. A request hook refuses any request outside base_url's origin and path, including URLs taken from API responses, so the Authorization header only ever goes to the API.
  • APIError.body holds the error response's parsed JSON, or its text when it isn't JSON. detail, the message and to_dict() are unchanged, because the CLI prints them.
  • Retries.
    • A POST is retried only when the connection failed, so the request never reached the server. Before, a POST that timed out while waiting for the response was sent again, so an import or a $match could run twice. GET, PUT, PATCH and DELETE retry as before.
    • Once retries run out, every method raises the httpx exception (httpx.ReadTimeout and so on), not tenacity.RetryError.
  • match_concepts takes rows, full $match rows with properties such as concept_class, datatype or synonyms, and reranker=True.
  • list_expansion_concepts lists the concepts in a collection version's expansion, a page at a time. It uses the version's default expansion unless an expansion ID is given.
  • changelog calls $changelog for two versions of a source or collection. While OCL is still computing it, it returns {"status": "pending", ...}: HTTP 202 after OCL's own wait, or 409 when the same request is already queued.
  • Docs. The README has a short "Use as a Python library" section, which also says what the library raises. CLAUDE.md points API logic at the core.

Test Plan

  • pytest -q: 49 passed, up from 24. The new tests/test_core_package.py and tests/test_client.py use pytest-httpx.
  • ruff check src tests: all checks passed.
  • The 0.2.0 wheel was built and installed into a clean environment. import openconceptlab and ocl --version both work there.
  • CI is green on this PR (Python 3.10 and 3.13).

Notes

  • Releasing 0.2.0 to PyPI and tagging v0.2.0 happen after merge.
  • The review summaries follow as comments.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH

paynejd and others added 9 commits October 21, 2026 09:09
…eptlab core package

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
…eme, headers, timeout

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
… failed; raise the httpx error after retries

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
…ersions

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
…IError.body, error docs, CI permissions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
@paynejd paynejd added the type/refactor Code restructuring, no behavior change label Oct 21, 2026
@paynejd

paynejd commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Codex review, pass 1 (codex-cli 0.163.0, commit 5169185): clean, no findings. It reviewed the full diff against the ticket's acceptance criteria, the repository's conventions and its tests. The full pass is on the tracking ticket.

@paynejd

paynejd commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Simplification review (Claude subagent, commit 5169185): two possible test cuts, both declined. One is the check that the exported APIError is an exception type. The other is the test that pins the terms-to-rows path the CLI uses. No commits were added, so the code Codex pass 1 reviewed is the final code. Details are on the tracking ticket.

…rigin

A request hook on the httpx client refuses any request outside base_url's
origin and path, including URLs taken from API responses, so the
Authorization header only ever goes to the API.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvHhCgtEqAD6xKyJAbToDH
@paynejd

paynejd commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Adversarial review of token handling (Claude subagent, commit 5169185): one issue was fixed in this PR, and one hardening item is deferred.

  • Fixed in a544126: URLs taken from API responses or from callers could take the client to another host, along with its Authorization header. A request hook on the client now refuses any request outside base_url's origin and path before anything is sent, and three tests pin it.
  • Deferred to a follow-up: URL-encoding the identifiers in request paths. It's older than this PR and changes how the CLI sends some identifiers, so it needs its own change.
  • Checked and fine: redirects, error contents, logging, and thread safety with one client per user.

The full report is on the tracking ticket. A Codex check of a544126 follows.

@paynejd

paynejd commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Codex check of commits 5169185..a544126 (codex-cli 0.163.0): clean. The origin-guard commit breaks nothing, legitimate requests still work, and refusals are neither retried nor leak the token. Details are on the tracking ticket.

@paynejd

paynejd commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Adversarial re-verification (commit a544126): no material issues. The origin guard closes the cross-host finding, and legitimate requests still work. Four low-severity hardening notes about the hook are recorded on the tracking ticket as candidates for a later patch release.

@paynejd
paynejd merged commit 7aac08a into main Oct 21, 2026
2 checks passed
@paynejd
paynejd deleted the feat/507-openconceptlab-core branch October 21, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant