-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
custom serialization, deserialization for existing types #22
Comments
I also wanted to have This pattern looks a bit confusing, maybe I am not quite understanding it. How do these My original idea was to organize code for serialization, deserialization, and validation into the type definitions themselves. It looks like the de-serialization and validation logic is here, the serialization logic (I think) is here. This could be instead organized by having a This would result in a very similar pattern to the existing custom Also, perhaps the code used to validate values given its type can also be customized by the user in this manner? Here's what I was thinking: In quiz/types.py
quiz/build.pyInstead of In users custom code
|
The schema = quiz.Schema.from_path(..., override=[URI, MyOtherScalar, Float, MyEnum])
I agree that this is the best way to go about it.
Yes, this.
The serialization logic is a bit fragmented accross the codebase. Definitely one of the uglier bits. Here is a rough overview of what happens when creating and executing a query, regarding (de)serialization and validation: Step 1: The query is validated with Example of such a validation error, with GitHub API: schema.query[
_
# the `owner` field has the wrong type here.
# isinstance(4, str) is false
.repository(owner=4, name='Hello World')[
_.createdAt
]
] Step 2: The (validated) query is serialized by calling Step 3: The response from the server (JSON) is loaded through The code snipped you posted look good. I have some small remarks and will post later today. |
Thanks, this gives me a better picture. One last question -- how would serializer definitions actually get applied? E.g., I define I thought there would need to either be a mapping between over-rides and internally defined types (e.g. |
Hmmm, what I think you're aiming for is to override the base of all Enum base classTo specify a base class for all enums, I would prefer an explicit solution. Something like this: schema.from_path(..., enum_base=MyEnumBase)
class MyEnumBase(quiz.Enum):
"""A custom Enum base class.
It accepts and returns (valid) strings when interacting with GraphQL"""
@classmethod
def __gql_dump__(cls, value: str) -> str:
if value in cls._member_names_:
return value
else:
raise ValidationError(
'{!r} is not a valid enum member'.format(value))
@classmethod
def __gql_load__(cls, data: str) -> str:
# I'm using an assert here, because this is just a sanity check.
assert data in cls._member_names_, 'unexpected enum value from server'
return data Class overridesschema.from_path(..., overrides=[MySpecificEnum, MyScalar, ...]) Default scalars in class Float(quiz.ScalarProxy):
"""A GraphQL type definition for `float`.
It is not meant to be instantiated."""
@staticmethod
def __gql_dump__(value: Union[float, int]) -> str:
if math.isnan(value) or math.isinf(value):
raise quiz.ValidationError('Float value cannot be NaN or Infinity')
return str(value)
@staticmethod
def __gql_load__(value: Union[float, int]) -> float:
return float(value)
class ID(quiz.ScalarProxy):
"""The GraphQL ID type. Accepts and returns `str`"""
@staticmethod
def __gql_dump__(value: str) -> str:
return value
@staticmethod
def __gql_load__(value: str) -> str:
return value
class Integer(quiz.ScalarProxy):
"""The GraphQL integer type. Accepts and returns 32-bit integers"""
@staticmethod
def __gql_dump__(obj) -> str:
if not MIN_INT > obj > MAX_INT:
raise quiz.ValidationError(
'number not representable as 32-bit int')
return str(obj)
@staticmethod
def __gql_dump__(value: int) -> int:
return value
class Boolean(quiz.ScalarProxy):
...
class String(quiz.ScalarProxy):
...
class GenericScalar(quiz.ScalarProxy):
"""Base class for generic GraphQL scalars.
Accepts any of int, float, bool, and str"""
@staticmethod
def __gql_dump__(obj) -> str:
...
@staticmethod
def __gql_load__(obj: T) -> T:
...
User-defined override types could look like this: class DateTime(quiz.ScalarProxy):
"""An example of a custom scalar proxy.
Not meant to be instantiated, simply accepts and loads `datatime`"""
@staticmethod
def __gql_dump__(value: datetime) -> str:
return str(value.timestamp())
@staticmethod
def __gql_load__(value: int) -> datetime:
return datetime.fromtimestamp(value)
class URI(quiz.Scalar):
"""An example of a custom scalar"""
def __init__(self, url: str):
self.components = urllib.parse.urlparse(url)
def __gql_dump__(self) -> str:
return self.components.geturl()
@classmethod
def __gql_load__(cls, data: str) -> URI:
return cls(data)
|
Yeah, the use case I am going for would need to override the base |
With
Scalar
s, it is possible to create new classes which have custom GraphQL load/dump logic.However, it can be useful to define load/dump logic for existing types. Examples:
datetime
/date
/time
objectsMy first thoughts are something like this:
What I'm happy about:
GenericScalar
Schema
constructors (as is now done with scalars).Not sure about:
TypeProxy
?The text was updated successfully, but these errors were encountered: