Skip to content

Commit

Permalink
add initial token parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
karol-gruszczyk committed Oct 16, 2018
1 parent 02d26bd commit 00b7d31
Show file tree
Hide file tree
Showing 18 changed files with 106 additions and 36 deletions.
29 changes: 26 additions & 3 deletions slothql/ast/parsers/query_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re
import string
import typing as t

from slothql import ast
Expand All @@ -7,6 +9,14 @@ class QueryParseError(Exception):
pass


class UnexpectedEOF(QueryParseError):
pass


class ClosingBracketNotFound(QueryParseError):
pass


def strip_comments(query: str) -> str:
def strip():
for line in query.replace("\t", " ").split("\n"):
Expand All @@ -18,12 +28,25 @@ def strip():
return "".join(strip())


def get_matching_bracket_index(open_bracket: str, close_bracket: str) -> int:
pass
WORD_PATTERN = re.compile(r"[a-zA-Z_]\w*")
BRACKET_PATTERN = re.compile(r"[{}()]")

TOKENS: t.List[t.Pattern] = [WORD_PATTERN, BRACKET_PATTERN]


def parse_next(value: str, cursor: int) -> t.Tuple[str, int, t.Pattern]:
for i, char in enumerate(value[cursor:]):
if char in string.whitespace:
continue
for token_pattern in TOKENS:
if re.match(token_pattern, char):
token = next(re.finditer(token_pattern, value[cursor + i:])).group()
return token, cursor + i + len(token), token_pattern
raise UnexpectedEOF()


class QueryParser:
def __init__(self, query: str):
def __init__(self, query: str) -> None:
assert isinstance(
query, str
), f"expected query to be of type str, not {repr(query)}"
Expand Down
25 changes: 25 additions & 0 deletions slothql/ast/parsers/tests/test_query_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ def test_strip_comments(query: str, stripped: str):
assert stripped == query_parser.strip_comments(query)


@pytest.mark.parametrize(
"value, cursor, expected",
(
("", 0, query_parser.UnexpectedEOF()),
("text", 0, ("text", 4, query_parser.WORD_PATTERN)),
("text", 1, ("ext", 4, query_parser.WORD_PATTERN)),
(" \n\t text", 0, ("text", 8, query_parser.WORD_PATTERN)),
("text{abc", 0, ("text", 4, query_parser.WORD_PATTERN)),
("text(abc", 0, ("text", 4, query_parser.WORD_PATTERN)),
("text{abc", 4, ("{", 5, query_parser.BRACKET_PATTERN)),
("text{abc", 5, ("abc", 8, query_parser.WORD_PATTERN)),
),
)
def test_parse_next(value: str, cursor: int, expected):
if isinstance(expected, Exception):
with pytest.raises(type(expected)) as exc_info:
query_parser.parse_next(value, cursor)

assert isinstance(exc_info.value, type(expected)) and str(expected) == str(
exc_info.value
)
else:
assert expected == query_parser.parse_next(value, cursor)


@pytest.mark.skip
@pytest.mark.parametrize(
"query, expected",
Expand Down
2 changes: 1 addition & 1 deletion slothql/ast/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AstArgument:
class AstSelection:
name: str
arguments: t.Optional[t.List[AstArgument]] = None
selections: t.Optional[t.List['AstSelection']] = None
selections: t.Optional[t.List["AstSelection"]] = None


@dataclasses.dataclass()
Expand Down
6 changes: 4 additions & 2 deletions slothql/django/types/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from slothql.django.utils import Relation
from slothql.types.object import Object, ObjectMeta, ObjectOptions
from slothql.django import utils
from slothql.utils import annotations

from .registry import TypeRegistry
from .filter import DjangoFilter
Expand Down Expand Up @@ -150,10 +151,11 @@ def resolve(
info: slothql.ResolveInfo,
args: slothql.ResolveArgs,
field: slothql.Field,
) -> t.Iterable:
) -> t.Optional[t.Union[t.Iterable, t.Any]]:
resolved = resolver(obj, info, args)
if resolved is None:
queryset: models.QuerySet = cls._meta.model._default_manager.get_queryset()
model = annotations.get_non_optional(cls._meta.model)
queryset: models.QuerySet = model._default_manager.get_queryset()
queryset = optimize_queryset(queryset, info.selection)
else:
queryset = (
Expand Down
6 changes: 3 additions & 3 deletions slothql/django/types/tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ def test_register_field__invalid_django_field(field, type_registry: TypeRegistry


def test_clear(type_registry: TypeRegistry):
type_registry._type_mapping = {"field": "whatever"}
type_registry._type_mapping = {"field": "whatever"} # type: ignore
type_registry.clear()
assert type_registry._type_mapping == {}


def test_unregister(type_registry: TypeRegistry):
type_registry._type_mapping = {
type_registry._type_mapping = { # type: ignore
models.CharField: "whatever",
models.TextField: "wtf",
}
Expand All @@ -45,6 +45,6 @@ def test_get(type_registry: TypeRegistry):


def test_get__not_supported(type_registry: TypeRegistry):
type_registry._type_mapping = {bool: True, str: "wtf"}
type_registry._type_mapping = {bool: True, str: "wtf"} # type: ignore
with pytest.raises(NotImplementedError):
type_registry.get(42)
1 change: 1 addition & 0 deletions slothql/django/utils/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def get_related_model(descriptor) -> t.Optional[t.Type[models.Model]]:
return descriptor.field.related_model
elif isinstance(getattr(descriptor, "related", None), ForeignObjectRel):
return descriptor.related.related_model
return None


def field_is_selectable(field: models.Field) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion slothql/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class Executor(SyncExecutor):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self.error: t.Optional[Exception] = None

Expand Down
2 changes: 1 addition & 1 deletion slothql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def valid(self) -> bool:
class Query:
__slots__ = "schema", "operation", "ast", "errors"

def __init__(self, schema: slothql.Schema, operation: slothql.Operation):
def __init__(self, schema: slothql.Schema, operation: slothql.Operation) -> None:
assert isinstance(
operation.query, str
), f"Expected query string, got {operation.query}"
Expand Down
10 changes: 6 additions & 4 deletions slothql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


class TypeMap(dict):
def __init__(self, *types: t.Type[base.BaseType]):
def __init__(self, *types: t.Type[base.BaseType]) -> None:
super().__init__(functools.reduce(self.type_reducer, types, {}))

def type_reducer(self, type_map: dict, of_type: t.Type[base.BaseType]):
Expand All @@ -37,10 +37,12 @@ def type_reducer(self, type_map: dict, of_type: t.Type[base.BaseType]):


class ProxyTypeMap(dict):
def __init__(self, type_map: TypeMap, auto_camelcase: bool = False):
def __init__(self, type_map: TypeMap, auto_camelcase: bool = False) -> None:
super().__init__()
self.auto_camelcase = auto_camelcase
self.camelcase_type_map = collections.defaultdict(dict)
self.camelcase_type_map: t.Dict[
str, t.Dict[str], str
] = collections.defaultdict(dict)
for of_type in type_map.values():
self.get_graphql_type(of_type)

Expand Down Expand Up @@ -190,7 +192,7 @@ def __init__(
directives=None,
types=None,
auto_camelcase: bool = False,
):
) -> None:
type_map = TypeMap(resolve_lazy_type(query))
graphql_type_map = ProxyTypeMap(type_map, auto_camelcase=auto_camelcase)
query = query and graphql_type_map[resolve_lazy_type(query)._meta.name]
Expand Down
4 changes: 3 additions & 1 deletion slothql/types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@ def merge_field(mcs, old, new):


class BaseType(metaclass=BaseMeta):
_meta: BaseOptions

@staticmethod
def __new__(cls, *more, **kwargs):
assert (
not cls._meta.abstract
), f"Abstract type {cls.__name__} can not be instantiated"
return super().__new__(cls)

def __init__(self):
def __init__(self) -> None:
raise RuntimeError(
f"`{self.__class__.__name__}` initialization is not supported"
)
Expand Down
2 changes: 1 addition & 1 deletion slothql/types/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class EnumValue:
__slots__ = "value", "description"

def __init__(self, value, description: str = None):
def __init__(self, value, description: str = None) -> None:
self.value = value
self.description = description

Expand Down
2 changes: 1 addition & 1 deletion slothql/types/fields/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(
name: str = None,
parent=None,
filterable=True,
):
) -> None:
self._type = of_type

assert resolver is None or resolution.is_valid_partial_resolver(
Expand Down
15 changes: 9 additions & 6 deletions slothql/types/fields/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ def __new__(
):
return super().__new__(mcs, name, bases, attrs, options_class, **kwargs)

def create_class(
cls, name: str, fields: t.Dict[str, Field], **kwargs
) -> "FilterMeta":
return cls.__class__(name, (cls,), {**fields, "Meta": type("Meta", (), kwargs)})


class Filter(base.BaseType, metaclass=FilterMeta):
_meta: FilterOptions

SKIP = object()

def __init__(self, **kwargs):
@classmethod
def create_class(
cls, name: str, fields: t.Dict[str, Field], **kwargs
) -> t.Type["Filter"]:
return type(FilterMeta)(
name, (cls,), {**fields, "Meta": type("Meta", (), kwargs)}
)

def __init__(self, **kwargs) -> None:
for name, value in kwargs.items():
if name not in self._meta.fields:
raise TypeError(
Expand Down
18 changes: 9 additions & 9 deletions slothql/types/fields/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,45 @@


class Integer(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=IntegerType, **kwargs)


class Float(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=FloatType, **kwargs)


class String(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=StringType, **kwargs)


class Boolean(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=BooleanType, **kwargs)


class ID(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=IDType, **kwargs)


class JsonString(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=JsonStringType, **kwargs)


class DateTime(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=DateTimeType, **kwargs)


class Date(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=DateType, **kwargs)


class Time(Field):
def __init__(self, **kwargs):
def __init__(self, **kwargs) -> None:
super().__init__(of_type=TimeType, **kwargs)
9 changes: 9 additions & 0 deletions slothql/utils/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,12 @@ def wrapped(*args, **kwargs):
return func(*args, **kwargs)

return wrapped


T = t.TypeVar("T")


def get_non_optional(v: t.Optional[T]) -> T:
if v is None:
raise ValueError("v cannot be None")
return v
2 changes: 1 addition & 1 deletion slothql/utils/laziness.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class LazyInitProxy:
def __init__(self, new, cls, *args, **kwargs):
def __init__(self, new, cls, *args, **kwargs) -> None:
self.__obj = None
self.__new = new
self.__cls = cls
Expand Down
5 changes: 4 additions & 1 deletion slothql/utils/singleton.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import typing as t


class Singleton(type):
_instances = {}
_instances: t.Dict[type, object] = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
Expand Down
2 changes: 1 addition & 1 deletion slothql/utils/tests/test_laziness.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Executed(Exception):


class Class(LazyInitMixin):
def __init__(self, a="foo", b="bar"):
def __init__(self, a="foo", b="bar") -> None:
self.a = a
self.b = b
raise Executed
Expand Down

0 comments on commit 00b7d31

Please sign in to comment.