Minimal Python-facing provider library for Observer.
If you are new to this surface, start with HOWTO.md first.
The most important framing is this: Python is usually the authoring surface, not the semantic subject under test.
In most successful Observer uses of Python, the Python tests are expressing verification against some other real subject:
- a CLI
- a service
- a package artifact
- a generated file set
- a workflow step
- a protocol boundary
That is the preferred pattern. Do not reduce Observer to "run one Python wrapper script and trust its exit code" if what you actually need is granular verification of a richer product surface.
This aims at the same DX bar as the Rust, TypeScript, and C libraries:
- authors write
describe(...),test(...),it(...), andexpect(...) - default identity is derived deterministically from explicit suite path plus test title
- optional
id=provides a stable override when needed - observation is bounded and opt-in through
ctx.observe().* - direct host and embedded
observedispatch are owned by the library
The common path stays human-first. The deterministic boundary stays explicit.
Preferred:
- author many small Python tests
- make each test name a real product behavior
- expose those tests through the Observer provider host boundary
- let Observer inventory, suites, reports, and product stages operate on those granular targets
Anti-pattern:
- write one orchestration-heavy Python wrapper
- expose only that wrapper as one test target
- treat wrapper exit status as the proof
The preferred pattern gives you meaningful target identity, better failure localization, and much better derived artifacts.
HOWTO.md: detailed user manual covering authoring, determinism, host transport, inventory derivation, and end-to-end workflowobserver.py: public APItests/test_observer.py: stdlib-only self-test coverage for the Python SDKexample_smoke.py: tiny collection and execution examplehost_example.py: tinylist/runhost examplehost_embed_example.py: own-main styleobservenamespace examplepyproject.toml: installable packaging metadata for the standalone Python modulestarter/: runnable project-shaped example with provider host, inventory derivation, suite run, and snapshot verificationstarter-embedded/: runnable app-shaped example where the application keepsmain()and routesobserve ...through its own CLIstarter-failure/: runnable failing companion showing the same provider flow with one intentionally failing exported test
If you want the full end-to-end workflow rather than isolated snippets, start with starter/, then read starter-embedded/, and then compare those with starter-failure/.
from observer import collect_tests, describe, expect, test
def build_tests():
with describe("database"):
@test("access to the database")
def _(ctx):
ctx.stdout("ok\n")
expect(True).to_be_truthy()
tests = collect_tests(build_tests)When id is omitted, Observer derives a deterministic identity from suite path, test title, and duplicate occurrence order.
If a test wants a refactor-stable identity, it opts into id explicitly:
@test("access to the database", id="database/access")
def _(ctx):
expect(True).to_be_truthy()If a test wants to emit observational data, it uses the author context directly:
@test("access to the database", id="database/access")
def _(ctx):
observer = ctx.observe()
assert observer.metric("wall_time_ns", 104233.0)
assert observer.vector("request_latency_ns", [1000.0, 1100.0, 980.0])
assert observer.tag("resource_path", "fixtures/config.json")
expect(True).to_be_truthy()- explicit
id, when present, must be non-empty - resolved canonical identities must be unique
- resolved targets must be unique
- deterministic sorting is by canonical name, then target
In this first cut, the resolved identity is used for both canonical name and target.
python3 lib/python/example_smoke.pypython3 -m unittest discover -s lib/python/tests -p 'test_*.py'The Python library now includes minimal packaging metadata in pyproject.toml.
That means the module can be installed directly from this folder:
python3 -m pip install ./lib/pythonpython3 lib/python/host_example.py list
python3 lib/python/host_example.py observe --target pkg::smoke --timeout-ms 1000
python3 lib/python/host_example.py run --target pkg::fail --timeout-ms 1000The library owns the standard provider host transport for Python too. A direct host can stay nearly trivial:
from observer import collect_tests, describe, expect, observer_host_main, test
def build_tests():
with describe("pkg"):
@test("smoke test", id="pkg::smoke")
def smoke(ctx):
ctx.stdout("ok\n")
expect(True).to_be_truthy()
if __name__ == "__main__":
observer_host_main("python", collect_tests(build_tests))For developer-facing usage, prefer observe. run remains available for compatibility with the standardized outer provider contract.
If a project already owns its CLI, the library can also serve an embedded observe namespace:
import sys
from observer import collect_tests, describe, expect, observer_host_dispatch_embedded, test
def build_tests():
with describe("pkg"):
@test("embedded smoke test", id="pkg::embedded-smoke")
def smoke(ctx):
ctx.stdout("ok\n")
expect(True).to_be_truthy()
def app_main(argv):
print("app main", " ".join(argv[1:]))
if __name__ == "__main__":
tests = collect_tests(build_tests)
if len(sys.argv) > 1 and sys.argv[1] == "observe":
observer_host_dispatch_embedded("python", "observe", tests)
else:
app_main(sys.argv)If you want the full app-shaped workflow rather than just the embedded snippet, read starter-embedded/.