Open rohansharmaa opened 3 years ago
These are called arguments
in GraphQL world (knowing the name makes it easier to search).
Take a look at examples at https://sgqlc.readthedocs.io/en/latest/sgqlc.operation.html#selecting-to-generate-queries, in particular look for arguments
and you'll find this one, among others:
>>> op.repository(id='repo1').__fields__(
... issues={'title_contains': 'bug'}, # adds field and children
... )
This should also work:
>>> op.repository(id='repo1').issues(title_contains='bug')
Then in your case, the following should work:
op.reviews(where={'id': {'_eq': Variable('some_number') }}).__fields__('id', 'review_text')
Two advices:
sgqlc-codegen operation
to generate the Python code from the GraphQL DSL... (and if it's broken, let me know!)Variable('some_number')
as written above, this way you keep the same query and just change the variable, often helps the backend implementations, some can cache the parsed queryThis is what I tried,
class Query(Type):
reviews = Field(reviews ,args={'where':dict})
op = Operation(Query)
rating = op.reviews(where={'id':{'_eq': 12}}).__fields__('id','review_text')
But it gave me a KeyError: <class 'dict'>
followed by a TypeError: Not BaseType or mapped: <class 'dict'>
Is dict
not supported for arguments, or am I doing it wrong?
I will also try the sgqlc-codegen operation
as you suggested, but I'm just curious as to why the above method does not work.
there isn't a mapping to a generic dict
, you must create Input
types for your where
argument. Then you declare which keys do you expect, their types (including wrappers/modifiers such as list, non-null).
How is your server defining this? You need to use the same types and signatures in the client side, otherwise the server may reject it, in particular if you're using variables, any modifier mismatch will cause errors.
If you didn't write the server yet, I recommend you to go explicit and do not try to map a generic JSON object (you can't because all the object keys must be fixed, you'd have to create an array of key-value objects instead). Example schema:
input WhereIntRange {
min: Int!
max: Int!
}
input WhereInt {
eq: Int
lt: Int
gt: Int
le: Int
ge: Int
range: WhereIntRange
}
input ReviewsWhere {
id: WhereInt
}
type Query {
reviews(where: ReviewsWhere): [Review!]!
}
@rohansharmaa I assume you are trying to do the same that I am - using Hasura as a backend. Just use the code generator to generate the "Query" part and then your Operation that you posted above should work fine.
So far I've had no trouble integrating sgqlc with Hasura. The code generator works like a charm and I'm successfully running queries. Regarding the syntax, @barbieri thanks for noting that the code generator can transform raw graphql queries to python code! It allows to use the Hasura documentation and GraphiQL to make a query and then see how it should look in gsqlc.
Let me follow up on the original question. I found that it's possible to write Hasuras dict-like filter queries using the gsqlc syntax. See here two equivalent filter queries:
op.user(where={"height_cm": {"_gte": 200}}) # dict-syntax
op.user(where=schema.user_bool_exp(height_cm=schema.Int_comparison_exp(_gte=200))) # gsql-syntax
I am wondering why the codegen doesn't generate the gsql-syntax but instead the dict-syntax? I am wondering which is better to use since the gsql-syntax is more type safe but the dict-syntax is easier to read/write.
is this still an issue? Also, I highly recommend these kind of parameters to be set via Variables
, so you give it a name that is used across the operation.
The actual value of the variable you send as a plain Python "JSON-like" object, the server will handle all the needed checks to avoid errors.
I was on a massive time crunch, so as of now I've manually defined all the queries as a string and using those. Once I find the time to experiment, I'll try out these suggestions and get back to you.
Haven't had time to try out the codegen
either., but since @philon123 has tried it out and it works fine for him, it should work for me too.
The actual value of the variable you send as a plain Python "JSON-like" object, the server will handle all the needed checks to avoid errors.
I love the generated types of sgqlc, because it will not allow me to write a broken query. Basically - if it compiles, it should work. That's great and I tried to apply the thought to Hasuras complex arguments. Do you think it's better not to validate the json argument before sending to the server?
[PT-BR]
@barbieri, pela localização acho que você é BR. Cara, parabéns pelo trabalho tá incrível. Veja se você pode me ajudar. A biblioteca funciona lisinha com várias operações do Hasura, limit
, where
, offset
e diversas outras, mas não consigo de maneira nenhuma, implementar o order_by
. Gerei o codegen direto da query para reproduzir de forma mais resumida. Agradeço imensamente pelo feedback.
[EN]
@barbieri, by the location I think you are BR. Dude, congratulations on the job. It is incredible! See if you can help me. The library works smoothly with several Hasura operations, limit
,where
, offset
and several others, but I am not in any way able to implement order_by
. I generated the codegen directly from the query to reproduce in a more summarized way. Thank you so much for the feedback.
import sgqlc.types
import sgqlc.operation
import hasura_schema
_schema = hasura_schema
_schema_root = _schema.hasura_schema
__all__ = ('Operations',)
def query_my_query():
_op = sgqlc.operation.Operation(_schema_root.query_type, name='MyQuery', variables=dict(order_by=sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(_schema.binance_assets_order_by)), default={'id': 'asc'})))
_op_binance_assets = _op.binance_assets(offset=10, order_by=sgqlc.types.Variable('order_by'))
_op_binance_assets.fk_info_assets_id()
_op_binance_assets.fiat()
return _op
class Query:
my_query = query_my_query()
class Operations:
query = Query
""" AssertionError: 'id' (str) is not a JSON Object """
print(query_my_query())
Traceback (most recent call last):
File "/home/hungertaker/Projects/pysura/graphql/introspection/teste.py", line 19, in <module>
class Query:
File "/home/hungertaker/Projects/pysura/graphql/introspection/teste.py", line 20, in Query
my_query = query_my_query()
File "/home/hungertaker/Projects/pysura/graphql/introspection/teste.py", line 12, in query_my_query
_op = sgqlc.operation.Operation(_schema_root.query_type, name='MyQuery', variables=dict(order_by=sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(_schema.binance_assets_order_by)), default={'id': 'asc'})))
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 2169, in __init__
typ(default)
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 973, in __new__
return [realize_type(v, selection_list) for v in json_data]
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 973, in <listcomp>
return [realize_type(v, selection_list) for v in json_data]
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 967, in realize_type
return t(v, selection_list)
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 949, in __new__
return realize_type(json_data, selection_list)
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 944, in realize_type
return t(v, selection_list)
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 2566, in __init__
super().__init__(_json_obj, _selection_list)
File "/home/hungertaker/Dev/ENVS/pysura/lib/python3.8/site-packages/sgqlc/types/__init__.py", line 1711, in __init__
assert json_data is None or isinstance(json_data, dict), \
AssertionError: 'id' (str) is not a JSON Object
Process finished with exit code 1
Hi @hungertaker , yes I'm also 🇧🇷 :-) But let's keep it in english so others may read it as well.
I need the description of binance_assets_order_by
, given the traceback it should be an object (class ContainerType
). Take GitHub's https://docs.github.com/en/graphql/reference/input-objects#repositoryorder it's a field (enum) and direction (also an enum)
in your example, it looks wrong. If that was generated, also send me the operation so I can see what's wrong in my codegen, but:
order_by=sgqlc.types.Arg(
sgqlc.types.list_of(sgqlc.types.non_null(_schema.binance_assets_order_by)), # list!
default={'id': 'asc'} # object, not a list...
)
If the variable is declared as $orderBy=[binance_assets_order_by!]
, the default should be [{'id': 'asc'}]
or something like that.
Worked perfectly. Thank you very much. The abstraction you made from graphql to python, in the case of Hasura, which is a mirror of Postgres, allows to consult the entire endpoint, with a few classes. Soon I share with the community here some examples and ideas.
The query, codegen and schema.binance_assets_order_by:
query MyQuery($order_by: [binance_assets_order_by!] = {id: asc}) {
binance_assets(offset: 10, order_by: $order_by) {
fk_info_assets_id
fiat
}
}
import sgqlc.types
import sgqlc.operation
import hasura_schema
import json
_schema = hasura_schema
_schema_root = _schema.hasura_schema
__all__ = ('Operations',)
def query_my_query():
_op = sgqlc.operation.Operation(_schema_root.query_type, name='MyQuery', variables=dict(
order_by=sgqlc.types.Arg(sgqlc.types.list_of(
sgqlc.types.non_null(_schema.binance_assets_order_by)),
default={'id': 'asc'}))) # Correct default=[{'id': 'asc'}]
_op_binance_assets = _op.binance_assets(offset=10, order_by=sgqlc.types.Variable('order_by'))
_op_binance_assets.fk_info_assets_id()
_op_binance_assets.fiat()
return _op
class Query:
my_query = query_my_query()
class Operations:
query = Query
class binance_assets_order_by(sgqlc.types.Input):
__schema__ = hasura_schema
__field_names__ = ('fiat', 'fk_info_assets_id', 'id', 'last_update', 'name', 'pairs_by_quote_symbol_id_aggregate', 'pairs_aggregate', 'symbol')
fiat = sgqlc.types.Field(order_by, graphql_name='fiat')
fk_info_assets_id = sgqlc.types.Field(order_by, graphql_name='fk_info_assets_id')
id = sgqlc.types.Field(order_by, graphql_name='id')
last_update = sgqlc.types.Field(order_by, graphql_name='last_update')
name = sgqlc.types.Field(order_by, graphql_name='name')
pairs_by_quote_symbol_id_aggregate = sgqlc.types.Field('binance_pairs_aggregate_order_by', graphql_name='pairsByQuoteSymbolId_aggregate')
pairs_aggregate = sgqlc.types.Field('binance_pairs_aggregate_order_by', graphql_name='pairs_aggregate')
symbol = sgqlc.types.Field(order_by, graphql_name='symbol')
yes, your query is wrong, I wonder it was passing the checks, but if you try to paste that in GraphiQL/Explorer it should fail. Could you check with the latest master? Now it's doing much more checks and I think it would point out the error.
Exactly. The query is wrong. I had tested it on Hasura's Graphi and as it passed I thought everything was ok. I generated codegen again with the [...] ones and everything worked out. I'm going to test it on the new master and I'm already giving the feeedback.
@hungertaker could you confirm the current master reports the incorrect query?
https://pypi.org/project/sgqlc/14.0/ is released with that fix, so you can use the package.
I was trying to query a list of objects based on the property of a child object. Is it possible to query selection lists with this syntax?
op.repositories(owner='Alice').__fields__(
issues={'title_contains': 'bug'}, # adds field and children
)
it should be, isn't it working? could you paste the error or at least print the operation and paste the results?
Just notice this will only query issues
, ok?
HI, thank you. It is saying that the key doesn't exist. I put it into a colab here. I was using the introspection and code generation (really nice - thank you) and finding the generated filter object with that key for the object I was querying, but maybe the schema isn't implemented to perform that operation?
https://colab.research.google.com/drive/1p1UjF4qPLWNPK05s1dMRA34OMEu9ouN-#scrollTo=kGECcPjsQVVk
op = Operation(schema.Query)
positions = op.positions(first=2, where={"pool": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8"}).__fields__(
tick_upper={"tick_idx_lte": 200000}
)
endpoint(op)
KeyError Traceback (most recent call last)
<ipython-input-9-6102748212fb> in <module>()
3 tick_upper={"tick_idx_lte": 200000}
4 )
----> 5 endpoint(op)
7 frames
/usr/local/lib/python3.7/dist-packages/sgqlc/types/__init__.py in __to_graphql_input__(self, values, indent, indent_string)
2391 args = []
2392 for k, v in values.items():
-> 2393 p = self[k]
2394 args.append(p.__to_graphql_input__(v))
2395 s.extend((', '.join(args), ')'))
KeyError: 'tick_idx_lte'
I also tried this formulation:
op = Operation(schema.Query)
op.positions(
first=2,
where={"pool": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8", "tick_upper": {"tick_idx_lte": 200000}}
)
endpoint(op)
and got back this error:
op = Operation(schema.Query)
op.positions(
first=2,
where={"pool": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8", "tick_upper": {"tick_idx_lte": 200000}}
)
endpoint(op)
GraphQL query failed with 1 errors
{'errors': [{'locations': [{'column': 93, 'line': 2}],
'message': 'Unexpected `"tick_idx_lte"[StringValue]`\nExpected `Name`, `:` or `}`'}]}
Well, I just did the introspection here and checked it, you can search for Position_filter
, both in the json
or the generated Python file. The tick_upper
is a simple string, other similar fields are:
class Position_filter(sgqlc.types.Input):
# ...
tick_upper = sgqlc.types.Field(String, graphql_name='tickUpper')
tick_upper_not = sgqlc.types.Field(String, graphql_name='tickUpper_not')
tick_upper_gt = sgqlc.types.Field(String, graphql_name='tickUpper_gt')
tick_upper_lt = sgqlc.types.Field(String, graphql_name='tickUpper_lt')
tick_upper_gte = sgqlc.types.Field(String, graphql_name='tickUpper_gte')
tick_upper_lte = sgqlc.types.Field(String, graphql_name='tickUpper_lte')
tick_upper_in = sgqlc.types.Field(sgqlc.types.list_of(sgqlc.types.non_null(String)), graphql_name='tickUpper_in')
tick_upper_not_in = sgqlc.types.Field(sgqlc.types.list_of(sgqlc.types.non_null(String)), graphql_name='tickUpper_not_in')
tick_upper_contains = sgqlc.types.Field(String, graphql_name='tickUpper_contains')
tick_upper_not_contains = sgqlc.types.Field(String, graphql_name='tickUpper_not_contains')
tick_upper_starts_with = sgqlc.types.Field(String, graphql_name='tickUpper_starts_with')
tick_upper_not_starts_with = sgqlc.types.Field(String, graphql_name='tickUpper_not_starts_with')
tick_upper_ends_with = sgqlc.types.Field(String, graphql_name='tickUpper_ends_with')
tick_upper_not_ends_with = sgqlc.types.Field(String, graphql_name='tickUpper_not_ends_with')
You can confirm in JSON:
{
"defaultValue": null,
"description": null,
"name": "tickUpper",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
{
"defaultValue": null,
"description": null,
"name": "tickUpper_not",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
The only occurrence of tickIdx_lte
(tick_idx_lte
) is in Tick_filter
:
{
"defaultValue": null,
"description": null,
"name": "tickIdx_lte",
"type": {
"kind": "SCALAR",
"name": "BigInt",
"ofType": null
}
},
And Tick_filter
is used in Pool.ticks
, Query.ticks
, Subscription.ticks
... none in Positions
.
Thanks for taking a look and that helps for looking at the schema to see what is possible in the future. Looks like the filter I need is not implemented.
Sorry, I'm new to this. Is there a way to generate conditional queries like -
I'm only asking about the "where: { id : { _eq : } } "part, how do I add such conditions in my
Operation
?