Skip to content

Commit 51747d0

Browse files
committed
feat: allow creation of replane client without connection options
1 parent 0a96bec commit 51747d0

6 files changed

Lines changed: 93 additions & 22 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,20 @@ async with AsyncReplane(
7474
```python
7575
from replane import Replane
7676

77+
# Option 1: Provide credentials in constructor
7778
replane = Replane(
7879
base_url="https://cloud.replane.dev",
7980
sdk_key="rp_...",
8081
)
8182
replane.connect()
8283

84+
# Option 2: Provide credentials in connect()
85+
replane = Replane()
86+
replane.connect(
87+
base_url="https://cloud.replane.dev",
88+
sdk_key="rp_...",
89+
)
90+
8391
# Use configs
8492
rate_limit = replane.configs["rate-limit"]
8593
user_client = replane.with_context({"user_id": "123"})

docs/source/configuration.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Both `Replane` and `AsyncReplane` accept the same configuration options:
1010
from replane import Replane
1111

1212
replane = Replane(
13-
# Required
13+
# Can be provided here or in connect()
1414
base_url="https://replane.example.com",
1515
sdk_key="rp_...",
1616

@@ -26,30 +26,38 @@ replane = Replane(
2626
)
2727
```
2828

29-
### Required Options
29+
### Connection Options
3030

3131
#### `base_url`
3232

3333
- **Type:** `str`
34-
- **Required:** Yes
34+
- **Required:** Yes (in constructor or `connect()`)
3535

36-
The base URL of your Replane server.
36+
The base URL of your Replane server. Can be provided in the constructor or in `connect()`.
3737

3838
```python
39-
base_url="https://replane.example.com"
40-
base_url="http://localhost:3000" # Local development
39+
# In constructor
40+
replane = Replane(base_url="https://replane.example.com", sdk_key="...")
41+
42+
# Or in connect()
43+
replane = Replane(sdk_key="...")
44+
replane.connect(base_url="https://replane.example.com")
4145
```
4246

4347
#### `sdk_key`
4448

4549
- **Type:** `str`
46-
- **Required:** Yes
50+
- **Required:** Yes (in constructor or `connect()`)
4751

48-
Your SDK key from the Replane dashboard. SDK keys are scoped to a specific project and environment.
52+
Your SDK key from the Replane dashboard. Can be provided in the constructor or in `connect()`.
4953

5054
```python
51-
sdk_key="rp_abc123..." # Production
52-
sdk_key="rp_test_xyz789..." # Testing/staging
55+
# In constructor
56+
replane = Replane(base_url="...", sdk_key="rp_abc123...")
57+
58+
# Or in connect()
59+
replane = Replane(base_url="...")
60+
replane.connect(sdk_key="rp_abc123...")
5361
```
5462

5563
### Optional Options

docs/source/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ with Replane(
3232
Or without context manager:
3333

3434
```python
35+
# Credentials in constructor
3536
replane = Replane(base_url="...", sdk_key="...")
3637
replane.connect()
3738

39+
# Or in connect()
40+
replane = Replane()
41+
replane.connect(base_url="...", sdk_key="...")
42+
3843
rate_limit = replane.configs["rate-limit"]
3944

4045
replane.close()

docs/source/quickstart.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,20 @@ If you prefer manual lifecycle management:
7171
```python
7272
from replane import Replane
7373

74+
# Option 1: Provide credentials in constructor
7475
replane = Replane(
7576
base_url="https://replane.example.com",
7677
sdk_key="rp_...",
7778
)
7879
replane.connect()
7980

81+
# Option 2: Provide credentials in connect()
82+
replane = Replane()
83+
replane.connect(
84+
base_url="https://replane.example.com",
85+
sdk_key="rp_...",
86+
)
87+
8088
try:
8189
rate_limit = replane.configs["rate-limit"]
8290
user_client = replane.with_context({"user_id": "123"})

replane/_async.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,8 @@ class AsyncReplane(Generic[ConfigsT]):
309309

310310
def __init__(
311311
self,
312-
base_url: str,
313-
sdk_key: str,
312+
base_url: str | None = None,
313+
sdk_key: str | None = None,
314314
*,
315315
context: dict[str, ContextValue] | None = None,
316316
defaults: dict[str, Any] | None = None,
@@ -325,8 +325,8 @@ def __init__(
325325
"""Initialize the async Replane client.
326326
327327
Args:
328-
base_url: Base URL of the Replane server.
329-
sdk_key: SDK key for authentication.
328+
base_url: Base URL of the Replane server. Can also be provided in connect().
329+
sdk_key: SDK key for authentication. Can also be provided in connect().
330330
context: Default context for override evaluation.
331331
defaults: Default values for configs if not loaded from server.
332332
required: List of config names that must be present on init.
@@ -364,7 +364,7 @@ def __init__(
364364
if required:
365365
logger.debug("Required configs: %s", required)
366366

367-
self._base_url = base_url.rstrip("/")
367+
self._base_url = base_url.rstrip("/") if base_url else None
368368
self._sdk_key = sdk_key
369369
# Generate replaneClientId and set it as base context.
370370
# User-provided context values take precedence (merged on top).
@@ -399,20 +399,41 @@ def __init__(
399399
self._stream_task: asyncio.Task[None] | None = None
400400
self._http_client: httpx.AsyncClient | None = None
401401

402-
async def connect(self, *, wait: bool = True) -> None:
402+
async def connect(
403+
self,
404+
base_url: str | None = None,
405+
sdk_key: str | None = None,
406+
*,
407+
wait: bool = True,
408+
) -> None:
403409
"""Connect to the Replane server and start receiving updates.
404410
405411
This starts a background task that maintains the SSE connection.
406412
407413
Args:
414+
base_url: Base URL of the Replane server. Overrides constructor value.
415+
sdk_key: SDK key for authentication. Overrides constructor value.
408416
wait: If True, wait until initial configs are loaded.
409417
410418
Raises:
419+
ValueError: If base_url or sdk_key is not provided here or in constructor.
411420
ReplaneError: If connection fails or required configs are missing.
412421
"""
413422
if self._closed:
414423
raise ClientClosedError()
415424

425+
# Apply overrides from connect() if provided
426+
if base_url is not None:
427+
self._base_url = base_url.rstrip("/")
428+
if sdk_key is not None:
429+
self._sdk_key = sdk_key
430+
431+
# Validate required parameters
432+
if not self._base_url:
433+
raise ValueError("base_url is required - provide it in constructor or connect()")
434+
if not self._sdk_key:
435+
raise ValueError("sdk_key is required - provide it in constructor or connect()")
436+
416437
logger.debug("connect() called, wait=%s", wait)
417438

418439
# Use request_timeout for the handshake (server should respond quickly).

replane/_sync.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,8 @@ class Replane(Generic[ConfigsT]):
311311

312312
def __init__(
313313
self,
314-
base_url: str,
315-
sdk_key: str,
314+
base_url: str | None = None,
315+
sdk_key: str | None = None,
316316
*,
317317
context: dict[str, ContextValue] | None = None,
318318
defaults: dict[str, Any] | None = None,
@@ -327,8 +327,8 @@ def __init__(
327327
"""Initialize the Replane client.
328328
329329
Args:
330-
base_url: Base URL of the Replane server.
331-
sdk_key: SDK key for authentication.
330+
base_url: Base URL of the Replane server. Can also be provided in connect().
331+
sdk_key: SDK key for authentication. Can also be provided in connect().
332332
context: Default context for override evaluation.
333333
defaults: Default values for configs if not loaded from server.
334334
required: List of config names that must be present on init.
@@ -359,7 +359,7 @@ def __init__(
359359
logger.debug("Default configs: %s", list(defaults.keys()))
360360
if required:
361361
logger.debug("Required configs: %s", required)
362-
self._base_url = base_url.rstrip("/")
362+
self._base_url = base_url.rstrip("/") if base_url else None
363363
self._sdk_key = sdk_key
364364
# Generate replaneClientId and set it as base context.
365365
# User-provided context values take precedence (merged on top).
@@ -394,20 +394,41 @@ def __init__(
394394
self._stream_thread: threading.Thread | None = None
395395
self._stop_event = threading.Event()
396396

397-
def connect(self, *, wait: bool = True) -> None:
397+
def connect(
398+
self,
399+
base_url: str | None = None,
400+
sdk_key: str | None = None,
401+
*,
402+
wait: bool = True,
403+
) -> None:
398404
"""Connect to the Replane server and start receiving updates.
399405
400406
This starts a background thread that maintains the SSE connection.
401407
402408
Args:
409+
base_url: Base URL of the Replane server. Overrides constructor value.
410+
sdk_key: SDK key for authentication. Overrides constructor value.
403411
wait: If True, block until initial configs are loaded.
404412
405413
Raises:
414+
ValueError: If base_url or sdk_key is not provided here or in constructor.
406415
ReplaneError: If connection fails or required configs are missing.
407416
"""
408417
if self._closed:
409418
raise ClientClosedError()
410419

420+
# Apply overrides from connect() if provided
421+
if base_url is not None:
422+
self._base_url = base_url.rstrip("/")
423+
if sdk_key is not None:
424+
self._sdk_key = sdk_key
425+
426+
# Validate required parameters
427+
if not self._base_url:
428+
raise ValueError("base_url is required - provide it in constructor or connect()")
429+
if not self._sdk_key:
430+
raise ValueError("sdk_key is required - provide it in constructor or connect()")
431+
411432
logger.debug("connect() called, wait=%s", wait)
412433

413434
self._stream_thread = threading.Thread(

0 commit comments

Comments
 (0)