Conversation
|
This is in a state where it more or less works as expected: from tiled.client import from_uri
from tiled.queries import KeyIn, KeyNotIn
c = from_uri('http://localhost:8000', api_key="secret")
# Put some data in the db
c.write_array([37,83], metadata={"name" : "FB326", "start" : { "Detectors" : "none" }})
c.write_array([24,18], metadata={"sample" : "TEST"})
# Demonstrate searching for a record with a surface level key
c.search(KeyIn("name"))
>> <Container {'96f7f6ca-7a9d-4cea-9f05-9b6c661ce82f'}>
# Demonstrate searching for a record with a given NESTED key
c.search(KeyIn("Detectors"))
>> <Container {'96f7f6ca-7a9d-4cea-9f05-9b6c661ce82f'}>
# Search for a nonsense key that is in no records
c.search(KeyIn("skeytty"))
>> <Container {}>
# Find records that DO NOT contain a given (nested) key
c.search(KeyNotIn("Detectors"))
>> <Container {'02a11fff-5b29-4dab-9080-e80084e88702'}>
# Find all records that don't have a nonsense key (in this case all of them)
c.search(KeyNotIn("Sketty"))
<Container {'96f7f6ca-7a9d-4cea-9f05-9b6c661ce82f', ...} ~2 entries>However I should request some quick stylistic feedback from @danielballan before continuing with docs and tests.
|
|
The existing is_primary_color = In("color", ["red", "yellow", "blue"])
c.search(is_primary_color)I believe SXSS-592 wants to query, "Was any value recorded for this key?" we_wrote_down_the_color = HasKey("color")
c.search(we_wrote_down_the_color)The idea is to eliminate results where some specific metadata was not even captured. And of course With respect to this PR, then, I would expect:
|
|
Dan's summary of the intent of SXSS-592 is exactly correct. We want to know if a key exists (possibly explicitly nested). This is extremely useful for cleaning up old metadata, e.g, Find all runs that don't have any data_session key In the latter case, we probably will have done a Also useful for looking for metadata from certain instruments, etc, to identify only runs where a certain detector was used. |
|
And of course, we want the nesting to be explicit. If we search for |
|
Removing any variety of def has_key(query, tree, invert):
if tree.engine.url.get_dialect().name == "sqlite":
condition = orm.Node.metadata_.op("->")(query.key) != None
else:
keys = query.key.split(".")
condition = orm.Node.metadata_.op("#>")(cast(keys, ARRAY(TEXT))) != None
condition = not_(condition) if invert else condition
return tree.new_variation(conditions=tree.conditions + [condition])This appears to yield the desired results: |
|
The behavior looks aligned with the requirements now. Seems ready for adding docs and unit tests in We should also give some consideration for the name. I think
I'll add |
|
Ooh, I like the KeyX/KeyY naming pattern more than KeyX/KeyNotX. Exists/Missing is a fairly good pair. Off the top of my head, KeyPresent/KeyAbsent may also be a good option, as "Absent" is a bit more neutral than "Missing", which does somehow carry the implication that the key should be there, and Present/Absent are definitely antonyms in English, whereas exists and missing have just slightly different contexts. Either option sounds good to me though. |
|
Any objection to If not I'll elect |
|
Oh, great idea. Let’s go with that. If we start with just |
|
I'm having problems with postgresql after merging in the latest Tiled code: According to the official docs the However this stopped working after 9b9fb46 Casting to tiled/tiled/catalog/adapter.py Line 1442 in bc57e94 The same exact code works in earlier commit 6fd9ede @genematx or @danielballan can you pull this PR and see what's up when you get a chance? |
|
I think the issue was with the import; there is and also |
|
Thanks, @genematx! Looking good, @Kezzsim. Last thing: Above, I think @cjtitus made a persuasive case that KeyPresent/KeyAbsent are a nice pair of names, at the same time that you made the case for KeyExists with an optional boolean. Thoughts on renaming KeyExists to KeyPresent? Then, if later on we find that we want to make the "reverse" more discoverable to users, we can always come back and add KeyAbsent. My feelings is that the boolean is likely good enough, but if we choose a name with a good antonym, we keep the option open for later. |
|
Testing interactively, I'm not convinced the nested keys work as advertised in the docstring. In [29]: x = c.write_dataframe({'a': [1,2,3]}, key='x', metadata={"sample": {"color": "blue"}})
In [30]: c
Out[30]: <Container {'x'}>
In [31]: c.search(KeyPresent("sample.color"))
Out[31]: <Container {}>Since this is almost certainly important for our applications (e.g. querying on keys nested inside |
|
Last time I tested I was having local environment issues, but now I think those are sorted and I still see a problem with nested keys (at least on SQLite). Server: ❯ uv run --all-extras tiled --version
0.1.0b35.dev22+g6af4cb181
❯ uv run --all-extras tiled serve catalog --temp --api-key secretClient: ❯ uv run --all-extras --with ipython ipython
Python 3.12.11 | packaged by conda-forge | (main, Jun 4 2025, 14:45:31) [GCC 13.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import tiled
In [2]: tiled.__version__
Out[2]: '0.1.0b35.dev22+g6af4cb181'
In [3]: from tiled.queries import KeyPresent
In [4]: from tiled.client import from_uri
c
In [5]: c = from_uri('http://localhost:8000', api_key='secret')
In [6]: y = c.create_container("y", metadata={"sample": {"color": "red"}})
In [7]: c.search(KeyPresent("sample")) # good
Out[7]: <Container {'y'}>
In [8]: c.search(KeyPresent("asdf")) # good
Out[8]: <Container {}>
In [9]: c.search(KeyPresent("sample.color")) # bad
Out[9]: <Container {}> |
|
Same, with pixi instead of uv: |
|
As discussed with @danielballan there's now a way to insert more specific test data on the fly to tiled which eventually will make db tests more atomic. It does not work with map adapter presently because that appears to be static, but this does let us test nested keys. |



Requested by @cjtitus in NSLS-II Ticket SXSS-592.
This attempts to register queries that can tell if a container's metadata has a KEY matching a particular string in it.