Skip to content

Commit 1a1d12e

Browse files
committed
feat(fdc): Add execute_graphql signatures and integration test suite
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.
1 parent 28a50e2 commit 1a1d12e

3 files changed

Lines changed: 679 additions & 29 deletions

File tree

firebase_admin/dataconnect.py

Lines changed: 126 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
'/services/{service_id}:{endpoint_id}'
5555
)
5656

57+
_EXECUTE_GRAPHQL_ENDPOINT = 'executeGraphql'
58+
_EXECUTE_GRAPHQL_READ_ENDPOINT = 'executeGraphqlRead'
59+
5760
# Generic Type Parameters
5861
_Data = TypeVar("_Data")
5962
_Variables = TypeVar("_Variables")
@@ -102,7 +105,42 @@ def __post_init__(self):
102105
raise ValueError("connector cannot be empty")
103106

104107

108+
class Impersonation(dict):
109+
"""Represents impersonation configuration for DataConnect requests."""
110+
111+
@staticmethod
112+
def unauthenticated() -> 'Impersonation':
113+
"""Returns impersonation configuration for unauthenticated requests."""
114+
return Impersonation(unauthenticated=True)
115+
116+
@staticmethod
117+
def authenticated(auth_claims: Dict[str, Any]) -> 'Impersonation':
118+
"""Returns impersonation configuration for authenticated requests.
119+
120+
# TODO: More strongly type auth_claims later.
121+
"""
122+
return Impersonation(authClaims=auth_claims)
123+
124+
125+
@dataclass
126+
class GraphqlOptions(Generic[_Variables]):
127+
variables: Optional[_Variables] = None
128+
operation_name: Optional[str] = None
129+
impersonate: Optional[Union[Impersonation, Dict[str, Any]]] = None
130+
131+
132+
@dataclass
133+
class ExecuteGraphqlResponse(Generic[_Data]):
134+
"""Represents the response from a DataConnect GraphQL execution.
135+
136+
Attributes:
137+
data: The raw JSON dictionary returned by the GraphQL execution.
138+
"""
139+
data: _Data
140+
141+
105142
class DataConnect:
143+
106144
"""Represents a Firebase Data Connect client instance.
107145
108146
This client provides access to the Firebase Data Connect service
@@ -127,6 +165,66 @@ def app(self) -> App:
127165
def config(self) -> ConnectorConfig:
128166
return self._config
129167

168+
def execute_graphql(
169+
self,
170+
query: str,
171+
options: Optional[GraphqlOptions[_Variables]] = None,
172+
variables_type: Type[_Variables] = Any,
173+
) -> ExecuteGraphqlResponse[Any]:
174+
"""Executes a GraphQL query or mutation and returns the result.
175+
176+
Args:
177+
query: string containing the GraphQL query
178+
options: GraphqlOptions instance containing operational parameters such as
179+
variables, operation name, or impersonation context (optional).
180+
variables_type: The expected structure for the request variables
181+
182+
Returns:
183+
ExecuteGraphqlResponse: An ExecuteGraphqlResponse containing the raw
184+
response data dictionary.
185+
186+
Raises:
187+
ValueError: If the arguments are invalid from the local inputs side.
188+
InvalidArgumentError: If GraphQL syntax validation fails on the server.
189+
PermissionDeniedError: If an @auth policy directive blocks execution due to
190+
insufficient permission.
191+
NotFoundError: If a specified resource is not found, or the request is rejected
192+
by undisclosed reasons, such as whitelisting.
193+
InternalError: If the server response payload is invalid or malformed.
194+
FirebaseError: The base platform exception.
195+
"""
196+
raise NotImplementedError
197+
198+
def execute_graphql_read(
199+
self,
200+
query: str,
201+
options: Optional[GraphqlOptions[_Variables]] = None,
202+
variables_type: Type[_Variables] = Any,
203+
) -> ExecuteGraphqlResponse[Any]:
204+
"""Executes a read-only GraphQL query and returns the result.
205+
206+
Args:
207+
query: string containing the read-only GraphQL query
208+
options: GraphqlOptions instance containing operational parameters such as
209+
variables, operation name, or impersonation context (optional).
210+
variables_type: The expected structure for the request variables
211+
212+
Returns:
213+
ExecuteGraphqlResponse: An ExecuteGraphqlResponse containing the raw
214+
response data dictionary.
215+
216+
Raises:
217+
ValueError: If the arguments are invalid from the local inputs side.
218+
InvalidArgumentError: If GraphQL syntax validation fails on the server.
219+
PermissionDeniedError: If an @auth policy directive blocks execution due to
220+
insufficient permission.
221+
NotFoundError: If a specified resource is not found, or the request is rejected
222+
by undisclosed reasons, such as whitelisting.
223+
InternalError: If the server response payload is invalid or malformed.
224+
FirebaseError: The base platform exception.
225+
"""
226+
raise NotImplementedError
227+
130228

131229
class _DataConnectService:
132230
"""Service that maintains a collection of DataConnect clients."""
@@ -171,35 +269,6 @@ def client(config: ConnectorConfig, app: Optional[App] = None) -> DataConnect:
171269
return dc_service.get_client(config)
172270

173271

174-
class Impersonation(dict):
175-
"""Represents impersonation configuration for DataConnect requests."""
176-
177-
@staticmethod
178-
def unauthenticated() -> 'Impersonation':
179-
"""Returns impersonation configuration for unauthenticated requests."""
180-
return Impersonation(unauthenticated=True)
181-
182-
@staticmethod
183-
def authenticated(auth_claims: Dict[str, Any]) -> 'Impersonation':
184-
"""Returns impersonation configuration for authenticated requests.
185-
186-
# TODO: More strongly type auth_claims later.
187-
"""
188-
return Impersonation(authClaims=auth_claims)
189-
190-
191-
@dataclass
192-
class GraphqlOptions(Generic[_Variables]):
193-
variables: Optional[_Variables] = None
194-
operation_name: Optional[str] = None
195-
impersonate: Optional[Union[Impersonation, Dict[str, Any]]] = None
196-
197-
198-
@dataclass
199-
class ExecuteGraphqlResponse(Generic[_Data]):
200-
data: _Data
201-
202-
203272
def _get_emulator_host() -> Optional[str]:
204273
return _utils.get_emulator_host("DATA_CONNECT_EMULATOR_HOST")
205274

@@ -414,3 +483,31 @@ def _parse_graphql_response(
414483
)
415484

416485
return ExecuteGraphqlResponse(data=resp_dict.get("data"))
486+
487+
def _execute_graphql_helper(
488+
self,
489+
query: str,
490+
endpoint: str,
491+
options: Optional[GraphqlOptions[_Variables]] = None,
492+
variables_type: Type[_Variables] = Any,
493+
) -> ExecuteGraphqlResponse[Any]:
494+
"""Helper method to execute GraphQL queries or mutations against a specified endpoint."""
495+
raise NotImplementedError
496+
497+
def execute_graphql(
498+
self,
499+
query: str,
500+
options: Optional[GraphqlOptions[_Variables]] = None,
501+
variables_type: Type[_Variables] = Any,
502+
) -> ExecuteGraphqlResponse[Any]:
503+
"""Executes a GraphQL query or mutation and returns the result."""
504+
raise NotImplementedError
505+
506+
def execute_graphql_read(
507+
self,
508+
query: str,
509+
options: Optional[GraphqlOptions[_Variables]] = None,
510+
variables_type: Type[_Variables] = Any,
511+
) -> ExecuteGraphqlResponse[Any]:
512+
"""Executes a read-only GraphQL query and returns the result."""
513+
raise NotImplementedError

0 commit comments

Comments
 (0)