- platforms/: eBay platform adapter (snipe integration layer) - docs/: developer guide, module reference, getting-started docs - scripts/: utility scripts for development and deployment
3.8 KiB
Adding a Module to cf-core
This guide walks through extracting a pattern from a product into a shared cf-core module. The goal is to move battle-tested implementations here once they've stabilized in at least two products.
When to add a module
Add a module when:
- The same pattern exists in two or more products with minor variations
- The interface is stable enough that changing it would require coordinated updates across products
- The code has no product-specific business logic baked in
Do not add a module for:
- One-off utilities that only one product needs
- Anything still in active design flux
- Product-specific configuration or policy decisions
Module structure
circuitforge_core/
└── mymodule/
├── __init__.py # Public API — what products import
├── base.py # Core implementation
└── backends/ # Optional: pluggable backends
├── __init__.py
├── local.py
└── cloud.py
Keep the public API in __init__.py clean. Products should import from circuitforge_core.mymodule, not from internal submodules.
Step 1: Define the interface
Write the public interface first — the classes and functions products will call. Get this right before implementing, because changing it requires updating every product shim.
# circuitforge_core/mymodule/__init__.py
from .base import MyThing, get_my_thing
__all__ = ["MyThing", "get_my_thing"]
Step 2: Implement with a stub
Start with a minimal working implementation. Stub out anything uncertain:
# circuitforge_core/mymodule/base.py
class MyThing:
def __init__(self, config: dict):
self._config = config
def do_thing(self, input: str) -> str:
raise NotImplementedError("Override in product or backend")
Step 3: Write tests
Tests go in circuitforge_core/tests/test_mymodule.py. Use pytest. The cf env has pytest installed.
conda run -n cf python -m pytest tests/test_mymodule.py -v
Cover:
- Happy path with realistic input
- Missing config / bad input (fail loudly, not silently)
- Cloud vs local mode if applicable
Step 4: Update pyproject.toml
Add any new dependencies:
[project.optional-dependencies]
mymodule = ["some-dep>=1.0"]
Use optional dependency groups so products that don't use the module don't pay the install cost.
Step 5: Write the docs page
Add docs/modules/mymodule.md following the pattern of the existing module docs. Include:
- Import path
- Why this module exists / design rationale
- Full public API with examples
- Any gotchas or non-obvious behavior
- Status (Stable / Stub)
Update docs/modules/index.md and mkdocs.yml to include the new page.
Step 6: Update products
In each product that uses the pattern:
- Add a shim if the product needs to override behavior
- Replace the inline implementation with imports from cf-core
- Run the product's tests
The shim pattern:
# myproduct/app/mything.py
from circuitforge_core.mymodule import get_my_thing as _base_get_my_thing
from .config import get_settings
def get_my_thing():
settings = get_settings()
return _base_get_my_thing(config=settings.mything_config)
Licensing boundary
The module's license depends on what it does:
| Code | License |
|---|---|
| Discovery, pipeline, data access | MIT |
| LLM inference, AI features, fine-tuned model access | BSL 1.1 |
| Anything that would give SaaS competitors a free AI product | BSL 1.1 |
When in doubt, BSL 1.1. See the licensing guide for the full decision tree.
Versioning
cf-core uses semantic versioning. Adding a new module with a stable API is a minor version bump. Breaking an existing interface is a major bump and requires coordinated updates to all products.
Update pyproject.toml and CHANGELOG.md before merging.