Lock a workspace¶
Warning
Workspace support is experimental. Discovery rules, member validation, and the auto-promoted build policy may change.
A workspace is a parent project that declares one or more in-tree
members. When nab lock runs against a member (or the root itself),
the resolver prefers the in-tree members over PyPI for any package
whose canonical name matches a member’s [project].name.
Declaring a workspace¶
The workspace root carries a [tool.nab.workspace] table:
# /repo/pyproject.toml
[tool.nab.workspace]
members = [
"packages/core",
"packages/extras",
"tools/cli",
]
workspace is a project-scope key like any other, so the same table
can be written at the top level of the project-directory nab.toml
instead:
# /repo/nab.toml
[workspace]
members = ["packages/core", "packages/extras", "tools/cli"]
members is a list of literal directory paths relative to the
workspace root. Globs (*, ?, [) are rejected; spell out each
member.
Every listed member must contain a pyproject.toml with a
[project].name. Two members cannot share the same canonical name
(Foo_Bar and foo-bar collide). Both raise
WorkspaceDiscoveryError at config-parse time.
How discovery works¶
The project being locked declares the workspace, in either of its two
config files, and its members are read relative to that project’s
directory. That is the path nab lock takes at a workspace root.
When nab lock <member>/pyproject.toml is invoked and the member
declares no workspace of its own, nab walks upwards from the member’s
directory for the first ancestor that declares one, in either file:
[tool.nab.workspace] in its pyproject.toml, or [workspace] in
its nab.toml. The pyproject.toml is checked first.
Each member of the matched workspace contributes a local source. The provider prefers those local sources over PyPI for any requirement whose canonical name matches.
Interaction with [[tool.nab.local-sources]]¶
Explicit [[tool.nab.local-sources]] entries always win. When a
workspace-discovered member shares a canonical name with an
explicit entry, the discovered entry is dropped and the override is
logged at INFO so it can be audited. Use this to point a workspace
member at a checkout outside the tree without removing it from the
members list.
Build policy floor¶
Workspace members frequently declare dynamic = ["version"] or
similar. nab promotes the active [tool.nab].build-policy to at
least build-local whenever a workspace is in scope, so PEP 517
metadata extraction runs even when the user-set policy is stricter.
A policy that is already build-local or build-remote is left
alone.
Scope of [tool.nab] keys¶
Workspace discovery flows these from the root into the file being
locked: the workspace members (merged into local-sources), and
the build-policy floor described above. Everything else under
[tool.nab] is scoped to the pyproject being locked: conflicts,
default-groups, constraints, matrix, mode, requires-python,
uploaded-prior-to, build-policy itself, dist-policy, vcs,
indexes, and environment. Locking a member with nab lock packages/core/pyproject.toml reads only that file’s keys; the
root’s are ignored.
Declare conflicts and default-groups on each member that needs them. The same applies to constraints: a root-level constraint table does not constrain a member resolve.
Example layout¶
/repo
├── pyproject.toml # carries [tool.nab.workspace]
├── packages/
│ ├── core/
│ │ ├── pyproject.toml # [project].name = "myorg-core"
│ │ └── src/...
│ └── extras/
│ ├── pyproject.toml # [project].name = "myorg-extras"
│ └── src/...
└── tools/
└── cli/
├── pyproject.toml # [project].name = "myorg-cli"
└── src/...
# /repo/pyproject.toml
[project]
name = "myorg"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"myorg-core",
"myorg-extras",
"myorg-cli",
]
[tool.nab.workspace]
members = [
"packages/core",
"packages/extras",
"tools/cli",
]
nab lock /repo/pyproject.toml resolves the three workspace
dependencies against the in-tree directories rather than fetching
them from PyPI. Transitive dependencies of each member are still
resolved through the configured indexes.