encore-dev 2.0.0a15__tar.gz → 2.0.0a17__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/PKG-INFO +27 -219
  2. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/README.md +26 -218
  3. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/config/__init__.py +1 -1
  4. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/postgres/__init__.py +41 -17
  5. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/pyproject.toml +1 -1
  6. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/pyproject.toml.orig +1 -1
  7. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/__init__.py +0 -0
  8. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/config/_build.py +0 -0
  9. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/config/_markers.py +0 -0
  10. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/cron/__init__.py +0 -0
  11. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/env/__init__.py +0 -0
  12. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/__init__.py +0 -0
  13. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/_loader.py +0 -0
  14. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/_native.pyi +0 -0
  15. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/lazy.py +0 -0
  16. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/runtime.py +0 -0
  17. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/internal/startup.py +0 -0
  18. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/logs/__init__.py +0 -0
  19. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/logs/_console.py +0 -0
  20. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/logs/_runtime.py +0 -0
  21. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/metrics/__init__.py +0 -0
  22. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/metrics/_instrument.py +0 -0
  23. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/metrics/_metrics.py +0 -0
  24. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/metrics/_provider.py +0 -0
  25. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/pubsub/__init__.py +0 -0
  26. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/py.typed +0 -0
  27. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/secrets/__init__.py +0 -0
  28. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/services/__init__.py +0 -0
  29. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/shutdown/__init__.py +0 -0
  30. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/__init__.py +0 -0
  31. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/objects/__init__.py +0 -0
  32. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/__init__.py +0 -0
  33. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/_base.py +0 -0
  34. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/_responses.py +0 -0
  35. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/_values.py +0 -0
  36. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/bitmap.py +0 -0
  37. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/generic.py +0 -0
  38. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/geo.py +0 -0
  39. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/hash.py +0 -0
  40. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/json.py +0 -0
  41. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/list.py +0 -0
  42. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/pubsub.py +0 -0
  43. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/set.py +0 -0
  44. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/sorted_set.py +0 -0
  45. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/stream.py +0 -0
  46. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/storage/redis/string.py +0 -0
  47. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/__init__.py +0 -0
  48. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/_context.py +0 -0
  49. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/_encode.py +0 -0
  50. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/_provider.py +0 -0
  51. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/_span.py +0 -0
  52. {encore_dev-2.0.0a15 → encore_dev-2.0.0a17}/encore/tracing/_tracer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: encore-dev
3
- Version: 2.0.0a15
3
+ Version: 2.0.0a17
4
4
  Summary: Encore SDK for Python — infrastructure resources
5
5
  Author: Encore
6
6
  License: MPL-2.0
@@ -15,16 +15,29 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  The Python SDK for [Encore](https://encore.dev) — **infrastructure resources only**.
17
17
 
18
- The distribution is named `encore-dev`; Python code imports it as `encore`.
19
-
20
18
  Declare infrastructure (SQL databases, pub/sub topics & subscriptions, object
21
19
  storage buckets, Redis databases, cron jobs, metrics, secrets) as module-level variables.
22
- Encore's parser discovers them for provisioning and wiring, and the native runtime
23
- binding (`encore_sdk_core` over PyO3) connects them to the provisioned
24
- infrastructure at run time.
20
+ Encore discovers these declarations, provisions the resources, and connects your
21
+ application to them at runtime.
25
22
 
26
23
  Services are declared by the language-agnostic `encore.service.json` file, not in
27
- Python — this SDK is infrastructure-only (the counterpart of the Go/TS/Rust SDKs).
24
+ Python. Use the SDK with your preferred Python web framework.
25
+
26
+ ## Installation
27
+
28
+ Requires Python 3.9 or later. Install from PyPI with pip:
29
+
30
+ ```sh
31
+ python -m pip install encore-dev
32
+ ```
33
+
34
+ Or add it to a uv project:
35
+
36
+ ```sh
37
+ uv add encore-dev
38
+ ```
39
+
40
+ ## Declare resources
28
41
 
29
42
  ```python
30
43
  from encore import pubsub, secrets
@@ -65,7 +78,8 @@ Declare `postgres.Database("orders", migrations="./migrations")` at module scope
65
78
  reference a database declared elsewhere.
66
79
 
67
80
  `query(sql, *params)` returns a `Cursor`. Use `async for` to stream rows,
68
- `await query.one()` to fetch one row or `None` (rejecting multiple rows), or
81
+ `await query.one()` to fetch exactly one row (raising `EncoreError` on no rows or
82
+ more than one), `await query.first()` to fetch the first row or `None`, or
69
83
  `await query.all()` to collect all rows in a list. Each query can be consumed
70
84
  once. Reusing it or mixing helpers with iteration raises `EncoreError`, including
71
85
  after partial iteration or an error. Helpers close their cursors automatically.
@@ -77,7 +91,7 @@ dictionaries keyed by column name. Values are bound with `$1`, `$2`, etc.
77
91
 
78
92
  ```python
79
93
  async def find_order(database: postgres.Queryable, order_id: int):
80
- return await database.query("SELECT * FROM orders WHERE id = $1", order_id).one()
94
+ return await database.query("SELECT * FROM orders WHERE id = $1", order_id).first()
81
95
 
82
96
 
83
97
  async def handle_order(order_id: int):
@@ -91,7 +105,7 @@ select `read()`, `write()`, or `admin()` before handing it to another function.
91
105
  Direct database operations infer writer permissions. Reference creation records
92
106
  reader, writer, or admin permissions at that call site.
93
107
 
94
- Like the Rust SDK, handles share the database's pool. A `read()` handle runs on
108
+ Handles share the database's pool. A `read()` handle runs on
95
109
  read-only connections, so its writes fail with SQLSTATE 25006 and its
96
110
  transactions are read-only; it reads from the database's read-only pool on a
97
111
  replica if the runtime config has one, unless `primary=True` is set.
@@ -122,11 +136,6 @@ async def first_order():
122
136
  return row
123
137
  ```
124
138
 
125
- Native Postgres integration tests can be enabled with
126
- `ENCORE_PYTHON_POSTGRES_TEST_HOST=host:port`. They expect an isolated Postgres
127
- instance with database/user `postgres` and password `encore-test`, and create and
128
- remove a temporary schema.
129
-
130
139
  ## Pub/Sub payloads
131
140
 
132
141
  Use `pubsub.Subscription[T]` to choose how payloads are decoded. Encore validates
@@ -303,94 +312,9 @@ If cleanup panics after the deadline has already recorded `Timeout`, the core
303
312
  logs the failure and preserves `Timeout` as the reported result. A timeout is
304
313
  not evidence that all cleanup operations eventually succeeded.
305
314
 
306
- ## Packaging
307
-
308
- `encore-dev` contains the Python SDK and is built with uv:
309
-
310
- ```sh
311
- uv build sdk/py/infra
312
- ```
313
-
314
- For release packaging, stage the SDK and stamp its version without changing the
315
- checked-in manifest:
316
-
317
- ```sh
318
- cargo xtask pack-python --version v2.0.0-alpha.1
319
- ```
320
-
321
- This writes `encore_dev-2.0.0a1-py3-none-any.whl`, the source distribution, and
322
- SHA-256 sidecars to `dist/python/` (override with `--output`). Alpha and beta
323
- versions become `aN` and `bN`; nightly and development versions become `.devN`.
324
- The command requires uv and the native build prerequisites (including Protoc).
325
- It builds the native runtime to generate fresh type stubs, but packages only the
326
- pure Python SDK and does not publish to PyPI.
327
-
328
- The Rust extension lives in the parent `sdk/py/` directory and is packaged separately by Maturin.
329
- Its `encore-runtime` wheel is an internal artifact for the CLI, not a dependency
330
- of the public SDK:
331
-
332
- ```sh
333
- # With Maturin installed:
334
- cargo xtask build-py-runtime --release --wheel
335
- # Optionally select the wheel's target (requires a suitable toolchain):
336
- cargo xtask build-py-runtime --release --wheel --target aarch64-apple-darwin
337
- ```
338
-
339
- SDK distributions are written to `sdk/py/infra/dist/`; native wheels go to
340
- `sdk/py/dist/`.
341
-
342
- ## Building locally
343
-
344
- From the repository root:
345
-
346
- ```sh
347
- cargo xtask build-py-runtime
348
- ```
349
-
350
- This stages `_encore_runtime` inside `infra/encore/` for the SDK's local loader,
351
- and in `runtimes/python/` for the development CLI. It also regenerates
352
- `encore/internal/_native.pyi` from the compiled extension. Edit native signatures
353
- in Rust, then rerun this command to update the stub. An editable install of the SDK
354
- can then load the native extension without setting `PYTHONPATH`:
355
-
356
- ```sh
357
- uv run --project sdk/py/infra pytest sdk/py/infra/tests
358
- ```
359
-
360
- The CLI sets `ENCORE_PYTHON_RUNTIME_DIR` for runs, tests, and exec commands.
361
- The SDK selects the native library using the running interpreter's build ABI:
362
- standard CPython uses the root `abi3` library; free-threaded Python 3.14 uses
363
- `cp314t/_encore_runtime.so` (`.pyd` on Windows). This selection is unchanged if
364
- a free-threaded interpreter temporarily enables its GIL. Unsupported ABIs fail
365
- with a missing-runtime error instead of falling back to an incompatible library.
366
-
367
- Build the local free-threaded variant separately:
368
-
369
- ```sh
370
- cargo xtask build-py-runtime --python-runtime cp314t
371
- ```
372
-
373
- The default builds `abi3`. Both variants can coexist. Native-only wheel builds
374
- (`--wheel`) continue to use maturin's interpreter selection instead.
375
-
376
- `ENCORE_PYTHON_RUNTIME_LIB` overrides directory selection with an explicit library.
377
- An explicit library or directory is authoritative; failures do not fall back.
378
- Without either variable, the loader searches beside itself and in parent
379
- and `dist/` directories using the same ABI layout.
380
-
381
- Configure the development CLI's `ENCORE_RUNTIMES_PATH` to point at this checkout's
382
- `runtimes/` directory. Published SDK wheels exclude native libraries; standalone
383
- use requires a runtime directory or explicit library from the CLI installation.
384
-
385
- App builds install `encore-dev` and copy the runtime directory, preserving its ABI
386
- variants for selection by the deployment interpreter. An explicit library override
387
- is packaged separately and remains authoritative. Cross-platform runtime retrieval
388
- is not yet supported.
389
-
390
315
  ## Resource initialization
391
316
 
392
- Importing the SDK synchronously initializes the native Encore runtime, matching
393
- TypeScript. Run applications through Encore so runtime configuration is available
317
+ Importing the SDK synchronously initializes the Encore runtime. Run applications through Encore so runtime configuration is available
394
318
  before imports. Initialization failures surface during import, before normal
395
319
  application startup completes.
396
320
 
@@ -398,39 +322,12 @@ Buckets, SQL databases, pub/sub topics, and secrets store their declarations and
398
322
  resolve native handles on first use, caching the results with thread-safe
399
323
  initialization. These lookups are synchronous against the already initialized
400
324
  runtime. Importing application modules requires the configured runtime and native
401
- extension. SDK unit tests supply a minimal runtime configuration
402
- in `tests/conftest.py`; integration tests use `encore test`.
325
+ extension. Use `encore test` to run application tests with Encore-managed
326
+ resources.
403
327
 
404
328
  Initialize independently in each worker process. Forking an already initialized
405
329
  native runtime is not supported by this startup model.
406
330
 
407
- ## Linting and formatting
408
-
409
- From the repository root, lint the SDK and its tests:
410
-
411
- ```sh
412
- uv run --locked --project sdk/py/infra ruff check sdk/py/infra
413
- ```
414
-
415
- Add `--fix` to apply safe fixes. The enabled rules check syntax/style errors,
416
- undefined and unused names, common bug patterns, and Google-style docstrings
417
- (`E4`, `E7`, `E9`, `F`, `B`, `D`).
418
-
419
- From the repository root, format the SDK and its tests with Ruff:
420
-
421
- ```sh
422
- uv run --project sdk/py/infra ruff format sdk/py/infra
423
- ```
424
-
425
- To check formatting without changing files:
426
-
427
- ```sh
428
- uv run --project sdk/py/infra ruff format --check sdk/py/infra
429
- ```
430
-
431
- The generated `_native.pyi` is excluded from linting and formatting because it
432
- is owned by the PyO3 stub generator. It is still checked by ty.
433
-
434
331
  ## Metrics
435
332
 
436
333
  Metrics are OpenTelemetry instruments of the meter `encore`, declared at module
@@ -483,63 +380,6 @@ trace.set_tracer_provider(tracer_provider())
483
380
  metrics.set_meter_provider(meter_provider())
484
381
  ```
485
382
 
486
- ## Documentation style
487
-
488
- Follow the [Google Python docstring guide](https://google.github.io/styleguide/pyguide.html#docstrings)
489
- for modules, classes, functions, methods, and Python examples in documentation.
490
- Start docstrings with a concise summary on the opening triple-double-quote line.
491
- End the summary with punctuation, keep it within 80 characters, and separate any
492
- further explanation with a blank line. Use imperative summaries for functions
493
- ("Return the current phase.") and noun phrases for classes and properties.
494
-
495
- Use `Args:`, `Returns:`, `Yields:`, `Raises:`, and `Attributes:` as appropriate,
496
- with four-space indentation for entries and continuation lines. Document behavior
497
- callers need to know: units, defaults, side effects, resource ownership, and
498
- relevant exceptions. Parameter names must match the signature, including `*args`
499
- and `**kwargs`; type annotations usually make repeated type descriptions
500
- unnecessary. Document public data attributes in the class's `Attributes:` section
501
- and constructor arguments in `__init__`.
502
-
503
- Simple APIs can use a one-line docstring when the summary and signature explain
504
- their behavior completely. Omit `Returns:` for functions returning only `None`;
505
- use `Yields:` for generators, describing each yielded item. For a decorated
506
- context manager, describe the context manager returned to the caller. Put module
507
- examples under `Typical usage example:`. Keep README prose and examples consistent
508
- with these API contracts; Markdown guides retain their normal heading structure.
509
-
510
- Ruff enforces the Google docstring convention through the existing lint command
511
- and pre-commit hook. Test docstrings are optional; add them to explain unusual
512
- setup or behavior rather than repeating test names. Existing test docstrings
513
- still follow the same formatting rules. Review documentation for accuracy as
514
- well as formatting: Ruff cannot verify behavioral contracts.
515
-
516
- ## Type checking
517
-
518
- From the repository root, check the SDK, native stubs, and tests with ty:
519
-
520
- ```sh
521
- uv run --project sdk/py/infra ty check --project sdk/py/infra
522
- ```
523
-
524
- The checker uses the minimum Python version from `requires-python` and resolves
525
- dependencies from the SDK's virtual environment.
526
-
527
- ## Pre-commit hooks
528
-
529
- From the repository root, install the prek hooks once per checkout:
530
-
531
- ```sh
532
- uv run --locked --project sdk/py/infra prek install
533
- ```
534
-
535
- The hooks apply safe Ruff lint fixes, format the SDK, and check it with ty when
536
- relevant SDK files change. If a hook changes files, stage those changes and commit
537
- again. All hooks use the same locked tools as CI. To run them manually:
538
-
539
- ```sh
540
- uv run --locked --project sdk/py/infra prek run --all-files
541
- ```
542
-
543
383
  ## Object storage
544
384
 
545
385
  Declare a bucket at module scope, then use its async methods:
@@ -747,35 +587,3 @@ is cancelled.
747
587
  `db.connection_string` (also available on write handles) returns the direct server
748
588
  URL and grants broad read-write access. External clients using that URL do not
749
589
  apply Encore's key prefixes or tracing. Read-only handles do not expose this URL.
750
-
751
- ## Testing Postgres
752
-
753
- Build the native runtime from the repository root:
754
-
755
- ```sh
756
- cargo xtask build-py-runtime
757
- ```
758
-
759
- Then run the Postgres tests from `sdk/py/infra`:
760
-
761
- ```sh
762
- uv sync --locked
763
- uv run pytest tests/test_sqldb.py tests/test_postgres_runner.py -v
764
- ```
765
-
766
- The integration runner starts one disposable `postgres:17` Docker container and
767
- removes it after the suite. To use an existing test server, set
768
- `ENCORE_PYTHON_POSTGRES_TEST_HOST=localhost:5432`. The server must provide a
769
- `postgres` database and user with password `encore-test` and schema creation
770
- privileges. Use a dedicated test server.
771
-
772
- `tests/test_postgres_runner.py` launches `tests/postgres_integration_suite.py`
773
- in a separate process to isolate the native runtime configuration. The suite is
774
- collected explicitly by the runner and excluded from normal pytest discovery
775
- by its filename.
776
- Each test and Hypothesis example gets a fresh schema. The suite includes
777
- parameter round trips, generated transaction and cursor operation sequences,
778
- and synchronized cancellation regressions. Hypothesis uses deterministic generation and reports minimized failing examples.
779
- Sequences are repeatable for unchanged tests and the same Hypothesis version.
780
- Resource cleanup and bounded polling live in `tests/postgres_test_support.py`.
781
- These integration tests are run locally and are not executed by CI.
@@ -2,16 +2,29 @@
2
2
 
3
3
  The Python SDK for [Encore](https://encore.dev) — **infrastructure resources only**.
4
4
 
5
- The distribution is named `encore-dev`; Python code imports it as `encore`.
6
-
7
5
  Declare infrastructure (SQL databases, pub/sub topics & subscriptions, object
8
6
  storage buckets, Redis databases, cron jobs, metrics, secrets) as module-level variables.
9
- Encore's parser discovers them for provisioning and wiring, and the native runtime
10
- binding (`encore_sdk_core` over PyO3) connects them to the provisioned
11
- infrastructure at run time.
7
+ Encore discovers these declarations, provisions the resources, and connects your
8
+ application to them at runtime.
12
9
 
13
10
  Services are declared by the language-agnostic `encore.service.json` file, not in
14
- Python — this SDK is infrastructure-only (the counterpart of the Go/TS/Rust SDKs).
11
+ Python. Use the SDK with your preferred Python web framework.
12
+
13
+ ## Installation
14
+
15
+ Requires Python 3.9 or later. Install from PyPI with pip:
16
+
17
+ ```sh
18
+ python -m pip install encore-dev
19
+ ```
20
+
21
+ Or add it to a uv project:
22
+
23
+ ```sh
24
+ uv add encore-dev
25
+ ```
26
+
27
+ ## Declare resources
15
28
 
16
29
  ```python
17
30
  from encore import pubsub, secrets
@@ -52,7 +65,8 @@ Declare `postgres.Database("orders", migrations="./migrations")` at module scope
52
65
  reference a database declared elsewhere.
53
66
 
54
67
  `query(sql, *params)` returns a `Cursor`. Use `async for` to stream rows,
55
- `await query.one()` to fetch one row or `None` (rejecting multiple rows), or
68
+ `await query.one()` to fetch exactly one row (raising `EncoreError` on no rows or
69
+ more than one), `await query.first()` to fetch the first row or `None`, or
56
70
  `await query.all()` to collect all rows in a list. Each query can be consumed
57
71
  once. Reusing it or mixing helpers with iteration raises `EncoreError`, including
58
72
  after partial iteration or an error. Helpers close their cursors automatically.
@@ -64,7 +78,7 @@ dictionaries keyed by column name. Values are bound with `$1`, `$2`, etc.
64
78
 
65
79
  ```python
66
80
  async def find_order(database: postgres.Queryable, order_id: int):
67
- return await database.query("SELECT * FROM orders WHERE id = $1", order_id).one()
81
+ return await database.query("SELECT * FROM orders WHERE id = $1", order_id).first()
68
82
 
69
83
 
70
84
  async def handle_order(order_id: int):
@@ -78,7 +92,7 @@ select `read()`, `write()`, or `admin()` before handing it to another function.
78
92
  Direct database operations infer writer permissions. Reference creation records
79
93
  reader, writer, or admin permissions at that call site.
80
94
 
81
- Like the Rust SDK, handles share the database's pool. A `read()` handle runs on
95
+ Handles share the database's pool. A `read()` handle runs on
82
96
  read-only connections, so its writes fail with SQLSTATE 25006 and its
83
97
  transactions are read-only; it reads from the database's read-only pool on a
84
98
  replica if the runtime config has one, unless `primary=True` is set.
@@ -109,11 +123,6 @@ async def first_order():
109
123
  return row
110
124
  ```
111
125
 
112
- Native Postgres integration tests can be enabled with
113
- `ENCORE_PYTHON_POSTGRES_TEST_HOST=host:port`. They expect an isolated Postgres
114
- instance with database/user `postgres` and password `encore-test`, and create and
115
- remove a temporary schema.
116
-
117
126
  ## Pub/Sub payloads
118
127
 
119
128
  Use `pubsub.Subscription[T]` to choose how payloads are decoded. Encore validates
@@ -290,94 +299,9 @@ If cleanup panics after the deadline has already recorded `Timeout`, the core
290
299
  logs the failure and preserves `Timeout` as the reported result. A timeout is
291
300
  not evidence that all cleanup operations eventually succeeded.
292
301
 
293
- ## Packaging
294
-
295
- `encore-dev` contains the Python SDK and is built with uv:
296
-
297
- ```sh
298
- uv build sdk/py/infra
299
- ```
300
-
301
- For release packaging, stage the SDK and stamp its version without changing the
302
- checked-in manifest:
303
-
304
- ```sh
305
- cargo xtask pack-python --version v2.0.0-alpha.1
306
- ```
307
-
308
- This writes `encore_dev-2.0.0a1-py3-none-any.whl`, the source distribution, and
309
- SHA-256 sidecars to `dist/python/` (override with `--output`). Alpha and beta
310
- versions become `aN` and `bN`; nightly and development versions become `.devN`.
311
- The command requires uv and the native build prerequisites (including Protoc).
312
- It builds the native runtime to generate fresh type stubs, but packages only the
313
- pure Python SDK and does not publish to PyPI.
314
-
315
- The Rust extension lives in the parent `sdk/py/` directory and is packaged separately by Maturin.
316
- Its `encore-runtime` wheel is an internal artifact for the CLI, not a dependency
317
- of the public SDK:
318
-
319
- ```sh
320
- # With Maturin installed:
321
- cargo xtask build-py-runtime --release --wheel
322
- # Optionally select the wheel's target (requires a suitable toolchain):
323
- cargo xtask build-py-runtime --release --wheel --target aarch64-apple-darwin
324
- ```
325
-
326
- SDK distributions are written to `sdk/py/infra/dist/`; native wheels go to
327
- `sdk/py/dist/`.
328
-
329
- ## Building locally
330
-
331
- From the repository root:
332
-
333
- ```sh
334
- cargo xtask build-py-runtime
335
- ```
336
-
337
- This stages `_encore_runtime` inside `infra/encore/` for the SDK's local loader,
338
- and in `runtimes/python/` for the development CLI. It also regenerates
339
- `encore/internal/_native.pyi` from the compiled extension. Edit native signatures
340
- in Rust, then rerun this command to update the stub. An editable install of the SDK
341
- can then load the native extension without setting `PYTHONPATH`:
342
-
343
- ```sh
344
- uv run --project sdk/py/infra pytest sdk/py/infra/tests
345
- ```
346
-
347
- The CLI sets `ENCORE_PYTHON_RUNTIME_DIR` for runs, tests, and exec commands.
348
- The SDK selects the native library using the running interpreter's build ABI:
349
- standard CPython uses the root `abi3` library; free-threaded Python 3.14 uses
350
- `cp314t/_encore_runtime.so` (`.pyd` on Windows). This selection is unchanged if
351
- a free-threaded interpreter temporarily enables its GIL. Unsupported ABIs fail
352
- with a missing-runtime error instead of falling back to an incompatible library.
353
-
354
- Build the local free-threaded variant separately:
355
-
356
- ```sh
357
- cargo xtask build-py-runtime --python-runtime cp314t
358
- ```
359
-
360
- The default builds `abi3`. Both variants can coexist. Native-only wheel builds
361
- (`--wheel`) continue to use maturin's interpreter selection instead.
362
-
363
- `ENCORE_PYTHON_RUNTIME_LIB` overrides directory selection with an explicit library.
364
- An explicit library or directory is authoritative; failures do not fall back.
365
- Without either variable, the loader searches beside itself and in parent
366
- and `dist/` directories using the same ABI layout.
367
-
368
- Configure the development CLI's `ENCORE_RUNTIMES_PATH` to point at this checkout's
369
- `runtimes/` directory. Published SDK wheels exclude native libraries; standalone
370
- use requires a runtime directory or explicit library from the CLI installation.
371
-
372
- App builds install `encore-dev` and copy the runtime directory, preserving its ABI
373
- variants for selection by the deployment interpreter. An explicit library override
374
- is packaged separately and remains authoritative. Cross-platform runtime retrieval
375
- is not yet supported.
376
-
377
302
  ## Resource initialization
378
303
 
379
- Importing the SDK synchronously initializes the native Encore runtime, matching
380
- TypeScript. Run applications through Encore so runtime configuration is available
304
+ Importing the SDK synchronously initializes the Encore runtime. Run applications through Encore so runtime configuration is available
381
305
  before imports. Initialization failures surface during import, before normal
382
306
  application startup completes.
383
307
 
@@ -385,39 +309,12 @@ Buckets, SQL databases, pub/sub topics, and secrets store their declarations and
385
309
  resolve native handles on first use, caching the results with thread-safe
386
310
  initialization. These lookups are synchronous against the already initialized
387
311
  runtime. Importing application modules requires the configured runtime and native
388
- extension. SDK unit tests supply a minimal runtime configuration
389
- in `tests/conftest.py`; integration tests use `encore test`.
312
+ extension. Use `encore test` to run application tests with Encore-managed
313
+ resources.
390
314
 
391
315
  Initialize independently in each worker process. Forking an already initialized
392
316
  native runtime is not supported by this startup model.
393
317
 
394
- ## Linting and formatting
395
-
396
- From the repository root, lint the SDK and its tests:
397
-
398
- ```sh
399
- uv run --locked --project sdk/py/infra ruff check sdk/py/infra
400
- ```
401
-
402
- Add `--fix` to apply safe fixes. The enabled rules check syntax/style errors,
403
- undefined and unused names, common bug patterns, and Google-style docstrings
404
- (`E4`, `E7`, `E9`, `F`, `B`, `D`).
405
-
406
- From the repository root, format the SDK and its tests with Ruff:
407
-
408
- ```sh
409
- uv run --project sdk/py/infra ruff format sdk/py/infra
410
- ```
411
-
412
- To check formatting without changing files:
413
-
414
- ```sh
415
- uv run --project sdk/py/infra ruff format --check sdk/py/infra
416
- ```
417
-
418
- The generated `_native.pyi` is excluded from linting and formatting because it
419
- is owned by the PyO3 stub generator. It is still checked by ty.
420
-
421
318
  ## Metrics
422
319
 
423
320
  Metrics are OpenTelemetry instruments of the meter `encore`, declared at module
@@ -470,63 +367,6 @@ trace.set_tracer_provider(tracer_provider())
470
367
  metrics.set_meter_provider(meter_provider())
471
368
  ```
472
369
 
473
- ## Documentation style
474
-
475
- Follow the [Google Python docstring guide](https://google.github.io/styleguide/pyguide.html#docstrings)
476
- for modules, classes, functions, methods, and Python examples in documentation.
477
- Start docstrings with a concise summary on the opening triple-double-quote line.
478
- End the summary with punctuation, keep it within 80 characters, and separate any
479
- further explanation with a blank line. Use imperative summaries for functions
480
- ("Return the current phase.") and noun phrases for classes and properties.
481
-
482
- Use `Args:`, `Returns:`, `Yields:`, `Raises:`, and `Attributes:` as appropriate,
483
- with four-space indentation for entries and continuation lines. Document behavior
484
- callers need to know: units, defaults, side effects, resource ownership, and
485
- relevant exceptions. Parameter names must match the signature, including `*args`
486
- and `**kwargs`; type annotations usually make repeated type descriptions
487
- unnecessary. Document public data attributes in the class's `Attributes:` section
488
- and constructor arguments in `__init__`.
489
-
490
- Simple APIs can use a one-line docstring when the summary and signature explain
491
- their behavior completely. Omit `Returns:` for functions returning only `None`;
492
- use `Yields:` for generators, describing each yielded item. For a decorated
493
- context manager, describe the context manager returned to the caller. Put module
494
- examples under `Typical usage example:`. Keep README prose and examples consistent
495
- with these API contracts; Markdown guides retain their normal heading structure.
496
-
497
- Ruff enforces the Google docstring convention through the existing lint command
498
- and pre-commit hook. Test docstrings are optional; add them to explain unusual
499
- setup or behavior rather than repeating test names. Existing test docstrings
500
- still follow the same formatting rules. Review documentation for accuracy as
501
- well as formatting: Ruff cannot verify behavioral contracts.
502
-
503
- ## Type checking
504
-
505
- From the repository root, check the SDK, native stubs, and tests with ty:
506
-
507
- ```sh
508
- uv run --project sdk/py/infra ty check --project sdk/py/infra
509
- ```
510
-
511
- The checker uses the minimum Python version from `requires-python` and resolves
512
- dependencies from the SDK's virtual environment.
513
-
514
- ## Pre-commit hooks
515
-
516
- From the repository root, install the prek hooks once per checkout:
517
-
518
- ```sh
519
- uv run --locked --project sdk/py/infra prek install
520
- ```
521
-
522
- The hooks apply safe Ruff lint fixes, format the SDK, and check it with ty when
523
- relevant SDK files change. If a hook changes files, stage those changes and commit
524
- again. All hooks use the same locked tools as CI. To run them manually:
525
-
526
- ```sh
527
- uv run --locked --project sdk/py/infra prek run --all-files
528
- ```
529
-
530
370
  ## Object storage
531
371
 
532
372
  Declare a bucket at module scope, then use its async methods:
@@ -734,35 +574,3 @@ is cancelled.
734
574
  `db.connection_string` (also available on write handles) returns the direct server
735
575
  URL and grants broad read-write access. External clients using that URL do not
736
576
  apply Encore's key prefixes or tracing. Read-only handles do not expose this URL.
737
-
738
- ## Testing Postgres
739
-
740
- Build the native runtime from the repository root:
741
-
742
- ```sh
743
- cargo xtask build-py-runtime
744
- ```
745
-
746
- Then run the Postgres tests from `sdk/py/infra`:
747
-
748
- ```sh
749
- uv sync --locked
750
- uv run pytest tests/test_sqldb.py tests/test_postgres_runner.py -v
751
- ```
752
-
753
- The integration runner starts one disposable `postgres:17` Docker container and
754
- removes it after the suite. To use an existing test server, set
755
- `ENCORE_PYTHON_POSTGRES_TEST_HOST=localhost:5432`. The server must provide a
756
- `postgres` database and user with password `encore-test` and schema creation
757
- privileges. Use a dedicated test server.
758
-
759
- `tests/test_postgres_runner.py` launches `tests/postgres_integration_suite.py`
760
- in a separate process to isolate the native runtime configuration. The suite is
761
- collected explicitly by the runner and excluded from normal pytest discovery
762
- by its filename.
763
- Each test and Hypothesis example gets a fresh schema. The suite includes
764
- parameter round trips, generated transaction and cursor operation sequences,
765
- and synchronized cancellation regressions. Hypothesis uses deterministic generation and reports minimized failing examples.
766
- Sequences are repeatable for unchanged tests and the same Hypothesis version.
767
- Resource cleanup and bounded polling live in `tests/postgres_test_support.py`.
768
- These integration tests are run locally and are not executed by CI.
@@ -59,7 +59,7 @@ Changes:
59
59
  Files:
60
60
  A config's value is written in a file named after it,
61
61
  ``checkout.config.toml`` (or ``.json``), beside the declaring service's
62
- ``encore.service.json``, or beside ``encore.app`` for a shared or global
62
+ ``encore.service.json``, or beside ``encore.app.json`` for a shared or global
63
63
  config. Overlays change it for some environments only:
64
64
  ``checkout.production.config.toml`` for an environment type,
65
65
  ``checkout.@prod-eu.config.toml`` for one environment, and
@@ -53,7 +53,7 @@ class ExecResult:
53
53
 
54
54
 
55
55
  class Cursor:
56
- """A SQL cursor consumed once by iteration, one(), or all().
56
+ """A SQL cursor consumed once by iteration, one(), first(), or all().
57
57
 
58
58
  Execution starts when iteration advances or a helper is awaited. Starting
59
59
  one consumption mode prevents any other consumption of this query, including
@@ -62,8 +62,8 @@ class Cursor:
62
62
 
63
63
  Exhaust a stream or call aclose() to release its connection. For early exits,
64
64
  use the query as an async context manager. Entering the context does not
65
- execute or consume the query. Leaving it closes the stream. The one() and
66
- all() helpers close their cursors automatically.
65
+ execute or consume the query. Leaving it closes the stream. The one(),
66
+ first(), and all() helpers close their cursors automatically.
67
67
  """
68
68
 
69
69
  def __init__(self, rows: AsyncGenerator[Row, None]) -> None:
@@ -89,23 +89,47 @@ class Cursor:
89
89
  """
90
90
  return self._consume()
91
91
 
92
- async def one(self) -> Row | None:
93
- """Return zero or one row and close the cursor.
92
+ async def one(self) -> Row:
93
+ """Return the query's only row and close the cursor.
94
+
95
+ Use first() when the query may match no rows.
94
96
 
95
97
  Returns:
96
- A dictionary of column names to values, or None if no rows match.
98
+ A dictionary of column names to values.
97
99
 
98
100
  Raises:
99
- EncoreError: The query fails or returns more than one row.
101
+ EncoreError: The query fails, returns no rows, or returns more than
102
+ one row.
100
103
  """
101
104
  rows = self._consume()
102
105
  try:
103
- first = None
106
+ found = None
104
107
  async for row in rows:
105
- if first is not None:
108
+ if found is not None:
106
109
  raise EncoreError("query returned more than one row")
107
- first = row
108
- return first
110
+ found = row
111
+ if found is None:
112
+ raise EncoreError("query returned no rows")
113
+ return found
114
+ finally:
115
+ await rows.aclose()
116
+
117
+ async def first(self) -> Row | None:
118
+ """Return the query's first row, if any, and close the cursor.
119
+
120
+ Rows after the first are not read.
121
+
122
+ Returns:
123
+ A dictionary of column names to values, or None if no rows match.
124
+
125
+ Raises:
126
+ EncoreError: The query fails.
127
+ """
128
+ rows = self._consume()
129
+ try:
130
+ async for row in rows:
131
+ return row
132
+ return None
109
133
  finally:
110
134
  await rows.aclose()
111
135
 
@@ -231,9 +255,9 @@ class Queryable:
231
255
  def query(self, sql: str, *params: Any) -> Cursor:
232
256
  """Create a query for streaming or fetching rows.
233
257
 
234
- Consume the query once with async iteration, one(), or all(). No SQL runs
235
- until iteration advances or a helper is awaited. Exhaust the stream or
236
- close it before reusing its connection or transaction. Use
258
+ Consume the query once with async iteration, one(), first(), or all().
259
+ No SQL runs until iteration advances or a helper is awaited. Exhaust the
260
+ stream or close it before reusing its connection or transaction. Use
237
261
  the query as an async context manager when leaving iteration early.
238
262
 
239
263
  Args:
@@ -445,9 +469,9 @@ class Database:
445
469
  def query(self, sql: str, *params: Any) -> Cursor:
446
470
  """Create a query for streaming or fetching rows.
447
471
 
448
- Consume the query once with async iteration, one(), or all(). No SQL runs
449
- until iteration advances or a helper is awaited. See Queryable.query
450
- for stream cleanup requirements.
472
+ Consume the query once with async iteration, one(), first(), or all().
473
+ No SQL runs until iteration advances or a helper is awaited. See
474
+ Queryable.query for stream cleanup requirements.
451
475
 
452
476
  Args:
453
477
  sql: A SQL statement with positional placeholders such as $1 and $2.
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "encore-dev"
7
- version = "2.0.0a15"
7
+ version = "2.0.0a17"
8
8
  description = "Encore SDK for Python — infrastructure resources"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "encore-dev"
7
- version = "2.0.0a15"
7
+ version = "2.0.0a17"
8
8
  description = "Encore SDK for Python — infrastructure resources"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"