-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
096e288
commit fc84b77
Showing
8 changed files
with
364 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
idea/ | ||
.idea/ | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
### Proof of concept NDC lambda connector for Python. | ||
|
||
This is a work in progress. | ||
|
||
To see a more in-depth example that implements the Python SDK please see: https://github.com/hasura/ndc-turso-python | ||
|
||
Currently, the proposed functionality will look something like this: | ||
|
||
```python | ||
from hasura_ndc_lambda import FunctionConnector, start | ||
|
||
connector = FunctionConnector() | ||
|
||
|
||
@connector.register_query | ||
def do_the_thing(x: int) -> str: | ||
print(x) | ||
return "Hello World" | ||
|
||
|
||
@connector.register_mutation | ||
def some_mutation_function(arg1: str, arg2: int) -> str: | ||
# Mutation function implementation | ||
return f"Hey {arg1} {arg2}" | ||
|
||
|
||
if __name__ == "__main__": | ||
start(connector) | ||
``` | ||
|
||
There will be support for built-in scalar types (int, float, str, bool) and also planned support for Pydantic types. | ||
|
||
|
||
## TODO: Allow adding of async queries/mutations? | ||
|
||
## TODO: Add Pydantic type introspections |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
GET http://localhost:8080/capabilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
POST http://localhost:8080/mutation | ||
Content-Type: application/json | ||
|
||
{ | ||
"operations": [ | ||
{ | ||
"type": "procedure", | ||
"name": "some_mutation_function", | ||
"arguments": { | ||
"arg1": "hello", | ||
"arg2": 1 | ||
}, | ||
"fields": null | ||
} | ||
], | ||
"collection_relationships": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
POST http://localhost:8080/query | ||
Content-Type: application/json | ||
|
||
{ | ||
"collection": "Artist", | ||
"query": { | ||
"fields": { | ||
"ArtistId": { | ||
"type": "column", | ||
"column": "ArtistId" | ||
}, | ||
"Name": { | ||
"type": "column", | ||
"column": "Name" | ||
}, | ||
"Albums": { | ||
"type": "relationship", | ||
"query": { | ||
"fields": { | ||
"AlbumId": { | ||
"type": "column", | ||
"column": "AlbumId" | ||
}, | ||
"Title": { | ||
"type": "column", | ||
"column": "Title" | ||
}, | ||
"Tracks": { | ||
"type": "relationship", | ||
"query": { | ||
"fields": { | ||
"TrackId": { | ||
"type": "column", | ||
"column": "TrackId" | ||
}, | ||
"Name": { | ||
"type": "column", | ||
"column": "Name" | ||
} | ||
} | ||
}, | ||
"relationship": "[{\"namespace\":\"unknown_namespace\",\"name\":\"Album\"},\"Tracks\"]", | ||
"arguments": {} | ||
} | ||
} | ||
}, | ||
"relationship": "[{\"namespace\":\"unknown_namespace\",\"name\":\"Artist\"},\"Albums\"]", | ||
"arguments": {} | ||
} | ||
}, | ||
"limit": 10 | ||
}, | ||
"arguments": {}, | ||
"collection_relationships": { | ||
"[{\"namespace\":\"unknown_namespace\",\"name\":\"Album\"},\"Tracks\"]": { | ||
"column_mapping": { | ||
"AlbumId": "AlbumId" | ||
}, | ||
"relationship_type": "array", | ||
"target_collection": "Track", | ||
"arguments": {} | ||
}, | ||
"[{\"namespace\":\"unknown_namespace\",\"name\":\"Artist\"},\"Albums\"]": { | ||
"column_mapping": { | ||
"ArtistId": "ArtistId" | ||
}, | ||
"relationship_type": "array", | ||
"target_collection": "Album", | ||
"arguments": {} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
GET http://localhost:8080/schema |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
from hasura_ndc.connector import Connector | ||
from hasura_ndc.main import start | ||
from hasura_ndc.models import * | ||
from pydantic import BaseModel | ||
import inspect | ||
|
||
|
||
class Configuration(BaseModel): | ||
pass | ||
|
||
|
||
class State(BaseModel): | ||
pass | ||
|
||
|
||
class FunctionConnector(Connector[Configuration, State]): | ||
|
||
def __init__(self): | ||
super().__init__(Configuration, State) | ||
self.query_functions = {} | ||
self.mutation_functions = {} | ||
|
||
async def parse_configuration(self, configuration_dir: str) -> Configuration: | ||
config = Configuration() | ||
return config | ||
|
||
async def try_init_state(self, configuration: Configuration, metrics: Any) -> State: | ||
return State() | ||
|
||
async def get_capabilities(self, configuration: Configuration) -> CapabilitiesResponse: | ||
return CapabilitiesResponse( | ||
version="^0.1.0", | ||
capabilities=Capabilities( | ||
query=QueryCapabilities( | ||
aggregates=LeafCapability(), | ||
variables=LeafCapability(), | ||
explain=LeafCapability() | ||
), | ||
mutation=MutationCapabilities( | ||
transactional=LeafCapability(), | ||
explain=None | ||
), | ||
relationships=RelationshipCapabilities( | ||
relation_comparisons=LeafCapability(), | ||
order_by_aggregate=LeafCapability() | ||
) | ||
) | ||
) | ||
|
||
async def query_explain(self, | ||
configuration: Configuration, | ||
state: State, | ||
request: QueryRequest) -> ExplainResponse: | ||
pass | ||
|
||
async def mutation_explain(self, | ||
configuration: Configuration, | ||
state: State, | ||
request: MutationRequest) -> ExplainResponse: | ||
pass | ||
|
||
async def fetch_metrics(self, | ||
configuration: Configuration, | ||
state: State) -> Optional[None]: | ||
pass | ||
|
||
async def health_check(self, | ||
configuration: Configuration, | ||
state: State) -> Optional[None]: | ||
pass | ||
|
||
async def get_schema(self, configuration: Configuration) -> SchemaResponse: | ||
functions = [] | ||
procedures = [] | ||
|
||
for name, func in self.query_functions.items(): | ||
function_info = self.generate_function_info(name, func) | ||
functions.append(function_info) | ||
|
||
for name, func in self.mutation_functions.items(): | ||
procedure_info = self.generate_procedure_info(name, func) | ||
procedures.append(procedure_info) | ||
|
||
schema_response = SchemaResponse( | ||
scalar_types={}, | ||
functions=functions, | ||
procedures=procedures, | ||
object_types={}, | ||
collections=[] | ||
) | ||
|
||
return schema_response | ||
|
||
def generate_function_info(self, name, func): | ||
signature = inspect.signature(func) | ||
arguments = { | ||
arg_name: { | ||
"type": self.get_type_info(arg_type) | ||
} | ||
for arg_name, arg_type in signature.parameters.items() | ||
} | ||
return FunctionInfo( | ||
name=name, | ||
arguments=arguments, | ||
result_type=self.get_type_info(signature.return_annotation) | ||
) | ||
|
||
def generate_procedure_info(self, name, func): | ||
signature = inspect.signature(func) | ||
arguments = { | ||
arg_name: { | ||
"type": self.get_type_info(arg_type) | ||
} | ||
for arg_name, arg_type in signature.parameters.items() | ||
} | ||
return ProcedureInfo( | ||
name=name, | ||
arguments=arguments, | ||
result_type=self.get_type_info(signature.return_annotation) | ||
) | ||
|
||
@staticmethod | ||
def get_type_info(typ): | ||
if isinstance(typ, inspect.Parameter): | ||
typ = typ.annotation | ||
if typ == int: | ||
return Type(type="named", name="Int") | ||
elif typ == float: | ||
return Type(type="named", name="Float") | ||
elif typ == str: | ||
return Type(type="named", name="String") | ||
elif typ == bool: | ||
return Type(type="named", name="Boolean") | ||
else: | ||
return Type(type="named", name=typ.__name__) | ||
|
||
async def query(self, configuration: Configuration, state: State, request: QueryRequest) -> QueryResponse: | ||
print("QUERY") | ||
print(request) | ||
print(self.query_functions) | ||
return [ | ||
RowSet( | ||
aggregates=None, | ||
rows=None | ||
) | ||
] | ||
|
||
async def mutation(self, configuration: Configuration, | ||
state: State, | ||
request: MutationRequest) -> MutationResponse: | ||
responses = [] | ||
for operation in request.operations: | ||
operation_name = operation.name | ||
func = self.mutation_functions[operation_name] | ||
args = operation.arguments if operation.arguments else {} | ||
response = func(**args) | ||
responses.append(response) | ||
return MutationResponse( | ||
operation_results=[ | ||
MutationOperationResults( | ||
type="procedure", | ||
result=response | ||
) for response in responses | ||
] | ||
) | ||
|
||
def register_query(self, func): | ||
self.query_functions[func.__name__] = func | ||
return func | ||
|
||
def register_mutation(self, func): | ||
self.mutation_functions[func.__name__] = func | ||
return func | ||
|
||
|
||
# # TODO: Handle Pydantic types in the introspection | ||
# class Tmp(BaseModel): | ||
# x: str | ||
# y: int | ||
# z: float | ||
|
||
connector = FunctionConnector() | ||
|
||
|
||
@connector.register_query | ||
def do_the_thing(x: int) -> str: | ||
print(x) | ||
return "Hello World" | ||
|
||
|
||
@connector.register_mutation | ||
def some_mutation_function(arg1: str, arg2: int) -> str: | ||
# Mutation function implementation | ||
return f"Hey {arg1} {arg2}" | ||
|
||
|
||
if __name__ == "__main__": | ||
start(connector) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
annotated-types==0.6.0 | ||
anyio==4.3.0 | ||
asgiref==3.8.1 | ||
certifi==2024.2.2 | ||
charset-normalizer==3.3.2 | ||
click==8.1.7 | ||
Deprecated==1.2.14 | ||
fastapi==0.110.2 | ||
googleapis-common-protos==1.63.0 | ||
grpcio==1.62.2 | ||
h11==0.14.0 | ||
hasura-ndc==0.11 | ||
idna==3.7 | ||
importlib-metadata==7.0.0 | ||
opentelemetry-api==1.24.0 | ||
opentelemetry-exporter-otlp-proto-common==1.24.0 | ||
opentelemetry-exporter-otlp-proto-grpc==1.24.0 | ||
opentelemetry-instrumentation==0.45b0 | ||
opentelemetry-instrumentation-asgi==0.45b0 | ||
opentelemetry-instrumentation-fastapi==0.45b0 | ||
opentelemetry-instrumentation-logging==0.45b0 | ||
opentelemetry-instrumentation-requests==0.45b0 | ||
opentelemetry-propagator-b3==1.24.0 | ||
opentelemetry-proto==1.24.0 | ||
opentelemetry-sdk==1.24.0 | ||
opentelemetry-semantic-conventions==0.45b0 | ||
opentelemetry-util-http==0.45b0 | ||
protobuf==4.25.3 | ||
pydantic==2.7.0 | ||
pydantic_core==2.18.1 | ||
requests==2.31.0 | ||
sniffio==1.3.1 | ||
starlette==0.37.2 | ||
typing_extensions==4.11.0 | ||
urllib3==2.2.1 | ||
uvicorn==0.29.0 | ||
wrapt==1.16.0 | ||
zipp==3.18.1 |