Skip to content

[SYNPY-1880] feat: added per row validation to Grid data class #1428

Open
linglp wants to merge 29 commits into
developfrom
SYNPY-1880-clean
Open

[SYNPY-1880] feat: added per row validation to Grid data class #1428
linglp wants to merge 29 commits into
developfrom
SYNPY-1880-clean

Conversation

@linglp

@linglp linglp commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem:

Add the ability to query rows in a Synapse Grid session and retrieve row level validation results

Solution:

All dataclasses added:
Please note that some data classes are renamed to avoid name collision with the data classes in table (i.e. Query -> GridQuery)
GridQuery Query
QueryRequest QueryRequest
GridQueryJobRequest GridQueryJobRequest / GridQueryJobResponse
GridQueryResult QueryResult
GridRow Row
SelectColumn SelectColumn
GridQueryValidationResult ValidationResults
SelectItem SelectItem
SelectByName SelectByName
SelectAll SelectAll
CountStar CountStar
SelectSelection SelectSelection
Filter Filter
RowValidationResultFilter RowValidationResultFilter
CellValueFilter CellValueFilter
RowSelectionFilter RowSelectionFilter
RowIsValidFilter RowIsValidFilter
RowIdFilter RowIdFilter
ValidationOperator ValidationOperator
CellValueOperator CellValueOperator
GridReplica GridReplica
CreateReplicaRequest CreateReplicaRequest

Replica support

  • Added the create_grid_replica API call (api/curation_services.py, POST /grid/session/{sessionId}/replica)
  • Added GridReplica and CreateReplicaRequest dataclasses
  • Added Grid.create_replica_async/create_replica methods

Grid.validate_rows_async / validate_rows
New method that submits a QueryRequest against a grid session and returns per-row validation results

Grid.connect_async / connect

  • Added as an async context manager (@asynccontextmanager) and its true sync counterpart
  • Creates the grid session and one replica up front, binds the replica to the Grid instance for the duration of the with/async with block, and clears it on exit

`Synapse.allow_client_caching(False)` this will use the last created
instance from the Synapse class constructor.

Returns:

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.

All these changes are confusing. I did not delete these and they are still there if you use VS code to see.

syn = Synapse()
syn.login()

# Export modified grid data back to the record set

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.

All these changes are confusing. I did not delete these and they are still there if you use VS code to see.

return self
return None

def synchronize(

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.

Same above. The function is still there.


@skip_async_to_sync
@asynccontextmanager
async def connect_async(

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.

Based on John Hill's suggestion here, we will need to use connect function to interact with the replica.

timeout=timeout, synapse_client=synapse_client
)

if not request.query_result or not request.query_result.rows:

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.

Initially I raised it as an error but eventually decided that here it should be a warning. It could be that there's no row to validate or the Filter or SelectColumn didn't return anything.

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.

The typing isn't quite right then. It says it returns a GridQueryResult, but if GridQueryJobRequest.query_result is None, that is what will be returned. All the examples assume it will not be None as well. I would strongly consider raising an exception here.

@otel_trace_method(
method_to_trace_name=lambda self, **kwargs: f"Grid_Validate_Rows_Session_ID: {self.session_id}"
)
async def validate_rows_async(

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.

This is also important to review. Examples in the docstring demonstrated how you could use this function with connect.

@linglp
linglp marked this pull request as ready for review July 17, 2026 15:57
@linglp
linglp requested a review from a team as a code owner July 17, 2026 15:57
Copilot AI review requested due to automatic review settings July 17, 2026 15:57

Copilot AI 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.

Pull request overview

Adds structured Grid query support to the experimental curator models so callers can query a Grid session and retrieve per-row JSON-schema validation results, including support for creating/binding Grid replicas via a new API endpoint and convenience connect context managers.

Changes:

  • Added new Grid query/validation dataclasses (SelectItem/Filter hierarchies, QueryRequest, query result models) and a new GridQueryJobRequest async job type.
  • Added Grid replica support end-to-end: new REST API call, replica/request models, and Grid.create_replica_async plus Grid.connect_async/connect to bind a replica for a session scope.
  • Added async unit tests and updated experimental reference docs to surface the new APIs.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/synapseclient/models/async/unit_test_curation_async.py Adds unit tests covering new Grid query/validation dataclasses, replica creation, connect context manager, and row validation flow.
synapseclient/models/mixins/asynchronous_job.py Registers the async job endpoint mapping for GridQueryJobRequest.
synapseclient/models/curation.py Implements the new Grid query/validation dataclasses, replica workflow, connect context managers, and validate_rows_async.
synapseclient/models/init.py Re-exports newly added curator models/enums for the public synapseclient.models namespace.
synapseclient/core/constants/concrete_types.py Adds concreteType constants for the new Grid query and filter/select item types.
synapseclient/api/curation_services.py Adds create_grid_replica REST API wrapper (POST /grid/session/{sessionId}/replica).
synapseclient/api/init.py Exposes the new create_grid_replica API function via the API package.
docs/reference/experimental/sync/curator.md Updates experimental sync reference to include new Grid methods and types.
docs/reference/experimental/async/curator.md Updates experimental async reference to include new Grid methods and types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread synapseclient/models/curation.py
Comment thread synapseclient/models/curation.py
Comment on lines +5107 to +5120
grid_replica = await create_grid_replica(
session_id=self.session_id,
create_replica_request=CreateReplicaRequest(
self.session_id
).to_synapse_request(),
synapse_client=synapse_client,
)
replica_data = grid_replica.get("replica")
if not replica_data:
raise ValueError(
f"Replica could not be created for grid session '{self.session_id}': "
f"no replica was returned in the Synapse response."
)
return GridReplica().fill_from_dict(replica_data)
Comment thread synapseclient/models/curation.py Outdated
Comment thread synapseclient/models/__init__.py Outdated
Comment thread synapseclient/models/curation.py Outdated
Comment thread synapseclient/models/curation.py Outdated
synapse_client=synapse_client,
)

replica = self.create_replica(synapse_client=synapse_client)

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.

Shouldn't this be checking if a replica already exists for this session before creating a new one?

@linglp linglp Jul 17, 2026

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.

When I looked at the front-end code here, they always create a brand-new replica regardless of whether the session is new or pre-existing, and they also don't check if the replicas exist for this session or not. I think here we should match the front-end behavior.

Comment thread synapseclient/models/curation.py Outdated
@linglp

linglp commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@andrewelamb Per Nick's comment here, I think maybe there's no harsh enforcement of the replica limit? But based on my understanding of John Hill's comment here, we shouldn't create replica IDs wastefully (i.e. per function call). There's no way from the client side at least to "delete" a replica, and every replica created just becomes permanent part of the document (see his comment here)

To answer your second question: the idea here is that we reuse one replica for the lifetime of one with block — every validate_rows/validate_rows_async call made inside that block shares the same replica ID. When John said that the replica ID should be re-used in a session here, I think he meant within one with block, and not separate scripts or the whole Python process.

Comment thread synapseclient/models/curation.py
Comment thread synapseclient/models/curation.py
Comment thread synapseclient/models/curation.py Outdated
Comment thread synapseclient/models/curation.py

@BryanFauble BryanFauble 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.

Let's look to add a few end-to-end integration tests, the rest of the logic looks good to me!

@linglp
linglp requested a review from BryanFauble July 22, 2026 18:51
@linglp
linglp requested a review from andrewelamb July 23, 2026 17:23
timeout=timeout, synapse_client=synapse_client
)

if not request.query_result or not request.query_result.rows:

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.

The typing isn't quite right then. It says it returns a GridQueryResult, but if GridQueryJobRequest.query_result is None, that is what will be returned. All the examples assume it will not be None as well. I would strongly consider raising an exception here.


Attributes:
grid_session_id: The ID of the grid session.
replica: Information about a replica. Populated after calling the

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 doesn't seem to be a real attribute?

A grid replica is an in-memory document that represents a 'copy' of the
grid. Each replica is identified by a unique replicaId, issued by the
'hub'. A user can have more than one replica at a time (i.e. using
multiple browser tabs/machines). A user is limited to 10 replicas

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.

I thought we determined this limit didn't exist?

A grid replica is an in-memory document that represents a 'copy' of the
grid. Each replica is identified by a unique replicaId, issued by the
'hub'. A user can have more than one replica at a time (i.e. using
multiple browser tabs/machines). A user is limited to 10 replicas

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.

see above comment

https://rest-docs.synapse.org/rest/POST/grid/session/sessionId/replica.html

Note: Only the user that started the grid session may create a replica.
A user is limited to 10 replicas per-hour per-grid-session.

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.

I thought we determined this limit doesn't exist?

@BryanFauble BryanFauble 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.

Thanks for the updates! Nothing additional beyond what has already been flagged.

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