Skip to content

Tagging system for items - #1937

Open
gpetretto wants to merge 7 commits into
datalab-org:ml-evs/bump-pydantic-final-finalfrom
Matgenix:gp/tags_scoped
Open

Tagging system for items#1937
gpetretto wants to merge 7 commits into
datalab-org:ml-evs/bump-pydantic-final-finalfrom
Matgenix:gp/tags_scoped

Conversation

@gpetretto

Copy link
Copy Markdown
Contributor

As anticipated in #1764, this PR introduces tags for annotating items with a dedicated management page and two permission scopes.
The feature is off by default, enabled through the PYDATALAB_ENABLE_TAGS variable.

Note that the branch is based on the pydatinc2 one. The idea is that it will be merged in main after the pydantic2 branch is merged in main.

Here are the main features of the tags:

Scopes

Two scopes:

  • Global: only managed by admins, all users can use them
  • Personal: users can manage their personal tags. Only available to the user that created it

Notes:

  • A user may apply a tag to an item only if it's global or their own
    personal tag; they may remove any tag from an item they can edit
    (including another user's personal tag).
  • Names are unique within a scope. Identity is the immutable id.

Data model

  • Tag entry in a new tags collection.
  • The scope is handled through scope and owner attributes. Explicitly decided not to use HasOwner as, after internal discussions, we considered that belonging to multiple creators or groups may open the road to all sort of accessibility issues. This seemed a different was of scoping the ownership. If preferred the same logic could probably be achieved using HasOwner and enforcing in the code that only one creator can be set, with no creator meaning global tag.
  • Items has a tags field through the HasTags mixin

Frontend

  • Tags field on item pages: searchable multi-select.
  • /tags management page: create/edit/delete the tags.
  • Samples table: Tags column with a tag filter.

@ml-evs ml-evs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • fix e2e tests by making the test user an admin
  • can we easily click on a tag and have it apply the filter?
  • can we set up the global tags pre-defined at the server level?

Maybe future PR?

  • group admin (and future project) admin level tags (future PR)

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.69960% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.74%. Comparing base (623edac) to head (f122121).

Files with missing lines Patch % Lines
pydatalab/src/pydatalab/routes/v0_1/tags.py 91.66% 11 Missing ⚠️
pydatalab/src/pydatalab/models/traits.py 79.31% 6 Missing ⚠️
pydatalab/src/pydatalab/routes/v0_1/items.py 91.66% 3 Missing ⚠️
pydatalab/src/pydatalab/mongo.py 95.83% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                          @@
##           ml-evs/bump-pydantic-final-final    #1937      +/-   ##
====================================================================
+ Coverage                             80.24%   80.74%   +0.49%     
====================================================================
  Files                                    83       85       +2     
  Lines                                  7739     7986     +247     
====================================================================
+ Hits                                   6210     6448     +238     
- Misses                                 1529     1538       +9     
Files with missing lines Coverage Δ
pydatalab/src/pydatalab/config.py 81.04% <100.00%> (+0.12%) ⬆️
pydatalab/src/pydatalab/feature_flags.py 75.90% <100.00%> (+0.59%) ⬆️
pydatalab/src/pydatalab/models/__init__.py 100.00% <100.00%> (ø)
pydatalab/src/pydatalab/models/items.py 96.55% <100.00%> (ø)
pydatalab/src/pydatalab/models/tags.py 100.00% <100.00%> (ø)
pydatalab/src/pydatalab/models/utils.py 88.44% <100.00%> (+0.15%) ⬆️
pydatalab/src/pydatalab/routes/v0_1/__init__.py 100.00% <100.00%> (ø)
pydatalab/src/pydatalab/mongo.py 87.85% <95.83%> (+1.00%) ⬆️
pydatalab/src/pydatalab/routes/v0_1/items.py 85.25% <91.66%> (+0.41%) ⬆️
pydatalab/src/pydatalab/models/traits.py 94.61% <79.31%> (-4.41%) ⬇️
... and 1 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gpetretto

Copy link
Copy Markdown
Contributor Author
  • fix e2e tests by making the test user an admin

    • can we easily click on a tag and have it apply the filter?

    • can we set up the global tags pre-defined at the server level?

Maybe future PR?

* group admin (and future project) admin level tags (future PR)
  • The e2e testing has been fixed by adding a task to create an initial admin user for the tests. Seems easy enough and to run the e2e tests locally the developer just needs to run the same task manually once.
  • I have added the filtering on the click of the tags in the table. I am not sure if the graphical visualization when the tag is highlighted with the mouse hover is the best, but I have tried a few different options and it is probably a matter of taste. Changing should be a couple of lines of CSS anyway.
  • After some further discussions, it seemed that the option to pre load tags from a file is not clear how to best deal with it. If this needs to be useful for a real system, it is not clear how this would behave under a redeployment: if a set of tags is pre-defined, and an admin renames one of the tags before the system is redeployed, how to ensure that the original tag is not recreated? This would probably require adding to the tags collection a custom "identifier" other than the name and the immutable_id, that can be used in the pre-fill. Not sure if it is worth. On the other hand, for the tests it means that the set of tags is predefined in the beginning and if the tests act to modify those tags the outcome of the tests may depend on the order in which they are executed.
  • renamed the "personal" tags to "user-defined"
  • Not implemented the group scope for the tags as it may go in subsequent PR.

@davidwaroquiers davidwaroquiers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quickly reviewed the changes. Looks all good to me. Maybe the only thing could be to explicitly mention somewhere (maybe both in the code docstrings, e.g. in HasTags, and in the documentation, there is no doc about the tags for now, probably good to add when this has been reviewed by @datalab-org/datalab-dev )

Comment on lines +16 to 17
- `AccessScope.USER`: a user-defined tag owned by exactly one user; only that
user can list, use, edit and delete it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe good to clarify that any tag that is applied to an item or any other object that would have HasTags will be visible to any user who has access to that item. Not sure if this should be here, in HasTags, somewhere in the docs, or in multiple of these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it would be better to specify this in the docstrings. The natural place may be AccessScope description, but it is not obvious that this would be a general behaviour. Maybe HasTags?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's indeed put it in HasTags, and then it can be added later in the user doc somewhere at some point.

@davidwaroquiers davidwaroquiers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good to me

Comment thread pydatalab/tasks.py
@@ -1,3 +1,4 @@
# This file was edited with the assistance of an AI model and requires human review from the contributor.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove after review ;-)

@gpetretto

Copy link
Copy Markdown
Contributor Author

@ml-evs we think this might be ready for your review.

@gpetretto
gpetretto marked this pull request as ready for review July 24, 2026 12:36

@OMWalmsley OMWalmsley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature seems to be working on my machine. My comments are mostly about replacing the custom jsonify errors with werkzeug.exceptions, similar to what has been done previously. This should hopefully standardise things a bit more and reduce the line count slightly.

try:
scope = AccessScope(data.get("scope") or AccessScope.USER.value)
except ValueError:
return (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of the routes have custom jsonify errors, these could be converted into werkzeug exceptions (https://werkzeug.palletsprojects.com/en/stable/exceptions/) as has been done in various other places in the code base.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @OMWalmsley for this comment. We were wondering what should be used within datalab. We actually looked at how it's done and basically custom jsonify errors (~110) are more common in datalab than werkzeug errors (~50) so we went for the jsonify errors initially but I agree that it would be good to standardize all of this. We can of course convert all these custom jsonify errors to their corresponding werkzeug exceptions. More generally, maybe it would be good to open an issue to standardize all of these in the other routes as well ? @ml-evs may have some comment on this too ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following today's meeting with @ml-evs and @OMWalmsley, the plan is to use werkzeug exceptions when it corresponds to a clear api route exception, but raise a proper and adequate error/exception when it is a runtime/datalab related error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have replaced most of the jsonify exceptions in tags.py with werkzeug.


if scope == AccessScope.GLOBAL:
if not _is_admin():
return (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to previous comment (Werkzeug errors)

if owner is None:
return (
jsonify(
status="error",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here and various other places I haven't listed

if _name_conflict_exists(name, scope, owner):
return (
jsonify(status="error", message=f"A tag named {name!r} already exists."),
409, # 409: Conflict

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here (Werkzeug errors). For example: werkzeug.exceptions.Conflict

Comment thread pydatalab/src/pydatalab/mongo.py Outdated
@ml-evs ml-evs added this to the v0.8.x milestone Jul 27, 2026
@ml-evs
ml-evs force-pushed the ml-evs/bump-pydantic-final-final branch 3 times, most recently from 0969519 to 6d809c7 Compare August 9, 2026 13:10
@ml-evs
ml-evs requested a review from be-smith August 12, 2026 09:47
@ml-evs
ml-evs force-pushed the ml-evs/bump-pydantic-final-final branch 2 times, most recently from f112cf0 to 765d3b1 Compare August 13, 2026 23:12
@ml-evs
ml-evs force-pushed the ml-evs/bump-pydantic-final-final branch 2 times, most recently from 623e1da to c075b7a Compare August 20, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants