Skip to content

Commit 7de10ec

Browse files
committed
feat(sdk): add codegen and operations client
1 parent 686048d commit 7de10ec

File tree

6 files changed

+376
-4
lines changed

6 files changed

+376
-4
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
TCA_API_TOKEN=your-api-token-here
2+
TCA_API_URL=https://api.thecompaniesapi.com
3+
TCA_VISITOR_ID=your-visitor-id
4+
TCA_TIMEOUT=30

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ You can also contact us on our [livechat](https://www.thecompaniesapi.com/) if y
2929
pip install thecompaniesapi
3030
```
3131

32+
**That's it!** The SDK includes pre-generated types and works immediately after installation.
33+
3234
## 🔑 Initialize the client
3335

3436
Get your API token from [your settings page](https://www.thecompaniesapi.com/settings/api-tokens) and initialize our client with `Client`.
@@ -530,6 +532,57 @@ response = tca.fetch_openapi()
530532
schema = response["data"] # The OpenAPI schema
531533
```
532534

535+
## 🧪 Testing
536+
537+
### Unit Tests
538+
539+
Run the unit tests that don't require an API token:
540+
541+
```bash
542+
pytest tests/test_client.py -m unit
543+
```
544+
545+
### Integration Tests
546+
547+
The SDK includes comprehensive integration tests that make real API calls. To run them:
548+
549+
1. **Create a `.env` file** in the project root:
550+
```bash
551+
# Required: Your API token from https://www.thecompaniesapi.com/settings/api-tokens
552+
TCA_API_TOKEN=your-api-token-here
553+
554+
# Optional configurations
555+
TCA_API_URL=https://api.thecompaniesapi.com
556+
TCA_VISITOR_ID=your-visitor-id
557+
TCA_TIMEOUT=30
558+
```
559+
560+
2. **Install test dependencies:**
561+
```bash
562+
pip install -e ".[test]"
563+
```
564+
565+
3. **Run integration tests:**
566+
```bash
567+
# Run all integration tests
568+
pytest tests/test_integration.py -m integration -v
569+
570+
# Run all tests except integration tests
571+
pytest -m "not integration"
572+
573+
# Run all tests (unit + integration)
574+
pytest
575+
```
576+
577+
The integration tests cover:
578+
- Company search (GET and POST methods)
579+
- Company counting and filtering
580+
- Company lookup by email and domain
581+
- Complex query serialization
582+
- Error handling and edge cases
583+
- Dynamic method access
584+
- API health checks
585+
533586
## 🔗 Links
534587

535588
- [The Companies API](https://www.thecompaniesapi.com)

pyproject.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies = [
1111
]
1212
description = "Python SDK for The Companies API"
1313
readme = "README.md"
14-
requires-python = ">=3.7"
14+
requires-python = ">=3.9"
1515
classifiers = [
1616
"Programming Language :: Python :: 3",
1717
"License :: OSI Approved :: MIT License",
@@ -21,7 +21,12 @@ classifiers = [
2121
test = [
2222
"pytest >= 6.0",
2323
"pytest-mock >= 3.0",
24-
"responses >= 0.20.0"
24+
"responses >= 0.20.0",
25+
"python-dotenv >= 0.19.0"
26+
]
27+
codegen = [
28+
"datamodel-code-generator[http] >= 0.21.0",
29+
"pydantic >= 2.0.0"
2530
]
2631
[project.urls]
2732
"Homepage" = "https://www.thecompaniesapi.com/"
@@ -32,3 +37,7 @@ testpaths = ["tests"]
3237
python_files = ["test_*.py"]
3338
python_classes = ["Test*"]
3439
python_functions = ["test_*"]
40+
markers = [
41+
"integration: marks tests as integration tests (may be slow)",
42+
"unit: marks tests as unit tests (fast)"
43+
]

scripts/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Scripts
2+
3+
This directory contains utility scripts for maintaining the SDK.
4+
5+
## Schema Generation
6+
7+
### `generate_schema.py`
8+
9+
Fetches the OpenAPI schema from The Companies API and generates Python types and operations map.
10+
11+
> **Note**: Generated code is **included in the pip package**, so end users don't need to run this script. This is only for SDK maintainers when the API schema changes.
12+
13+
#### Usage (For SDK Maintainers)
14+
15+
```bash
16+
# Install code generation dependencies
17+
pip install -e ".[codegen]"
18+
19+
# Run the generation script
20+
python scripts/generate_schema.py
21+
22+
# Commit the updated generated files
23+
git add src/thecompaniesapi/generated/
24+
git commit -m "Update generated schema"
25+
```
26+
27+
#### Environment Variables
28+
29+
- `TCA_API_VERSION` - API version to use (default: `v2`)
30+
- `TCA_API_URL` - Base API URL (default: `https://api.thecompaniesapi.com`)
31+
32+
#### Generated Files
33+
34+
The script generates the following files in `src/thecompaniesapi/generated/`:
35+
36+
- `models.py` - Pydantic models for all API types
37+
- `operations_map.py` - Operations map with paths, methods, and parameters
38+
- `__init__.py` - Exports for the generated module
39+
40+
#### Example
41+
42+
```bash
43+
# Generate with custom API version
44+
TCA_API_VERSION=v2 python scripts/generate_schema.py
45+
46+
# Generate from staging environment
47+
TCA_API_URL=https://staging-api.thecompaniesapi.com python scripts/generate_schema.py
48+
```
49+
50+
The generated types are automatically picked up by the `Client` class to provide type-safe method calls.
51+
52+
#### When to Regenerate
53+
54+
Regenerate and commit the files when:
55+
- The Companies API OpenAPI schema is updated
56+
- New endpoints are added
57+
- Existing endpoint signatures change
58+
- Response models are modified
59+
60+
#### For End Users
61+
62+
End users of the pip package (`pip install thecompaniesapi`) get pre-generated code and don't need to run any generation scripts. The SDK works out of the box.

scripts/generate_schema.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Schema generation script for The Companies API Python SDK.
4+
"""
5+
6+
import json
7+
import os
8+
import sys
9+
import subprocess
10+
from pathlib import Path
11+
import requests
12+
13+
14+
def fetch_openapi_schema() -> dict:
15+
"""Fetch the OpenAPI schema from the API."""
16+
version = os.getenv('TCA_API_VERSION', 'v2')
17+
api_url = os.getenv('TCA_API_URL', 'https://api.thecompaniesapi.com')
18+
schema_url = f"{api_url}/{version}/openapi"
19+
20+
print(f"Pulling schema from: {schema_url}")
21+
22+
try:
23+
response = requests.get(schema_url, timeout=30)
24+
response.raise_for_status()
25+
return response.json()
26+
except requests.RequestException as e:
27+
print(f"Error fetching schema: {e}")
28+
sys.exit(1)
29+
30+
31+
def extract_operations_map(schema: dict) -> dict:
32+
"""Extract operations map from OpenAPI schema."""
33+
operations = {}
34+
35+
paths = schema.get('paths', {})
36+
for path, path_operations in paths.items():
37+
for method, operation in path_operations.items():
38+
if method.lower() in ['get', 'post', 'put', 'patch', 'delete']:
39+
operation_id = operation.get('operationId')
40+
if operation_id:
41+
# Extract path parameters
42+
path_params = []
43+
for param in operation.get('parameters', []):
44+
if param.get('in') == 'path':
45+
path_params.append(param.get('name'))
46+
47+
operations[operation_id] = {
48+
'path': path,
49+
'method': method.lower(),
50+
'pathParams': path_params
51+
}
52+
53+
return operations
54+
55+
56+
def generate_pydantic_types(schema: dict, output_file: Path) -> None:
57+
"""Generate Pydantic models from OpenAPI schema."""
58+
print(f"Generating Pydantic types...")
59+
60+
# Save schema to temporary file
61+
temp_schema_file = output_file.parent / 'temp_schema.json'
62+
with open(temp_schema_file, 'w', encoding='utf-8') as f:
63+
json.dump(schema, f, indent=2)
64+
65+
try:
66+
# Use command line interface which is more stable
67+
cmd = [
68+
'datamodel-codegen',
69+
'--input', str(temp_schema_file),
70+
'--input-file-type', 'openapi',
71+
'--output', str(output_file),
72+
'--output-model-type', 'pydantic_v2.BaseModel',
73+
'--target-python-version', '3.9',
74+
'--use-title-as-name',
75+
]
76+
77+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
78+
print(f"Pydantic types written to: {output_file}")
79+
80+
except subprocess.CalledProcessError as e:
81+
print(f"Error generating types: {e}")
82+
print(f"stdout: {e.stdout}")
83+
print(f"stderr: {e.stderr}")
84+
raise
85+
finally:
86+
# Clean up temporary file
87+
if temp_schema_file.exists():
88+
temp_schema_file.unlink()
89+
90+
91+
def generate_operations_map_file(operations: dict, output_file: Path) -> None:
92+
"""Generate operations map Python file."""
93+
content = f'''"""
94+
Auto-generated operations map for The Companies API.
95+
This file is generated by scripts/generate_schema.py - do not edit manually.
96+
"""
97+
98+
from typing import Dict, List
99+
100+
# Operations map extracted from OpenAPI schema
101+
operations_map = {json.dumps(operations, indent=4)}
102+
103+
# Type alias for operations map
104+
OperationsMap = Dict[str, Dict[str, any]]
105+
'''
106+
107+
with open(output_file, 'w', encoding='utf-8') as f:
108+
f.write(content)
109+
110+
print(f"Operations map written to: {output_file}")
111+
112+
113+
def update_generated_init(generated_dir: Path) -> None:
114+
"""Update __init__.py in generated directory to export main types."""
115+
init_file = generated_dir / '__init__.py'
116+
117+
content = '''"""
118+
Generated types and operations for The Companies API.
119+
"""
120+
121+
from .operations_map import operations_map, OperationsMap
122+
123+
try:
124+
# Import commonly used types - adjust as needed
125+
from .models import *
126+
except ImportError:
127+
# Handle case where models haven't been generated yet
128+
pass
129+
130+
__all__ = ['operations_map', 'OperationsMap']
131+
'''
132+
133+
with open(init_file, 'w', encoding='utf-8') as f:
134+
f.write(content)
135+
136+
137+
def main():
138+
"""Main function to update schema and generate types."""
139+
# Get project root directory
140+
script_dir = Path(__file__).parent
141+
project_root = script_dir.parent
142+
generated_dir = project_root / 'src' / 'thecompaniesapi' / 'generated'
143+
144+
# Create generated directory if it doesn't exist
145+
generated_dir.mkdir(exist_ok=True)
146+
147+
# Fetch OpenAPI schema
148+
schema = fetch_openapi_schema()
149+
150+
# Extract operations map
151+
operations = extract_operations_map(schema)
152+
print(f"Found {len(operations)} operations")
153+
154+
# Generate Pydantic types
155+
types_file = generated_dir / 'models.py'
156+
generate_pydantic_types(schema, types_file)
157+
158+
# Generate operations map
159+
operations_file = generated_dir / 'operations_map.py'
160+
generate_operations_map_file(operations, operations_file)
161+
162+
# Update __init__.py
163+
update_generated_init(generated_dir)
164+
165+
print("\n✨ Schema generation completed successfully!")
166+
print(f" 📁 Generated files in: {generated_dir}")
167+
print(f" 🔧 Operations: {len(operations)}")
168+
print("\n🚀 You can now use the generated types in your SDK!")
169+
170+
171+
if __name__ == '__main__':
172+
main()

0 commit comments

Comments
 (0)