feat: add project_group_name to projects.create() - #835
Conversation
Python port of braintrustdata/braintrust-sdk-javascript#2541. `init_logger`, `init`, `init_dataset`, and `Eval`/`EvalAsync` now accept `project_group_name`. When the named project does not exist yet, it is created inside that project group, which lets callers who only hold project-creation permission on a group (rather than on the whole organization) register projects. The option is only honored when the project is looked up by name -- it is ignored when `project_id` is supplied -- and it is omitted from the wire payload entirely when unset, so existing callers see no change in behavior or requests. Notes on the port: - The JS SDK registers projects through `api/project/register`; the Python SDK was migrated to the generated `POST /v1/project` resource in #705. Both are backed by the same `createProjectSchema`, which the API half of this change extends, so the new field is threaded into that body via a shared `_register_project` helper. - `init`/`init_dataset` need no extra round trip here. Unlike the JS versions, they already resolve the project via project registration and then register the experiment/dataset by `project_id`. - The JS change also keys its project-metadata LRU cache on the group name. Python's `_compute_logger_metadata` has no such cache, so there is nothing to key. - New parameters are appended to each signature rather than inserted next to `project_id`, so existing positional callers are unaffected. `test_project_group.py` drives the real entrypoints against a local HTTP server (`api/_test_server.scripted_server`) and asserts on the request bodies the SDK actually sends, including that the field is absent when unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f11743afda
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
| if project_group_name is not None: | ||
| # Only present when set, so exported span components (and therefore anything that resolves | ||
| # them with an older SDK) are byte-for-byte unchanged for callers not using project groups. | ||
| compute_metadata_args["project_group_name"] = project_group_name |
There was a problem hiding this comment.
Preserve the project group during W3C trace propagation
When a logger initialized with project_group_name is still unresolved, its spans retain this dictionary, but Span.inject() routes through _get_parent_info() / _get_otel_parent(), which serializes only project_name:<name>. A receiving service then reconstructs metadata containing only project_name and may register the project outside the requested group—or fail for the group-scoped users this feature is intended to support. Include the group in the propagation representation or resolve the project ID before emitting the baggage value.
Useful? React with 👍 / 👎.
| project_info = state.api_client().projects.post_project( | ||
| body={"name": project or GLOBAL_PROJECT, "org_name": state.org_name} | ||
| ) | ||
| project_info = _register_project(state, project or GLOBAL_PROJECT, project_group_name=project_group_name) |
There was a problem hiding this comment.
i would probably inline this - cruft otherwise
Luca Forstner (lforst)
left a comment
There was a problem hiding this comment.
I have reservations about project groups being in the SDK but unblocking.
project_group_name to create projects inside a project groupproject_group_name to projects.create()
Ref: braintrustdata/braintrust-sdk-javascript#2550
Python port of the JS change.
projects.create()now takes an optionalproject_group_name.When
braintrust pushorproject.publish()registers a project that doesn't exist yet, it gets created inside that group.This lets people who only have project-creation permission on a group (not the whole org) register projects.
We deliberately don't add this to
init_logger,init,init_datasetorEval.Loggers resolve their project on every startup, and passing a group turns that cheap lookup into an uncached "create or verify group membership" call that locks the group's row on the backend.
Every logger cold start in that group would wait on the same lock, and logging would start failing if the project ever gets moved out of the group.
Creating a project is a one-time setup step, so it belongs on the push/publish path.
(An earlier version of this PR added the option to those entrypoints, mirroring JS #2541. The JS SDK has since moved it in #2550, and this PR now follows that.)
ProjectIdCache.get()passesproject.project_group_nametoget_by_name(), which only addsproject_group_nameto thePOST /v1/projectbody when it's set, so requests without a group are unchanged.Evaluators pushed via
braintrust pushstill register their project without a group, same as in JS.One difference to JS: the JS SDK registers projects through
api/project/register, while the Python SDK uses the generated/v1/projectresource since #705.Both are backed by
createProjectSchema, so I think the field should work on both.If the API change turns out to only cover
api/project/register, this needs to switch endpoints.The generated
CreateProjecttype doesn't have the field yet, hence thecast.Added tests in
test_framework2.pyfor the field being sent and being omitted when unset.test_framework2.py,test_logger.py,test_framework.py, thecli/tests andnox -s pylintpass.🤖 Generated with Claude Code
Co-authored by StarfolkAI (@starfolkai)[bot]