Skip to content

Commit

Permalink
Generate reference docs (mirumee#1020)
Browse files Browse the repository at this point in the history
Co-authored-by: Mateusz Sopiński <[email protected]>
  • Loading branch information
rafalp and mat-sop authored Feb 10, 2023
1 parent 51daab0 commit ec88d78
Show file tree
Hide file tree
Showing 22 changed files with 2,849 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ignore=snapshots
load-plugins=pylint.extensions.bad_builtin, pylint.extensions.mccabe

[MESSAGES CONTROL]
disable=C0103, C0111, C0209, C0412, I0011, R0101, R0801, R0901, R0902, R0903, R0912, R0913, R0914, R0915, R1260, W0231, W0511, W0621, W0703
disable=C0103, C0111, C0209, C0412, I0011, R0101, R0801, R0901, R0902, R0903, R0912, R0913, R0914, R0915, R1260, W0105, W0231, W0511, W0621, W0703

[SIMILARITIES]
ignore-imports=yes
6 changes: 3 additions & 3 deletions ariadne/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
__version__ = "0.17.0.dev1"

from .enums import (
EnumType,
set_default_enum_values_on_schema,
Expand Down Expand Up @@ -30,7 +28,7 @@
from .schema_names import SchemaNameConverter, convert_schema_names
from .schema_visitor import SchemaDirectiveVisitor
from .subscriptions import SubscriptionType
from .types import SchemaBindable
from .types import Extension, ExtensionSync, SchemaBindable
from .unions import UnionType
from .utils import (
convert_camel_case_to_snake,
Expand All @@ -41,7 +39,9 @@

__all__ = [
"EnumType",
"Extension",
"ExtensionManager",
"ExtensionSync",
"FallbackResolversSetter",
"InterfaceType",
"MutationType",
Expand Down
132 changes: 127 additions & 5 deletions ariadne/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,65 @@


class EnumType(SchemaBindable):
"""Bindable mapping Python values to enumeration members in a GraphQL schema.
# Example
Given following GraphQL enum:
```graphql
enum UserRole {
MEMBER
MODERATOR
ADMIN
}
```
You can use `EnumType` to map it's members to Python `Enum`:
```python
user_role_type = EnumType(
"UserRole",
{
"MEMBER": 0,
"MODERATOR": 1,
"ADMIN": 2,
}
)
```
`EnumType` also works with dictionaries:
```python
user_role_type = EnumType(
"UserRole",
{
"MEMBER": 0,
"MODERATOR": 1,
"ADMIN": 2,
}
)
```
"""

def __init__(
self, name: str, values=Union[Dict[str, Any], enum.Enum, enum.IntEnum]
self, name: str, values: Union[Dict[str, Any], enum.Enum, enum.IntEnum]
) -> None:
"""Initializes the `EnumType` with `name` and `values` mapping.
# Required arguments
`name`: a `str` with the name of GraphQL enum type in GraphQL schema to
bind to.
`values`: a `dict` or `enums.Enum` with values to use to represent GraphQL
enum's in Python logic.
"""
self.name = name
try:
self.values = values.__members__ # pylint: disable=no-member
except AttributeError:
self.values = values
self.values = cast(Dict[str, Any], getattr(values, "__members__", values))

def bind_to_schema(self, schema: GraphQLSchema) -> None:
"""Binds this `EnumType` instance to the instance of GraphQL schema."""
graphql_type = schema.type_map.get(self.name)
self.validate_graphql_type(graphql_type)
graphql_type = cast(GraphQLEnumType, graphql_type)
Expand All @@ -67,6 +116,13 @@ def bind_to_schema(self, schema: GraphQLSchema) -> None:
graphql_type.values[key].value = value

def bind_to_default_values(self, schema: GraphQLSchema) -> None:
"""Populates default values of input fields and args in the GraphQL schema.
This step is required because GraphQL query executor doesn't perform a
lookup for default values defined in schema. Instead it simply pulls the
value from fields and arguments `default_value` attribute, which is
`None` by default.
"""
for _, _, arg, key_list in find_enum_values_in_schema(schema):
type_ = resolve_null_type(arg.type)
type_ = cast(GraphQLNamedInputType, type_)
Expand All @@ -89,6 +145,8 @@ def bind_to_default_values(self, schema: GraphQLSchema) -> None:
)

def validate_graphql_type(self, graphql_type: Optional[GraphQLNamedType]) -> None:
"""Validates that schema's GraphQL type associated with this `EnumType`
is an `enum`."""
if not graphql_type:
raise ValueError("Enum %s is not defined in the schema" % self.name)
if not isinstance(graphql_type, GraphQLEnumType):
Expand All @@ -99,6 +157,17 @@ def validate_graphql_type(self, graphql_type: Optional[GraphQLNamedType]) -> Non


def set_default_enum_values_on_schema(schema: GraphQLSchema):
"""Sets missing Python values for GraphQL enums in schema.
Recursively scans GraphQL schema for enums and their values. If `value`
attribute is empty, its populated with with a string of its GraphQL name.
This string is then used to represent enum's value in Python instead of `None`.
# Requires arguments
`schema`: a GraphQL schema to set enums default values in.
"""
for type_object in schema.type_map.values():
if isinstance(type_object, GraphQLEnumType):
set_default_enum_values(type_object)
Expand All @@ -111,6 +180,59 @@ def set_default_enum_values(graphql_type: GraphQLEnumType):


def validate_schema_enum_values(schema: GraphQLSchema) -> None:
"""Raises `ValueError` if GraphQL schema has input fields or arguments with
default values that are undefined enum values.
# Example schema with invalid field argument
This schema fails to validate because argument `role` on field `users`
specifies `REVIEWER` as default value and `REVIEWER` is not a member of
the `UserRole` enum:
```graphql
type Query {
users(role: UserRole = REVIEWER): [User!]!
}
enum UserRole {
MEMBER
MODERATOR
ADMIN
}
type User {
id: ID!
}
```
# Example schema with invalid input field
This schema fails to validate because field `role` on input `UserFilters`
specifies `REVIEWER` as default value and `REVIEWER` is not a member of
the `UserRole` enum:
```graphql
type Query {
users(filter: UserFilters): [User!]!
}
input UserFilters {
name: String
role: UserRole = REVIEWER
}
enum UserRole {
MEMBER
MODERATOR
ADMIN
}
type User {
id: ID!
}
```
"""

for type_name, field_name, arg, _ in find_enum_values_in_schema(schema):
if is_invalid_enum_value(arg):
raise ValueError(
Expand Down
49 changes: 44 additions & 5 deletions ariadne/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,64 @@
import os
from typing import Optional, Union

from .constants import HTTP_STATUS_400_BAD_REQUEST


class HttpError(Exception):
"""Base class for HTTP errors raised inside the ASGI and WSGI servers."""

status = ""

def __init__(self, message=None) -> None:
def __init__(self, message: Optional[str] = None) -> None:
"""Initializes the `HttpError` with optional error message.
# Optional arguments
`message`: a `str` with error message to return in response body or
`None`.
"""
super().__init__()
self.message = message


class HttpBadRequestError(HttpError):
"""Raised when request did not contain the data required to execute
the GraphQL query."""

status = HTTP_STATUS_400_BAD_REQUEST


class GraphQLFileSyntaxError(Exception):
def __init__(self, schema_file, message) -> None:
"""Raised by `load_schema_from_path` when loaded GraphQL file has invalid syntax."""

def __init__(self, file_path: Union[str, os.PathLike], message: str) -> None:
"""Initializes the `GraphQLFileSyntaxError` with file name and error.
# Required arguments
`file_path`: a `str` or `PathLike` object pointing to a file that
failed to validate.
`message`: a `str` with validation message.
"""
super().__init__()
self.message = self.format_message(schema_file, message)

def format_message(self, schema_file, message):
return f"Could not load {schema_file}:\n{message}"
self.message = self.format_message(file_path, message)

def format_message(self, file_path: Union[str, os.PathLike], message: str):
"""Builds final error message from path to schema file and error message.
Returns `str` with final error message.
# Required arguments
`file_path`: a `str` or `PathLike` object pointing to a file that
failed to validate.
`message`: a `str` with validation message.
"""
return f"Could not load {file_path}:\n{message}"

def __str__(self):
"""Returns error message."""
return self.message
Loading

0 comments on commit ec88d78

Please sign in to comment.