Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to `fabric-comanage-api` are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] - 2026-10-21

### Security

- Bumped `urllib3` minimum to `>=2.8.0` to remediate
[CVE-2026-97688](https://github.com/advisories) (affects 2.6.2 through 2.7.x).
- Declared `idna>=3.15` as a direct runtime dependency. The 0.2.2 fix for
CVE-2026-45409 only pinned `idna` in `uv.lock`, so installs from PyPI could
still resolve a vulnerable version.

## [0.2.2] - 2026-05-28

### Security
Expand Down
95 changes: 32 additions & 63 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,40 @@
# CLAUDE.md
# fabric-comanage-api

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
Python 3 client wrapper for the [COmanage REST API v1](https://spaces.at.internet2.edu/display/COmanage/REST+API+v1), published to PyPI as `fabric-comanage-api`. Part of the FABRIC Testbed. MIT.

## Project Overview
## Stack
Python >=3.10. Runtime deps: `requests`, `urllib3`, `idna` (pinned directly for CVE floors). Dev: `pytest`, `requests-mock`, `python-dotenv`, `ruff`. setuptools backend (PEP 621); version sourced dynamically from `comanage_api.__VERSION__`.

**fabric-comanage-api** — a Python 3 client wrapper for the [COmanage REST API v1](https://spaces.at.internet2.edu/display/COmanage/REST+API+v1), published to PyPI as `fabric-comanage-api` (current version 0.2.0). Part of the [FABRIC Testbed](https://github.com/fabric-testbed) project. MIT licensed.

## Build & Install

```bash
# Development setup with uv
uv venv --python 3.12
uv pip install -e ".[dev]"

# Install from PyPI
pip install fabric-comanage-api
```

Build is configured via `pyproject.toml` (PEP 621 metadata, setuptools backend). Version is sourced from `comanage_api.__VERSION__`. Requires Python >= 3.9.

## Dependencies

Runtime: `requests>=2.25.0`. Dev: `pytest`, `requests-mock`, `python-dotenv` (installed via `pip install -e ".[dev]"`).

## Testing

Unit tests use `pytest` + `requests-mock`. Run with:
## Conventions
- `ComanageApi` (`comanage_api/__init__.py`) is the single public class. It holds connection state + a `requests.Session` (HTTP Basic Auth) and centralizes all I/O in helpers `_get`/`_post`/`_put`/`_delete`/`_get_by_entity`.
- Each API domain lives in its own private module as a mixin class (e.g. `COUsMixin`, `SshKeysMixin`); `ComanageApi` inherits all nine and they call the shared HTTP helpers via `self`. Methods named `<resource>_<action>` (e.g. `cous_add`, `cous_view_all`).
- `_coorgidentitylinks.py` — CoOrgIdentityLink (requires COmanage v4.0.0+)
- `_copeople.py` — CoPerson
- `_copersonroles.py` — CoPersonRole
- `_cous.py` — COU (Collaborative Organizational Unit)
- `_emailaddresses.py` — EmailAddress
- `_identifiers.py` — Identifier
- `_names.py` — Name
- `_orgidentities.py` — OrgIdentity
- `_sshkeys.py` — SshKey (requires COmanage v4.0.0+, experimental)
- Methods validate params against instance-level option sets (`STATUS_OPTIONS`, `AFFILIATION_OPTIONS`, `SSH_KEY_OPTIONS`, `ENTITY_OPTIONS`, `PERSON_OPTIONS`, `EMAILADDRESS_OPTIONS`) before any HTTP call; bad input raises `ValueError`.
- Unimplemented endpoints raise `NotImplementedError`.
- Commit messages reference GitHub issues with `[#N]` prefix.

## Commands
```bash
uv run pytest -v
uv venv --python 3.12 && uv pip install -e ".[dev]" # dev setup
uv run pytest -v # tests (requests-mock, no live calls)
uv run ruff check . && uv run ruff format . # lint/format (line-length 120, examples/ excluded)
```
Examples in `examples/` hit a LIVE COmanage instance: copy `template.env` -> `.env`, fill creds, then `uv run python examples/cous_example.py`.

138 tests across 10 files in `tests/` cover all endpoint modules: successful responses, parameter validation (`ValueError`), HTTP error propagation, and edge cases (deduplication, 204 empty, `parent_id=0`).
## Gotchas
- All requests have a configurable timeout (default 30s) and auto-retry w/ exponential backoff on 429/500/502/503/504.
- `_coorgidentitylinks.py` requires COmanage v4.0.0+; `_sshkeys.py` requires v4.0.0+ and is experimental.
- Logging via stdlib `logging` under the `comanage_api` logger — silent by default.
- Config env vars: `COMANAGE_API_USER`/`_PASS`, `COMANAGE_API_CO_NAME`/`_CO_ID`, `COMANAGE_API_URL`, optional `COMANAGE_API_SSH_KEY_AUTHENTICATOR_ID`.

The `examples/` directory contains per-endpoint scripts that exercise the API against a live COmanage instance. To run them, copy `template.env` to `.env`, fill in credentials, then run individual example scripts (e.g., `uv run python examples/cous_example.py`).

## Architecture

`ComanageApi` (in `comanage_api/__init__.py`) is the single public class. It holds connection state (`_CO_API_URL`, `_CO_API_USER`, `_CO_API_PASS`, `_CO_API_ORG_ID`, `_CO_API_ORG_NAME`) and a `requests.Session` for HTTP Basic Auth.

**HTTP helpers** on `ComanageApi` (`_get`, `_post`, `_put`, `_delete`, `_get_by_entity`) centralize all request/response handling. Each API domain module delegates to these helpers.

Each API domain lives in its own private module (`_copeople.py`, `_cous.py`, `_sshkeys.py`, etc.). These modules define standalone functions that accept a `ComanageApi` instance as `self`. The `__init__.py` imports these functions and wraps them as methods on `ComanageApi`, creating a facade.

**API endpoint modules:**
- `_coorgidentitylinks.py` — CoOrgIdentityLink (requires COmanage v4.0.0+)
- `_copeople.py` — CoPerson
- `_copersonroles.py` — CoPersonRole
- `_cous.py` — COU (Collaborative Organizational Unit)
- `_emailaddresses.py` — EmailAddress
- `_identifiers.py` — Identifier
- `_names.py` — Name
- `_orgidentities.py` — OrgIdentity
- `_sshkeys.py` — SshKey (requires COmanage v4.0.0+, experimental)

**Pattern for each module:** functions named `<resource>_<action>` (e.g., `cous_add`, `cous_view_all`, `cous_delete`) that build paths/params/bodies and delegate to `self._get()`, `self._post()`, `self._put()`, or `self._delete()`. Unimplemented endpoints raise `NotImplementedError`.

## Configuration

Environment variables (see `template.env`):
- `COMANAGE_API_USER` / `COMANAGE_API_PASS` — API credentials
- `COMANAGE_API_CO_NAME` / `COMANAGE_API_CO_ID` — target CO
- `COMANAGE_API_URL` — registry base URL
- `COMANAGE_API_SSH_KEY_AUTHENTICATOR_ID` — optional SSH key authenticator plugin ID

## Conventions

- Instance-level constants for valid option sets: `STATUS_OPTIONS`, `AFFILIATION_OPTIONS`, `SSH_KEY_OPTIONS`, `ENTITY_OPTIONS`, `PERSON_OPTIONS`, `EMAILADDRESS_OPTIONS`.
- All API methods validate parameters against these option sets before making HTTP calls.
- Commit messages reference GitHub issues with `[#N]` prefix.
## Don't
- Don't run `examples/` against production without verifying `.env` points at a test CO — they perform real add/delete.
2 changes: 1 addition & 1 deletion comanage_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ._sshkeys import SshKeysMixin

# fabric-comanage-api version
__VERSION__ = "0.2.2"
__VERSION__ = "0.2.3"

# Library logging: NullHandler prevents "last resort" output for callers
# who don't configure logging. Callers who want logs should add their own
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ classifiers = [
]
dependencies = [
"requests>=2.32.4",
"urllib3>=2.7.0",
"urllib3>=2.8.0",
"idna>=3.15",
]

[project.optional-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_retry_adapter_mounted(self, api, mock_adapter):
assert isinstance(adapter, HTTPAdapter)

def test_version(self):
assert __VERSION__ == '0.2.2'
assert __VERSION__ == '0.2.3'


class TestOptionSets:
Expand Down
14 changes: 8 additions & 6 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading