Problem or Use Case
After #375 introduces the builder pattern (Repository.builder(...).build()), the most common use case — connecting to an existing table — requires two chained calls:
# Current (post-#375): builder pattern for everything
repo = await Repository.builder("my-app", region="us-east-1").build()
limiter = RateLimiter(repository=repo)
# With namespace
repo = await Repository.builder("my-app", region="us-east-1").namespace("tenant-a").build()
This is verbose for the 80% case (connect to existing infrastructure, no stack creation). A connect() classmethod provides a more Pythonic, one-call entry point:
# Proposed: connect() for the common case
repo = await Repository.connect("my-app", region="us-east-1")
limiter = RateLimiter(repository=repo)
# With namespace
repo = await Repository.connect("my-app", region="us-east-1", namespace="tenant-a")
The builder remains available for advanced/programmatic use (infrastructure provisioning, custom IAM, Lambda configuration), but connect() becomes the recommended API for documentation and tutorials.
Proposed Solution
Repository.connect() / SyncRepository.connect()
@classmethod
async def connect(
cls,
name: str,
region: str | None = None,
*,
endpoint_url: str | None = None,
namespace: str = "default",
config_cache_ttl: int = 60,
auto_update: bool = True,
) -> "Repository":
"""Connect to existing zae-limiter infrastructure.
This is the recommended entry point for most applications.
For infrastructure provisioning, use Repository.builder() instead.
"""
repo = cls(
name=name,
region=region,
endpoint_url=endpoint_url,
config_cache_ttl=config_cache_ttl,
_skip_deprecation_warning=True,
)
repo._auto_update = auto_update
# Resolve namespace (must already exist)
namespace_id = await repo._resolve_namespace(namespace)
if namespace_id is None:
raise NamespaceNotFoundError(namespace)
repo._namespace_id = namespace_id
repo._namespace_name = namespace
repo._reinitialize_config_cache(namespace_id)
# Version check (skip for local endpoints)
if not endpoint_url:
if auto_update:
await repo._check_and_update_version_auto()
else:
await repo._check_version_strict()
repo._builder_initialized = True
return repo
connect() is a thin classmethod that resolves the namespace and performs a version check — it does not provision infrastructure or register namespaces.
Migration scope
All documentation examples, test fixtures, and CLAUDE.md references should migrate from direct __init__ or builder to connect():
| Location |
Current pattern |
New pattern |
| CLAUDE.md examples |
Repository(name=..., region=...) |
await Repository.connect(...) |
docs/ guides |
Repository(name=..., region=...) |
await Repository.connect(...) |
| Unit test fixtures |
Repository(name=..., region=...) |
await Repository.connect(...) or builder |
| E2E test fixtures |
Repository(name=..., region=...) |
await Repository.connect(...) |
| Benchmark fixtures |
Repository(name=..., region=...) |
await Repository.connect(...) |
| Doctests |
Repository(name=..., region=...) |
await Repository.connect(...) |
Direct __init__ deprecation
Repository.__init__() emits a DeprecationWarning directing users to connect() or builder(). The constructor remains functional for backward compatibility but is no longer the documented path.
When to use which
| Pattern |
Use case |
Repository.connect(...) |
Connect to existing table (80% case) |
Repository.builder(...).build() |
Provision infrastructure, custom IAM, advanced config |
Repository(...) |
Deprecated — backward compatibility only |
Alternatives Considered
- Only builder, no connect(): Rejected —
builder().build() is verbose for the common case and unfamiliar to Python developers who expect factory classmethods (e.g., datetime.fromtimestamp(), Path.cwd())
- Rename builder to connect(): Rejected — the builder pattern is needed for the infrastructure provisioning use case where multiple configuration options are chained
- Keep init as recommended: Rejected —
__init__ cannot do async initialization (namespace resolution, version check), forcing a two-step pattern
Acceptance Criteria
connect() classmethod
Deprecation
Documentation migration
Test migration
Sync parity
Tests for connect()
Dependencies
Problem or Use Case
After #375 introduces the builder pattern (
Repository.builder(...).build()), the most common use case — connecting to an existing table — requires two chained calls:This is verbose for the 80% case (connect to existing infrastructure, no stack creation). A
connect()classmethod provides a more Pythonic, one-call entry point:The builder remains available for advanced/programmatic use (infrastructure provisioning, custom IAM, Lambda configuration), but
connect()becomes the recommended API for documentation and tutorials.Proposed Solution
Repository.connect()/SyncRepository.connect()connect()is a thin classmethod that resolves the namespace and performs a version check — it does not provision infrastructure or register namespaces.Migration scope
All documentation examples, test fixtures, and CLAUDE.md references should migrate from direct
__init__or builder toconnect():Repository(name=..., region=...)await Repository.connect(...)docs/guidesRepository(name=..., region=...)await Repository.connect(...)Repository(name=..., region=...)await Repository.connect(...)or builderRepository(name=..., region=...)await Repository.connect(...)Repository(name=..., region=...)await Repository.connect(...)Repository(name=..., region=...)await Repository.connect(...)Direct
__init__deprecationRepository.__init__()emits aDeprecationWarningdirecting users toconnect()orbuilder(). The constructor remains functional for backward compatibility but is no longer the documented path.When to use which
Repository.connect(...)Repository.builder(...).build()Repository(...)Alternatives Considered
builder().build()is verbose for the common case and unfamiliar to Python developers who expect factory classmethods (e.g.,datetime.fromtimestamp(),Path.cwd())__init__cannot do async initialization (namespace resolution, version check), forcing a two-step patternAcceptance Criteria
connect()classmethodRepository.connect(name, region, *, endpoint_url, namespace, config_cache_ttl, auto_update)is an async classmethodSyncRepository.connect(...)is a sync classmethod (generated viahatch run generate-sync)connect()has its own thin implementation (resolves namespace, version check; no infrastructure provisioning)connect()returns a fully initializedRepository(namespace resolved, version checked)connect()acceptsnamespaceparameter (default:"default")connect()docstring directs users tobuilder()for infrastructure provisioningDeprecation
Repository.__init__()emitsDeprecationWarningwith message directing toconnect()orbuilder()SyncRepository.__init__()emits sameDeprecationWarningDocumentation migration
connect()as primary examplebuilder()for stack provisioningdocs/getting-started.mdusesconnect()for first exampledocs/guide/basic-usage.mdusesconnect()docs/infra/deployment.mdusesbuilder()for provisioning,connect()for runtimeconnect()where direct__init__is currently shown (remaining occurrences are in historical plans, ADRs, and migration scripts)Test migration
tests/unit/useconnect()or builder instead of direct__init__tests/e2e/useconnect()or buildertests/benchmark/useconnect()or builder (AWS fixtures use builder; moto fixtures use_skip_deprecation_warning)Repository(name=..., region=...)directly (grep verification:grep -rn "Repository(" tests/ --include="*.py"returns onlyconnect()orbuilder()calls)Sync parity
SyncRepository.connect()generated viahatch run generate-sync, produces no diffSyncRepository.connect()patternTests for connect()
connect()returns initialized Repositoryconnect()passes namespace to builderconnect()passes config_cache_ttl to builderconnect()passes auto_update to builderRepository.__init__()emits DeprecationWarningpytest tests/unit/passesmypy src/zae_limiter/passes with no new errorsDependencies