├── .circleci └── config.yml ├── .dockerignore ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── NEWS.rst ├── README.rst ├── ast ├── ast_cython.py ├── ast_cython_c.py ├── ast_cython_impl.py ├── ast_cython_visitor.py ├── ast_cython_visitor_impl.py └── build_ast.py ├── examples └── visitor_example.py ├── graphql_parser ├── .gitignore ├── GraphQLAstNode.pxd ├── GraphQLAstNode.pyx ├── GraphQLParser.pyx ├── __init__.py ├── cGraphQLAstNode.pxd └── cGraphQLParser.pxd ├── setup.py ├── tests └── test_parse.py └── tox.ini /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | working_directory: ~/py-graphqlparser 6 | docker: 7 | - image: ubuntu:16.04 8 | steps: 9 | - checkout 10 | - run: 11 | name: apt update 12 | command: apt-get -q update 13 | - run: 14 | name: apt required packages 15 | command: apt-get install -qy build-essential python python3 python3-pip python3-dev git cmake bison flex pkg-config 16 | - run: 17 | name: install cython 18 | command: pip3 install -q cython==0.27.3 19 | - run: 20 | name: git submodules 21 | command: | 22 | mkdir -p -m 700 ~/.ssh 23 | ssh-keyscan github.com >> ~/.ssh/known_hosts 24 | git submodule -q sync 25 | git submodule -q update -f --init 26 | - run: 27 | name: build libgraphqlparser 28 | command: | 29 | cd libgraphqlparser 30 | cmake . 31 | make 32 | - run: 33 | name: ast 34 | command: python2 ast/build_ast.py 35 | - run: 36 | name: build ext 37 | command: LDFLAGS="-L./libgraphqlparser" CFLAGS="-Ilibgraphqlparser/c -Ilibgraphqlparser" python3 setup.py build_ext 38 | - run: 39 | name: install package 40 | command: pip3 install -e . 41 | - run: 42 | name: install pytest 43 | command: pip3 install pytest 44 | - run: 45 | name: test 46 | command: python3 -m pytest 47 | environment: 48 | LD_LIBRARY_PATH: "./libgraphqlparser" 49 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git/ 2 | **/__pycache__/ 3 | .circleci/ 4 | .pytest_cache/ 5 | .tox/ 6 | .wheelhouse/ 7 | **/CMakeCache.txt 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *~ 4 | *.c 5 | /build/ 6 | /.wheelhouse/ 7 | /.python-version 8 | /.tox 9 | /.cache 10 | /dist/ 11 | /*.egg-info 12 | .pytest_cache 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libgraphqlparser"] 2 | path = libgraphqlparser 3 | url = git@github.com:graphql/libgraphqlparser.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update -qy 4 | RUN apt-get install -qy build-essential python python3 python3-pip python3-dev git cmake bison flex pkg-config 5 | RUN pip3 install cython 6 | 7 | RUN mkdir /tmp/build 8 | COPY libgraphqlparser /tmp/build/libgraphqlparser 9 | COPY ast /tmp/build/ast 10 | COPY examples /tmp/build/examples 11 | COPY graphql_parser /tmp/build/graphql_parser 12 | COPY setup.py *.rst /tmp/build/ 13 | 14 | 15 | RUN cd /tmp/build/libgraphqlparser && \ 16 | cmake . && \ 17 | make && \ 18 | cp libgraphqlparser.so /usr/local/lib 19 | RUN cd /tmp/build && \ 20 | python2 ast/build_ast.py && \ 21 | LDFLAGS="-L./libgraphqlparser" CFLAGS="-Ilibgraphqlparser/c -Ilibgraphqlparser" \ 22 | python3 setup.py build_ext && \ 23 | pip3 install . 24 | ENV LD_LIBRARY_PATH=/usr/local/lib 25 | 26 | ENTRYPOINT ["python3", "/tmp/build/examples/visitor_example.py"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For py-grahpqlparser software 4 | 5 | Copyright (c) 2015-present, Elastic Coders, Srls All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include examples *.py 2 | recursive-include ast *.py 3 | recursive-include graphql_parser *.pyx *.pxd 4 | recursive-exclude graphql_parser *.c *.h *.cpp 5 | include NEWS.rst 6 | -------------------------------------------------------------------------------- /NEWS.rst: -------------------------------------------------------------------------------- 1 | News 2 | ---- 3 | 4 | v0.0.4 5 | ------ 6 | 7 | - compatible with latest libgraphqlparser 0.7.0 8 | - built wheels for python 3.6 9 | 10 | 11 | v0.0.3 12 | ------ 13 | 14 | - fixed packaging and building 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Graphql parser based on libgraphqlparser 2 | ========================================= 3 | 4 | .. image:: https://circleci.com/gh/elastic-coders/py-graphqlparser.svg?style=svg 5 | :target: https://circleci.com/gh/elastic-coders/py-graphqlparser 6 | 7 | Python2.7+ Python3.4+ class-based bindings to libgraphqlparser; just a thin layer on top of ``libgraphqlparser`` C API. 8 | 9 | Still **EXPERIMENTAL** 10 | 11 | 12 | Installing 13 | ---------- 14 | 15 | First install ``libgraphqlparser`` following instructions on `libgraphqlparser github page`_ . 16 | 17 | Next you can install ``graphqlparser``. The easiest way is using precompiled wheels which are usually available 18 | on `graphqlparser github releases`_ 19 | 20 | Pick the right wheel for your platform and python version, then install it using pip:: 21 | 22 | pip install https://github.com/elastic-coders/py-graphqlparser/releases/download/v0.0.4/graphqlparser-0.0.4-cp36-cp36m-linux_x86_64.whl 23 | 24 | 25 | As an alternative you can install ``graphqlparser`` from source distribution: 26 | 27 | - Install ``cython`` 28 | - Set an env var ``$GRAPHQL_HOME`` to the folder where ``libgraphqlparser.so`` and ``Ast.h`` are 29 | - Install ``graphqlparser`` with pip:: 30 | 31 | LDFLAGS="-L$GRAPHQL_HOME" CFLAGS="-I$GRAPHQL_HOME/c -I$GRAPHQL_HOME" pip install graphqlparser 32 | 33 | 34 | Usage 35 | ----- 36 | 37 | Make sure ``libgraphqlparser`` is available to the loader. You can add its base dir to ``LD_LIBRARY_PATH``. 38 | 39 | Then you can start parsing by creating your custom visitor class: 40 | 41 | .. code-block:: python 42 | 43 | from graphql_parser import GraphQLAstVisitor 44 | 45 | class MyVisitor(GraphQLAstVisitor.GraphQLAstVisitor): 46 | 47 | def visit_field(self, node): 48 | print('start field %s visit' % node) 49 | # Return 1 to keep visiting children, 0 to skip them 50 | return 1 51 | 52 | def end_visit_field(self, node): 53 | print('end field %s visit' % node) 54 | 55 | And using it to visit a parsed query: 56 | 57 | .. code-block:: python 58 | 59 | from graphql_parser import GraphQLParser 60 | 61 | query = '{query{}}' 62 | node = GraphQLParser.graphql_parse_string(query) 63 | MyVisitor().visit_node(node) 64 | 65 | See also ``examples`` folder. 66 | 67 | 68 | Building from source checkout 69 | ----------------------------- 70 | 71 | Rebuild the generated cython files from the libgraphql AST (usually not needed) 72 | 73 | - download submodules with ``git checkout --recursive`` 74 | - build libgraphql library in folder ``./libgraphqlparser`` (python2.7 required for building) 75 | (usually ``pushd libgraphqlparser && cmake . && make && popd`` works) 76 | - generate source code with ``python ast/build_ast.py`` 77 | - you can now switch to python 3 78 | - install ``cython`` 79 | - run:: 80 | 81 | LDFLAGS="-L./libgraphqlparser" CFLAGS="-Ilibgraphqlparser/c -Ilibgraphqlparser" python setup.py build_ext 82 | 83 | 84 | To create a wheel distribution: 85 | 86 | - install wheel: ``pip install wheel`` 87 | - create wheelhouse ``mkdir .wheelhouse`` 88 | - build with ``pip wheel --wheel-dir=.wheelhouse .`` 89 | 90 | 91 | Known issues 92 | ------------ 93 | 94 | - Only (lightly) tested on python3 95 | - Unicode string handling not yet complete (a mixture of bytes and strings all over) 96 | - Exceptions in the visitor's class callbacks are ignored 97 | - libgraphqlparser is **dynamically** linked but It would be better if it was linked statically 98 | 99 | 100 | TODO 101 | ---- 102 | 103 | - build more wheel packages for linux 32 bit and other platforms 104 | 105 | 106 | .. _libgraphqlparser github page: https://github.com/graphql/libgraphqlparser 107 | .. _graphqlparser github releases: https://github.com/elastic-coders/py-graphqlparser/releases/ 108 | -------------------------------------------------------------------------------- /ast/ast_cython.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | from casing import snake 9 | import re 10 | import ast_cython_c 11 | 12 | CMODULE_NAME = 'GraphQLAst' 13 | 14 | 15 | class Printer(object): 16 | '''Printer for the Ast Python Object level cython interface. 17 | 18 | ''' 19 | def start_file(self): 20 | print ast_cython_c.license_comment() + ''' 21 | 22 | cimport %s 23 | 24 | 25 | cdef class GraphQLAst: 26 | """Base class for all Ast pieces""" 27 | pass 28 | 29 | ''' % ast_cython_c.CMODULE_NAME 30 | 31 | def end_file(self): 32 | pass 33 | 34 | def start_type(self, name): 35 | st_name = ast_cython_c.struct_name(name) 36 | print ''' 37 | cdef class %(name)s(GraphQLAst): 38 | 39 | cdef const %(cmodule)s.%(name)s* _wrapped 40 | 41 | @staticmethod 42 | cdef create(const cGraphQLAst.%(name)s *thing) 43 | 44 | ''' % {'name': st_name, 'cmodule': ast_cython_c.CMODULE_NAME} 45 | self._current_type = name 46 | 47 | def field(self, type, name, nullable, plural): 48 | pass 49 | 50 | def end_type(self, name): 51 | print 52 | print 53 | 54 | def start_union(self, name): 55 | pass 56 | 57 | def union_option(self, option): 58 | pass 59 | 60 | def end_union(self, name): 61 | print 62 | -------------------------------------------------------------------------------- /ast/ast_cython_c.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | from casing import snake 9 | import re 10 | import ast_cython 11 | from license import C_LICENSE_COMMENT 12 | 13 | CMODULE_NAME = 'cGraphQLAst' 14 | 15 | 16 | def struct_name(type): 17 | return 'GraphQLAst' + type 18 | 19 | 20 | def return_type(type): 21 | if type == 'OperationKind' or type == 'string': 22 | return 'const char *' 23 | 24 | if type == 'boolean': 25 | return 'int' 26 | 27 | return 'const %s *' % struct_name(type) 28 | 29 | 30 | def field_prototype(owning_type, type, name, nullable, plural): 31 | st_name = struct_name(owning_type) 32 | if plural: 33 | return 'int %s_get_%s_size(const %s *node)' % ( 34 | st_name, snake(name), st_name) 35 | else: 36 | ret_type = return_type(type) 37 | return '%s %s_get_%s(const %s *node)' % ( 38 | ret_type, st_name, snake(name), st_name) 39 | 40 | 41 | def license_comment(): 42 | return re.sub(re.compile(r'^[ ]*', re.MULTILINE), '#', C_LICENSE_COMMENT) + '# @generated' 43 | 44 | 45 | class Printer(object): 46 | '''Printer for the Ast low-level C cython interface. 47 | 48 | ''' 49 | def start_file(self): 50 | print license_comment() + ''' 51 | 52 | cdef extern from "GraphQLAst.h": 53 | 54 | ''' 55 | 56 | def end_file(self): 57 | pass 58 | 59 | def start_type(self, name): 60 | # Forward declarations for AST nodes. 61 | st_name = struct_name(name) 62 | print ' struct ' + st_name + ':' 63 | print ' pass' 64 | self._current_type = name 65 | self._current_type_fields = [] 66 | 67 | def field(self, type, name, nullable, plural): 68 | self._current_type_fields.append((type, name, nullable, plural)) 69 | print ' ' + field_prototype(self._current_type, type, name, nullable, plural) 70 | 71 | def end_type(self, name): 72 | print 73 | print 74 | 75 | def start_union(self, name): 76 | print ' struct ' + struct_name(name) + ':' 77 | print ' pass' 78 | 79 | def union_option(self, option): 80 | pass 81 | 82 | def end_union(self, name): 83 | print 84 | -------------------------------------------------------------------------------- /ast/ast_cython_impl.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | from casing import snake 9 | import ast_cython_c 10 | import ast_cython 11 | 12 | SIMPLE_RETURN_CASTS = { 13 | 'boolean': 'bool', 14 | 'int': '', 15 | 'string': '', # XXX decode? 16 | 'OperationKind': '' 17 | } 18 | 19 | SOURCE_TYPE_CASTS = { 20 | 'IntValue': 'int', 21 | 'FloatValue': 'float', 22 | 'StringValue': '', # XXX decode? 23 | 'BooleanValue': 'bool', 24 | 'EnumValue': '' # XXX decode? 25 | } 26 | 27 | def field_prototype(owning_type, type, name, nullable, plural): 28 | _map = {'cmodule': ast_cython_c.CMODULE_NAME, 29 | 'owning_st': ast_cython_c.struct_name(owning_type), 30 | 'snake': snake(name), 31 | 'return_st': ast_cython_c.struct_name(type)} 32 | if plural: 33 | return ''' 34 | def get_%(snake)s_size(self): 35 | return int(%(cmodule)s.%(owning_st)s_get_%(snake)s_size(self._wrapped)) 36 | ''' % _map 37 | if type in SIMPLE_RETURN_CASTS: 38 | # TODO: convert string to unicode 39 | if owning_type in SOURCE_TYPE_CASTS: 40 | _map['cast'] = SOURCE_TYPE_CASTS[owning_type] 41 | else: 42 | _map['cast'] = SIMPLE_RETURN_CASTS[type] 43 | return ''' 44 | def get_%(snake)s(self): 45 | val = %(cmodule)s.%(owning_st)s_get_%(snake)s(self._wrapped) 46 | if val is None: 47 | return None 48 | return %(cast)s(val) 49 | ''' % _map 50 | elif type in ['Type', 'Value']: 51 | # XXX this types have no functions... 52 | return ''' 53 | ''' 54 | else: 55 | # python object return type 56 | return ''' 57 | def get_%(snake)s(self): 58 | cdef const %(cmodule)s.%(return_st)s *next 59 | next = %(cmodule)s.%(owning_st)s_get_%(snake)s(self._wrapped) 60 | if next is NULL: 61 | return None 62 | return %(return_st)s.create(next) 63 | ''' % _map 64 | 65 | class Printer(object): 66 | '''Printer for a visitor in cython 67 | ''' 68 | 69 | def __init__(self): 70 | self._types = [] 71 | 72 | def start_file(self): 73 | print ast_cython_c.license_comment() + ''' 74 | 75 | cimport %s 76 | 77 | cdef class GraphQLAst: 78 | """Base class for all Ast pieces""" 79 | pass 80 | 81 | ''' % ast_cython_c.CMODULE_NAME 82 | 83 | def start_type(self, name): 84 | self._current_type = name 85 | _map = {'snake': snake(name), 'name': name} 86 | print ''' 87 | 88 | cdef class %(name)s(GraphQLAst): 89 | 90 | @staticmethod 91 | cdef create(const %(cmodule)s.%(name)s *thing): 92 | node = %(name)s() 93 | node._wrapped = thing 94 | return node 95 | 96 | ''' % {'name': ast_cython_c.struct_name(name), 97 | 'cmodule': ast_cython_c.CMODULE_NAME} 98 | 99 | def field(self, type, name, nullable, plural): 100 | print field_prototype(self._current_type, type, name, nullable, plural) 101 | 102 | def end_type(self, name): 103 | print 104 | print 105 | 106 | def end_file(self): 107 | pass 108 | 109 | def start_union(self, name): 110 | pass 111 | 112 | def union_option(self, option): 113 | pass 114 | 115 | def end_union(self, name): 116 | pass 117 | -------------------------------------------------------------------------------- /ast/ast_cython_visitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | from casing import snake 9 | import ast_cython_c 10 | 11 | 12 | class Printer(object): 13 | '''Printer for a visitor in cython 14 | ''' 15 | 16 | def __init__(self): 17 | self._types = [] 18 | 19 | def start_file(self): 20 | print ast_cython_c.license_comment() + ''' 21 | 22 | cdef extern from "GraphQLAstVisitor.h": 23 | 24 | struct GraphQLAstNode: 25 | pass 26 | 27 | 28 | ''' 29 | 30 | def start_type(self, name): 31 | self._types.append(name) 32 | _map = {'snake': snake(name), 'name': name} 33 | print ' struct %s:' % ast_cython_c.struct_name(name) 34 | print ' pass' 35 | print ' ctypedef int (*visit_%(snake)s_func)(GraphQLAst%(name)s*, void*)' % _map 36 | print ' ctypedef void (*end_visit_%(snake)s_func)(GraphQLAst%(name)s*, void*)' % _map 37 | 38 | def field(self, type, name, nullable, plural): 39 | pass 40 | 41 | def end_type(self, name): 42 | pass 43 | 44 | def end_file(self): 45 | print ' struct GraphQLAstVisitorCallbacks:' 46 | for name in self._types: 47 | _map = {'snake': snake(name)} 48 | print ' visit_%(snake)s_func visit_%(snake)s' % _map 49 | print ' end_visit_%(snake)s_func end_visit_%(snake)s' % _map 50 | 51 | print ''' 52 | void graphql_node_visit(GraphQLAstNode *node, 53 | const GraphQLAstVisitorCallbacks *callbacks, 54 | void *userData) 55 | ''' 56 | 57 | def start_union(self, name): 58 | pass 59 | 60 | def union_option(self, option): 61 | pass 62 | 63 | def end_union(self, name): 64 | pass 65 | -------------------------------------------------------------------------------- /ast/ast_cython_visitor_impl.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | from casing import snake 9 | import ast_cython_c 10 | 11 | CMODULE_NAME = 'cGraphQLAstVisitor' 12 | 13 | 14 | class Printer(object): 15 | '''Printer for a visitor implementation in cython 16 | ''' 17 | 18 | def __init__(self): 19 | self._types = [] 20 | 21 | def start_file(self): 22 | print ast_cython_c.license_comment() + ''' 23 | 24 | from libc.string cimport memset 25 | cimport %(cmodule)s 26 | cimport GraphQLAstNode 27 | cimport cGraphQLAstNode 28 | cimport cGraphQLAst 29 | cimport GraphQLAst 30 | 31 | cdef class GraphQLAstVisitor: 32 | 33 | def visit_node(self, node): 34 | cdef %(cmodule)s.GraphQLAstVisitorCallbacks callbacks_c 35 | memset(&callbacks_c, 0, sizeof(callbacks_c)) 36 | set_callbacks(&callbacks_c) 37 | cdef void* userData = self 38 | cdef cGraphQLAstNode.GraphQLAstNode *node_c; 39 | node_c = (node)._node 40 | %(cmodule)s.graphql_node_visit(node_c, &callbacks_c, userData) 41 | 42 | ''' % {'cmodule': CMODULE_NAME} 43 | 44 | def start_type(self, name): 45 | self._types.append(name) 46 | 47 | def field(self, type, name, nullable, plural): 48 | pass 49 | 50 | def end_type(self, name): 51 | pass 52 | 53 | def end_file(self): 54 | for type in self._types: 55 | _map = {'snake': snake(type), 'name': type, 'cmodule': CMODULE_NAME} 56 | print ''' 57 | 58 | cdef int _visit_%(snake)s(const %(cmodule)s.GraphQLAst%(name)s* node, void* userData, int end): 59 | cdef GraphQLAstVisitor visitor 60 | ast = GraphQLAst.GraphQLAst%(name)s.create(node) 61 | if userData is not NULL: 62 | visitor = userData 63 | attname = 'end_visit_%(snake)s' if end else 'visit_%(snake)s' 64 | fun = getattr(visitor, attname, None) 65 | if fun is not None: 66 | retval = fun(ast) 67 | return 0 if retval is None else retval 68 | 69 | cdef int visit_%(snake)s(const %(cmodule)s.GraphQLAst%(name)s* node, void* userData): 70 | return _visit_%(snake)s(node, userData, 0) 71 | 72 | cdef void end_visit_%(snake)s(const %(cmodule)s.GraphQLAst%(name)s* node, void* userData): 73 | _visit_%(snake)s(node, userData, 1) 74 | ''' % _map 75 | 76 | print ''' 77 | 78 | cdef set_callbacks(%(cmodule)s.GraphQLAstVisitorCallbacks *callbacks): 79 | ''' % {'cmodule': CMODULE_NAME} 80 | 81 | for type in self._types: 82 | _map = {'snake': snake(type), 'name': type, 'cmodule': CMODULE_NAME} 83 | print ''' 84 | callbacks.visit_%(snake)s = &visit_%(snake)s 85 | callbacks.end_visit_%(snake)s = &end_visit_%(snake)s 86 | ''' % _map 87 | 88 | 89 | def start_union(self, name): 90 | pass 91 | 92 | def union_option(self, option): 93 | pass 94 | 95 | def end_union(self, name): 96 | pass 97 | -------------------------------------------------------------------------------- /ast/build_ast.py: -------------------------------------------------------------------------------- 1 | """Generate source code using libgraphql AST utilities""" 2 | import os 3 | import sys 4 | import subprocess 5 | 6 | AST_GENERATED_FILES = [ 7 | ('ast_cython_c', 'cGraphQLAst.pxd'), 8 | ('ast_cython', 'GraphQLAst.pxd'), 9 | ('ast_cython_impl', 'GraphQLAst.pyx'), 10 | ('ast_cython_visitor', 'cGraphQLAstVisitor.pxd'), 11 | ('ast_cython_visitor_impl', 'GraphQLAstVisitor.pyx'), 12 | ] 13 | 14 | 15 | def get_libgraphqlparser_ast_home(): 16 | return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'libgraphqlparser', 'ast')) 17 | 18 | 19 | def get_my_ast_home(): 20 | return os.path.abspath(os.path.join(os.path.dirname(__file__))) 21 | 22 | 23 | def get_my_target_home(): 24 | return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'graphql_parser')) 25 | 26 | 27 | def main(): 28 | cwd = get_libgraphqlparser_ast_home() 29 | target_home = get_my_target_home() 30 | env = os.environ.copy() 31 | env['PYTHONPATH'] = env.get('PYTHONPATH', '') + os.path.pathsep + get_my_ast_home() 32 | for language, target in AST_GENERATED_FILES: 33 | with open(os.path.join(target_home, target), 'wb') as target_f: 34 | contents = subprocess.check_output([sys.executable, 'ast.py', language, 'ast.ast'], 35 | cwd=cwd, env=env) 36 | target_f.write(contents) 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /examples/visitor_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from graphql_parser import GraphQLAstVisitor 4 | from graphql_parser import GraphQLParser 5 | 6 | 7 | class Visitor(GraphQLAstVisitor.GraphQLAstVisitor): 8 | 9 | def __init__(self): 10 | self.level = 0 11 | 12 | def visit_document(self, node): 13 | print('document start') 14 | return 1 15 | 16 | def visit_operation_definition(self, node): 17 | print(node.__class__, node.get_operation()) 18 | return 1 19 | 20 | def visit_variable_definition(self, node): 21 | print(node.__class__) 22 | return 1 23 | 24 | def visit_selection_set(self, node): 25 | print(node.__class__) 26 | return 1 27 | 28 | def visit_selection(self, node): 29 | print(node.__class__) 30 | return 1 31 | 32 | def visit_field(self, node): 33 | print(node.__class__, node.get_name().get_value(), node.get_alias()) 34 | return 1 35 | 36 | def visit_argument(self, node): 37 | print(node.__class__, node.get_name().get_value()) 38 | return 1 39 | 40 | def visit_fragment_spread(self, node): 41 | print(node.__class__, node.get_name().get_value()) 42 | return 1 43 | 44 | def visit_inline_fragment(self, node): 45 | print(node.__class__, node.get_name().get_value()) 46 | return 1 47 | 48 | def visit_fragment_definition(self, node): 49 | print(node.__class__, node.get_name().get_value()) 50 | return 1 51 | 52 | def visit_variable(self, node): 53 | print(node.__class__, node.get_name().get_value()) 54 | return 1 55 | 56 | def visit_object_field(self, node): 57 | print(node.__class__, node.get_name().get_value()) 58 | return 1 59 | 60 | def visit_directive(self, node): 61 | print(node.__class__, node.get_name().get_value()) 62 | return 1 63 | 64 | def visit_int_value(self, node): 65 | print(node.__class__, node.get_value()) 66 | return 1 67 | 68 | def visit_string_value(self, node): 69 | print(node.__class__, node.get_value()) 70 | return 1 71 | 72 | if __name__ == '__main__': 73 | import argparse 74 | parser = argparse.ArgumentParser(description='GraphQL parser example') 75 | parser.add_argument('query') 76 | args = parser.parse_args() 77 | node = GraphQLParser.graphql_parse_string(args.query) 78 | Visitor().visit_node(node) 79 | -------------------------------------------------------------------------------- /graphql_parser/.gitignore: -------------------------------------------------------------------------------- 1 | cGraphQLAst.pxd 2 | GraphQLAst.pxd 3 | GraphQLAst.pyx 4 | cGraphQLAstVisitor.pxd 5 | GraphQLAstVisitor.pyx 6 | *~ 7 | *.c 8 | .python-version 9 | *.so 10 | *.html 11 | .pytest_cache 12 | -------------------------------------------------------------------------------- /graphql_parser/GraphQLAstNode.pxd: -------------------------------------------------------------------------------- 1 | cimport cGraphQLAstNode 2 | 3 | cdef class GraphQLAstNode: 4 | 5 | cdef cGraphQLAstNode.GraphQLAstNode* _node 6 | 7 | @staticmethod 8 | cdef create(cGraphQLAstNode.GraphQLAstNode* node_c) 9 | -------------------------------------------------------------------------------- /graphql_parser/GraphQLAstNode.pyx: -------------------------------------------------------------------------------- 1 | cimport cGraphQLAstNode 2 | 3 | 4 | cdef class GraphQLAstNode: 5 | 6 | def __dealloc__(self): 7 | if self._node is not NULL: 8 | cGraphQLAstNode.graphql_node_free(self._node) 9 | 10 | @staticmethod 11 | cdef create(cGraphQLAstNode.GraphQLAstNode *node_c): 12 | node = GraphQLAstNode() 13 | node._node = node_c 14 | return node 15 | -------------------------------------------------------------------------------- /graphql_parser/GraphQLParser.pyx: -------------------------------------------------------------------------------- 1 | cimport cGraphQLParser 2 | cimport cGraphQLAstNode 3 | cimport cGraphQLAst 4 | cimport GraphQLAstNode 5 | 6 | 7 | class GraphQLParseError(Exception): 8 | pass 9 | 10 | 11 | def graphql_parse_string(text): 12 | cdef const char* error_c = NULL 13 | cdef const char* text_c 14 | cdef cGraphQLAstNode.GraphQLAstNode* node_c 15 | byte_string = text.encode() 16 | text_c = byte_string 17 | node_c = cGraphQLParser.graphql_parse_string(text_c, &error_c) 18 | if node_c is NULL: 19 | mesg = error_c.decode() if error_c is not NULL else None 20 | raise GraphQLParseError(mesg) 21 | return GraphQLAstNode.GraphQLAstNode.create(node_c) 22 | -------------------------------------------------------------------------------- /graphql_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic-coders/py-graphqlparser/c935d2782c224b6a70880eac09773a5d9d905e72/graphql_parser/__init__.py -------------------------------------------------------------------------------- /graphql_parser/cGraphQLAstNode.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "GraphQLAstNode.h": 2 | struct GraphQLAstNode: 3 | pass 4 | 5 | void graphql_node_free(GraphQLAstNode* node) 6 | -------------------------------------------------------------------------------- /graphql_parser/cGraphQLParser.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "GraphQLParser.h": 2 | struct GraphQLAstNode: 3 | pass 4 | 5 | GraphQLAstNode* graphql_parse_string(const char* text, const char** error) 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from setuptools import setup, Extension 4 | 5 | from Cython.Build import cythonize 6 | 7 | extensions = cythonize([ 8 | Extension('graphql_parser.{}'.format(cls_name), 9 | ['graphql_parser/{}.pyx'.format(cls_name)], 10 | libraries=['graphqlparser'], 11 | include_dirs=['.']) 12 | for cls_name in ['GraphQLParser', 'GraphQLAstVisitor', 13 | 'GraphQLAst', 'GraphQLAstNode'] 14 | ]) 15 | 16 | setup( 17 | name='graphqlparser', 18 | version='0.0.4', 19 | author='Marco Paolini', 20 | author_email='markopaolini@gmail.com', 21 | description='Python bindings for libgraphqlparser (Cython-based)', 22 | long_description='\n\n'.join([open('README.rst', 'r').read(), 23 | '-----', 24 | open('NEWS.rst', 'r').read()]), 25 | url='https://github.com/elastic-coders/py-graphqlparser', 26 | packages=['graphql_parser'], 27 | install_requires=['cython'], 28 | package_data={'graphql_parser': ['*.pxd', '*.pyx']}, 29 | include_package_data=True, 30 | ext_modules=extensions, 31 | license='BSD', 32 | keywords=['graphql', 'parser', 'libgraphql'], 33 | classifiers=[ 34 | 'Development Status :: 3 - Alpha', 35 | 'Environment :: Console', 36 | 'Environment :: Web Environment', 37 | 'Intended Audience :: Developers', 38 | 'License :: OSI Approved :: BSD License', 39 | 'Operating System :: POSIX :: Linux', 40 | 'Programming Language :: Python :: 2.7', 41 | 'Programming Language :: Python :: 3', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Programming Language :: Python :: 3.5', 44 | 'Programming Language :: Python :: Implementation :: CPython', 45 | 'Topic :: Internet :: WWW/HTTP', 46 | 'Topic :: Software Development :: Libraries :: Python Modules', 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /tests/test_parse.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_parse_ok(): 5 | from graphql_parser import GraphQLParser 6 | assert GraphQLParser.graphql_parse_string('{query {id}}') 7 | 8 | 9 | def test_parse_bad(): 10 | from graphql_parser import GraphQLParser 11 | with pytest.raises(GraphQLParser.GraphQLParseError): 12 | assert GraphQLParser.graphql_parse_string('{query {id') 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27,py33,py34,py35 3 | 4 | [testenv] 5 | deps = pytest 6 | Cython 7 | commands=py.test 8 | --------------------------------------------------------------------------------