Skip to content

Commit ea91a25

Browse files
committed
Fix linting and additional doc details
1 parent fd458da commit ea91a25

12 files changed

Lines changed: 99 additions & 76 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525

2626
- **`IntentLease.from_dict()` KeyError on server responses**`acquire_lease()` threw `KeyError('status')` because the server's `LeaseResponse` model does not include a `status` field (it uses `acquired_at`, `expires_at`, and `released_at` to represent lease state). `IntentLease.from_dict()` now derives status from these fields: `RELEASED` if `released_at` is set, `EXPIRED` if `expires_at` is in the past, otherwise `ACTIVE`. Also handles the field name difference `acquired_at` (server) vs `created_at` (SDK). Backward compatible with the SDK's own serialization format.
2727

28+
- **Stale database singleton after server restart**`get_database()` cached the `Database` instance at module level and never checked whether `database_url` changed between calls. When the protocol server restarted on a different port (e.g., `openintent_server_8001.db``openintent_server_8002.db`), the singleton kept pointing at the old file. Writes went to the old database; reads came from the new (empty) one — intents appeared created but were invisible to `list_intents`. The singleton now tracks its URL and recreates the connection when the URL changes.
29+
2830
- **Example and test updates** — All examples (`basic_usage.py`, `openai_multi_agent.py`, `multi_agent/coordinator.py`, `compliance_review/coordinator.py`) and tests updated to use dict-format constraints.
2931

3032
### Changed

CONTRIBUTING.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ Thank you for your interest in contributing to the OpenIntent Python SDK! This d
2525

2626
We use the following tools for code quality:
2727

28-
- **Black** for code formatting
29-
- **Ruff** for linting
28+
- **Ruff** for linting and code formatting
3029
- **mypy** for type checking
30+
- **pre-commit** for automatic formatting on every commit
31+
32+
Set up pre-commit hooks (runs automatically with `make install-dev`):
33+
34+
```bash
35+
pip install pre-commit
36+
pre-commit install
37+
```
3138

3239
Before submitting a PR, run:
3340

3441
```bash
35-
black openintent/
42+
ruff format openintent/
3643
ruff check openintent/ --fix
3744
mypy openintent/
3845
```

Makefile

Lines changed: 57 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,89 +8,90 @@ PORT ?= 8000
88
MCP_ROLE ?= reader
99

1010
help: ## Show this help message
11-
@echo "OpenIntent SDK available targets:"
12-
@echo ""
13-
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
14-
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
15-
@echo ""
16-
@echo "Variables (override with VAR=value):"
17-
@echo " PORT Server port (default: $(PORT))"
18-
@echo " MCP_ROLE MCP server role (default: $(MCP_ROLE))"
19-
@echo " PYTHON Python binary (default: $(PYTHON))"
11+
@echo "OpenIntent SDK — available targets:"
12+
@echo ""
13+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
14+
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
15+
@echo ""
16+
@echo "Variables (override with VAR=value):"
17+
@echo " PORT Server port (default: $(PORT))"
18+
@echo " MCP_ROLE MCP server role (default: $(MCP_ROLE))"
19+
@echo " PYTHON Python binary (default: $(PYTHON))"
2020

2121
install: ## Install SDK with server + all adapters
22-
$(PIP) install -e ".[server,all-adapters]"
22+
$(PIP) install -e ".[server,all-adapters]"
2323

2424
install-dev: ## Install SDK with dev + server + all extras
25-
$(PIP) install -e ".[dev,server,all-adapters]"
25+
$(PIP) install -e ".[dev,server,all-adapters]"
26+
@if command -v pre-commit >/dev/null 2>&1; then pre-commit install; fi
2627

2728
install-all: ## Install everything including MCP dependencies
28-
$(PIP) install -e ".[dev,server,all-adapters]"
29-
$(PIP) install mcp
30-
npm install -g @openintentai/mcp-server
29+
$(PIP) install -e ".[dev,server,all-adapters]"
30+
$(PIP) install mcp
31+
npm install -g @openintentai/mcp-server
3132

3233
server: ## Start the OpenIntent server
33-
openintent-server --port $(PORT)
34+
openintent-server --port $(PORT)
3435

3536
test: ## Run the full test suite
36-
$(PYTHON) -m pytest tests/ -v
37+
$(PYTHON) -m pytest tests/ -v
3738

3839
test-quick: ## Run tests without slow markers
39-
$(PYTHON) -m pytest tests/ -v -m "not slow"
40+
$(PYTHON) -m pytest tests/ -v -m "not slow"
4041

4142
test-mcp: ## Run MCP-related tests only
42-
$(PYTHON) -m pytest tests/ -v -k "mcp"
43+
$(PYTHON) -m pytest tests/ -v -k "mcp"
4344

4445
lint: ## Run linter + formatter check + type checker
45-
ruff check openintent/
46-
black --check openintent/
47-
mypy openintent/
46+
ruff check openintent/
47+
ruff format --check openintent/
48+
mypy openintent/
4849

4950
format: ## Auto-format code
50-
black openintent/ tests/
51-
ruff check --fix openintent/
51+
ruff format openintent/ tests/ examples/
52+
ruff check --fix openintent/
5253

5354
typecheck: ## Run type checker only
54-
mypy openintent/
55+
mypy openintent/
5556

5657
setup-mcp: ## Install MCP dependencies (Python + Node)
57-
@echo "Installing Python MCP SDK..."
58-
$(PIP) install mcp
59-
@echo "Installing OpenIntent MCP server (Node)..."
60-
npm install -g @openintentai/mcp-server
61-
@echo ""
62-
@echo "MCP setup complete. Next steps:"
63-
@echo " make server — Start the OpenIntent server"
64-
@echo " make mcp-server — Start the MCP server (in another terminal)"
65-
@echo " make full-stack — Start both together"
58+
@echo "Installing Python MCP SDK..."
59+
$(PIP) install mcp
60+
@echo "Installing OpenIntent MCP server (Node)..."
61+
npm install -g @openintentai/mcp-server
62+
@echo ""
63+
@echo "MCP setup complete. Next steps:"
64+
@echo " make server — Start the OpenIntent server"
65+
@echo " make mcp-server — Start the MCP server (in another terminal)"
66+
@echo " make full-stack — Start both together"
6667

6768
mcp-server: ## Start the MCP server (connects to local OpenIntent server)
68-
OPENINTENT_SERVER_URL=http://localhost:$(PORT) \
69-
OPENINTENT_API_KEY=dev-user-key \
70-
OPENINTENT_MCP_ROLE=$(MCP_ROLE) \
71-
$(NPX) -y @openintentai/mcp-server
69+
OPENINTENT_SERVER_URL=http://localhost:$(PORT) \
70+
OPENINTENT_API_KEY=dev-user-key \
71+
OPENINTENT_MCP_ROLE=$(MCP_ROLE) \
72+
$(NPX) -y @openintentai/mcp-server
7273

7374
full-stack: ## Start OpenIntent server + MCP server together
74-
@echo "Starting OpenIntent server on port $(PORT)..."
75-
@openintent-server --port $(PORT) &
76-
@sleep 2
77-
@echo "Starting MCP server (role: $(MCP_ROLE))..."
78-
@OPENINTENT_SERVER_URL=http://localhost:$(PORT) \
79-
OPENINTENT_API_KEY=dev-user-key \
80-
OPENINTENT_MCP_ROLE=$(MCP_ROLE) \
81-
$(NPX) -y @openintentai/mcp-server
75+
@echo "Starting OpenIntent server on port $(PORT)..."
76+
@openintent-server --port $(PORT) &
77+
@sleep 2
78+
@echo "Starting MCP server (role: $(MCP_ROLE))..."
79+
@OPENINTENT_SERVER_URL=http://localhost:$(PORT) \
80+
OPENINTENT_API_KEY=dev-user-key \
81+
OPENINTENT_MCP_ROLE=$(MCP_ROLE) \
82+
$(NPX) -y @openintentai/mcp-server
8283

8384
check: ## Verify installation and connectivity
84-
@echo "Checking Python SDK..."
85-
@$(PYTHON) -c "import openintent; print(f' openintent {openintent.__version__}')" 2>/dev/null || echo " openintent: NOT INSTALLED"
86-
@echo "Checking MCP SDK..."
87-
@$(PYTHON) -c "import mcp; print(' mcp: OK')" 2>/dev/null || echo " mcp: NOT INSTALLED (run: make setup-mcp)"
88-
@echo "Checking MCP server (Node)..."
89-
@$(NPX) -y @openintentai/mcp-server --version 2>/dev/null && echo " mcp-server: OK" || echo " mcp-server: NOT INSTALLED (run: make setup-mcp)"
90-
@echo "Checking OpenIntent server..."
91-
@curl -sf http://localhost:$(PORT)/api/v1/intents > /dev/null 2>&1 && echo " server: RUNNING on port $(PORT)" || echo " server: NOT RUNNING (run: make server)"
85+
@echo "Checking Python SDK..."
86+
@$(PYTHON) -c "import openintent; print(f' openintent {openintent.__version__}')" 2>/dev/null || echo " openintent: NOT INSTALLED"
87+
@echo "Checking MCP SDK..."
88+
@$(PYTHON) -c "import mcp; print(' mcp: OK')" 2>/dev/null || echo " mcp: NOT INSTALLED (run: make setup-mcp)"
89+
@echo "Checking MCP server (Node)..."
90+
@$(NPX) -y @openintentai/mcp-server --version 2>/dev/null && echo " mcp-server: OK" || echo " mcp-server: NOT INSTALLED (run: make setup-mcp)"
91+
@echo "Checking OpenIntent server..."
92+
@curl -sf http://localhost:$(PORT)/api/v1/intents > /dev/null 2>&1 && echo " server: RUNNING on port $(PORT)" || echo " server: NOT RUNNING (run: make server)"
9293

9394
clean: ## Remove build artifacts and caches
94-
rm -rf build/ dist/ *.egg-info .pytest_cache .mypy_cache .ruff_cache
95-
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
96-
find . -type f -name "*.pyc" -delete 2>/dev/null || true
95+
rm -rf build/ dist/ *.egg-info .pytest_cache .mypy_cache .ruff_cache
96+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
97+
find . -type f -name "*.pyc" -delete 2>/dev/null || true

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ pip install -e ".[dev,server]"
714714

715715
pytest # Run tests
716716
ruff check openintent/ # Lint
717-
black openintent/ # Format
717+
ruff format openintent/ # Format
718718
mypy openintent/ # Type check
719719
openintent-server # Start dev server
720720
```

docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525

2626
- **`IntentLease.from_dict()` KeyError on server responses**`acquire_lease()` threw `KeyError('status')` because the server's `LeaseResponse` model does not include a `status` field (it uses `acquired_at`, `expires_at`, and `released_at` to represent lease state). `IntentLease.from_dict()` now derives status from these fields: `RELEASED` if `released_at` is set, `EXPIRED` if `expires_at` is in the past, otherwise `ACTIVE`. Also handles the field name difference `acquired_at` (server) vs `created_at` (SDK). Backward compatible with the SDK's own serialization format.
2727

28+
- **Stale database singleton after server restart**`get_database()` cached the `Database` instance at module level and never checked whether `database_url` changed between calls. When the protocol server restarted on a different port (e.g., `openintent_server_8001.db``openintent_server_8002.db`), the singleton kept pointing at the old file. Writes went to the old database; reads came from the new (empty) one — intents appeared created but were invisible to `list_intents`. The singleton now tracks its URL and recreates the connection when the URL changes.
29+
2830
- **Example and test updates** — All examples (`basic_usage.py`, `openai_multi_agent.py`, `multi_agent/coordinator.py`, `compliance_review/coordinator.py`) and tests updated to use dict-format constraints.
2931

3032
### Changed

examples/multi_agent/coordinator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ async def plan(self, topic: str) -> PortfolioSpec:
6060
title="Research Phase",
6161
description=f"Research the topic: {topic}",
6262
assign="research-agent",
63-
constraints={"rules": ["max_cost_usd:0.50", "required_confidence:0.75"]},
63+
constraints={
64+
"rules": ["max_cost_usd:0.50", "required_confidence:0.75"]
65+
},
6466
initial_state={"phase": "research"},
6567
),
6668
# Phase 2: Writing (depends on Research)

openintent/adapters/gemini_adapter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ def generate_content(
127127
messages_count = (
128128
1
129129
if isinstance(contents, str)
130-
else len(contents) if isinstance(contents, list) else 1
130+
else len(contents)
131+
if isinstance(contents, list)
132+
else 1
131133
)
132134

133135
if self._config.log_requests:

openintent/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3018,7 +3018,9 @@ def list_federated_agents(
30183018
headers=headers,
30193019
)
30203020
data = self._handle_response(response)
3021-
agents: list[dict[str, Any]] = data if isinstance(data, list) else data.get("agents", [])
3021+
agents: list[dict[str, Any]] = (
3022+
data if isinstance(data, list) else data.get("agents", [])
3023+
)
30223024
return agents
30233025

30243026
def federation_dispatch(
@@ -5099,7 +5101,9 @@ async def list_federated_agents(
50995101
headers=headers,
51005102
)
51015103
data = self._handle_response(response)
5102-
agents: list[dict[str, Any]] = data if isinstance(data, list) else data.get("agents", [])
5104+
agents: list[dict[str, Any]] = (
5105+
data if isinstance(data, list) else data.get("agents", [])
5106+
)
51035107
return agents
51045108

51055109
async def federation_dispatch(

openintent/demo_agents.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ async def handle_intent(self, intent: Any) -> dict[str, Any]:
153153
rules = raw_constraints.get("rules", [])
154154
constraints = "\n".join(str(r) for r in rules) if rules else ""
155155
else:
156-
constraints = "\n".join(str(c) for c in raw_constraints) if raw_constraints else ""
156+
constraints = (
157+
"\n".join(str(c) for c in raw_constraints) if raw_constraints else ""
158+
)
157159

158160
prompt = f"Research the following topic:\n\n{topic}"
159161
if constraints:

openintent/models.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,14 +932,20 @@ def from_dict(cls, data: dict[str, Any]) -> "IntentLease":
932932
if expires_str:
933933
try:
934934
expires = datetime.fromisoformat(expires_str)
935-
status = LeaseStatus.ACTIVE if datetime.now(expires.tzinfo) < expires else LeaseStatus.EXPIRED
935+
status = (
936+
LeaseStatus.ACTIVE
937+
if datetime.now(expires.tzinfo) < expires
938+
else LeaseStatus.EXPIRED
939+
)
936940
except (ValueError, TypeError):
937941
status = LeaseStatus.ACTIVE
938942
else:
939943
status = LeaseStatus.ACTIVE
940944

941945
created_at_str = data.get("created_at") or data.get("acquired_at")
942-
created_at = datetime.fromisoformat(created_at_str) if created_at_str else datetime.now()
946+
created_at = (
947+
datetime.fromisoformat(created_at_str) if created_at_str else datetime.now()
948+
)
943949

944950
return cls(
945951
id=data["id"],

0 commit comments

Comments
 (0)