Skip to content

Cullinan 2.0 Migration Guide

Version: v0.90
Author: Plumeink

This guide helps you migrate from Cullinan 1.x to 2.0 (0.90).

Breaking Changes

1. Single Entry Point

Before (1.x):

from cullinan.core import get_injection_registry, get_service_registry

registry = get_injection_registry()
registry.add_provider_source(my_source)

After (2.0):

from cullinan.core.container import ApplicationContext, Definition, ScopeType

ctx = ApplicationContext()
ctx.register(Definition(
    name='MyService',
    factory=lambda c: MyService(),
    scope=ScopeType.SINGLETON,
    source='service:MyService'
))
ctx.refresh()

2. Registry Freeze

In 2.0, the registry is frozen after refresh(). Any attempt to register new dependencies will raise RegistryFrozenError.

Before (1.x):

# Could register at any time
registry.register('NewService', NewService)

After (2.0):

ctx = ApplicationContext()
ctx.register(...)  # OK before refresh
ctx.refresh()
ctx.register(...)  # RegistryFrozenError!

3. Scope Enforcement

Request-scoped dependencies now strictly require a RequestContext.

Before (1.x):

# Might silently fail or return wrong instance
instance = registry.get('RequestScoped')

After (2.0):

ctx.enter_request_context()
try:
    instance = ctx.get('RequestScoped')  # OK
finally:
    ctx.exit_request_context()

# Without context:
ctx.get('RequestScoped')  # ScopeNotActiveError!

4. Structured Exceptions

All exceptions now carry structured diagnostic fields.

Before (1.x):

try:
    registry.resolve('Missing')
except Exception as e:
    print(str(e))  # Generic message

After (2.0):

from cullinan.core.diagnostics import DependencyNotFoundError

try:
    ctx.get('Missing')
except DependencyNotFoundError as e:
    print(e.dependency_name)      # 'Missing'
    print(e.resolution_path)      # ['ParentService', 'Missing']
    print(e.candidate_sources)    # [{'source': '...', 'reason': '...'}]

5. Circular Dependency Detection

Circular dependencies now produce stable, ordered chains.

Before (1.x):

# Unordered, inconsistent output
CircularDependencyError: Circular dependency detected

After (2.0):

# Stable, ordered chain
CircularDependencyError: Circular dependency detected: A -> B -> C -> A

Migration Steps

Step 1: Update Imports

# Old imports (deprecated)
from cullinan.core import get_injection_registry, Inject, InjectByName

# New imports (2.0)
from cullinan.core.container import ApplicationContext, Definition, ScopeType

Step 2: Convert Service Registration

# Old style
@service
class UserService:
    user_repo = Inject()

# New style
ctx.register(Definition(
    name='UserService',
    factory=lambda c: UserService(user_repo=c.get('UserRepository')),
    scope=ScopeType.SINGLETON,
    source='service:UserService'
))

Step 3: Update Application Startup

# Old style (app.py)
from cullinan.app import CullinanApplication
app = CullinanApplication()
app.run()

# New style
from cullinan.core.container import ApplicationContext

ctx = ApplicationContext()
# ... register definitions ...
ctx.refresh()
# ... run tornado ...
ctx.shutdown()

Step 4: Handle Request Scope

# In request handler
class MyHandler(RequestHandler):
    def get(self):
        ctx.enter_request_context()
        try:
            service = ctx.get('RequestScopedService')
            # ... use service ...
        finally:
            ctx.exit_request_context()

Deprecated APIs

The following APIs are deprecated in 2.0 and will be removed in 3.0:

Deprecated API Replacement
get_injection_registry() ApplicationContext
get_service_registry() ApplicationContext.register()
@service decorator with auto-inject Explicit Definition registration
Inject() / InjectByName() factory with ctx.get()
DependencyInjector ApplicationContext

Compatibility Mode

During migration, you can enable compatibility mode (deprecated, will be removed):

from cullinan.core.container import ApplicationContext

ctx = ApplicationContext()
ctx.set_strict_mode(False)  # Allow some legacy behaviors

Warning: Compatibility mode is for migration only. Always migrate to strict mode before production deployment.

Testing Migration

Run all 2.0 tests to verify your migration:

python -m pytest tests/test_ioc_di_v2_*.py -v

Getting Help