tide generate writes a typed client into your repository, scoped to the
namespaces your caller consumes. The generated code is committed: a schema
change appears in review as a diff of the API your application calls, and
the application builds from a clean checkout with no atlantis server, no
buf, and no shared SDK.
language: in tide.yaml selects Go or Python. Absent means Go, so a
tide.yaml written before Python was supported generates what it always
did. One output_dir holds one language.
What gets generated
With output_dir and generate: set in tide.yaml, tide generate
fetches the canonical schema from the server, so proto field numbers match
the server exactly. It filters that schema to the generate: namespaces.
A cross-namespace foreign key is a scalar column, so referencing another
namespace’s entity does not require generating its types. It then emits
proto sources and typed wrappers under output_dir, inside your own
module with your own import paths, running buf generate for the wire
types.
Each entity gets a client with its six RPCs (Get, BatchGet, Create,
Update, Delete, Query) and a typed method per custom query and
procedure. A keyless entity gets no client: without a key there is
nothing to address a row by. A namespace declaring jobs also gets a typed
args type, handler type, and Register<Job> helper per job.
Python gets both flavours from one emitter: AuthorClient over a
grpc.Channel and AsyncAuthorClient over a grpc.aio.Channel, in the
same file. An entity declaring partition by takes its tenant as a
required constructor argument rather than per call — Go leaves the header
to the caller, but Python has no compiler to catch the one call site that
forgot it, and the failure is Unauthenticated in production.
generate owns only the roots it writes, and leaves everything else under
output_dir alone. It refuses a tree generated by a different caller, and
a tree generated in the other language.
language |
Roots it owns | Files |
|---|---|---|
go |
atlantis/, pb/, client/ |
buf.gen.yaml, buf.yaml |
python |
atlantis/ |
buf.gen.yaml, buf.yaml |
Python has one root because protoc derives a module path from the proto
path: atlantis/library/v1/book.proto must produce
atlantis/library/v1/book_pb2.py for from atlantis.library.v1 import book_pb2 to resolve, so buf’s output lands beside the clients that import
it rather than in a pb/ of its own.
Python needs output_dir on the import path
The same rule has a consequence for packaging: the generated code imports
atlantis.<namespace>.v1, so output_dir must itself be a Python source
root. There is no flag that changes it. In pyproject.toml:
[tool.hatch.build.targets.wheel]
packages = ["src/atlantis"]tide init --language python prints this stanza with your own
output_dir filled in. Without it the failure is a ModuleNotFoundError
in your own code, which tide generate never sees and cannot report.
The runtime the generated code imports is an ordinary dependency:
pip install atlantis-clientWhy a stale client still works
Proto field numbers are preserved by field name across applies. A removed field’s number is retired and reserved for as long as the entity exists, and a new field takes the smallest free number. An old client therefore keeps sending a shape the server understands. It cannot see fields added since it was generated, and code using a new entity will not compile until you regenerate. A column whose type changes keeps its number with a new proto type, so regenerate after a type change before relying on that field.
tide apply never regenerates the client; it refreshes only the
.tide-cache/ mirror. Its success message reminds you to run
tide generate.
The manifest and --check
tide generate writes tide.manifest.json beside the code: the caller,
the namespaces, the module prefix, the language, and a committed hash per
generated file. A Go manifest records no language, so a tree generated
before Python existed regenerates byte-identically.
tide generate --checkverifies the tree against the manifest with no server, no credentials, and nobuf. It catches hand-edited, missing, and leftover files.tide generate --check --against-serveralso reports whether the schema has moved since this client was generated. It needs credentials for that.
The manifest records no timestamp and no checkpoint hash, so two runs that produce identical code produce an identical manifest.
Related
- Use the Python client — connecting, querying, paging, and running a worker.
- The typed query surface — what the generated
Querycan express. tide generate— flags and exit codes.- Set up CI — where
--checkbelongs in a pipeline.