Skip to content

feat(fdc): Add execute_graphql and execute_graphql_read support with Pythonic impersonation - #970

Open
mk2023 wants to merge 18 commits into
winefrom
barolo-seed
Open

feat(fdc): Add execute_graphql and execute_graphql_read support with Pythonic impersonation#970
mk2023 wants to merge 18 commits into
winefrom
barolo-seed

Conversation

@mk2023

@mk2023mk2023 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Overview

✨ Adds public execute_graphql and execute_graphql_read methods to DataConnect, refactors Impersonation to use Pythonic auth_claims (PEP 8 snake_case) in Python land while translating to authClaims during JSON payload serialization, and includes comprehensive unit and emulator integration test suites.

Highlights

✨ Key changes:

  • Public API (firebase_admin.dataconnect): Added execute_graphql for query/mutation execution and execute_graphql_read for read-only queries with mutation validation.
  • Pythonic Impersonation: Updated Impersonation to store auth_claims in Python land, with automatic auth_claims -> authClaims key translation in _prepare_graphql_payload for network serialization.
  • Options Validation: Updated _validate_impersonation_options to validate auth_claims in Python land.
  • Unit Tests (tests/test_data_connect.py): Added 82 unit tests covering method execution, payload serialization, dataclass variables, impersonation options, and error parsing.
  • Integration Tests & Emulator Seeding (integration/): Added 20 integration tests against the Data Connect emulator, including seed.sh data seeding (fred_id, jeff_id, email_id) and state cleanup (UPSERT_FRED_EMAIL).
  • CI Workflow (.github/workflows/ci.yml): Added --cert flag and emulator seeding step for CI integration testing.
Detailed Changelog

Core SDK Changes

  • firebase_admin/dataconnect.py:
    • Implemented execute_graphql and execute_graphql_read on DataConnect.
    • Added Impersonation class with unauthenticated() and authenticated(auth_claims=...).
    • Updated _prepare_graphql_payload to translate auth_claims to authClaims via dictionary key popping.
    • Added _validate_impersonation_options and ExecuteGraphqlResponse dataclass.

Integration & Emulator Changes

  • integration/test_data_connect.py: Added 20 integration tests covering queries, mutations, multi-operation documents, impersonated requests, and mutation state cleanup.
  • integration/emulators/seed.sh: Added script to seed initial test data (fred_id, jeff_id, email_id) into the emulator.
  • .github/workflows/ci.yml: Added --cert flag and emulator seeding to integration test workflow.

Unit Test Changes

  • tests/test_data_connect.py: Added 82 unit tests, refactoring shared constants (TEST_URL, TEST_HEADERS, TEST_PAYLOAD, TEST_AUTH_CLAIMS, TEST_VARIABLES) and dataclasses (UserProfile, CreateUserVariables, User).

Testing Strategy

  • Unit Tests: pytest tests/test_data_connect.py (82/82 PASSED)
  • Integration Tests: DATA_CONNECT_EMULATOR_HOST=127.0.0.1:9399 pytest integration/test_data_connect.py --cert tests/data/service_account.json (20/20 PASSED)
  • Linter: Rated 10.00/10 via ./lint.sh

Context Sources Used:

  • id: firebase-admin-python

mk2023 added 5 commits July 20, 2026 14:09
Implemented _make_gql_request on _DataConnectApiClient to execute and handle responses/errors for GraphQL operations. Added corresponding unit tests in tests/test_data_connect.py.
Refactored _parse_graphql_response and added robust recursive type deserialization to _DataConnectApiClient.
- Implemented _deserialize_type and _deserialize_dataclass helper methods to support nested dataclasses, generic lists (List[T]), generic dictionaries (Dict[K, V]), Unions (Union[...]), Enums, and primitive casting.
- Enhanced _make_gql_request error handling to prevent silent error swallowing when the errors key is present.
- Added comprehensive unit test coverage in tests/test_data_connect.py.
…helper
- Introduced QueryError subclass of FirebaseError for Data Connect GraphQL query/mutation errors and exposed it in __all__.
- Extracted _check_graphql_errors helper method on _DataConnectApiClient.
- Updated error handling for non-dictionary response payloads in _parse_graphql_response to raise InternalError.
- Note: Did not edit parse_graphql_response because we are waiting on whether this will even be a function or not.
…nt instantiation
- Removed output deserialization helpers (_extract_actual_type, _deserialize_type, _deserialize_dataclass) to return raw JSON payload dictionaries (ExecuteGraphqlResponse.data), aligning Data Connect with Firestore and Realtime Database patterns for user-defined schemas.
- Updated DataConnect.__init__ to immediately instantiate _DataConnectApiClient for consistency with Node.js and other Python Admin SDK services.
- Updated test suite in tests/test_data_connect.py to cover raw response parsing and immediate client instantiation.
Added execute_graphql and execute_graphql_read method signatures and docstrings to DataConnect and _DataConnectApiClient. Also introduced a comprehensive integration test suite in integration/test_data_connect.py translated from Node.js Admin SDK integration tests.

@gemini-code-assistgemini-code-assistBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces execute_graphql and execute_graphql_read methods to the DataConnect client, along with corresponding integration and unit tests. The review feedback highlights that several of these new methods are left unimplemented (raising NotImplementedError) and provides their implementation details. Additionally, the reviewer identifies a critical type-checking bug in the validation logic when variables_type is Any, and points out a mismatch in a test assertion message.

Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadtests/test_data_connect.py Outdated

@stephenarosajstephenarosaj left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

in progress, leaving comments early - have to look at test cases still

Comment threadfirebase_admin/dataconnect.py
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadintegration/test_data_connect.py Outdated
Added an explicit __init__ constructor to Impersonation to validate parameter configurations at object instantiation time. Enforced choosing either unauthenticated=True or auth_claims, with support for both auth_claims (snake_case) and authClaims (camelCase). Updated class docstring to recommend factory methods. Also added unit test suite TestImpersonation in tests/test_data_connect.py.
@mk2023
mk2023 changed the base branch from barolo to wineJuly 29, 2026 07:00
@mk2023
mk2023 marked this pull request as ready for review July 29, 2026 07:01
mk2023and others added 3 commits July 29, 2026 00:12
…mulator test setup
Implemented execute_graphql and execute_graphql_read methods on DataConnect and _DataConnectApiClient. Configured Data Connect emulator schema, connector, queries, mutations, seed script, and GitHub Actions CI workflow step.

@stephenarosajstephenarosaj left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM with some changes requested - mostly small stuff, only a few blocking comments

Comment threadfirebase_admin/dataconnect.py
Comment threadfirebase_admin/dataconnect.py
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadintegration/emulators/seed.sh
Comment threadintegration/test_data_connect.py Outdated
Comment threadintegration/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py

@stephenarosajstephenarosaj left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Accidentally selected Approve but mean Request changes - re-submitting review

LGTM with some changes requested - mostly small stuff, only a few blocking comments

@mk2023
mk2023 requested a review from jonathanedeyJuly 31, 2026 19:58
Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadtests/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py Outdated
Comment threadtests/test_data_connect.py Outdated
…leanup
- Impersonation API: Updated Impersonation to use auth_claims (snake_case) in Python land, while translating it to authClaims (camelCase) in _prepare_graphql_payload during JSON wire serialization.
- Impersonation Validation: Updated _validate_impersonation_options to validate auth_claims in Python land.
- Integration Tests (integration/test_data_connect.py): Added UPDATED_FRED_EMAIL mutation test fixtures with real state changes, restored initial state via UPSERT_FRED_EMAIL cleanup at the end of mutation tests, reordered query tests before mutations, and removed redundant read impersonation test cases.

@stephenarosajstephenarosaj left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@mk2023mk2023 changed the title feat(fdc): Add execute_graphql signatures and integration test suitefeat(fdc): Add execute_graphql and execute_graphql_read support with Pythonic impersonationAug 3, 2026
@jonathanedeyjonathanedey added the release:stage Stage a release candidate label Aug 4, 2026

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

Thanks @mk2023! This overall looks great, with a few comments mainly on testing! I've also added the integration test tag so those tests should run from your next commit.

Comment threadfirebase_admin/dataconnect.py Outdated
Comment thread.github/workflows/ci.yml Outdated
- name: Run Functions emulator tests
run: firebase emulators:exec --config integration/emulators/firebase.json --only tasks,functions --project fake-project-id 'CLOUD_TASKS_EMULATOR_HOST=localhost:9499 pytest integration/test_functions.py'
- name: Run Data Connect emulator tests
run: firebase emulators:exec --config integration/emulators/firebase.json --only dataconnect --project fake-project-id './integration/emulators/seed.sh && DATA_CONNECT_EMULATOR_HOST=localhost:9399 pytest integration/test_data_connect.py --cert=tests/data/service_account.json'

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.

We shouldn't need to pass credentials here since this is only running against emulator but i'm guessing the test suite was complaining for one.

To get around that we override that check in the integration test file if the emulator host is set. For reference see:

defintegration_conf(request):
host_override=os.environ.get('CLOUD_TASKS_EMULATOR_HOST')
ifhost_override:
return_utils.EmulatorAdminCredentials(), 'fake-project-id'
returnconftest.integration_conf(request)
@pytest.fixture(scope='module')
defapp(request):
cred, project_id=integration_conf(request)
returnfirebase_admin.initialize_app(
cred, options={'projectId': project_id}, name='integration-functions')
@pytest.fixture(scope='module', autouse=True)
defdefault_app():
# Overwrites the default_app fixture in conftest.py.
# This test suite should not use the default app. Use the app fixture instead.
pass

@mk2023mk2023Aug 4, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Thanks for letting me know!
I removed the credentials flag from ci.yml! Unlike test_functions.py which explicitly passes a named app instance to every call, test_data_connect.py relies on dataconnect.client() resolving the default app, so default_app initializes the default app directly instead of using pass. Let me know if this isn't optimal!

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.

Yeah, that sounds like it should be fine as long as we define the default_app(request) fixture to use the same integration_conf(request) fixture override.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Awesome, thanks! That's how I defined default_app(request) in integration/test_data_connect.py!

@mk2023mk2023Aug 5, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Update: Defining default_app in test_data_connect.py caused session app collisions (ValueError: The default Firebase app already exists) during full integration test runs.
Thus, I aligned the code with existing codebase conventions to use a named app fixture and set default_app to pass. I also had to add a dc_client(app) fixture to inject the client into tests rather than instantiating dataconnect.client() in every method. Now, my code adheres to established project conventions and significantly reduces boilerplate code!

Additionally, the stage_release check revealed tests were attempting to run against live GCP, so I added a check_emulator fixture to safely skip execution when DATA_CONNECT_EMULATOR_HOST is absent.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Update: stage_release check is supposed to fail because we do not have a prod project set up to run tests against. Deleted check_emulator!

)


class TestExecuteGraphql:

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.

Since these integration tests are ran against the same project and config that we use for our Admin Node tests we should ensure that they are compatible. The tests can use the same data but we should ensure we populate and clean up data so that they are idempotent similar to node
https://github.com/firebase/firebase-admin-node/blob/f9bff6b2db209edc182fbae27701e127ec5d71b6/test/integration/data-connect.spec.ts#L162-L176

@mk2023mk2023Aug 4, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Done! I added a setup_and_cleanup_database pytest fixture (autouse=True) to match Node's beforeEach / afterEach lifecycle. To avoid calling execute_graphql() during setup and teardown before tests run, the fixture executes raw HTTP bash scripts (seed.sh and cleanup.sh via curl) before and after each test. This populates initial state (fred_id, jeff_id, email_id) before each test and wipes the database clean (email_deleteMany, user_deleteMany) afterwards, ensuring 100% test idempotency! Let me know if you think this is okay!

@stephenarosajstephenarosajAug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

oops i missed this comment - this changes what i originally said in this comment - if both SDKs are using the same project, we shouldn't have to set up any fdc service or deploy anything - they should already be set up from the node SDK!

Comment threadfirebase_admin/dataconnect.py Outdated
Comment threadfirebase_admin/dataconnect.py
…t suite
- Type Annotations: Added from __future__ import annotations to dataconnect.py for clean return type hints.
- Integration Test Fixtures: Added setup_and_cleanup_database fixture in integration/test_data_connect.py using raw HTTP bash scripts (seed.sh and cleanup.sh) before/after every test without relying on the SDK under test.
- Emulator Cleanup: Added integration/emulators/cleanup.sh script executing raw HTTP POST deleteMany mutations via curl.
- Test Naming & CI: Updated TestImpersonation test method names to start with test_impersonation_ and removed unnecessary credentials flag from ci.yml.
… CI collision
Updated default_app fixture in integration/test_data_connect.py to safely delete pre-existing default app before initializing with EmulatorAdminCredentials, allowing dataconnect.client() to implicitly use default_app.
…ta_connect.py
Aligned integration/test_data_connect.py with test_functions.py and test_db.py by using a named app fixture ('integration-dataconnect'), overriding default_app with pass, and injecting dc_client fixture into test methods.
…ot set
Added a check_emulator autouse module fixture in integration/test_data_connect.py.
Unlike services with dynamic resource creation (e.g., Realtime Database), Data Connect requires a pre-deployed Cloud SQL Postgres schema and connector. Since live integration projects do not host these resources, Data Connect integration tests are strictly emulator-only and are safely skipped when DATA_CONNECT_EMULATOR_HOST is absent.
…ta_connect.py
Removed check_emulator fixture from integration/test_data_connect.py.
Note that stage_release integration test runs are expected to fail until the expected GraphQL schema and connector are deployed to the FDC service within the GCP project tied to the service key.
if [ -z "${response}" ]; then
echo "Failed to receive response from Data Connect emulator at ${ENDPOINT}" >&2
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this one should also have an errors check just like the other one

actually - we shouldn't duplicate this code at all. how about we instead make a single setup_teardown.sh file, so the code can be shared between each use case?

Comment threadintegration/test_data_connect.py Outdated

@pytest.fixture(scope='module', autouse=True)
def check_emulator():
"""Skips Data Connect integration tests if emulator host is not set."""

@stephenarosajstephenarosajAug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

we shouldn't be skipping the integration tests if the emulator host isn't set - in that case, we should be running against production!

for this, i think we'll need to initialize the FDC service in the project for the service key we use for github actions test runs - and then deploy the schema + connectors we have in this repo to that project. i believe only Lahiru and @jonathanedey can do this

i think we should either remove the stage release tag or get that project set up

EDIT: according to this comment, the project used here and in the node admin SDK are actually the same - so there's no need to set anything up!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

for now, we can test against production manually using the same project we use for manual testing in the admin node SDK - i'll share the cert files with you

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.

Good point, sorry about that folks, I'll remove the tag to unblock this until we can get that setup correctly.

@@ -0,0 +1,22 @@
#!/bin/bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

something i forgot to think about: placement of these files. they should probs live in the integration/dataconnect folder, so it's clear these are ONLY for fdc

@jonathanedeyjonathanedey removed the release:stage Stage a release candidate label Aug 5, 2026
Sign up for freeto 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

@mk2023@stephenarosaj@jonathanedey@itsrakhil