Skip to content

The generated client

tide generate writes a typed Go or Python client into a repository, committed so schema changes appear in review as an API diff.

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-client

Why 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 --check verifies the tree against the manifest with no server, no credentials, and no buf. It catches hand-edited, missing, and leftover files.
  • tide generate --check --against-server also 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close