pax_global_header00006660000000000000000000000064145350405530014516gustar00rootroot0000000000000052 comment=a084895d270cb8f9865ee33daab5356fc3ab3ba1 graphene-mongo-0.4.1/000077500000000000000000000000001453504055300144265ustar00rootroot00000000000000graphene-mongo-0.4.1/.github/000077500000000000000000000000001453504055300157665ustar00rootroot00000000000000graphene-mongo-0.4.1/.github/workflows/000077500000000000000000000000001453504055300200235ustar00rootroot00000000000000graphene-mongo-0.4.1/.github/workflows/ci.yml000066400000000000000000000020721453504055300211420ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Test Package on: push: branches: [ "master" ] pull_request: branches: [ "master" ] permissions: contents: read jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python: ["3.8", "3.9", "3.10", "3.11","3.12"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - name: Lint with ruff run: | python -m pip install ruff make lint - name: Install dependencies run: | python -m pip install poetry poetry config virtualenvs.create false poetry install --with dev - name: Run Tests run: make test - name: Build Package run: | poetry buildgraphene-mongo-0.4.1/.github/workflows/lint.yml000066400000000000000000000011761453504055300215210ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Lint on: [push, pull_request] permissions: contents: read jobs: build: strategy: matrix: python: ["3.12"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - name: Lint with ruff run: | python -m pip install ruff make lintgraphene-mongo-0.4.1/.github/workflows/publish.yml000066400000000000000000000026251453504055300222210ustar00rootroot00000000000000name: Publish to PyPI on: release: types: [published] permissions: contents: read jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python: ["3.8", "3.9", "3.10", "3.11", "3.12"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - name: Install dependencies run: | python -m pip install poetry poetry config virtualenvs.create false poetry install --with dev - name: Lint run: | make lint - name: Run Tests run: make test - name: Build Package run: | poetry build publish: needs: build runs-on: ubuntu-latest permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install poetry - name: Build package run: | poetry build - name: Build package run: | poetry build - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1graphene-mongo-0.4.1/.gitignore000066400000000000000000000001671453504055300164220ustar00rootroot00000000000000.coverage .cache/ .eggs/ .idea/ _build/ build/ dist/ graphene_mongo.egg-info/ htmlcov/ *.pyc *.swo *.swp venv/ .vscode/graphene-mongo-0.4.1/LICENSE000066400000000000000000000020741453504055300154360ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018-Present Abaw Chen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. graphene-mongo-0.4.1/MANIFEST.in000066400000000000000000000001711453504055300161630ustar00rootroot00000000000000global-exclude *.py[cod] recursive-exclude examples * recursive-exclude graphene-mongo/tests * include README.md LICENSE graphene-mongo-0.4.1/Makefile000066400000000000000000000014301453504055300160640ustar00rootroot00000000000000clean: @rm -f .coverage 2> /dev/null @rm -rf .eggs 2> /dev/null @rm -rf .cache 2> /dev/null @rm -rf ./graphene_mongo/.cache 2> /dev/null @rm -rf build 2> /dev/null @rm -rf dist 2> /dev/null @rm -rf graphene_mongo.egg-info 2> /dev/null @find . -name "*.pyc" -delete @find . -name "*.swp" -delete @find . -name "__pycache__" -delete lint: @ruff check graphene_mongo @ruff format . --check test: clean pytest graphene_mongo/tests --cov=graphene_mongo --cov-report=html --cov-report=term register-pypitest: #python setup.py register -r pypitest deploy-pypitest: clean poetry build #poetry publish --repository testpypi twine upload --repository testpypi dist/* register: #python setup.py register -r pypi deploy: clean poetry build twine upload dist/* #poetry publish graphene-mongo-0.4.1/README.md000066400000000000000000000072761453504055300157210ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/graphql-python/graphene-mongo.svg?branch=master)](https://travis-ci.org/graphql-python/graphene-mongo) [![Coverage Status](https://coveralls.io/repos/github/graphql-python/graphene-mongo/badge.svg?branch=master)](https://coveralls.io/github/graphql-python/graphene-mongo?branch=master) [![Documentation Status](https://readthedocs.org/projects/graphene-mongo/badge/?version=latest)](http://graphene-mongo.readthedocs.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/graphene-mongo.svg)](https://badge.fury.io/py/graphene-mongo) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/graphene-mongo.svg)](https://pypi.python.org/pypi/graphene-mongo/) [![Downloads](https://pepy.tech/badge/graphene-mongo)](https://pepy.tech/project/graphene-mongo) [![Lint](https://github.com/graphql-python/graphene-mongo/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/graphql-python/graphene-mongo/actions/workflows/lint.yml) [![Test Package](https://github.com/graphql-python/graphene-mongo/actions/workflows/ci.yml/badge.svg)](https://github.com/graphql-python/graphene-mongo/actions/workflows/ci.yml) # Graphene-Mongo A [Mongoengine](https://mongoengine-odm.readthedocs.io/) integration for [Graphene](http://graphene-python.org/). ## Installation For installing graphene-mongo, just run this command in your shell ``` pip install graphene-mongo ``` ## Examples Here is a simple Mongoengine model as `models.py`: ```python from mongoengine import Document from mongoengine.fields import StringField class User(Document): meta = {'collection': 'user'} first_name = StringField(required=True) last_name = StringField(required=True) ``` To create a GraphQL schema and sync executor; for it you simply have to write the following: ```python import graphene from graphene_mongo import MongoengineObjectType from .models import User as UserModel class User(MongoengineObjectType): class Meta: model = UserModel class Query(graphene.ObjectType): users = graphene.List(User) def resolve_users(self, info): return list(UserModel.objects.all()) schema = graphene.Schema(query=Query) ``` Then you can simply query the schema: ```python query = ''' query { users { firstName, lastName } } ''' result = await schema.execute(query) ``` To create a GraphQL schema and async executor; for it you simply have to write the following: ```python import graphene from graphene_mongo import AsyncMongoengineObjectType from graphene_mongo.utils import sync_to_async from concurrent.futures import ThreadPoolExecutor from .models import User as UserModel class User(AsyncMongoengineObjectType): class Meta: model = UserModel class Query(graphene.ObjectType): users = graphene.List(User) async def resolve_users(self, info): return await sync_to_async(list, thread_sensitive=False, executor=ThreadPoolExecutor())(UserModel.objects.all()) schema = graphene.Schema(query=Query) ``` Then you can simply query the schema: ```python query = ''' query { users { firstName, lastName } } ''' result = await schema.execute_async(query) ``` To learn more check out the following [examples](examples/): * [Flask MongoEngine example](examples/flask_mongoengine) * [Django MongoEngine example](examples/django_mongoengine) * [Falcon MongoEngine example](examples/falcon_mongoengine) ## Contributing After cloning this repo, ensure dependencies are installed by running: ```sh pip install -r requirements.txt ``` After developing, the full test suite can be evaluated by running: ```sh make test ``` graphene-mongo-0.4.1/README.rst000066400000000000000000000040561453504055300161220ustar00rootroot00000000000000.. image:: https://travis-ci.org/graphql-python/graphene-mongo.svg?branch=master :target: https://travis-ci.org/graphql-python/graphene-mongo .. image:: https://coveralls.io/repos/github/graphql-python/graphene-mongo/badge.svg?branch=master :target: https://coveralls.io/github/graphql-python/graphene-mongo?branch=master .. image:: https://badge.fury.io/py/graphene-mongo.svg :target: https://badge.fury.io/py/graphene-mongo .. image:: https://img.shields.io/pypi/pyversions/graphene-mongo.svg :target: https://pypi.python.org/pypi/graphene-mongo/ Graphene-Mongo ============== A `Mongoengine `__ integration for `Graphene `__. Installation ------------ For installing graphene-mongo, just run this command in your shell .. code:: bash pip install graphene-mongo Examples -------- Here is a simple Mongoengine model as `models.py`: .. code:: python from mongoengine import Document from mongoengine.fields import StringField class User(Document): meta = {'collection': 'user'} first_name = StringField(required=True) last_name = StringField(required=True) To create a GraphQL schema for it you simply have to write the following: .. code:: python import graphene from graphene_mongo import MongoengineObjectType from .models import User as UserModel class User(MongoengineObjectType): class Meta: model = UserModel class Query(graphene.ObjectType): users = graphene.List(User) def resolve_users(self, info): return list(UserModel.objects.all()) schema = graphene.Schema(query=Query) Then you can simply query the schema: .. code:: python query = ''' query { users { firstName, lastName } } ''' result = await schema.execute_async(query) To learn more check out the `Flask MongoEngine example `__ graphene-mongo-0.4.1/docs/000077500000000000000000000000001453504055300153565ustar00rootroot00000000000000graphene-mongo-0.4.1/docs/Makefile000066400000000000000000000166771453504055300170370ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Graphene.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Graphene" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." graphene-mongo-0.4.1/docs/conf.py000066400000000000000000000302711453504055300166600ustar00rootroot00000000000000import os on_rtd = os.environ.get("READTHEDOCS", None) == "True" # -*- coding: utf-8 -*- # # Graphene documentation build configuration file, created by # sphinx-quickstart on Sun Sep 11 18:30:51 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.coverage", "sphinx.ext.viewcode", ] if not on_rtd: extensions += ["sphinx.ext.githubpages"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Graphene Mongo" copyright = "Graphene 2018" author = "Abaw Chen" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "0.1" # The full version, including alpha/beta/rc tags. release = "0.1.2" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = 'alabaster' # if on_rtd: # html_theme = 'sphinx_rtd_theme' import sphinx_graphene_theme html_theme = "sphinx_graphene_theme" html_theme_path = [sphinx_graphene_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'Graphene v1.0.dev' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = "Graphenedoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [(master_doc, "Graphene.tex", "Graphene Documentation", "Syrus Akbary", "manual")] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "graphene_django", "Graphene Django Documentation", [author], 1)] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "Graphene-Django", "Graphene Django Documentation", author, "Graphene Django", "One line description of project.", "Miscellaneous", ) ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The basename for the epub file. It defaults to the project name. # epub_basename = project # The HTML theme for the epub output. Since the default themes are not # optimized for small screen space, using the same theme for HTML and epub # output is usually not wise. This defaults to 'epub', a theme designed to save # visual space. # # epub_theme = 'epub' # The language of the text. It defaults to the language option # or 'en' if the language is not set. # # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # # epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. # # epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] # The depth of the table of contents in toc.ncx. # # epub_tocdepth = 3 # Allow duplicate toc entries. # # epub_tocdup = True # Choose between 'default' and 'includehidden'. # # epub_tocscope = 'default' # Fix unsupported image types using the Pillow. # # epub_fix_images = False # Scale large images. # # epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. # # epub_show_urls = 'inline' # If false, no index is generated. # # epub_use_index = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"https://docs.python.org/": None} graphene-mongo-0.4.1/docs/fields.rst000066400000000000000000000010701453504055300173540ustar00rootroot00000000000000Supported Fields ============================ Mongoengine Fields ------------------ - BooleanField - DecimalField - DateTimeField - DictField - EmailField - EmbeddedDocumentField - EmbeddedDocumentListField - FileField - FloatField - GenericReferenceField - IntField - LazyReferenceField - ListField - LongField - MapField - MultiPolygonField - ObjectIdField - ReferenceField - PointField - PolygonField - SequenceField - StringField - URLField - UUIDField Advanced -------- - Self-reference relationship - List of self-reference relationship - Inheritance field graphene-mongo-0.4.1/docs/index.rst000066400000000000000000000001461453504055300172200ustar00rootroot00000000000000Graphene-Mongo =================== Contents: .. toctree:: :maxdepth: 0 tutorial fields graphene-mongo-0.4.1/docs/requirements.txt000066400000000000000000000001051453504055300206360ustar00rootroot00000000000000# Docs template http://graphene-python.org/sphinx_graphene_theme.zip graphene-mongo-0.4.1/docs/tutorial.rst000066400000000000000000000117551453504055300177640ustar00rootroot00000000000000Mongoengine + Flask Tutorial ============================== Graphene comes with builtin support to Mongoengine, which makes quite easy to operate with your current models. Note: The code in this tutorial is pulled from the `Flask Mongoengine example app `__. Setup the Project ----------------- .. code:: bash # Create the project directory mkdir flask_graphene_mongo cd flask_graphene_mongo # [Optional but suggested] Create a virtualenv to isolate our package dependencies locally virtualenv env source env/bin/activate # Install required packages pip install Flask pip install Flask-GraphQL pip install graphene-mongo # Install mongomock or you have to run a real mongo server instance somewhere. pip install mongomock Defining our models ------------------- Let's get start with following models: .. code:: python # flask_graphene_mongo/models.py from datetime import datetime from mongoengine import Document from mongoengine.fields import ( DateTimeField, ReferenceField, StringField, ) class Department(Document): meta = {'collection': 'department'} name = StringField() class Role(Document): meta = {'collection': 'role'} name = StringField() class Employee(Document): meta = {'collection': 'employee'} name = StringField() hired_on = DateTimeField(default=datetime.now) department = ReferenceField(Department) role = ReferenceField(Role) Schema ------ Here I assume you guys have the basic knowledge of how schema works in GraphQL, that I define the *root type* as the `Query` class below with the ability to list all employees. .. code:: python # flask_graphene_mongo/schema.py import graphene from graphene.relay import Node from graphene_mongo import MongoengineConnectionField, MongoengineObjectType from models import Department as DepartmentModel from models import Employee as EmployeeModel from models import Role as RoleModel class Department(MongoengineObjectType): class Meta: model = DepartmentModel interfaces = (Node,) class Role(MongoengineObjectType): class Meta: model = RoleModel interfaces = (Node,) class Employee(MongoengineObjectType): class Meta: model = EmployeeModel interfaces = (Node,) class Query(graphene.ObjectType): node = Node.Field() all_employees = MongoengineConnectionField(Employee) all_role = MongoengineConnectionField(Role) role = graphene.Field(Role) schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) Creating some data ------------------ By putting some data to make this demo can run directly: .. code:: python # flask_graphene_mongo/database.py from mongoengine import connect from models import Department, Employee, Role # You can connect to a real mongo server instance by your own. connect('graphene-mongo-example', host='mongomock://localhost', alias='default') def init_db(): # Create the fixtures engineering = Department(name='Engineering') engineering.save() hr = Department(name='Human Resources') hr.save() manager = Role(name='manager') manager.save() engineer = Role(name='engineer') engineer.save() peter = Employee(name='Peter', department=engineering, role=engineer) peter.save() roy = Employee(name='Roy', department=engineering, role=engineer) roy.save() tracy = Employee(name='Tracy', department=hr, role=manager) tracy.save() Creating GraphQL and GraphiQL views in Flask -------------------------------------------- There is only one URL from which GraphQL is accessed, and we take the advantage of ``Flask-GraphQL`` to generate the GraphQL interface for easily accessed by a browser: .. code:: python # flask_graphene_mongo/app.py from database import init_db from flask import Flask from flask_graphql import GraphQLView from schema import schema app = Flask(__name__) app.debug = True default_query = ''' { allEmployees { edges { node { id, name, department { id, name }, role { id, name } } } } }'''.strip() app.add_url_rule( '/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True) ) if __name__ == '__main__': init_db() app.run() Testing ------- We are ready to launch the server! .. code:: bash $ python app.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) Then go to `http://localhost:5000/graphql `__ to test your first query. graphene-mongo-0.4.1/examples/000077500000000000000000000000001453504055300162445ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/000077500000000000000000000000001453504055300220735ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/.gitignore000066400000000000000000000000121453504055300240540ustar00rootroot00000000000000db.sqlite3graphene-mongo-0.4.1/examples/django_mongoengine/README.md000066400000000000000000000020331453504055300233500ustar00rootroot00000000000000 Example Django+MongoEngine Project ================================ This example project demos integration between Graphene, Django and MongoEngine. Getting started --------------- First you'll need to get the source of the project. Do this by cloning the whole Graphene repository: ```bash # Get the example project code git clone git@github.com:abawchen/graphene-mongo.git cd graphene-mongo/examples/django_mongoengine ``` Create a virtual environment. ```bash # Create a virtualenv in which we can install the dependencies virtualenv env source env/bin/activate ``` Now we can install our dependencies: ```bash pip install -r requirements.txt ``` Run the following command: ```python python manage.py migrate ``` Setup a mongodb connection and create a database. See the mongoengine connection details in the *settings.py* file Start the server: ```python python manage.py runserver ``` Now head on over to [http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql) and run some queries! For tests run: ```python pytest -v ``` graphene-mongo-0.4.1/examples/django_mongoengine/__init__.py000066400000000000000000000000001453504055300241720ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike/000077500000000000000000000000001453504055300230055ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike/__init__.py000066400000000000000000000000001453504055300251040ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike/apps.py000066400000000000000000000001231453504055300243160ustar00rootroot00000000000000from django.apps import AppConfig class BikeConfig(AppConfig): name = "bike" graphene-mongo-0.4.1/examples/django_mongoengine/bike/fixtures.py000066400000000000000000000023471453504055300252360ustar00rootroot00000000000000import pytest from .models import Bike, Shop def fixture_bike_data(): Bike.drop_collection() bike_one = Bike( name="Level R", brand="Mondraker", year="2020", size=["S", "M", "L", "XL"], wheel_size=27.5, type="MTB", ) bike_one.save() bike_two = Bike( name="CAADX ULTEGRA", brand="Cannondale", year="2019", size=["46", "51", "54", "58"], wheel_size=28, type="Gravel", ) bike_two.save() bike_three = Bike( id="507f1f77bcf86cd799439011", name="Moterra Neo", brand="Cannondale", year="2019", size=["M", "L", "XL"], wheel_size=29, type="EBike", ) bike_three.save() def fixture_shop_data(): Shop.drop_collection() shop_one = Shop( name="Big Wheel Bicycles", address="2438 Hart Ridge Road", website="https://www.bigwheelbike.test", ) shop_one.save() shop_two = Shop( name="Bike Tech", address="2175 Pearl Street", website="https://www.biketech.test", ) shop_two.save() @pytest.fixture(scope="module") def fixtures_data(): fixture_bike_data() fixture_shop_data() return True graphene-mongo-0.4.1/examples/django_mongoengine/bike/migrations/000077500000000000000000000000001453504055300251615ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike/migrations/__init__.py000066400000000000000000000000001453504055300272600ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike/models.py000066400000000000000000000010601453504055300246370ustar00rootroot00000000000000from mongoengine import Document from mongoengine.fields import ( FloatField, StringField, ListField, URLField, ObjectIdField, ) class Shop(Document): meta = {"collection": "shop"} ID = ObjectIdField() name = StringField() address = StringField() website = URLField() class Bike(Document): meta = {"collection": "bike"} ID = ObjectIdField() name = StringField() brand = StringField() year = StringField() size = ListField(StringField()) wheel_size = FloatField() type = StringField() graphene-mongo-0.4.1/examples/django_mongoengine/bike/mutations.py000066400000000000000000000040431453504055300254030ustar00rootroot00000000000000import graphene from django.core.exceptions import ObjectDoesNotExist from .models import Bike from .types import BikeType class BikeInput(graphene.InputObjectType): id = graphene.ID() name = graphene.String() brand = graphene.String() year = graphene.String() size = graphene.List(graphene.String) wheel_size = graphene.Float() type = graphene.String() class CreateBikeMutation(graphene.Mutation): bike = graphene.Field(BikeType) class Arguments: bike_data = BikeInput(required=True) def mutate(self, info, bike_data=None): bike = Bike( name=bike_data.name, brand=bike_data.brand, year=bike_data.year, size=bike_data.size, wheel_size=bike_data.wheel_size, type=bike_data.type, ) bike.save() return CreateBikeMutation(bike=bike) class UpdateBikeMutation(graphene.Mutation): bike = graphene.Field(BikeType) class Arguments: bike_data = BikeInput(required=True) @staticmethod def get_object(id): return Bike.objects.get(pk=id) def mutate(self, info, bike_data=None): bike = UpdateBikeMutation.get_object(bike_data.id) if bike_data.name: bike.name = bike_data.name if bike_data.brand: bike.brand = bike_data.brand if bike_data.year: bike.year = bike_data.year if bike_data.size: bike.size = bike_data.size if bike_data.wheel_size: bike.wheel_size = bike_data.wheel_size if bike_data.type: bike.type = bike_data.type bike.save() return UpdateBikeMutation(bike=bike) class DeleteBikeMutation(graphene.Mutation): class Arguments: id = graphene.ID(required=True) success = graphene.Boolean() def mutate(self, info, id): try: Bike.objects.get(pk=id).delete() success = True except ObjectDoesNotExist: success = False return DeleteBikeMutation(success=success) graphene-mongo-0.4.1/examples/django_mongoengine/bike/schema.py000066400000000000000000000013441453504055300246210ustar00rootroot00000000000000import graphene from graphene.relay import Node from graphene_mongo.fields import MongoengineConnectionField from .models import Shop from .types import BikeType, ShopType from .mutations import CreateBikeMutation, UpdateBikeMutation, DeleteBikeMutation class Mutations(graphene.ObjectType): create_bike = CreateBikeMutation.Field() update_bike = UpdateBikeMutation.Field() delete_bike = DeleteBikeMutation.Field() class Query(graphene.ObjectType): node = Node.Field() bikes = MongoengineConnectionField(BikeType) shop_list = graphene.List(ShopType) def resolve_shop_list(self, info): return Shop.objects.all() schema = graphene.Schema(query=Query, mutation=Mutations, types=[BikeType, ShopType]) graphene-mongo-0.4.1/examples/django_mongoengine/bike/tests.py000066400000000000000000000144511453504055300245260ustar00rootroot00000000000000import pytest from django.urls import reverse from django.test import RequestFactory from graphene.test import Client from .schema import schema from .fixtures import fixtures_data def test_bikes_first_item_query(fixtures_data): query = """ { bikes(first: 1){ edges { node { name brand year size wheelSize type } } } }""" expected = { "data": { "bikes": { "edges": [ { "node": { "name": "Level R", "brand": "Mondraker", "year": "2020", "size": ["S", "M", "L", "XL"], "wheelSize": 27.5, "type": "MTB", } } ] } } } client = Client(schema) result = client.execute(query) assert result == expected def test_bikes_filter_by_type_item_query(fixtures_data): query = """ { bikes(first: 2, type: "Gravel"){ edges { node { name brand year size wheelSize type } } } }""" expected = { "data": { "bikes": { "edges": [ { "node": { "name": "CAADX ULTEGRA", "brand": "Cannondale", "year": "2019", "size": ["46", "51", "54", "58"], "wheelSize": 28, "type": "Gravel", } } ] } } } client = Client(schema) result = client.execute(query) assert result == expected def test_shop_data_query(fixtures_data): query = """{ shopList{ name address website } }""" expected = { "data": { "shopList": [ { "name": "Big Wheel Bicycles", "address": "2438 Hart Ridge Road", "website": "https://www.bigwheelbike.test", }, { "name": "Bike Tech", "address": "2175 Pearl Street", "website": "https://www.biketech.test", }, ] } } client = Client(schema) result = client.execute(query) assert result == expected @pytest.mark.django_db def test_create_bike_mutation(): query = """ mutation { createBike(bikeData:{ name:"Bullhorn", brand:"Pegas", year: "2019", size: ["56", "58" ], wheelSize: 28, type: "Fixie" }) { bike { name brand year size wheelSize type } } } """ expected = { "data": { "createBike": { "bike": { "name": "Bullhorn", "brand": "Pegas", "year": "2019", "size": ["56", "58"], "wheelSize": 28, "type": "Fixie", } } } } factory = RequestFactory() request = factory.post(reverse("graphql-query")) client = Client(schema) result = client.execute(query, context=request) assert result == expected @pytest.mark.django_db def test_update_bike_mutation(): query = """ mutation { updateBike(bikeData:{ id: "507f1f77bcf86cd799439011", name:"Moterra Neo Updated", year: "2020", wheelSize: 27.5, type: "EBike Updated" }) { bike { name brand year size wheelSize type } } } """ expected = { "data": { "updateBike": { "bike": { "name": "Moterra Neo Updated", "brand": "Cannondale", "year": "2020", "size": ["M", "L", "XL"], "wheelSize": 27.5, "type": "EBike Updated", } } } } factory = RequestFactory() request = factory.post(reverse("graphql-query")) client = Client(schema) result = client.execute(query, context=request) print(result) assert result == expected @pytest.mark.django_db def test_delete_bike_mutation(): query = """ mutation { deleteBike(id: "507f1f77bcf86cd799439011") { success } } """ expected = {"data": {"deleteBike": {"success": True}}} factory = RequestFactory() request = factory.post(reverse("graphql-query")) client = Client(schema) result = client.execute(query, context=request) assert result == expected graphene-mongo-0.4.1/examples/django_mongoengine/bike/types.py000066400000000000000000000004521453504055300245240ustar00rootroot00000000000000from graphene import relay from graphene_mongo import MongoengineObjectType from .models import Bike, Shop class BikeType(MongoengineObjectType): class Meta: model = Bike interfaces = (relay.Node,) class ShopType(MongoengineObjectType): class Meta: model = Shop graphene-mongo-0.4.1/examples/django_mongoengine/bike/urls.py000066400000000000000000000003561453504055300243500ustar00rootroot00000000000000from django.urls import path from django.views.decorators.csrf import csrf_exempt from graphene_django.views import GraphQLView urlpatterns = [ path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql-query") ] graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/000077500000000000000000000000001453504055300244775ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/__init__.py000066400000000000000000000000001453504055300265760ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/settings.py000066400000000000000000000067351453504055300267240ustar00rootroot00000000000000""" Django settings for bike_catalog project. Generated by 'django-admin startproject' using Django 2.2.3. For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os import mongoengine # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "_@^#*p_+mm-p8+#k&8i0=dnvt03$ycqmwqs4os0-+@+u6k-f_m" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "graphene_django", "graphene_mongo", "bike", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "bike_catalog.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ] }, } ] WSGI_APPLICATION = "bike_catalog.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # Password validation # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = "/static/" # MONGO DB connection\ _MONGODB_USER = "" _MONGODB_PASSWD = "" _MONGODB_HOST = "localhost" _MONGODB_NAME = "bike-catalog" _MONGODB_PORT = 27017 _MONGODB_DATABASE_HOST = "mongodb://%s:%s@%s/%s" % ( _MONGODB_USER, _MONGODB_PASSWD, _MONGODB_HOST, _MONGODB_NAME, ) mongoengine.connect(_MONGODB_NAME, host=_MONGODB_HOST, port=_MONGODB_PORT) GRAPHENE = {"SCHEMA": "bike.schema.schema"} # Where your Graphene schema lives graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/settings_test.py000066400000000000000000000002031453504055300277430ustar00rootroot00000000000000from .settings import * # flake8: noqa mongoengine.connect("graphene-mongo-test", host="mongomock://localhost", alias="default") graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/urls.py000066400000000000000000000014241453504055300260370ustar00rootroot00000000000000"""bike_catalog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, include urlpatterns = [path("admin/", admin.site.urls), path("", include("bike.urls"))] graphene-mongo-0.4.1/examples/django_mongoengine/bike_catalog/wsgi.py000066400000000000000000000006211453504055300260210ustar00rootroot00000000000000""" WSGI config for bike_catalog project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bike_catalog.settings") application = get_wsgi_application() graphene-mongo-0.4.1/examples/django_mongoengine/manage.py000066400000000000000000000011701453504055300236740ustar00rootroot00000000000000#!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys def main(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bike_catalog.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == "__main__": main() graphene-mongo-0.4.1/examples/django_mongoengine/pytest.ini000066400000000000000000000001511453504055300241210ustar00rootroot00000000000000[pytest] DJANGO_SETTINGS_MODULE = bike_catalog.settings_test python_files = tests.py test_*.py *_tests.pygraphene-mongo-0.4.1/examples/django_mongoengine/requirements.txt000066400000000000000000000001761453504055300253630ustar00rootroot00000000000000Django==2.2.28 pytest==4.6.3 pytest-django==3.5.1 mongoengine==0.27.0 mongomock==3.16.0 graphene-django==2.4.0 graphene-mongo graphene-mongo-0.4.1/examples/falcon_mongoengine/000077500000000000000000000000001453504055300220735ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/falcon_mongoengine/README.md000066400000000000000000000030461453504055300233550ustar00rootroot00000000000000 Example Falcon+MongoEngine Project ================================ This example project demos integration between Graphene, Falcon and MongoEngine. Getting started --------------- First you'll need to get the source of the project. Do this by cloning the whole Graphene repository: ```bash # Get the example project code git clone git@github.com:abawchen/graphene-mongo.git cd graphene-mongo/examples/falcon_mongoengine ``` Create a virtual environment. ```bash # Create a virtualenv in which we can install the dependencies virtualenv env source env/bin/activate ``` Now we can install our dependencies: ```bash pip install -r requirements.txt ``` Setup a mongodb connection and create a database. See the mongoengine connection details in the *app.py* file Start the server: On windows: ``` waitress-serve --port=9000 falcon_mongoengine.app:app ``` On Linux: ``` gunicorn -b 0.0.0.0:9000 falcon_mongoengine.app:app ``` Now head on over to [http://127.0.0.1:9000/graphql?query=](http://127.0.0.1:9000/graphql?query=) and run some queries! Example: ``` http://127.0.0.1:9000/graphql?query=query { categories(first: 1, name: "Travel") { edges { node { name color } } } } ``` ``` http://127.0.0.1:9000/graphql?query=query { bookmarks(first: 10) { pageInfo { startCursor endCursor hasNextPage hasPreviousPage } edges { node { name url category { name color } tags } } } } ``` For tests run: ```python pytest -v ``` graphene-mongo-0.4.1/examples/falcon_mongoengine/__init__.py000066400000000000000000000000001453504055300241720ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/falcon_mongoengine/api.py000066400000000000000000000021321453504055300232140ustar00rootroot00000000000000import json import falcon from .schema import schema def set_graphql_allow_header(req: falcon.Request, resp: falcon.Response, resource: object): resp.set_header("Allow", "GET, POST, OPTIONS") class HelloWorldResource: def on_get(self, req, resp): name = "Hello World!" resp.status = falcon.HTTP_200 resp.body = json.dumps({"respone": name, "status": resp.status}) def on_post(self, req, resp): pass @falcon.after(set_graphql_allow_header) class GraphQLResource: def on_get(self, req, resp): query = req.params["query"] result = await schema.execute_async(query) if result.data: data_ret = {"data": result.data} resp.status = falcon.HTTP_200 resp.body = json.dumps(data_ret, separators=(",", ":")) def on_post(self, req, resp): query = req.params["query"] result = await schema.execute_async(query) if result.data: data_ret = {"data": result.data} resp.status = falcon.HTTP_200 resp.body = json.dumps(data_ret, separators=(",", ":")) graphene-mongo-0.4.1/examples/falcon_mongoengine/app.py000066400000000000000000000004751453504055300232330ustar00rootroot00000000000000import falcon from mongoengine import connect from .api import GraphQLResource, HelloWorldResource connect("bookmarks_db", host="127.0.0.1", port=27017) app = application = falcon.API() helloWorld = HelloWorldResource() graphQL = GraphQLResource() app.add_route("/", helloWorld) app.add_route("/graphql", graphQL) graphene-mongo-0.4.1/examples/falcon_mongoengine/models.py000066400000000000000000000010421453504055300237250ustar00rootroot00000000000000from mongoengine import Document, CASCADE from mongoengine.fields import StringField, ListField, ReferenceField class Category(Document): meta = {"collection": "category"} name = StringField(max_length=140, required=True) color = StringField(max_length=7, required=True) class Bookmark(Document): meta = {"collection": "bookmark"} name = StringField(required=True) url = StringField(required=True) category = ReferenceField("Category", reverse_delete_rule=CASCADE) tags = ListField(StringField(max_length=50)) graphene-mongo-0.4.1/examples/falcon_mongoengine/pytest.ini000066400000000000000000000000651453504055300241250ustar00rootroot00000000000000[pytest] python_files = tests.py test_*.py *_tests.pygraphene-mongo-0.4.1/examples/falcon_mongoengine/requirements.txt000066400000000000000000000001401453504055300253520ustar00rootroot00000000000000falcon==2.0.0 mongoengine==0.17.0 graphene-mongo waitress==2.1.2 pytest==4.6.3 mongomock==3.16.0graphene-mongo-0.4.1/examples/falcon_mongoengine/schema.py000066400000000000000000000005361453504055300237110ustar00rootroot00000000000000import graphene from graphene_mongo.fields import MongoengineConnectionField from .types import CategoryType, BookmarkType class Query(graphene.ObjectType): categories = MongoengineConnectionField(CategoryType) bookmarks = MongoengineConnectionField(BookmarkType) schema = graphene.Schema(query=Query, types=[CategoryType, BookmarkType]) graphene-mongo-0.4.1/examples/falcon_mongoengine/tests/000077500000000000000000000000001453504055300232355ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/falcon_mongoengine/tests/__init__.py000066400000000000000000000000001453504055300253340ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/falcon_mongoengine/tests/fixtures.py000066400000000000000000000022341453504055300254610ustar00rootroot00000000000000import pytest from examples.falcon_mongoengine.models import Category, Bookmark def fixture_category_data(): Category.drop_collection() category_one = Category(name="Travel", color="#ed008c") category_one.save() category_two = Category(name="Work", color="#1769ff") category_two.save() return category_one, category_two @pytest.fixture(scope="module") def fixtures_data(): category_one, category_two = fixture_category_data() Bookmark.drop_collection() bookmark_one = Bookmark( name="Travel tips", url="https://www.traveltips.test", category=category_one, tags=["travel", "tips", "howto"], ) bookmark_one.save() bookmark_two = Bookmark( name="DIY vacation", url="https://www.diyvacation.test", category=category_one, tags=["travel", "diy", "holiday", "vacation"], ) bookmark_two.save() bookmark_three = Bookmark( name="Awesome python", url="https://awesomelists.top/#repos/vinta/awesome-python", category=category_two, tags=["python", "dev", "awesome", "tutorial"], ) bookmark_three.save() return True graphene-mongo-0.4.1/examples/falcon_mongoengine/tests/tests.py000066400000000000000000000077531453504055300247650ustar00rootroot00000000000000import mongoengine from graphene.test import Client from examples.falcon_mongoengine.schema import schema from .fixtures import fixtures_data mongoengine.connect("graphene-mongo-test", host="mongomock://localhost", alias="default") def test_category_last_1_item_query(fixtures_data): query = """ { categories(last: 1){ edges { node { name color } } } }""" expected = { "data": { "categories": { "edges": [ { "node": { "name": "Work", "color": "#1769ff", } } ] } } } client = Client(schema) result = client.execute(query) assert result == expected def test_category_filter_item_query(fixtures_data): query = """ { categories(name: "Work"){ edges { node { name color } } } }""" expected = {"data": {"categories": {"edges": [{"node": {"name": "Work", "color": "#1769ff"}}]}}} client = Client(schema) result = client.execute(query) assert result == expected def test_bookmarks_first_2_items_query(fixtures_data): query = """ { bookmarks(first: 2){ edges { node { name url category { name color } tags } } } }""" expected = { "data": { "bookmarks": { "edges": [ { "node": { "name": "Travel tips", "url": "https://www.traveltips.test", "category": {"name": "Travel", "color": "#ed008c"}, "tags": ["travel", "tips", "howto"], } }, { "node": { "name": "DIY vacation", "url": "https://www.diyvacation.test", "category": {"name": "Travel", "color": "#ed008c"}, "tags": ["travel", "diy", "holiday", "vacation"], } }, ] } } } client = Client(schema) result = client.execute(query) assert result == expected def test_bookmarks_filter_items_query(fixtures_data): query = """ { bookmarks(first: 1, name: "Awesome python"){ edges { node { name url category { name color } tags } } } }""" expected = { "data": { "bookmarks": { "edges": [ { "node": { "name": "Awesome python", "url": "https://awesomelists.top/#repos/vinta/awesome-python", "category": {"name": "Work", "color": "#1769ff"}, "tags": ["python", "dev", "awesome", "tutorial"], } } ] } } } client = Client(schema) result = client.execute(query) assert result == expected graphene-mongo-0.4.1/examples/falcon_mongoengine/types.py000066400000000000000000000005461453504055300236160ustar00rootroot00000000000000from graphene import relay from graphene_mongo import MongoengineObjectType from .models import Bookmark, Category class CategoryType(MongoengineObjectType): class Meta: model = Category interfaces = (relay.Node,) class BookmarkType(MongoengineObjectType): class Meta: model = Bookmark interfaces = (relay.Node,) graphene-mongo-0.4.1/examples/flask_mongoengine/000077500000000000000000000000001453504055300217315ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/flask_mongoengine/README.md000066400000000000000000000033001453504055300232040ustar00rootroot00000000000000 Example Flask+MongoEngine Project ================================ This example project demos integration between Graphene, Flask and MongoEngine. The project contains three models, which are `Department`, `Employee` and `Role`. Getting started --------------- First you'll need to get the source of the project. Do this by cloning the whole Graphene repository: ```bash # Get the example project code git clone git@github.com:abawchen/graphene-mongo.git cd graphene-mongo/examples/flask_mongoengine ``` It is good idea (but not required) to create a virtual environment for this project. We'll do this using [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) to keep things simple, but you may also find something like [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to be useful: ```bash # Create a virtualenv in which we can install the dependencies virtualenv env source env/bin/activate ``` Now we can install our dependencies: ```bash pip install -r requirements.txt ``` Now the following command will setup the database, and start the server: ```bash python app.py ``` Now head on over to [http://127.0.0.1:5000/graphql](http://127.0.0.1:5000/graphql) and run some queries! Sample query: ``` { allEmployees { edges { node { id, name, department { id, name }, roles { edges { node { id, name } } }, leader { id, name } tasks { edges { node { name, deadline } } } } } } } ``` graphene-mongo-0.4.1/examples/flask_mongoengine/__init__.py000066400000000000000000000000001453504055300240300ustar00rootroot00000000000000graphene-mongo-0.4.1/examples/flask_mongoengine/app.py000066400000000000000000000014551453504055300230700ustar00rootroot00000000000000from database import init_db from flask import Flask from flask_graphql import GraphQLView from schema import schema app = Flask(__name__) app.debug = True default_query = """ { allEmployees { edges { node { id, name, department { id, name }, roles { edges { node { id, name } } }, leader { id, name } tasks { edges { node { name, deadline } } } } } } }""".strip() app.add_url_rule("/graphql", view_func=GraphQLView.as_view("graphql", schema=schema, graphiql=True)) if __name__ == "__main__": init_db() app.run() graphene-mongo-0.4.1/examples/flask_mongoengine/database.py000066400000000000000000000017031453504055300240500ustar00rootroot00000000000000from mongoengine import connect from .models import Department, Employee, Role, Task connect("graphene-mongo-example", host="mongomock://localhost", alias="default") def init_db(): # Create the fixtures engineering = Department(name="Engineering") engineering.save() hr = Department(name="Human Resources") hr.save() manager = Role(name="manager") manager.save() engineer = Role(name="engineer") engineer.save() debug = Task(name="Debug") test = Task(name="Test") tracy = Employee(name="Tracy", department=hr, roles=[engineer, manager], tasks=[]) tracy.save() peter = Employee( name="Peter", department=engineering, leader=tracy, roles=[engineer], tasks=[debug, test], ) peter.save() roy = Employee( name="Roy", department=engineering, leader=tracy, roles=[engineer], tasks=[debug], ) roy.save() graphene-mongo-0.4.1/examples/flask_mongoengine/models.py000066400000000000000000000014651453504055300235740ustar00rootroot00000000000000from datetime import datetime from mongoengine import Document, EmbeddedDocument from mongoengine.fields import ( DateTimeField, EmbeddedDocumentField, ListField, ReferenceField, StringField, ) class Department(Document): meta = {"collection": "department"} name = StringField() class Role(Document): meta = {"collection": "role"} name = StringField() class Task(EmbeddedDocument): name = StringField() deadline = DateTimeField(default=datetime.now) class Employee(Document): meta = {"collection": "employee"} name = StringField() hired_on = DateTimeField(default=datetime.now) department = ReferenceField(Department) roles = ListField(ReferenceField(Role)) leader = ReferenceField("Employee") tasks = ListField(EmbeddedDocumentField(Task)) graphene-mongo-0.4.1/examples/flask_mongoengine/requirements.txt000066400000000000000000000001041453504055300252100ustar00rootroot00000000000000Flask>=1.0.0 Flask-GraphQL==2.0.0 graphene-mongo mongomock==3.14.0 graphene-mongo-0.4.1/examples/flask_mongoengine/schema.py000066400000000000000000000024721453504055300235500ustar00rootroot00000000000000import graphene from graphene.relay import Node from graphene_mongo import MongoengineConnectionField, MongoengineObjectType from .models import Department as DepartmentModel from .models import Employee as EmployeeModel from .models import Role as RoleModel from .models import Task as TaskModel class Department(MongoengineObjectType): class Meta: model = DepartmentModel interfaces = (Node,) class Role(MongoengineObjectType): class Meta: model = RoleModel interfaces = (Node,) filter_fields = { "name": [ "exact", "icontains", "istartswith", ] } class Task(MongoengineObjectType): class Meta: model = TaskModel interfaces = (Node,) class Employee(MongoengineObjectType): class Meta: model = EmployeeModel interfaces = (Node,) filter_fields = { "name": [ "exact", "icontains", "istartswith", ] } class Query(graphene.ObjectType): node = Node.Field() all_employees = MongoengineConnectionField(Employee) all_roles = MongoengineConnectionField(Role) role = graphene.Field(Role) schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) graphene-mongo-0.4.1/graphene_mongo/000077500000000000000000000000001453504055300174165ustar00rootroot00000000000000graphene-mongo-0.4.1/graphene_mongo/__init__.py000066400000000000000000000007641453504055300215360ustar00rootroot00000000000000from .fields import MongoengineConnectionField from .fields_async import AsyncMongoengineConnectionField from .types import MongoengineObjectType, MongoengineInputType, MongoengineInterfaceType from .types_async import AsyncMongoengineObjectType __version__ = "0.1.1" __all__ = [ "__version__", "MongoengineObjectType", "AsyncMongoengineObjectType", "MongoengineInputType", "MongoengineInterfaceType", "MongoengineConnectionField", "AsyncMongoengineConnectionField", ] graphene-mongo-0.4.1/graphene_mongo/advanced_types.py000066400000000000000000000036761453504055300227750ustar00rootroot00000000000000import base64 import graphene class FileFieldType(graphene.ObjectType): content_type = graphene.String() md5 = graphene.String() chunk_size = graphene.Int() length = graphene.Int() data = graphene.String() # Support Graphene Federation v2 _shareable = True @classmethod def _resolve_fs_field(cls, field, name, default_value=None): v = getattr(field.instance, field.key) return getattr(v, name, default_value) def resolve_content_type(self, info): return FileFieldType._resolve_fs_field(self, "content_type") def resolve_md5(self, info): return FileFieldType._resolve_fs_field(self, "md5") def resolve_chunk_size(self, info): return FileFieldType._resolve_fs_field(self, "chunk_size", 0) def resolve_length(self, info): return FileFieldType._resolve_fs_field(self, "length", 0) def resolve_data(self, info): v = getattr(self.instance, self.key) data = v.read() if data is not None: return base64.b64encode(data).decode("utf-8") return None class _CoordinatesTypeField(graphene.ObjectType): type = graphene.String() # Support Graphene Federation v2 _shareable = True def resolve_type(self, info): return self["type"] def resolve_coordinates(self, info): return self["coordinates"] class PointFieldType(_CoordinatesTypeField): coordinates = graphene.List(graphene.Float) class PointFieldInputType(graphene.InputObjectType): type = graphene.String(default_value="Point") coordinates = graphene.List(graphene.Float, required=True) class PolygonFieldType(_CoordinatesTypeField): coordinates = graphene.List(graphene.List(graphene.List(graphene.Float))) class MultiPolygonFieldType(_CoordinatesTypeField): coordinates = graphene.List( graphene.List( graphene.List( graphene.List(graphene.Float), ) ) ) graphene-mongo-0.4.1/graphene_mongo/converter.py000066400000000000000000001034101453504055300217760ustar00rootroot00000000000000import asyncio import sys import graphene import mongoengine from graphene.types.json import JSONString from graphene.utils.str_converters import to_snake_case, to_camel_case from mongoengine.base import get_document, LazyReference from . import advanced_types from .utils import ( import_single_dispatch, get_field_description, get_query_fields, ExecutorEnum, sync_to_async, ) from concurrent.futures import ThreadPoolExecutor, as_completed singledispatch = import_single_dispatch() class MongoEngineConversionError(Exception): pass @singledispatch def convert_mongoengine_field(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): raise MongoEngineConversionError( "Don't know how to convert the MongoEngine field %s (%s)" % (field, field.__class__) ) @convert_mongoengine_field.register(mongoengine.EmailField) @convert_mongoengine_field.register(mongoengine.StringField) @convert_mongoengine_field.register(mongoengine.URLField) def convert_field_to_string(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.String( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.UUIDField) @convert_mongoengine_field.register(mongoengine.ObjectIdField) def convert_field_to_id(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.ID(description=get_field_description(field, registry), required=field.required) @convert_mongoengine_field.register(mongoengine.IntField) @convert_mongoengine_field.register(mongoengine.LongField) @convert_mongoengine_field.register(mongoengine.SequenceField) def convert_field_to_int(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Int(description=get_field_description(field, registry), required=field.required) @convert_mongoengine_field.register(mongoengine.BooleanField) def convert_field_to_boolean(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Boolean( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.FloatField) def convert_field_to_float(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Float( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.Decimal128Field) @convert_mongoengine_field.register(mongoengine.DecimalField) def convert_field_to_decimal(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Decimal( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.DateTimeField) def convert_field_to_datetime(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.DateTime( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.DateField) def convert_field_to_date(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Date( description=get_field_description(field, registry), required=field.required ) @convert_mongoengine_field.register(mongoengine.DictField) @convert_mongoengine_field.register(mongoengine.MapField) def convert_field_to_jsonstring(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return JSONString(description=get_field_description(field, registry), required=field.required) @convert_mongoengine_field.register(mongoengine.PointField) def convert_point_to_field(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Field( advanced_types.PointFieldType, description=get_field_description(field, registry), required=field.required, ) @convert_mongoengine_field.register(mongoengine.PolygonField) def convert_polygon_to_field(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Field( advanced_types.PolygonFieldType, description=get_field_description(field, registry), required=field.required, ) @convert_mongoengine_field.register(mongoengine.MultiPolygonField) def convert_multipolygon_to_field(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Field( advanced_types.MultiPolygonFieldType, description=get_field_description(field, registry), required=field.required, ) @convert_mongoengine_field.register(mongoengine.FileField) def convert_file_to_field(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Field( advanced_types.FileFieldType, description=get_field_description(field, registry), required=field.required, ) @convert_mongoengine_field.register(mongoengine.ListField) @convert_mongoengine_field.register(mongoengine.EmbeddedDocumentListField) @convert_mongoengine_field.register(mongoengine.GeoPointField) def convert_field_to_list(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): base_type = convert_mongoengine_field(field.field, registry=registry, executor=executor) if isinstance(base_type, graphene.Field): if isinstance(field.field, mongoengine.GenericReferenceField): def get_reference_objects(*args, **kwargs): document = get_document(args[0][0]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) document_field_type = document_field.get_type().type queried_fields = list() filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0][3][0])[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) return ( document.objects() .no_dereference() .only(*set(list(document_field_type._meta.required_fields) + queried_fields)) .filter(pk__in=args[0][1]) ) def get_non_querying_object(*args, **kwargs): model = get_document(args[0][0]) return [model(pk=each) for each in args[0][1]] def reference_resolver(root, *args, **kwargs): to_resolve = getattr(root, field.name or field.db_name) if to_resolve: choice_to_resolve = dict() querying_union_types = list(get_query_fields(args[0]).keys()) if "__typename" in querying_union_types: querying_union_types.remove("__typename") to_resolve_models = list() for each in querying_union_types: if executor == ExecutorEnum.SYNC: to_resolve_models.append(registry._registry_string_map[each]) else: to_resolve_models.append(registry._registry_async_string_map[each]) to_resolve_object_ids = list() for each in to_resolve: if isinstance(each, LazyReference): to_resolve_object_ids.append(each.pk) model = each.document_type._class_name if model not in choice_to_resolve: choice_to_resolve[model] = list() choice_to_resolve[model].append(each.pk) else: to_resolve_object_ids.append(each["_ref"].id) if each["_cls"] not in choice_to_resolve: choice_to_resolve[each["_cls"]] = list() choice_to_resolve[each["_cls"]].append(each["_ref"].id) pool = ThreadPoolExecutor(5) futures = list() for model, object_id_list in choice_to_resolve.items(): if model in to_resolve_models: futures.append( pool.submit( get_reference_objects, (model, object_id_list, registry, args), ) ) else: futures.append( pool.submit( get_non_querying_object, (model, object_id_list, registry, args), ) ) result = list() for x in as_completed(futures): result += x.result() result_object_ids = list() for each in result: result_object_ids.append(each.id) ordered_result = list() for each in to_resolve_object_ids: ordered_result.append(result[result_object_ids.index(each)]) return ordered_result return None async def get_reference_objects_async(*args, **kwargs): document = get_document(args[0]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field( document_field, registry, executor=ExecutorEnum.ASYNC ) document_field_type = document_field.get_type().type queried_fields = list() filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[3][0])[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) return await sync_to_async(list)( document.objects() .no_dereference() .only(*set(list(document_field_type._meta.required_fields) + queried_fields)) .filter(pk__in=args[1]) ) async def get_non_querying_object_async(*args, **kwargs): model = get_document(args[0]) return [model(pk=each) for each in args[1]] async def reference_resolver_async(root, *args, **kwargs): to_resolve = getattr(root, field.name or field.db_name) if to_resolve: choice_to_resolve = dict() querying_union_types = list(get_query_fields(args[0]).keys()) if "__typename" in querying_union_types: querying_union_types.remove("__typename") to_resolve_models = list() for each in querying_union_types: if executor == ExecutorEnum.SYNC: to_resolve_models.append(registry._registry_string_map[each]) else: to_resolve_models.append(registry._registry_async_string_map[each]) to_resolve_object_ids = list() for each in to_resolve: if isinstance(each, LazyReference): to_resolve_object_ids.append(each.pk) model = each.document_type._class_name if model not in choice_to_resolve: choice_to_resolve[model] = list() choice_to_resolve[model].append(each.pk) else: to_resolve_object_ids.append(each["_ref"].id) if each["_cls"] not in choice_to_resolve: choice_to_resolve[each["_cls"]] = list() choice_to_resolve[each["_cls"]].append(each["_ref"].id) loop = asyncio.get_event_loop() tasks = [] for model, object_id_list in choice_to_resolve.items(): if model in to_resolve_models: task = loop.create_task( get_reference_objects_async(model, object_id_list, registry, args) ) else: task = loop.create_task( get_non_querying_object_async(model, object_id_list, registry, args) ) tasks.append(task) result = await asyncio.gather(*tasks) result_object = {} for items in result: for item in items: result_object[item.id] = item ordered_result = list() for each in to_resolve_object_ids: ordered_result.append(result_object[each]) return ordered_result return None return graphene.List( base_type._type, description=get_field_description(field, registry), required=field.required, resolver=reference_resolver if executor == ExecutorEnum.SYNC else reference_resolver_async, ) return graphene.List( base_type._type, description=get_field_description(field, registry), required=field.required, ) if isinstance(base_type, (graphene.Dynamic)): base_type = base_type.get_type() if base_type is None: return base_type = base_type._type if graphene.is_node(base_type): return base_type._meta.connection_field_class(base_type) # Non-relationship field relations = (mongoengine.ReferenceField, mongoengine.EmbeddedDocumentField) if not isinstance(base_type, (graphene.List, graphene.NonNull)) and not isinstance( field.field, relations ): base_type = type(base_type) return graphene.List( base_type, description=get_field_description(field, registry), required=field.required, ) @convert_mongoengine_field.register(mongoengine.GenericEmbeddedDocumentField) @convert_mongoengine_field.register(mongoengine.GenericReferenceField) def convert_field_to_union(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): _types = [] for choice in field.choices: if isinstance(field, mongoengine.GenericReferenceField): _field = mongoengine.ReferenceField(get_document(choice)) elif isinstance(field, mongoengine.GenericEmbeddedDocumentField): _field = mongoengine.EmbeddedDocumentField(choice) _field = convert_mongoengine_field(_field, registry, executor=executor) _type = _field.get_type() if _type: _types.append(_type.type) else: # TODO: Register type auto-matically here. pass if len(_types) == 0: return None field_name = field.db_field if field_name is None: # Get db_field name from parent mongo_field for db_field_name, _mongo_parent_field in field.owner_document._fields.items(): if hasattr(_mongo_parent_field, "field") and _mongo_parent_field.field == field: field_name = db_field_name break name = to_camel_case( "{}_{}_union_type".format( field._owner_document.__name__, field_name, ) ) Meta = type("Meta", (object,), {"types": tuple(_types)}) _union = type(name, (graphene.Union,), {"Meta": Meta}) def reference_resolver(root, *args, **kwargs): de_referenced = getattr(root, field.name or field.db_name) if de_referenced: document = get_document(de_referenced["_cls"]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry, executor=executor) _type = document_field.get_type().type filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) querying_types = list(get_query_fields(args[0]).keys()) if _type.__name__ in querying_types: queried_fields = list() for each in get_query_fields(args[0])[_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) return ( document.objects() .no_dereference() .only(*list(set(list(_type._meta.required_fields) + queried_fields))) .get(pk=de_referenced["_ref"].id) ) return document() return None def lazy_reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: if document._cached_doc: return document._cached_doc queried_fields = list() document_field_type = registry.get_type_for_model( document.document_type, executor=executor ) querying_types = list(get_query_fields(args[0]).keys()) filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) if document_field_type._meta.name in querying_types: for each in get_query_fields(args[0])[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) _type = registry.get_type_for_model(document.document_type, executor=executor) return ( document.document_type.objects() .no_dereference() .only(*(set((list(_type._meta.required_fields) + queried_fields)))) .get(pk=document.pk) ) return document.document_type() return None async def reference_resolver_async(root, *args, **kwargs): de_referenced = getattr(root, field.name or field.db_name) if de_referenced: document = get_document(de_referenced["_cls"]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field( document_field, registry, executor=ExecutorEnum.ASYNC ) _type = document_field.get_type().type filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) querying_types = list(get_query_fields(args[0]).keys()) if _type.__name__ in querying_types: queried_fields = list() for each in get_query_fields(args[0])[_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) return await sync_to_async( document.objects() .no_dereference() .only(*list(set(list(_type._meta.required_fields) + queried_fields))) .get )(pk=de_referenced["_ref"].id) return await sync_to_async(document)() return None async def lazy_reference_resolver_async(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: if document._cached_doc: return document._cached_doc queried_fields = list() document_field_type = registry.get_type_for_model( document.document_type, executor=executor ) querying_types = list(get_query_fields(args[0]).keys()) filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) if document_field_type._meta.name in querying_types: for each in get_query_fields(args[0])[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) _type = registry.get_type_for_model(document.document_type, executor=executor) return await sync_to_async( document.document_type.objects() .no_dereference() .only(*(set((list(_type._meta.required_fields) + queried_fields)))) .get )(pk=document.pk) return await sync_to_async(document.document_type)() return None if isinstance(field, mongoengine.GenericLazyReferenceField): field_resolver = None required = False if field.db_field is not None: required = field.required resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, None, ) if resolver_function and callable(resolver_function): field_resolver = resolver_function return graphene.Field( _union, resolver=field_resolver if field_resolver else ( lazy_reference_resolver if executor == ExecutorEnum.SYNC else lazy_reference_resolver_async ), description=get_field_description(field, registry), required=required, ) elif isinstance(field, mongoengine.GenericReferenceField): field_resolver = None required = False if field.db_field is not None: required = field.required resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, None, ) if resolver_function and callable(resolver_function): field_resolver = resolver_function return graphene.Field( _union, resolver=field_resolver if field_resolver else ( reference_resolver if executor == ExecutorEnum.SYNC else reference_resolver_async ), description=get_field_description(field, registry), required=required, ) return graphene.Field(_union) @convert_mongoengine_field.register(mongoengine.EmbeddedDocumentField) @convert_mongoengine_field.register(mongoengine.ReferenceField) @convert_mongoengine_field.register(mongoengine.CachedReferenceField) def convert_field_to_dynamic(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): model = field.document_type def reference_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: queried_fields = list() _type = registry.get_type_for_model(field.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in field.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return ( field.document_type.objects() .no_dereference() .only(*(set(list(_type._meta.required_fields) + queried_fields))) .get(pk=document.id) ) return None def cached_reference_resolver(root, *args, **kwargs): if field: queried_fields = list() _type = registry.get_type_for_model(field.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in field.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return ( field.document_type.objects() .no_dereference() .only(*(set(list(_type._meta.required_fields) + queried_fields))) .get(pk=getattr(root, field.name or field.db_name)) ) return None async def reference_resolver_async(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: queried_fields = list() _type = registry.get_type_for_model(field.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in field.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return await sync_to_async( field.document_type.objects() .no_dereference() .only(*(set(list(_type._meta.required_fields) + queried_fields))) .get )(pk=document.id) return None async def cached_reference_resolver_async(root, *args, **kwargs): if field: queried_fields = list() _type = registry.get_type_for_model(field.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in field.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return await sync_to_async( field.document_type.objects() .no_dereference() .only(*(set(list(_type._meta.required_fields) + queried_fields))) .get )(pk=getattr(root, field.name or field.db_name)) return None def dynamic_type(): _type = registry.get_type_for_model(model, executor=executor) if not _type: return None if isinstance(field, mongoengine.EmbeddedDocumentField): return graphene.Field( _type, description=get_field_description(field, registry), required=field.required, ) field_resolver = None required = False if field.db_field is not None: required = field.required resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, None, ) if resolver_function and callable(resolver_function): field_resolver = resolver_function if isinstance(field, mongoengine.ReferenceField): return graphene.Field( _type, resolver=field_resolver if field_resolver else ( reference_resolver if executor == ExecutorEnum.SYNC else reference_resolver_async ), description=get_field_description(field, registry), required=required, ) else: return graphene.Field( _type, resolver=field_resolver if field_resolver else ( cached_reference_resolver if executor == ExecutorEnum.SYNC else cached_reference_resolver_async ), description=get_field_description(field, registry), required=required, ) return graphene.Dynamic(dynamic_type) @convert_mongoengine_field.register(mongoengine.LazyReferenceField) def convert_lazy_field_to_dynamic(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): model = field.document_type def lazy_resolver(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: if document._cached_doc: return document._cached_doc queried_fields = list() _type = registry.get_type_for_model(document.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return ( document.document_type.objects() .no_dereference() .only(*(set((list(_type._meta.required_fields) + queried_fields)))) .get(pk=document.pk) ) return None async def lazy_resolver_async(root, *args, **kwargs): document = getattr(root, field.name or field.db_name) if document: if document._cached_doc: return document._cached_doc queried_fields = list() _type = registry.get_type_for_model(document.document_type, executor=executor) filter_args = list() if _type._meta.filter_fields: for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) for each in get_query_fields(args[0]).keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) return await sync_to_async( document.document_type.objects() .no_dereference() .only(*(set((list(_type._meta.required_fields) + queried_fields)))) .get )(pk=document.pk) return None def dynamic_type(): _type = registry.get_type_for_model(model, executor=executor) if not _type: return None field_resolver = None required = False if field.db_field is not None: required = field.required resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, None, ) if resolver_function and callable(resolver_function): field_resolver = resolver_function return graphene.Field( _type, resolver=field_resolver if field_resolver else (lazy_resolver if executor == ExecutorEnum.SYNC else lazy_resolver_async), description=get_field_description(field, registry), required=required, ) return graphene.Dynamic(dynamic_type) if sys.version_info >= (3, 6): @convert_mongoengine_field.register(mongoengine.EnumField) def convert_field_to_enum(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): if not registry.check_enum_already_exist(field._enum_cls): registry.register_enum(field._enum_cls) _type = registry.get_type_for_enum(field._enum_cls) return graphene.Field( _type, description=get_field_description(field, registry), required=field.required, ) graphene-mongo-0.4.1/graphene_mongo/fields.py000066400000000000000000000702071453504055300212440ustar00rootroot00000000000000from __future__ import absolute_import import logging from collections import OrderedDict from functools import partial, reduce from itertools import filterfalse import bson import graphene import mongoengine import pymongo from bson import DBRef, ObjectId from graphene import Context from graphene.relay import ConnectionField from graphene.types.argument import to_arguments from graphene.types.dynamic import Dynamic from graphene.types.structures import Structure from graphene.types.utils import get_type from graphene.utils.str_converters import to_snake_case from graphql import GraphQLResolveInfo from graphql_relay import cursor_to_offset, from_global_id from mongoengine import QuerySet from mongoengine.base import get_document from promise import Promise from pymongo.errors import OperationFailure from .advanced_types import ( FileFieldType, MultiPolygonFieldType, PointFieldInputType, PointFieldType, PolygonFieldType, ) from .converter import MongoEngineConversionError, convert_mongoengine_field from .registry import get_global_registry from .utils import ( ExecutorEnum, connection_from_iterables, find_skip_and_limit, get_model_reference_fields, get_query_fields, ) PYMONGO_VERSION = tuple(pymongo.version_tuple[:2]) class MongoengineConnectionField(ConnectionField): def __init__(self, type, *args, **kwargs): get_queryset = kwargs.pop("get_queryset", None) if get_queryset: assert callable( get_queryset ), "Attribute `get_queryset` on {} must be callable.".format(self) self._get_queryset = get_queryset super(MongoengineConnectionField, self).__init__(type, *args, **kwargs) @property def executor(self) -> ExecutorEnum: return ExecutorEnum.SYNC @property def type(self): from .types import MongoengineObjectType _type = super(ConnectionField, self).type assert issubclass( _type, MongoengineObjectType ), "MongoengineConnectionField only accepts MongoengineObjectType types" assert _type._meta.connection, "The type {} doesn't have a connection".format( _type.__name__ ) return _type._meta.connection @property def node_type(self): return self.type._meta.node @property def model(self): return self.node_type._meta.model @property def order_by(self): return self.node_type._meta.order_by @property def required_fields(self): return tuple(set(self.node_type._meta.required_fields + self.node_type._meta.only_fields)) @property def registry(self): return getattr(self.node_type._meta, "registry", get_global_registry()) @property def args(self): _field_args = self.field_args _advance_args = self.advance_args _filter_args = self.filter_args _extended_args = self.extended_args if self._type._meta.non_filter_fields: for _field in self._type._meta.non_filter_fields: if _field in _field_args: _field_args.pop(_field) if _field in _advance_args: _advance_args.pop(_field) if _field in _filter_args: _filter_args.pop(_field) if _field in _extended_args: _filter_args.pop(_field) extra_args = dict( dict(dict(_field_args, **_advance_args), **_filter_args), **_extended_args ) for key in list(self._base_args.keys()): extra_args.pop(key, None) return to_arguments(self._base_args or OrderedDict(), extra_args) @args.setter def args(self, args): self._base_args = args def _field_args(self, items): def is_filterable(k): """ Remove complex columns from input args at this moment. Args: k (str): field name. Returns: bool """ if hasattr(self.fields[k].type, "_sdl"): return False if not hasattr(self.model, k): return False else: # else section is a patch for federated field error field_ = self.fields[k] type_ = field_.type while hasattr(type_, "of_type"): type_ = type_.of_type if hasattr(type_, "_sdl") and "@key" in type_._sdl: return False if isinstance(getattr(self.model, k), property): return False try: converted = convert_mongoengine_field( getattr(self.model, k), self.registry, self.executor ) except MongoEngineConversionError: return False if isinstance(converted, (ConnectionField, Dynamic)): return False if callable(getattr(converted, "type", None)) and isinstance( converted.type(), ( FileFieldType, PointFieldType, MultiPolygonFieldType, graphene.Union, PolygonFieldType, ), ): return False if ( getattr(converted, "type", None) and getattr(converted.type, "_of_type", None) and issubclass((get_type(converted.type.of_type)), graphene.Union) ): return False if isinstance(converted, (graphene.List)) and issubclass( getattr(converted, "_of_type", None), graphene.Union ): return False # below if condition: workaround for DB filterable field redefined as custom graphene type if ( hasattr(field_, "type") and hasattr(converted, "type") and converted.type != field_.type ): return False return True def get_filter_type(_type): """ Returns the scalar type. """ if isinstance(_type, Structure): return get_filter_type(_type.of_type) return _type() return {k: get_filter_type(v.type) for k, v in items if is_filterable(k)} @property def field_args(self): return self._field_args(self.fields.items()) @property def filter_args(self): filter_args = dict() if self._type._meta.filter_fields: for field, filter_collection in self._type._meta.filter_fields.items(): for each in filter_collection: if str(self._type._meta.fields[field].type) in ( "PointFieldType", "PointFieldType!", ): if each == "max_distance": filter_type = graphene.Int else: filter_type = PointFieldInputType else: filter_type = getattr( graphene, str(self._type._meta.fields[field].type).replace("!", ""), ) # handle special cases advanced_filter_types = { "in": graphene.List(filter_type), "nin": graphene.List(filter_type), "all": graphene.List(filter_type), } filter_type = advanced_filter_types.get(each, filter_type) filter_args[field + "__" + each] = graphene.Argument(type_=filter_type) return filter_args @property def advance_args(self): def get_advance_field(r, kv): field = kv[1] mongo_field = getattr(self.model, kv[0], None) if isinstance(mongo_field, mongoengine.PointField): r.update({kv[0]: graphene.Argument(PointFieldInputType)}) return r if isinstance( mongo_field, ( mongoengine.LazyReferenceField, mongoengine.ReferenceField, mongoengine.GenericReferenceField, ), ): r.update({kv[0]: graphene.ID()}) return r if isinstance(mongo_field, mongoengine.GenericReferenceField): r.update({kv[0]: graphene.ID()}) return r if callable(getattr(field, "get_type", None)): _type = field.get_type() if _type: node = ( _type.type._meta if hasattr(_type.type, "_meta") else _type.type._of_type._meta ) if "id" in node.fields and not issubclass( node.model, (mongoengine.EmbeddedDocument,) ): r.update({kv[0]: node.fields["id"]._type.of_type()}) return r return reduce(get_advance_field, self.fields.items(), {}) @property def extended_args(self): args = OrderedDict() for k, each in self.fields.items(): if hasattr(each.type, "_sdl"): args.update({k: graphene.ID()}) return args @property def fields(self): self._type = get_type(self._type) return self._type._meta.fields def get_queryset( self, model, info, required_fields=None, skip=None, limit=None, reversed=False, **args ) -> QuerySet: if required_fields is None: required_fields = list() if args: reference_fields = get_model_reference_fields(self.model) hydrated_references = {} for arg_name, arg in args.copy().items(): if arg_name in reference_fields and not isinstance( arg, mongoengine.base.metaclasses.TopLevelDocumentMetaclass ): try: reference_obj = reference_fields[arg_name].document_type( pk=from_global_id(arg)[1] ) except TypeError: reference_obj = reference_fields[arg_name].document_type(pk=arg) hydrated_references[arg_name] = reference_obj elif arg_name in self.model._fields_ordered and isinstance( getattr(self.model, arg_name), mongoengine.fields.GenericReferenceField, ): try: reference_obj = get_document( self.registry._registry_string_map[from_global_id(arg)[0]] )(pk=from_global_id(arg)[1]) except TypeError: reference_obj = get_document(arg["_cls"])(pk=arg["_ref"].id) hydrated_references[arg_name] = reference_obj elif "__near" in arg_name and isinstance( getattr(self.model, arg_name.split("__")[0]), mongoengine.fields.PointField, ): location = args.pop(arg_name, None) hydrated_references[arg_name] = location["coordinates"] if (arg_name.split("__")[0] + "__max_distance") not in args: hydrated_references[arg_name.split("__")[0] + "__max_distance"] = 10000 elif arg_name == "id": hydrated_references["id"] = from_global_id(args.pop("id", None))[1] args.update(hydrated_references) if self._get_queryset: queryset_or_filters = self._get_queryset(model, info, **args) if isinstance(queryset_or_filters, mongoengine.QuerySet): return queryset_or_filters else: args.update(queryset_or_filters) if limit is not None: if reversed: if self.order_by: order_by = self.order_by + ",-pk" else: order_by = "-pk" return ( model.objects(**args) .no_dereference() .only(*required_fields) .order_by(order_by) .skip(skip if skip else 0) .limit(limit) ) else: return ( model.objects(**args) .no_dereference() .only(*required_fields) .order_by(self.order_by) .skip(skip if skip else 0) .limit(limit) ) elif skip is not None: if reversed: if self.order_by: order_by = self.order_by + ",-pk" else: order_by = "-pk" return ( model.objects(**args) .no_dereference() .only(*required_fields) .order_by(order_by) .skip(skip) ) else: return ( model.objects(**args) .no_dereference() .only(*required_fields) .order_by(self.order_by) .skip(skip) ) return model.objects(**args).no_dereference().only(*required_fields).order_by(self.order_by) def default_resolver(self, _root, info, required_fields=None, resolved=None, **args): if required_fields is None: required_fields = list() args = args or {} for key, value in dict(args).items(): if value is None: del args[key] if _root is not None and not resolved: field_name = to_snake_case(info.field_name) if not hasattr(_root, "_fields_ordered"): if isinstance(getattr(_root, field_name, []), list): args["pk__in"] = [r.id for r in getattr(_root, field_name, [])] elif field_name in _root._fields_ordered and not ( isinstance(_root._fields[field_name].field, mongoengine.EmbeddedDocumentField) or isinstance( _root._fields[field_name].field, mongoengine.GenericEmbeddedDocumentField, ) ): if getattr(_root, field_name, []) is not None: args["pk__in"] = [r.id for r in getattr(_root, field_name, [])] _id = args.pop("id", None) if _id is not None: args["pk"] = from_global_id(_id)[-1] iterables = [] list_length = 0 skip = 0 count = 0 limit = None reverse = False first = args.pop("first", None) after = args.pop("after", None) if after: after = cursor_to_offset(after) last = args.pop("last", None) before = args.pop("before", None) if before: before = cursor_to_offset(before) has_next_page = False if resolved is not None: items = resolved if isinstance(items, QuerySet): try: if last is not None and after is not None: count = items.count(with_limit_and_skip=False) else: count = None except OperationFailure: count = len(items) else: count = len(items) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if isinstance(items, QuerySet): if limit: _base_query: QuerySet = ( items.order_by("-pk").skip(skip) if reverse else items.skip(skip) ) items = _base_query.limit(limit) has_next_page = len(_base_query.skip(limit).only("id").limit(1)) != 0 elif skip: items = items.skip(skip) else: if limit: if reverse: _base_query = items[::-1] items = _base_query[skip : skip + limit] has_next_page = (skip + limit) < len(_base_query) else: _base_query = items items = items[skip : skip + limit] has_next_page = (skip + limit) < len(_base_query) elif skip: items = items[skip:] iterables = list(items) list_length = len(iterables) elif callable(getattr(self.model, "objects", None)): if ( _root is None or args or isinstance(getattr(_root, field_name, []), MongoengineConnectionField) ): args_copy = args.copy() for key in args.copy(): if key not in self.model._fields_ordered: args_copy.pop(key) elif ( isinstance(getattr(self.model, key), mongoengine.fields.ReferenceField) or isinstance( getattr(self.model, key), mongoengine.fields.GenericReferenceField, ) or isinstance( getattr(self.model, key), mongoengine.fields.LazyReferenceField, ) or isinstance( getattr(self.model, key), mongoengine.fields.CachedReferenceField, ) ): if not isinstance(args_copy[key], ObjectId): _from_global_id = from_global_id(args_copy[key])[1] if bson.objectid.ObjectId.is_valid(_from_global_id): args_copy[key] = ObjectId(_from_global_id) else: args_copy[key] = _from_global_id elif isinstance(getattr(self.model, key), mongoengine.fields.EnumField): if getattr(args_copy[key], "value", None): args_copy[key] = args_copy[key].value if PYMONGO_VERSION >= (3, 7): if hasattr(self.model, "_meta") and "db_alias" in self.model._meta: count = ( mongoengine.get_db(self.model._meta["db_alias"])[ self.model._get_collection_name() ] ).count_documents(args_copy) else: count = ( mongoengine.get_db()[self.model._get_collection_name()] ).count_documents(args_copy) else: count = self.model.objects(args_copy).count() if count != 0: skip, limit, reverse = find_skip_and_limit( first=first, after=after, last=last, before=before, count=count ) iterables = self.get_queryset( self.model, info, required_fields, skip, limit, reverse, **args ) list_length = len(iterables) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args ) elif "pk__in" in args and args["pk__in"]: count = len(args["pk__in"]) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if limit: if reverse: args["pk__in"] = args["pk__in"][::-1][skip : skip + limit] else: args["pk__in"] = args["pk__in"][skip : skip + limit] elif skip: args["pk__in"] = args["pk__in"][skip:] iterables = self.get_queryset(self.model, info, required_fields, **args) list_length = len(iterables) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args ) elif _root is not None: field_name = to_snake_case(info.field_name) items = getattr(_root, field_name, []) count = len(items) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if limit: if reverse: _base_query = items[::-1] items = _base_query[skip : skip + limit] has_next_page = (skip + limit) < len(_base_query) else: _base_query = items items = items[skip : skip + limit] has_next_page = (skip + limit) < len(_base_query) elif skip: items = items[skip:] iterables = items list_length = len(iterables) if count: has_next_page = ( True if (0 if limit is None else limit) + (0 if skip is None else skip) < count else False ) has_previous_page = True if skip else False if reverse: iterables = list(iterables) iterables.reverse() skip = limit connection = connection_from_iterables( edges=iterables, start_offset=skip, has_previous_page=has_previous_page, has_next_page=has_next_page, connection_type=self.type, edge_type=self.type.Edge, pageinfo_type=graphene.PageInfo, ) connection.iterable = iterables connection.list_length = list_length return connection def chained_resolver(self, resolver, is_partial, root, info, **args): for key, value in dict(args).items(): if value is None: del args[key] required_fields = list() for field in self.required_fields: if field in self.model._fields_ordered: required_fields.append(field) for field in get_query_fields(info): if to_snake_case(field) in self.model._fields_ordered: required_fields.append(to_snake_case(field)) args_copy = args.copy() if not bool(args) or not is_partial: if isinstance(self.model, mongoengine.Document) or isinstance( self.model, mongoengine.base.metaclasses.TopLevelDocumentMetaclass ): connection_fields = [ field for field in self.fields if isinstance(self.fields[field], MongoengineConnectionField) ] def filter_connection(x): return any( [ connection_fields.__contains__(x), self._type._meta.non_filter_fields.__contains__(x), ] ) filterable_args = tuple( filterfalse(filter_connection, list(self.model._fields_ordered)) ) for arg_name, arg in args.copy().items(): if arg_name not in filterable_args + tuple(self.filter_args.keys()): args_copy.pop(arg_name) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args_copy ) # XXX: Filter nested args resolved = resolver(root, info, **args) if resolved is not None: if isinstance(resolved, list): if resolved == list(): return resolved elif not isinstance(resolved[0], DBRef): return resolved else: return self.default_resolver(root, info, required_fields, **args_copy) elif isinstance(resolved, QuerySet): args.update(resolved._query) args_copy = args.copy() for arg_name, arg in args.copy().items(): if "." in arg_name or arg_name not in self.model._fields_ordered + ( "first", "last", "before", "after", ) + tuple(self.filter_args.keys()): args_copy.pop(arg_name) if arg_name == "_id" and isinstance(arg, dict): operation = list(arg.keys())[0] args_copy["pk" + operation.replace("$", "__")] = arg[operation] if not isinstance(arg, ObjectId) and "." in arg_name: if isinstance(arg, dict): operation = list(arg.keys())[0] args_copy[ arg_name.replace(".", "__") + operation.replace("$", "__") ] = arg[operation] else: args_copy[arg_name.replace(".", "__")] = arg elif "." in arg_name and isinstance(arg, ObjectId): args_copy[arg_name.replace(".", "__")] = arg else: operations = ["$lte", "$gte", "$ne", "$in"] if isinstance(arg, dict) and any(op in arg for op in operations): operation = list(arg.keys())[0] args_copy[arg_name + operation.replace("$", "__")] = arg[operation] del args_copy[arg_name] return self.default_resolver( root, info, required_fields, resolved=resolved, **args_copy ) elif isinstance(resolved, Promise): return resolved.value else: return resolved return self.default_resolver(root, info, required_fields, **args) @classmethod def connection_resolver(cls, resolver, connection_type, root, info, **args): if root: for key, value in root.__dict__.items(): if value: try: setattr(root, key, from_global_id(value)[1]) except Exception as error: logging.debug("Exception Occurred: ", exc_info=error) iterable = resolver(root, info, **args) if isinstance(connection_type, graphene.NonNull): connection_type = connection_type.of_type on_resolve = partial(cls.resolve_connection, connection_type, args) if Promise.is_thenable(iterable): return Promise.resolve(iterable).then(on_resolve) return on_resolve(iterable) def wrap_resolve(self, parent_resolver): super_resolver = self.resolver or parent_resolver resolver = partial( self.chained_resolver, super_resolver, isinstance(super_resolver, partial) ) return partial(self.connection_resolver, resolver, self.type) graphene-mongo-0.4.1/graphene_mongo/fields_async.py000066400000000000000000000414721453504055300224430ustar00rootroot00000000000000from __future__ import absolute_import from functools import partial from itertools import filterfalse from typing import Coroutine import bson import graphene import mongoengine import pymongo from bson import DBRef, ObjectId from graphene import Context from graphene.relay import ConnectionField from graphene.utils.str_converters import to_snake_case from graphql import GraphQLResolveInfo from graphql_relay import cursor_to_offset, from_global_id from mongoengine import QuerySet from promise import Promise from pymongo.errors import OperationFailure from . import MongoengineConnectionField from .registry import get_global_async_registry from .utils import ( ExecutorEnum, connection_from_iterables, find_skip_and_limit, get_query_fields, sync_to_async, has_page_info, ) PYMONGO_VERSION = tuple(pymongo.version_tuple[:2]) class AsyncMongoengineConnectionField(MongoengineConnectionField): def __init__(self, type, *args, **kwargs): super(AsyncMongoengineConnectionField, self).__init__(type, *args, **kwargs) @property def executor(self): return ExecutorEnum.ASYNC @property def type(self): from .types_async import AsyncMongoengineObjectType _type = super(ConnectionField, self).type assert issubclass( _type, AsyncMongoengineObjectType ), "AsyncMongoengineConnectionField only accepts AsyncMongoengineObjectType types" assert _type._meta.connection, "The type {} doesn't have a connection".format( _type.__name__ ) return _type._meta.connection @property def fields(self): return super(AsyncMongoengineConnectionField, self).fields @property def registry(self): return getattr(self.node_type._meta, "registry", get_global_async_registry()) async def default_resolver(self, _root, info, required_fields=None, resolved=None, **args): if required_fields is None: required_fields = list() args = args or {} for key, value in dict(args).items(): if value is None: del args[key] if _root is not None and not resolved: field_name = to_snake_case(info.field_name) if not hasattr(_root, "_fields_ordered"): if isinstance(getattr(_root, field_name, []), list): args["pk__in"] = [r.id for r in getattr(_root, field_name, [])] elif field_name in _root._fields_ordered and not ( isinstance(_root._fields[field_name].field, mongoengine.EmbeddedDocumentField) or isinstance( _root._fields[field_name].field, mongoengine.GenericEmbeddedDocumentField, ) ): if getattr(_root, field_name, []) is not None: args["pk__in"] = [r.id for r in getattr(_root, field_name, [])] _id = args.pop("id", None) if _id is not None: args["pk"] = from_global_id(_id)[-1] iterables = [] list_length = 0 skip = 0 count = 0 limit = None reverse = False first = args.pop("first", None) after = args.pop("after", None) if after: after = cursor_to_offset(after) last = args.pop("last", None) before = args.pop("before", None) if before: before = cursor_to_offset(before) requires_page_info = has_page_info(info) has_next_page = False if resolved is not None: items = resolved if isinstance(items, QuerySet): try: if last is not None and after is not None: count = await sync_to_async(items.count)(with_limit_and_skip=False) else: count = None except OperationFailure: count = await sync_to_async(len)(items) else: count = len(items) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if isinstance(items, QuerySet): if limit: _base_query: QuerySet = ( await sync_to_async(items.order_by("-pk").skip)(skip) if reverse else await sync_to_async(items.skip)(skip) ) items = await sync_to_async(_base_query.limit)(limit) has_next_page = ( ( await sync_to_async(len)( await sync_to_async(_base_query.skip(limit).only("id").limit)(1) ) != 0 ) if requires_page_info else False ) elif skip: items = await sync_to_async(items.skip)(skip) else: if limit: if reverse: _base_query = items[::-1] items = _base_query[skip : skip + limit] else: _base_query = items items = items[skip : skip + limit] has_next_page = ( (skip + limit) < len(_base_query) if requires_page_info else False ) elif skip: items = items[skip:] iterables = await sync_to_async(list)(items) list_length = len(iterables) elif callable(getattr(self.model, "objects", None)): if ( _root is None or args or isinstance(getattr(_root, field_name, []), AsyncMongoengineConnectionField) ): args_copy = args.copy() for key in args.copy(): if key not in self.model._fields_ordered: args_copy.pop(key) elif ( isinstance(getattr(self.model, key), mongoengine.fields.ReferenceField) or isinstance( getattr(self.model, key), mongoengine.fields.GenericReferenceField, ) or isinstance( getattr(self.model, key), mongoengine.fields.LazyReferenceField, ) or isinstance( getattr(self.model, key), mongoengine.fields.CachedReferenceField, ) ): if not isinstance(args_copy[key], ObjectId): _from_global_id = from_global_id(args_copy[key])[1] if bson.objectid.ObjectId.is_valid(_from_global_id): args_copy[key] = ObjectId(_from_global_id) else: args_copy[key] = _from_global_id elif isinstance(getattr(self.model, key), mongoengine.fields.EnumField): if getattr(args_copy[key], "value", None): args_copy[key] = args_copy[key].value if PYMONGO_VERSION >= (3, 7): count = await sync_to_async( (mongoengine.get_db()[self.model._get_collection_name()]).count_documents )(args_copy) else: count = await sync_to_async(self.model.objects(args_copy).count)() if count != 0: skip, limit, reverse = find_skip_and_limit( first=first, after=after, last=last, before=before, count=count ) iterables = self.get_queryset( self.model, info, required_fields, skip, limit, reverse, **args ) iterables = await sync_to_async(list)(iterables) list_length = len(iterables) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args ) elif "pk__in" in args and args["pk__in"]: count = len(args["pk__in"]) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if limit: if reverse: args["pk__in"] = args["pk__in"][::-1][skip : skip + limit] else: args["pk__in"] = args["pk__in"][skip : skip + limit] elif skip: args["pk__in"] = args["pk__in"][skip:] iterables = self.get_queryset(self.model, info, required_fields, **args) iterables = await sync_to_async(list)(iterables) list_length = len(iterables) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args ) elif _root is not None: field_name = to_snake_case(info.field_name) items = getattr(_root, field_name, []) count = len(items) skip, limit, reverse = find_skip_and_limit( first=first, last=last, after=after, before=before, count=count ) if limit: if reverse: _base_query = items[::-1] items = _base_query[skip : skip + limit] else: _base_query = items items = items[skip : skip + limit] has_next_page = (skip + limit) < len(_base_query) if requires_page_info else False elif skip: items = items[skip:] iterables = items iterables = await sync_to_async(list)(iterables) list_length = len(iterables) if requires_page_info and count: has_next_page = ( True if (0 if limit is None else limit) + (0 if skip is None else skip) < count else False ) has_previous_page = True if requires_page_info and skip else False if reverse: iterables = await sync_to_async(list)(iterables) iterables.reverse() skip = limit connection = connection_from_iterables( edges=iterables, start_offset=skip, has_previous_page=has_previous_page, has_next_page=has_next_page, connection_type=self.type, edge_type=self.type.Edge, pageinfo_type=graphene.PageInfo, ) connection.iterable = iterables connection.list_length = list_length return connection async def chained_resolver(self, resolver, is_partial, root, info, **args): for key, value in dict(args).items(): if value is None: del args[key] required_fields = list() for field in self.required_fields: if field in self.model._fields_ordered: required_fields.append(field) for field in get_query_fields(info): if to_snake_case(field) in self.model._fields_ordered: required_fields.append(to_snake_case(field)) args_copy = args.copy() if not bool(args) or not is_partial: if isinstance(self.model, mongoengine.Document) or isinstance( self.model, mongoengine.base.metaclasses.TopLevelDocumentMetaclass ): connection_fields = [ field for field in self.fields if isinstance(self.fields[field], AsyncMongoengineConnectionField) ] def filter_connection(x): return any( [ connection_fields.__contains__(x), self._type._meta.non_filter_fields.__contains__(x), ] ) filterable_args = tuple( filterfalse(filter_connection, list(self.model._fields_ordered)) ) for arg_name, arg in args.copy().items(): if arg_name not in filterable_args + tuple(self.filter_args.keys()): args_copy.pop(arg_name) if isinstance(info, GraphQLResolveInfo): if not info.context: info = info._replace(context=Context()) info.context.queryset = self.get_queryset( self.model, info, required_fields, **args_copy ) # XXX: Filter nested args resolved = resolver(root, info, **args) if isinstance(resolved, Coroutine): resolved = await resolved if resolved is not None: # if isinstance(resolved, Coroutine): # resolved = await resolved if isinstance(resolved, list): if resolved == list(): return resolved elif not isinstance(resolved[0], DBRef): return resolved else: return await self.default_resolver(root, info, required_fields, **args_copy) elif isinstance(resolved, QuerySet): args.update(resolved._query) args_copy = args.copy() for arg_name, arg in args.copy().items(): if "." in arg_name or arg_name not in self.model._fields_ordered + ( "first", "last", "before", "after", ) + tuple(self.filter_args.keys()): args_copy.pop(arg_name) if arg_name == "_id" and isinstance(arg, dict): operation = list(arg.keys())[0] args_copy["pk" + operation.replace("$", "__")] = arg[operation] if not isinstance(arg, ObjectId) and "." in arg_name: if isinstance(arg, dict): operation = list(arg.keys())[0] args_copy[ arg_name.replace(".", "__") + operation.replace("$", "__") ] = arg[operation] else: args_copy[arg_name.replace(".", "__")] = arg elif "." in arg_name and isinstance(arg, ObjectId): args_copy[arg_name.replace(".", "__")] = arg else: operations = ["$lte", "$gte", "$ne", "$in"] if isinstance(arg, dict) and any(op in arg for op in operations): operation = list(arg.keys())[0] args_copy[arg_name + operation.replace("$", "__")] = arg[operation] del args_copy[arg_name] return await self.default_resolver( root, info, required_fields, resolved=resolved, **args_copy ) elif isinstance(resolved, Promise): return resolved.value else: return await resolved return await self.default_resolver(root, info, required_fields, **args) @classmethod async def connection_resolver(cls, resolver, connection_type, root, info, **args): if root: for key, value in root.__dict__.items(): if value: try: setattr(root, key, from_global_id(value)[1]) except Exception: pass iterable = await resolver(root=root, info=info, **args) if isinstance(connection_type, graphene.NonNull): connection_type = connection_type.of_type on_resolve = partial(cls.resolve_connection, connection_type, args) if Promise.is_thenable(iterable): iterable = Promise.resolve(iterable).then(on_resolve).value return on_resolve(iterable) graphene-mongo-0.4.1/graphene_mongo/registry.py000066400000000000000000000057731453504055300216540ustar00rootroot00000000000000from graphene import Enum from graphene_mongo.utils import ExecutorEnum class Registry(object): def __init__(self): self._registry = {} self._registry_async = {} self._registry_string_map = {} self._registry_async_string_map = {} self._registry_enum = {} def register(self, cls): from .types import GrapheneMongoengineObjectTypes from .types_async import AsyncGrapheneMongoengineObjectTypes assert ( issubclass(cls, GrapheneMongoengineObjectTypes) or issubclass(cls, AsyncGrapheneMongoengineObjectTypes) ), 'Only Mongoengine/Async Mongoengine object types can be registered, received "{}"'.format( cls.__name__ ) assert cls._meta.registry == self, "Registry for a Model have to match." if issubclass(cls, GrapheneMongoengineObjectTypes): self._registry[cls._meta.model] = cls self._registry_string_map[cls.__name__] = cls._meta.model.__name__ else: self._registry_async[cls._meta.model] = cls self._registry_async_string_map[cls.__name__] = cls._meta.model.__name__ # Rescan all fields for model, cls in self._registry.items(): cls.rescan_fields() def register_enum(self, cls): from enum import EnumMeta assert isinstance( cls, EnumMeta ), f'Only EnumMeta can be registered, received "{cls.__name__}"' if not cls.__name__.endswith("Enum"): name = cls.__name__ + "Enum" else: name = cls.__name__ cls.__name__ = name self._registry_enum[cls] = Enum.from_enum(cls) def get_type_for_model(self, model, executor: ExecutorEnum = ExecutorEnum.SYNC): if executor == ExecutorEnum.SYNC: return self._registry.get(model) else: return self._registry_async.get(model) def check_enum_already_exist(self, cls): return cls in self._registry_enum def get_type_for_enum(self, cls): return self._registry_enum.get(cls) registry = None async_registry = None inputs_registry = None async_inputs_registry = None def get_inputs_registry(): global inputs_registry if not inputs_registry: inputs_registry = Registry() return inputs_registry def get_inputs_async_registry(): global async_inputs_registry if not async_inputs_registry: async_inputs_registry = Registry() return async_inputs_registry def get_global_registry(): global registry if not registry: registry = Registry() return registry def get_global_async_registry(): global async_registry if not async_registry: async_registry = Registry() return async_registry def reset_global_registry(): global registry global inputs_registry registry = None inputs_registry = None def reset_global_async_registry(): global async_registry global async_inputs_registry async_registry = None async_inputs_registry = None graphene-mongo-0.4.1/graphene_mongo/tests/000077500000000000000000000000001453504055300205605ustar00rootroot00000000000000graphene-mongo-0.4.1/graphene_mongo/tests/__init__.py000066400000000000000000000000001453504055300226570ustar00rootroot00000000000000graphene-mongo-0.4.1/graphene_mongo/tests/conftest.py000066400000000000000000000114271453504055300227640ustar00rootroot00000000000000import os from datetime import datetime import pytest from .models import ( AnotherChild, Article, CellTower, Child, ChildRegisteredAfter, ChildRegisteredBefore, Editor, EmbeddedArticle, ParentWithRelationship, Player, ProfessorMetadata, ProfessorVector, Publisher, Reporter, ) current_dirname = os.path.dirname(os.path.abspath(__file__)) @pytest.fixture() def fixtures_dirname(): return os.path.join(current_dirname, "fixtures") @pytest.fixture(scope="module") def fixtures(): Publisher.drop_collection() publisher1 = Publisher(name="Newsco") publisher1.save() Editor.drop_collection() editor1 = Editor( id="1", first_name="Penny", last_name="Hardaway", metadata={"age": "20", "nickname": "$1"}, company=publisher1, ) image_filename = os.path.join(current_dirname, "fixtures", "image.jpg") with open(image_filename, "rb") as f: editor1.avatar.put(f, content_type="image/jpeg") editor1.save() editor2 = Editor(id="2", first_name="Grant", last_name="Hill") editor2.save() editor3 = Editor(id="3", first_name="Dennis", last_name="Rodman") editor3.save() Article.drop_collection() pub_date = datetime.strptime("2020-01-01", "%Y-%m-%d") article1 = Article(headline="Hello", editor=editor1, pub_date=pub_date) article1.save() article2 = Article(headline="World", editor=editor2, pub_date=pub_date) article2.save() article3 = Article(headline="Bye", editor=editor2, pub_date=pub_date) article3.save() Reporter.drop_collection() reporter1 = Reporter( id="1", first_name="Allen", last_name="Iverson", email="ai@gmail.com", awards=["2010-mvp"], generic_references=[article1], ) reporter1.articles = [article1, article2] embedded_article1 = EmbeddedArticle(headline="Real", editor=editor1) embedded_article2 = EmbeddedArticle(headline="World", editor=editor2) reporter1.embedded_articles = [embedded_article1, embedded_article2] reporter1.embedded_list_articles = [embedded_article2, embedded_article1] reporter1.generic_reference = article1 reporter1.save() Player.drop_collection() player1 = Player( first_name="Michael", last_name="Jordan", articles=[article1, article2], ) player1.save() player2 = Player( first_name="Magic", last_name="Johnson", opponent=player1, articles=[article3], ) player2.save() player3 = Player(first_name="Larry", last_name="Bird", players=[player1, player2]) player3.save() player1.players = [player2] player1.save() player2.players = [player1] player2.save() player4 = Player(first_name="Chris", last_name="Webber") player4.save() Child.drop_collection() child1 = Child(bar="BAR", baz="BAZ") child1.save() child2 = Child(bar="bar", baz="baz", loc=[10, 20]) child2.save() another_child1 = AnotherChild(bar="BAR", qux="QUX") another_child1.save() another_child2 = AnotherChild(bar="bar", qux="qux", loc=[20, 10]) another_child2.save() CellTower.drop_collection() ct = CellTower( code="bar", base=[ [ [-43.36556, -22.99669], [-43.36539, -23.01928], [-43.26583, -23.01802], [-43.36717, -22.98855], [-43.36636, -22.99351], [-43.36556, -22.99669], ] ], coverage_area=[ [ [ [-43.36556, -22.99669], [-43.36539, -23.01928], [-43.26583, -23.01802], [-43.36717, -22.98855], [-43.36636, -22.99351], [-43.36556, -22.99669], ] ] ], ) ct.save() ProfessorVector.drop_collection() professor_metadata = ProfessorMetadata( id="5e06aa20-6805-4eef-a144-5615dedbe32b", first_name="Steven", last_name="Curry", departments=["NBA", "MLB"], ) professor_vector = ProfessorVector(vec=[1.0, 2.3], metadata=professor_metadata) professor_vector.save() ParentWithRelationship.drop_collection() ChildRegisteredAfter.drop_collection() ChildRegisteredBefore.drop_collection() # This is one messed up family # She'd better have presence this time child3 = ChildRegisteredBefore(name="Akari") child4 = ChildRegisteredAfter(name="Kyouko") child3.save() child4.save() parent = ParentWithRelationship( name="Yui", before_child=[child3], after_child=[child4], ) parent.save() child3.parent = child4.parent = parent child3.save() child4.save() return True graphene-mongo-0.4.1/graphene_mongo/tests/fixtures/000077500000000000000000000000001453504055300224315ustar00rootroot00000000000000graphene-mongo-0.4.1/graphene_mongo/tests/fixtures/image.jpg000066400000000000000000001335201453504055300242210ustar00rootroot00000000000000‰PNG  IHDR• “Â8iCCPICC ProfileX…•Y 8UÝ×ßçÜ×<Ïó˜y&ó<Ïc*×<Ó5«$$SI†RxEŠFSB$%™2”"E¡TL!ßAõ¾ÿ÷ÿ<ß÷|ûyö9¿»öÚk¯µöÚÃ:RXXL @pHÙÖH—×ÙÅ•7 @Žä¦cmmŽ`ðûýŸeiáFÊSÉ-YÿÝþ¿:/ïpO k{x…{#øh%Ï0r˜y„.†`,¢%`$# "Xp ûî`•-챃ͷyìmõ잊D"û@½¥o”§/"‡:i£ñòAX/ XÓÓäû$³+88ÁTõø‡ßÿéñG&‰äûïØ²]ðúþáaA¤Øÿ§;þïù{ ¤Rù‘m·lÞò[`¨ÙFt‡†xXZ!˜ÁCþ^Ûü[ø_¤±Ã/þÏp=Äg€˜Ê‹¤o†`Nó‡Yšÿ¢kúøš ñ=lïab¿Óö"‡Úþ’Çx‡ØýÆ$òöX[<é‘:¿dž÷ó6ù-³)ÎÏÞiGO¸?ÊßÑÁÔ~hgö‹ç}œŸžåor¤í–ÎÈœ£€ÙÐv‡%þÛ.”šŸ¿‰å/lágo¼ÓµÏ“´­+‚¼ÃÍëéå­o°c*Ñ;Äá—þ¨Óaº¶¿øË¬ñ£š½ƒŒ¶èüî ²ûÝw! ¶{Ñ ,ÂÚ~G74cÉÔzG´80z@ð‚H¤z€Pü{çëç‘_;-†€ÈÀxÉ_”ß=œ¶[B§ˆä ÂÿôÓÝnõQ}ãuç) |¶[£¶{‚wf ù¹Ý+äÏhŽ`¡øÿ×螈®AHÝjû//ÍoÖ«5ÆbÅÐìhM´:Úyj#U­‚Vý­×ßü˜w˜ÌÌ0fó|¿"ù_šó 0‰èhøË:Z‡F¤*¢uш|D6šÍ$Ñ ÈH:h-dlE„úO]#ÿXü·/É"È` A› úo ¨Å©ÿHÙòÔ?}±£—Çoéýiù·zÿðŸò6û7'*uÕ…º‡z„jFÕ^T+ªÕƒº»…ÿÄÆôvlüÍv[Ÿ@DŽÿGú5æ–×ÂeªeædÖµ­Å¢Kö÷õ‹àÕAvko^“O©]¼r2²ªlíý;[Ë7Ûí=bîû›FBö89(tÿ¦…"k»& ù³Ó„‘uȆH»aëIŽÚ¡¡·@h•¸‘½K±H(u  €)°öÀìCüì‡Ä)DƒCà(HàÈç@ (•à*¸êA3¸€Ç  ƒH¬¼ÀXká "ıA<$ÉA*&d™C¶ äùB!P$tJ‚2 ÓÐ9è"T]‡¡{Ð#hz½†æ ¯ÐSÁŒ0, KÃ*°lÛÃ{a_ø'Ã'Ḿ×Á÷àÇð0< €QE‰bFñ¡$Q*(=”Êåƒ"£âQé¨|T)ªÕ„ÌôSÔ$jµŠÆ¢мhI$^ÑhOôt<:}]‰®CßG?E¿F/ bˆNŒF c‚qÆøb¢1)˜|Læ6¦Y;o1KX,–+‚UFÖž 6{›‰-ÆÖbÛ°Ø)ì"‡cÃIà4pV8.—‚;‹»‚kÅ âÞâVð”x¼ÞïŠÁ'âóñ—ñ-øAü ~@K"¨¬^„XB¡œÐDè#¼%¬QÐQˆPhPØSP¥( ¨¡è¤xIñ’’’ŸR•Ò†ÒŸ2²€òåCÊ×”«TôTâTzTnT‘T'©.QµQ=§úF$…‰ÚDWbñ$±ŠØAœ ®P3PKQ›P{Q¡.¤®£¤þDC ¢Ñ¡ÙGG“Os“¦fž–@+L«GK¢§-¤m¤¥]¤c “¥³¢ ¦Ë¤»L÷ˆn–G/Lo@ïEŸL_FßA?Å€b`ÐcðdHb(gèdxˈea4a `Ì`¼ÊØË¸ÀDϤÀäÈÃTÈt—i’Å,ÌlÂÄœÅ|ƒy„ù ‹‹7KK Ë Ë2+«6«7k:k-ë0ë6^6¶@¶l¶z¶qv4»8» {4ûyöNöyFuOŽtŽcœ0§8§-çAÎ2ÎÎE.n.#®0®³\\óÜÌÜÚÜܹÜ-Üs< <š<þ<¹<­<ïy™xuxƒx xïó.ðqòóEò]äëå[ãáwàOä¯å PðÈhXä´<$X-8&DRò:#Ô%´,,"ì$|\¸^xV„UÄD$N¤Zä¥(QTKô€h©èVLE,P¬X¬_W÷/ï“€%”$ü%Š%vav©î ÙUºkT’JRG2J²Zòµ³”¹T¢T½Ô'iAiWélé.éŸ2Š2A2å2/déeMee›d¿Ê‰ËyÊÊ Éå åÈ7ÈQPðV8¯ðL‘AÑBñ¸b»â†’²Y©FiNYPÙ]¹HyT…QÅZ%Så¡*FUWõˆj³êªš’Z„Ú µÏê’êê—Õgw‹ìöÞ]¾{Jƒ_ƒ¤qQcR“WÓ]󂿤ŸI«Të¶€¶—v…öŒŽ˜N€ÎOº2ºdÝÛºËzjz‡õÚôQúFúéú½ôç & ù } « ŒµcŒÍŒ³GM¸LËþ…ƒ¨C¤C»#£›c•㲓¾Ói§IgiçÃÎ]Ø]ü]\q®Ž®®‹{ öäíyë¦è–â6²WdoÌÞGûØ÷í»»Ÿf?iÿMwŒ»“ûe÷u’©”´èaâQä±à©çyÆóƒ—¶W®×œ·†÷iï ŸÓ>³¾¾9¾s~Z~ù~óþzþçü¿”,Z^ Ü r ª Æ»7†Ð‡†Üå “K ›< v ïÀÙŒ\…ï oˆ`D.Ù=‘¢‘Ç"_GiFF­D;Fߌ¡‹ ‰é‰M‹‰3Œûë ú çÁöC|‡Žz}XçðÅx(Þ#¾ýˆÀ‘ä#oŒ*R <ú$Q&ñtâ÷$§¤¦d®ä„ä©cFǪS¨SÈ)£ÇÕ—¤¢SýS{ÓäÓΦýL÷JïÎÉÈÏXÏôÌì>!{¢àÄæIŸ“½YJYçOaO…œÉÖÊ®_SÂU’Qòã‚ÿ…g.Ö• —æ—aË¢ÊÞ•;–wý¥òWU{EFÅÆ¥K“•¶•÷«”«ª.s^Ϊ†«#«ç®¸]鿪µ¡F²æb-smÆ5p-òÚûëî×Gn˜Ýh¿©r³æ–Э¢Û ·Ó렺غ…z¿úÉ—†FÓÆö&õ¦Ûw¤î\jæk.¼Ët7«…¢%¹e³5®u±-¬mþžï½©öýí/:œ;†îÛÜïí4ë|øÀðAG—NWëC‡ÍÔ5v«t×?Vz\×£Øsû‰â“Û½J½u}Ê} ýªýM»Zµï=Õú`Èdèñ°åðÀˆÃȳQ·ÑÉg^ÏfŸ=ÿ25¶ö"á%æeú8íxþçDé+±Wµ“J“w_ë¿îyc÷æÅ”çÔ‡éðéõ·Éïˆïògxfªfåf›ç çúßïyÿöC؇µù”t‹>‰~ºõYûsÏ‚óÂÛ/ä/›_3¿±}»ô]á{û¢õâÄRðÒÚrú ÛJåªÊj×§3kÑë¸õ‚ ±¦Ÿf?_non†‘È¤í« ©°_/@t€¡¹SìÙÉÍ~rù€‘·#$}€ï£’Ðvm¬ŽÏJà¡Ð ´¤ $ž¢n¤™§“¤÷f(cœbg‰eme§ápâ,çúƳ›7™ï‰ ­Ð áÇ¢@L^ÜGâÌ®nÉeiQÙ¹jùaEXIVy¯JºjÚëÝD Mw­4íë:/õðúJž†§ŒŒ'L!3As#‹Ë,«[ÖÏlVì˜í嬃N8׸ Bæ LJމl‰ªŒÎ‰‰ Šs9hrHã°r¼ÒÕ£f‰NIÞÉÇŽ¥ä/O½™Ö–Þ“1’ùêÄÌÉY_O-f/^ÌYÌý‘>ÃT°ë¬Ñ9ÏÂ#EÅ5ç[K_º8V:Y6Wþ½u‰©R¼J÷²[uô•Ü«7jj¿\§»!ÓîVøíSuUõM ÷;šÚîÜi¾}·¶¥ªµ¬­ø^^{zÇ¡ûv”ºX»VN>êë~ð¸£çÞ“æÞÚ¾‚þð½AâàÓ§…C>Ê#˜‘ÑÑÊgQϵǰc]H|)¾œÏžPŸ˜zubR}òÃë’7¶S¨©Úi‡éÕ·¹ïv½k±™ž=6'=7ý¾òCȼüüâÇÚOžŸé>ß^°^x÷åÐW–¯¾e}Y$-ù q4ý£sCjss{þ kpJ5‹¾ŽIÀ:ã4ð’ J~*¢µ 'm<] } Ã-³ ‰5•íû'%—<÷žÞ‹|­ü/…(…yDEMÄÜÅc%rv]—쑚•AËòÉí–wUˆPÌP*WnTy¢úFíûn¬‡¦¬–…vN–î5½~ý†x#.c9S3Oó‹Ëx«$ëc6)¶©véö™éŽÉN±Î~.ö®ú{´Ü ÷ºî‹ÞŸç~ÔîÑíÙéuÛ»Èç ¯“ŸŒ?•ÿ|@`SPUpaHVhbù€Y;œ'|-b8òjTJ´GŒA¬LœàA®Cl‡™âi`,%¼9Úx=)/9úØÞÓãú©æi¤ô£e>81qòSÖâ©åìÅÓßrr?æÍç:³r–öœjaHQEqïù©’¹ o/¾*}^6Pþ𯖊æKÝ•/óUï½Rtõy-ã5Ëë©Èîµz[ªÎ«¾°a° sG¡yÿÝc-­Ím-÷.·Ÿê8|?º3áAVWñòGç»O>Žì±{"Ù‹îë»ÑŸ10hóÔ`È`ØfÄc4òYòóãc‡_ø¼ÔgŸŸh|u|Òùµäü›wSÓÅo¼Óž¡šš-›;òÞÿƒ×¼ßÇàOaŸÃ¾¿F}‹ý½è¿d´L³|sÅ`åñªëêÇýëTcÛó/îCfÐ3Ø…Ee¡%Ð}˜8¬4v÷Þ MX¥è¦,¡Š&ÚRËÑPÓ,Ñ>§k£¯bÈa<ÌäËlË¢Á*ÆÆÄ¶Î>Ë1ÈÙÂUÃ]ÆSțϗ˟%"%D6áYí+—0ÞÅ' KÎIJ?”i’½,W Ÿ à®¨ª„UêSÎSqVeS}®V¬îµ[N«1¡Y§•¥í§£¯+¬G«ô¿ÌŽÝ1Î7ñ624+0·²ÀYtX&Y™X³Z¿·i±Í±ó³Ww :L8^u:älêÂäòʵrO(rþ¯î½»/a¿ž;Þ}€Täè¹Û‹ÊkÌû’Ï_ßu¿Vÿ„í@Øt4X/Òz,L'lå@5Ù9³«"¬"¾GD펚ˆNˆáйëÇ7v°úPÒaçxÑø¥# 9G}õ“Ä“YQ¦€”ïǧRŸ¤Õ¦gf2NàNŒ¼–•~*0Ûè4ýé9{ræsãòtòuϤžÅŸK/œ.f;/W¢zAõ¢b©t™h9ß_lt—(* U4H$i\q¿z¼æjíÓkë7DoºÞ:}{ ž±Á¥±¨i´sW¬Å¨Õ£íȽóí-¯îo>àëÒ{èû(³ûú㑞^±¾=ýg&žÊ þ4j÷¬qŒïEÞ¸ô+ê×ÑÓ³±-¿.­ÚlÍÿÎ7º­‚U É3O u€ìz$ϼ ÖDìU|¼ÀF5 <öçü€ÄäœÌ€ˆ$Ó4®HÞÒŒò hƒàX‡è!1HÉáH>Ø MÁÌëÂ^ðq$Ë„ P¨8T%jG«¡ƒÑeèçzŒ’‘u`!¬66ÛŽÃàLq§pÏð|ø |#Gp"T~PXP\¤X¦´¤¬¤BSyPu…ˆiÄOÔöÔÍH¦“M hÐNÓ¹ÐõÑÒßePa¨cTcì`²ešbŽdÁ²ä³ ³6°Y²Í²§rÈrLq–pypKp¯ð<àÍãóâWÀ ¼¼)”%$b&*!F[–¸³ë¼d¼”›´ª £Ì‚ì¹Ëòi ~ЦJRÊLÊ›*U'ÔÕ»wwjÜ×ìÒêÕÓ™Õ]ÒXdŸÃãM¦TfŒæ| ––V!Ö¹6Ͷoí‰ Ž.N‡/¸Üwq£Ü+³Ïqÿ!÷rR¯ÇŠ— ·Ï1ßf¿zgƒVCØ8ôgÂHbn²ù±¥ã¹i»Ò;3½O2e½Ê~’3ž·YÀ{NµÈüüþ ±¥ÊÇ.IV]¸"S3yýâ­}õ”5Í{[%Úy: –öPõ‰, eŠ>xyþÕ™7ƒïÜçV?Ò¾ò|—YR]Þ\MÿѰ6´~g£ìgئòöþms @È-`Ü@0ˆÙ 4‚>ðl@Ì4d ù@IP)tz£aØ&Ãçàø3Še†:„ªEM£ÙѶè t'Âh`bî`Ö±ZØ$ì#-Î÷î+^ŸƒGP'äæ) ‘9_§t¦¼…dÂdª!¢*ñ5%u õ M/­!m&]+½}7ƒÃ8’™þ`Êbg~Ìr€•™µŽÍ†í{,‘£œS›sš+›Û”‡šgœ÷&ßI~]AVÁBw…O‰øˆêŠ ‰ÓKàwa$ñRÔÒô2t²xÙU¹YùQ…nÅ{J÷”»U^¨~U§Þ-£a£é¯¡MÖñÓuÖ3ÒW5P0T122ÞoozѬË|Á’ÃÊÀ:9ÓríÎØç9ä:^pjuþ⪸'ÁíÉ>îýî}ž>^yÞ·}z}§ýÖ˜åƒìƒ£BÎ…¶…½'³„FDE^Š‹¡µˆË:øì°püá#SG}“h“»S"R±iÇ3Й©'9²:²sœóôΨŸU/T/V-»ˆ.}PUÁqén•G5Ó•ñšÎk}7oËÖj|ÜLÓ¢ßFn¯¸?×¥ûèFloQÿøà÷¡/#3ϦÆf_~½¦˜b|+8c<—?¯ü9ý[ÅrÐjïZòzÇÆ÷Ÿ«Ûó#«ŸpI  l€8 òÁ5ÐÞCH²€ÈPÔ½‡™a}8®€ÇPt(T2ª µVGÇ¡›ÐëL:f+†=ŠÇiâJñx|(~ˆ J(¦€)(†)õ)ïP©RÝ#ZßQ'ÒðѴѺÑ.Ñ¢—¤ÂÂHd¬dÒezÉËÂÍÒËz’̓]—Cœ“‘skœ»ç4o0Ÿ9¿Œ« VpUè‹ðg‘o¢âÔ‚»´%Ý¥¤‹edŸÊ}S`W4QJTîP¥RsS¿¦Cîª-:üº9úÌ5F®&t¦æç,C­låìÆ\{œ]žîñq[Ù—ä‘Â<†½”½‹| ~G(Ë‚-BAX=94‚;²#:2Öëà§øò„Ø£#‰ëÉð1| íqùÔð´¡ ‡Ì¹“©§¤²Ÿç¤æ©ç)¨:·¯ˆ¢øR‰ò…»¥ZeméWtWZW UÛ_é¯1¬m¼.zãÌ-üíÃuë iMÂwúï&¶*µÍµÝ·z€îºó(ü±DÏtïù~çAƧƒÃY£¦Ï6Ç®¼´Ÿ}9¹ñ&qõ6qžMzþpdþÓ'Ãϱ Å_N|ü¦ÿmùûåEËÅK~KKËQËs+n+}«z«Õ?ˆ?Â~ ®)®¬}Y7Y/]_۰߸úõÓùç•MhÓaóòÖü‡ûÈËm•.˜‰ÍÍoÂàN°‘½¹¹Vº¹¹Q†$/h Úùßgû¬¡ èÕêNø÷ÿ/ÿ]HÐ@z—ãÀiTXtXML:com.adobe.xmp 917 265 ˜E„Ü@IDATxì \TUúÇ0ƒò¦ ¨ (o&†‰EieQZº †ii‹eå®î_{o{Û^l·ÚÞÌJ7-6ݨpÕtÅ4¥Ä—¢²p%uWÅD¡€Agp€ÿsî;/—™añµçø‘¹÷¼>ç{ν÷<÷9ç\¯6r`ǘ`L€ 0&À˜`LÀÞ¤á$L€ 0&À˜`L€ 0&À$¬TrG`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`¬Tr`L€ 0&À˜`L€ 0 °Ré1:NȘ`L€ 0&À˜`Ú3`ÿ²lŒs‚Šê†OöNÃ]W•jBAæRÜþZ3’^HÁê©uUƜֶ”#ÌÍ»·Ävwû¼UX½à+d}©Gq±5ŸI Æâµñ­gñȸ#†ŒýÑ"Áÿ½7§„[Îù€ 0&À˜`L€ üœ¥²âpÄÝB<[±O.T*›ðÕþPÊÛM‡~þ5´×Y¯£µ-eQ ö5ž¥òæ^–F¡Gr0âFX«ýý5À9¢Têl¡¾anëÎj•Rù ÖÏ݈¼£€-0öˆ×¿ŠŽÙ1&À˜`L€ 0 ‡ÀQ*O.Ž˜gð¶¾R8gç -­Î= 9¸þ+¬¹­'z9HßrÊsPŒg^'MöéŒû±è š|Åë/xûu£Ÿ},>cL€ 0&À˜`ç5ó\©ô†Ž•Éóº:¾l¿/‚_p¸#UÓIä³í­7½µ4éÛìHÿ&ý’`L€ 0&À˜¸ œçJåÕ\3ƒÉËŽ…·^I_£=/г ;§O´;z×ÛrÏéZ³pL€ 0&À˜`¿2ç–RilÄþýpð`#tAÝÑÐÄé‡þýûtjš±¡û÷Öà`C#š¡A¯ PÊg zu¼šÍØp„ʧ´$Cc3ˆþ±‘ˆíߣKº†ÈÿÞÃ8h‚tÇaDÄRþý{ºÈßDñĺô_ú߆çÿ5SGF:Æ[CaG°ú±OñDž~Q¤‚ÑzK/´¡EÆÊ¯xS4̃þŽÆóÆXöÌx6Ï ¾}ýàâ ‘e+­á;yè;R,|ñÞ²›ëJùs.ö!ë™|¼˜¯%Ùº‘l@ mˆëJÚŽ“Ýñ¯7adÕNª¿ÁˆÑE¨¥‰“÷d aõǸõ…ÐE NT¿–6´žPò艼e7#ÖQý\ˆ¦5ìý N/Æ6?ü"|¡õ×·¢¥~' •&© zhì­ˆJZû_öç}ŠgÁ>Ñž}}(/j bi:ºŸÚâf¾1 ŽbQ{7ââ[£4Ên7öC„uÿì{ÿKÄ>n¤¶ìN ¦Zêg<ºY/|‹yùøôö¥ÿZâimóæ£ßàd› 2Ç`üðpcÙ”ex¶ÔìÕâ…n» )”^íö¾·é¯4{·âùõw`ªËÝn•¼[ÑÖ-Q¬S^}[0nÐ{hÑx£ê¾÷5Þ´G͛ϙ`L€ 0&ÀÎ?íGÑg¼UÈ¿ïôAÜTÒ´Ônˆ?ù´bþ+ùX=aVOªŽ!kZê0÷–½ø(𬒷:øüæÁ¨««Ç I«±a÷-ÅDJl܇Ç.+@þoœ¤"äjÁ3þ³§b¼C­Ô¡Xrö¤Þ’V£¿éC²9RÊHQ%«Ö§®ÀÃsÓ0}dKfÆý¿ hZÙ3[ñú3+ðå/´“®#NyœÄM—­F^Ñ-èoÉÁ½ƒƒùcôK&„@t»$¢þm˜ÿâ×8Þ;ÑBwæŽcý3KñøþÞèww? hM´'°fÕÿðn^ vþý:iý¤þY¥§ök[x\F}ã2qÔŠZ¦(”f#1½öÖJx¥8c*â ׊gŸÞ‚‚‡®Ã߯*/%š°ûÒ~ˆM–cˆº}^ÒDJe°âaùý±.„ä²ö§ÿÐn·®•JuÞ–¬èÀ©-%wâ²Z ë3;&À˜`L€ 0&p~°Ž–ÏJ=Hù}>ÞOCß«~Û?¸ÒøãˈÛ=/뉺í{ñØú*‡’6U6aÍ^èl®’F‹+#‚pc åeç¢Ã€©ZŒ¿ç ëÔG Ú¿ò;|K»öTÒ’µë’Ðn”¶›r¦AßÑa˜óÌWvi cëI ë#·ĉ[{ G€U¡¼¤?Ò"lò÷Õ¢ß}ùâgX/æÆš¾YX3…óÂÿÚBeæ¤Ô/Tk©|}y›o~~ÄœÚͲèM΢Gus¡äx¡ç=;P(Aß ]‰9Mˆâ«LÖu(DP\ b»Õbì3ßÈánY@ƒ´ÁjžšzAi¶aê° ¢×ûÊ|žõ5öZ±ª6xòBmãq‡É}¬M&…»³û¬ÆÍ£¨o+öO‡³'`L€ 0&À˜8OX5¹³ °qï—x¶oOô6¾|B{ã;¯@¤ÁR¿ož[^‰ÿ‘|¾øvEößHk- §,¸®¯².N‹G&%cìÅ¡–=jªEÞ‡…xýÐH´ˆêS‹Õûdq™ñùÝáoÎïÊK/ÂSiqÖ)‹£jãW˜ñZàI.,²E¤œ\¦*_ tðg/)¬ÿûM€eÐäKbñä„Á°V±UÛÿƒÙyu’’ÑãŠ^˜óÒ6ÜøöhYÁ³½à,4oÌJ¿ ‡…YJ{ÔP¾½ +›„—y%ÀõVk§%¢Ã#ò^?ˆÉ"¬Ž ”kô¿œ@y‹:ÌÁ9Ygîé…ð¾ê0oÄÂþs½ v_ ðC`ÝA©-ÆõkVíBՙй/∻±´Ûo¤6kC”C¯ôÆvòµùËòã’ š^Ü.Sõè†8*b§ë0GŸ¶̎ 0&À˜`L€ œ÷΢RiÄú¬ã–Ís´!}sO²2'èÄ ÅkwÃÃoþ€½tî‘on©ÆÂTÛ5rJ;xã/³Çá*õW'´¡ûûàûx¹†ÌHÝüùÁL}~%Ôã€Ùˆ“<©R(EΑã®ÁkGsñX¥%‹âޏl;Zå¼ñewt‹’å»ôÒD¼˜¦žªAäˆ+±"à[Ü’#¾bHŠkŸ±å¨ ©ÖøÍš|=&ö“3Tþú…ã÷èñÅke¨!¿M„ ìì³J2é×øšü– ¯‚ÆE[Ú£jû·Å×IuPðC¤PÚ+möùðß ßàñ´˜Ôì¼Cü±àƒ"Üòül¼ˆu8n»ÌÚ'šíÃÓïü ½Œ‹òÆÚا]© ÀþôüAÈW· iÿøE[’6 úà/ièKö°cL€ 0&À˜` ëÈýL׈¦…~à­(fÞxéíJ‹Hqx!UQV4Øý•²ÃŠ%†t0hXb{…ÒEƒëb¿Ô5×KŠ—°ìuWÄ05áÿ2¿ÁQV¦Á#ÃÌÓL½ÑÇÛù²E­4ÊŒX„§Û)”ápñ•˜nžkIJïê­´ÈP弃û¶W(•8~±¯|‘ŒuîN­4–D£j)áE—$âQ…R!)¾é®vÀ=€÷ôJÉB ¹ôU>~–6ó/²ïvºFÚÉÖHJ)”©§œÂ[¼û°†·ú[¾ü(ÅGH;…Rxú’òvÜ~Ù&õ¶²rê®ÿ+ê!D¦lí\«—܇¤pU˜]D>aL€ 0&À˜`ç³g©4èÑbžê׫?†©Œoj„—]„ÄÜ](¢Íq‡–¸»F+±¨S›ÏIñú]÷2,¢åsÞ~'Q&Mc F¸ò«))sö¨¤wqÕø0Ürã@$(Ÿ4‰KÆú9da’¬eî)úƒ”¥¹è«F±™òêX¾aãÂàu„¶–6G{«æÐ¡ÑŽJ¾„j)e³½Âæ"¤§b]TŽ«ÁcìËUò6 c×ç#Ï^W“ƒ›è3,!JLñÛŒÚo¿ÃÜ￳õ4‹O¤X×oz‡ø¢È oÀã 2yÙyÍ0|0Üóä N¶¡ à*¯Å®](.ÖcwUwôJëIC®sMs ΢0&À˜`L€ 0çΚRi¬n€X(Œ„Å™wÄt.'…„ã†nß¡HL‹td‰#Ó-¹ìÀiEVƲÒ÷2éåµ‘ÃÆõ‡OÖAɺ§£é›§vGEýI<›UŠ•ÿÅI2]£a§O¿Rõi ×Å=ÔLâ6\§ØH]¤é‰¡­Gð_ªb«ƒ)’ñª©¥.rr;ÈP+¦bZµV­?œ«æH mCÞQ³EÕ¶”ãFÚ×Öù¢²‰wy)ªmû"¨·½——PÖÝt¾=ÂÐó¤ü9‘×6yCÛÛ‡>¯¢ƒ/Mö ¦Ox$#n„›™q4&À˜`L€ 0&ÀN‰À©)•Æj² Å{‘9ܹ*âHÂæ:úн9 {»ùŽŽR8˜iMwœ7«Bd™"ÚVLóÆËË*QhÖŒt´áKo±é‹y›:½ sèÓ™×ÅbýãW¹Sêe­ ÝÜ"í­Ú•Ô¶˜Vô ïÀœkÝÍãFEFsƒ½UPMp )厔ʦöZ¡.ÄÇÒÆê|Ôç–¶P8:§Ýj'ÝT‰ZúœHÜTk{:ŠÊ~L€ 0&À˜`L€ œ^*¢s…íÏþ33âOÛ®Á\t!ÊÄP ÉÔ^!q”‡uI\M®T9Ò…ì§qªÂͧMV¥ÉV 8/>•FßÁLÄ“—„àÊ`›OuPZßZ£GŸ¦8qð ž‘æÊ:ÎßÖ·Oˆuš¬ãVØÆÇ­0Î&œùG>Qþº‡I‰íÖoOÕ¦FÞ:×JZ•ØèÈ‘£5Œžw&­´««£lÛû±úOapñ9 íZKŸlqþš£&'Õh_û0&À˜`L€ 0&àŠ€[ö3gTüŒð!B iÅ>Z7D1=ªXu+Kˆ/YÝ´´óªÐ“ŠvT7¨æCZb*Õøl?‹™²^šöŸc05ã4Õòz—[žš°c)•:¡þÐ÷ I^ãѽXMß¾4êºcüÔkлÿ\'þS ЄκQøŸýxýyÇRmon«Rh!f.L|ÕcŸé«Ý¿ 5¥ƒ~À—„SÄÒú9ƒÙA¡ ö,¬Š¶éP-’O‡ùTããH@Ñjçï+)•ÖœZðìi¦±îôjMbÂÁ’Jü"mˆÔ‚n}£0¬Ã©ËJêŸí/M*V|D[Þ|E4Æ_Ö}z‹ z%÷ÉÜuX(6jçüÐ]Õó5>‚C{÷3õkvL€ 0&À˜`L€ 8'àx$í<¾]ˆi°ú˜ƒ-S¥Ø¤@È_ÊÎ,Óu=ÑÓœ[óáýØå,¹9NCÁwø.B׫{PûMoH“Xºé€9¶“c ² fm¢ÍCI‘i.¯ÄüíMÈüü6”ª…Ð dàŒ2§÷·ì{ò—ZóαNÊ1{{õQvCõÆÙ¦)XZƒf(*JeBt]”ǡኌrÚ¾ÞxaµYVåºý6K¨‚HQµ´§ööº}h÷ßçH!&>[Œ?¾RŒ™ô”ÛïßA÷¸éŒGpØÖÄLÉþ8m,fŒÈ^V…’¾.Š ?:ëÞÁ´1”½©²ªêX{ès+9G\[nÛ'råÓ•y¹*‡Ã˜`L€ 0&À˜À™#àlÔí–1}+7þµx‡ã4Æ2|z\)ÆáŠŽvbj¶*j{ûaÚ½_8W,öâ¾ME2ihœƒ²¼Q¶îä49^ô]Ì9E–ÏghBäütdaë¡C¨nx/«À¹ ýÃCû§ ”­rò™ó¿Åbˆ98¨ÿq<éDYQvmÄÌjëšÉÛ’;¶„:/ÙýßÁñ¸Ä.ºÅ¶ã±Õ{l”àãØµ~5ƯÒYÚÀ.‰8ñ‹¶´§F!}ùXf› G÷~…Œÿk@Ô•èwEÂ/óGÄ•~n´Ó«½‘Ñ„?ÛŽ£µÝîÁ37壨¯J‰³FAwÕgaj¿þÖï=b©–ñè˜;å;î­ÊÃÓ_í?cC#ÉëAzN˜`L€ 0&ÀÎAжç‘h‘WX¿!ág:Œ²vÛ – û9mŽ(ßAôò“¬ƒraÜpƒb«ôFDX-؈ýªÑöÑýß`ú5ÿCåŠYÔ÷Ž s(o·¸ <~ÛX¿«Ú>œ6ZÿÌ2Ìi ´¬»œN–-á|㢠ä¦;y“žùl´Šcl8€¬éyØlž¢© p`)•rSýÑFâC̈5:ì!eMÍH(»ûó>ÁeO7!\Q`Ãh ®¢°«òìòÓpü>ξt‹îo7ìÅeƒÞÅø[þ‰ØA+0m•Qq®dÒàZK{ÊBꢂ±ûs%Ÿ÷1>ö=Œzø~NQÞ,Èñ.u®¬ªëë× bV±ÕiQµç ’“ÞÈïbD컸춽øü²žð·F’Žþ“ùF<°Ezqf_]t ž}ø3ÄŽ'9Ç¿‹Ä›¾ÃšáÁno4¤*ÊrjL^¾>xîÿ>Câ5ÿÆeÓd9,ù€ 0&À˜`L€ œ§ì>­ÄÀ8ܤ9†OhÇTïàîØ½½ÉsÿƒA±¤mFÉIúV`(:eÿ‹bì”ß‹/Åý[¶à­z*˜¾ÓÒ„ñ×d#6®;’4(ÞT¢Àžèwk epå#Ò©Ô^èó›>x–vi}>•qmÿfì5úâØ~ ‚ÓaŠÜâÌVA²°ý!ì¼LßkôîC]-n¸æ=Ä]€šZNß<üouw„þ¦—囇— rߊ˜0a’÷¡PT‘”5…ÑЃ£kÁ—9ÇÑxE(b®´*ZOÞz©Ã–°LvjïÙlêò,aâ`D¿¶6±ü£CÿO4·!.É=k]€m{šóêEùLùz•ä6eȇAøsšó-uÚE×F Õ·™6ÔõõGôTËÊPg+W{\†V£‘Vò ×G@KŸ‘±¾>ðBïQ½Ð»¥'hfŒ½ÎÙN ·'’Cqb|b'’B©éc÷OÝ­*Áã¸S}*@‡¨[ûáÄ|Þä‡:Êc`Jw‹ByÉ‹ð×qŽ:oÚhçJI å€viŠŠ!"Y⦆ wˆY1ÒøcÉ=É‹%}°×Ý5Ä2T⇷RúàÛ¶Ô%‹zôDe³ÚÐ>xºSJм8{ bTŒêH‰ùÝÝ‘C,ðûôQN¬”^°_ý¨Bi{êåmS?Û'ǤXÿ}Zÿvk"El­¯k…Ò~ƒs{ªM„R>ô=ÉvÅë0÷þkз¿½‡½~燉·YûœmLÑ™;êÐÞZóÎÁô™¿p›vŽÕÚ¨ôm¥ûúªØ¨³ó€;°ò¢¸ÖVWrç_&À˜`L€ 0&pþPƒ;]߸K±"£?’mâZ10·É©gh–<<±Ö%ƒ6¡aøÃã)˜{‰¿E9Õúj¢µºiêèýé—ãµ[âlÒ‰ÃnH4kZácð×LJã÷JÉôÍGµ{“,Š»$¶–?u]{K')U¯=v9î h·¤LuÓÀ?XÅ켑vI,>!…Ô< V èø·×Åxç±á˜e‘MþD‰È_‘®gpæNƒÛ†)v]9[]eа:SPÌDDšÕ”½ÚïŽÛ”¾é[÷'bJ¨Â¯}‚Kb£ðAF?;åsh¸Z(jÏ?ÁÜKƒìâ©sKŽíåOÞ€aÌŠ–z˜ P©Ó/Eδ°1Ç%úàªÏêM…ÍåwŽ•ú:ŠržvE"õmûɶ¶õ ·>¢ÝæJ¤dßs‰S¥?uëiyY¢”Ç¿L€ 0&À˜`Là|$àÕF®«×¬Âþ# h–æ’bì>ýàŒ¤;*É€ŸöÁ‘z½”‡¯_w„Dô¦]=;©ÆékQõãÏøI/vC!ŶظpЧ&;v†zTí?jNKÊŸ!4‘ýC;NëN ‘ÿ£¨«7JßÚô¥ÝQ#I p¨q»“c×DZc øù#v`$:/"}’å`5ªŽ4Q{Òi­!Ô‘Ã,Ÿý8UáõÇ(ÿC ÐZ(OÊ?´'õ¹P›üõ¨úþ µ'…û ±îÛCÅÚ\ß:ƒI’Ó—ÖÌÆ^Dõu§¿¸[ª¿Þp‚v£ß“´#®JwóáxL€ 0&À˜`Là#Ð¥Jå9V7‡ 0&À˜`L€ 0&ÀN3eöåi.†³gL€ 0&À˜`L€ 0 ‘+•b«r˜`L€ 0&À˜`gˆ+•g4Ø`L€ 0&À˜¸ °Ry!¶*׉ 0&À˜`L€ 0&p†°Ry†@s1L€ 0&À˜`L€ 0 ‘+•b«r˜`L€ 0&À˜`gˆ+•g4Ø`L€ 0&À˜¸ °Ry!¶*׉ 0&À˜`L€ 0&p†°Ry†@s1L€ 0&À˜`L€ 0 ‘+•b«r˜`L€ 0&À˜`gˆ+•g4Ø`L€ 0&À˜¸ °Ry!¶*׉ 0&À˜`L€ 0&p†°Ry†@s1L€ 0&À˜`L€ 0 ‘+•b«r˜`L€ 0&À˜`gˆ€ö •ÃÅ0&À˜`L€ œcÚÚÚÎ1‰.lq¼¼¼:UAnŸNá²DfÎnt–™:cV*ÕDøœ 0&À˜`8¡¬477£²²'NœÀÉ“'/ðŸÝêùøø [·nˆŠŠ‚¯¯/:ÀsûxÖ^q>v åó_Gcé8ÙØàYÁçq*ŸÀ ^41> ß^½;ì›ÎªêE–_Q9£ÃþL€ 0&À˜¸À( Kqq1ÂÃÃÑ£GIѹÀªyNUG(ðÇHy©©©ABB‚KÅ’ÛÇó¦ë,gcu5Ê~=z…¯g´Ú_Ÿ½Íd2áx]jkóÆ|è"ú{¤X²Réy¿å”L€ 0&À˜8ï´¶¶bÿþýFXXØy'ÿù,p5)1MMMˆ…··ã­M¸}N½…Ýæ|ÏѦ$w'KrkY)[ZN½ðó-ÞAAÐ7Ÿ„mˆ}û]§}ÓUÕ~}ê¸+Ƙ`L€ 0 ˜€°‚‰ÿB±‰‰‰¹€kznV­W¯^’ÅRiõ4XÅŸÛçÔÚÏ]Î'ŽE°Þ0ÕÕžZçsjS+Z©þ:šÛpô˜týPÝ7;ª¢ãW$¥á†rädg#{Ù2,SýÏÎÎBΖädÓo¹ÃÜô¥[½²‡¡Ž=ë‹7ZÒ”nY‰•Nò¶Mm0`²õ°\Êg‰æð [–ecKyg¤w˜Ñö4AðpÇyÒ>îäë(Ž¡²ÙY98mÛX×D²,y*í§Î¹kÎÝeæ¼Ov–\L‡°v™ÌÕâg>p÷ú©,ÈAV'¯SuY¿îsv¬]†¬ìµRÿî…¾[rV";{ *Ý»Vç©”›ƒâzƒ®}½zëä8Ùið=•þn:´ËVniÿœ0T"'ë|º°qñë.ñq™«>Ù%bX31Ô ¤JæjõGõ(tëú¡˜•e¨‘®9ûøÌ]&T•U¡¦ÚQ;¨ó0`í’å(,«@uuÊjOåþb-wW9Ý'éÚÏ\´‹æPëŸIwjýÝPW†ªŠ#ퟦz”ל?÷`}q.v×i‘<"Ê%|Û{¡£ˆue%¨¨>•<µöp$“c¿®*')ÃCP²n~Å6Ljٗ 0.!ÐÚFV:ZS(¦ÿêÿÓËÁÃSçùôW¿A˜vß ¹\S1濾ˆOÃìI fYôÈÙhüÌEPƒÑêW‹œC§>‚áeñéø@«oÔ´Ð Sy„ÔÚŽRk¡£$-ÎÆf¦È'©º½8—ê¡“$éXæs*µ…ÛÔÒ7À“öñ¸®R¿ÐH­imãö¹é´$=½YµHoÓ~ôjÅ®µO}ú}ÜcÖAŸì 1~~t­ˆkFíäëÇ«OjÜocµ•…¿:[>·!@¬LÄŠx>ê·jnd9¤.ë'õ}%½\‘îU$g܇1Q~J€G¿r¹æË‚®}!†‘®!q8sγþ®Üýtrok׋ÍÐO'×F‰æêÕ™’LØþE–‚¡¤³¹ÚÆT.i‘0˜+oÁícÏÚÃíì-»®œˆ«GAW¸[ÈZ9%¡#€ø€ 0&àI©<EÊ­BΣHgG©´džR%ìvŽž}-ÕÈZ¸5’ÁR‡¤ôé—ŒC+±|W0fÍNCKñ,[Weß°Á£‘1ár8R™P°r!vùÃì 4eÒAúqÈZ°Nγb^}§÷RY¶$1èj¡©—Vù4ˆNNÇ”1fe™,’9ËsPV'/Ú ‰OÁ´I#%¹ìjª/¥x¹OÖ^u!ñHϘ„°ê X´¦cg܇á¡ÂX°™ëÊ0|ò,Œ‰ñƒéÐ,\ZŒ”YlGõ4=Xbb^/ž˜ŠŒ´¡ÒÀP_¾Ës a B® ’+€lx/Aep´ÂÂ$¥•™_/°h]‰THYü—OÄÌ´>4x%òËêÌ…ë0xôdL¸<®}ª7,Fnu0¢´•(“3EXR:¦£õÅ4ýyªdAuaƒ1)c‡Ë·ä`]a™Åê ´³}Íé±áE(’:H¼ºéfH:‘³þ%r)§©›9ùe$Ó„ eÊ4ŒT f(ÅâÌ|$ŠÝëòQ9O¤;mw©¾ÙëP%õg mpàO;¹i‘~ïLuЧõIgý¬“²™j÷`%õCE¶ÈÁárÝÛ!ÕcKV&ÊÃé%Pšx ToßtÄ*=#cÄÕ"]$X•µ¬Éò¬ƒ èdL2†^íˆæwpí9¹víÛ_‡ø”tLCé,\Wø-JÊj$iuaI˜>}œT†äaó§–¦Á/_·²4!ˆ×¢²63fŽƒè«k*í׺¡‹lDL]sÔöÎúDiéôåtÍQÿ;É>I’ÝF,ó¡ »Öf#¯D®GPd4´ö7 š» [Íá ~™œ>…d;B÷¡5æ{` —/2f Ù°9y_ÓýS¾¾  Âà”4ºF£Pº1ëv×"aâ ¤ ¢¶¢þ’•¹M¡Ã1cÚ‹h-?}‰ù¹{åëÎX‚…ó*èýß Lp1@wÕ®Îï?bŠç’R€Â\”4„"ìKB›{05‚S–vmKlâ£Ä=Õñ&"BµªÜ¶ ×Ц¢ b“œ>•Ȟ¿ úøTz> !ô&­\òÓ¥ÌÀ”Ëéflãìû%•fóü²éÙ¤äæß„ZmfÍÏ=]7Ë-÷OMP<&MŸºµÛ;²ï¢{Wüu‰fW×›mRŠ7®¤6®’<5!ѧiI‹øÎËwØ~Îî?¶EÒ±=®SºæÞqð쥌]<·2QŒ>6V›Ç 6Ïb¿x ¢.UH3Ž.WI˧L€ 0S#ÐfžöÚÙ\¼ºwG„ØÐÆßß’´•Ö)¼ûËùùx xxêNaú«M‘âéîÀIo—›jЖŒô‰céo¤ûFiúU]e5ZèQM ,ñÐKÄÄŒÉH‚š’­dåt5ÅÇ€ê*#Œ4Zs–¾¤F&'ÊoåéaœœïØÂb¬A­"Ò'ODR¸…k°l‡˜hCk9­"E1ɩ雉º²|d樧Ҋxk(˜’б)ƒi±oV-Zcx•ß„ÂÝò:š²=Å4È7¢¸X>¯,*¦3„ªJÔ s )ÙþÑ;q"F'†£º(™i}*…-ZE %‘’šŠäÁa’\‹–íZÀ@Srë*ÊÐcÏÜ–€ä¤h)NPt"’ãCQš³XÅ'§bòäTÄQ²u«4ÍÈÒ>”¢Åh„±®eMQ$O:’#I‘Ú½»hîõÚ,¡`õÁ艓‘Nu7Ö”ÐÀa—TŽíCiV‘B©‹OÆDb$µ3‰Ýp9f4Â%£D““F}ÎUÿªÝ± «H¡ LÒG#Ú¯ù4PßÓnæ¯MÆ:l%…²>$ÉÃý\´û!dÓ ½ª‰¯©‘šN ¥Ðt›¤5_fê“®úYgd«Dö¡PúK²¥§Ä£ªD FÕ2wS} šdm§ ;Sêa‰)HM%¶&bµ*Òe ]5FT×Km<6) …ÈýÍY=\»‡ ²¥ö]ç©éc18Ì„²üUÒµf¢©--u¤P6Y®5cÍnlØÕþ ^Ä, ¥ùÚ .‡2RDõÒ°Ÿú*¥÷‹3Ñýƈ#õF¸ê®ÒQBd®"…2x0REÿö“dÏq°Î«|c–E¡ô ASU…ååX×X½HV(5þôB‚FÊTïÂ5™ØrØ f£œ$¹ÎŸ:û/»±xM¾¤PEÆàÈŠß@×hŽÔ[šj¥ûI“ѬpR=ëé¸éH-Ù8Z3å/ò3;?ÿP„©5%”~]µ«Ëû-‰ jÉVR(›B0øÒK0B}vÁ²]ÛÆSÛVP[J&Wù̇â±c¤)°þ‰£1y"Ý´ Är1v4Å 1Jƒ†’”Ë]õ{ PM˜¢b,™”‹ËûõµLñl Цv§{VT jèåY õ%A|Ç2ùº‰§û~úØ$ø5Ð}?S™žo’~ôU⾯A|¤üJÓõõfMwh‹xiPz‰3qr:âQA/ïÌ–gWå;k?7ž‰]rjÃÚ?{]öºƒÒK#-£qü,Ö"~õýêz*³ëj¿úé~giÊ£»íØ•íSRR‚yóæýª¦xºÃ¹•Ö¶*ÖÊÎþ …òÀ­,ÿÅygó8÷â{®T:QÝi†Žã´ˆzH2î#«†pÑ-X°®å4°—gÅZ‹o14¡É@Vµq‹©‚.ÚªùK‰ÕH£” ³ûôý‘Ô•»ŠP:c.7[mò‘†bºxÌš.[0bÂaX¸%Û QÐ1$›ŠQ4ˆG]u&­wÚEJ—ùí7…ë‹·Kñ§ÏBZ‚¨ E<)‰Â*˜_•*¿a-¦©OcBPV)þšÊÊhxCÊ% ˜"¯†z•Mq~儉3§`@4(††ö9¨ ÓA K¿oä→z¹ëJ¶Ñ@3sRâï›dϼRGÖ1-س»¡CÇ`ø ?ÔkSŒ”‘ƒ`ª?„šP”5Ae»ö †Œû¦È²Æ»hºsYE£¬´·èQG Ê ¤q˜2ˆ‘ßÞiû"›DJÛ¸Ëáo M(±l@%­J´vûD6g¡ƒ.LjâmXSJõNÖb½\W‡ýKŠ|j=jÛÔ1#@ØÃSH1(Äîbj=•¥B£! å£S‡S{æ`;j÷êb?TSXRÆL²,I á¬)2Jª—<“ÑZwú¤(¯£~æŽlU$›°%Nž!YÁ©ÓnDf^%õ5ÇN#¼ {P@•òL–gÉjI=8> ä"?Ò¤ÔþH5MžºG}ÑÏ ú[!JM×I·¯§úÚÕcû×¢DéZ“®’Òĵ–Ÿ¦4)$ßAS>#Äq4*v- e‘^¾ ·^k"¤lûúKׯ,åÚqò*D¨â¬m`õ9NõqÞ'’¥ˆŽÒ®¹m¤è06õ:H·‚qcQI/’ÚYNôÔ¿d«Xr¦¡+›“W—Êb‹%ÖÔ1é>—NVsà‹ì夀{vðÐ}±ðõ5ôŠÂãfͤå•ð?šLké%FÊPʶ ¬ªNRhä …¼vê£ü @L¹•#H5½¯ÆÌYÑ”7Y1uƒ1cö³@¬ µkqž|orvÿ‘ ŒÆŒG§@Vß 8hs.Ιï”e™¿;mk•OôkY#§Ó áb¨q’…¬ðër̾z8õ‰BÒ.E1CýQX@m”„d{®îKÁåBžLž=…îÖäEÑóaŠhÿ$ ]7Ûª¨WÄÅØÄ(ª)žu5ô⤄6Fš€Ëm^¥™<~òê®7útÙÑ5³‹®h’y=?EK¢ç“q^&*EÅ]•ß ?ëÚ·_GÏÄ.ºNµ¡>fŒÝ³W´»¸n]ö'ÏbÃåã.®5ôlŠus:g×\mÓùs§F «Ú§´´³gφžŒÂ=üðÃN366¢¾ ´¾ŒtùPww«“ó‰]Ôÿ^ëô¼|ÒMva⤡±{Õûðùøcd1::5×@Ÿ òõ9¥LO]ûQ”§99I§ ¶N]R·v…ú%`zz5–m(DMÍùR’ïÞÃÃez“<SOËUd%41 6Sb0ˆÄ-©¨§ J–²,o)H%´qõ¨±ÇIaá‘ÖΟ@ÃÆZSå…$²fn-Eé¡T¶èȺ˜€üÜ2Ú„£e”ÏàdË(¦ !X4M7U¼´ž6E:)®Ãn-Ñ ¤Ä•EUv'ÌÅš8Å$*à™FŽÐZ•×óeeWN­3×\>“ÿŠTÁ4¼2; Ïî˜0=Æe±;o ýáþHJÍÀ Õ“_«ó‡©†Þü¿ºUÉ¥Ó¿-’Æ,ì=Vç¬IÆ c–.²o½Ú RnTJ¥È/j8)96ÎQ»iÐuˆfR³‹ŠŠìË€}ÒQy¢Ÿ¹+Û1I6êaÖŽ!\š~:²Ý½Íå}IÜo4TW%¢O}¶H¬"  ©&eyPÝbh“%´[/±aAôˆ0$œ^o–²Ìa62#)Fƒ ‘‡«òk#陚ì䙪nutÞEשôT±<+DkuÔo?‹EUîÖkÒÜìåw•–^xAÊΜ9Ò¯8ß°aƒÓ2 ìÂŒ‡7aâÄ94ËÊÖ]Œ–Ìʼnª·=¶QΡcãMHùÝL{{î½T–Ùx`Rîë&bôèçlƒ»LjwÛÇY¢n»í6ÜsÏ=’B@3’RïîÙX„Wï™5–!MzûŸøÝ¥ýœeükñí¦R ºñJ›çÌ©‰ÑFë)ÅwÝþ‰æ7áî&°‹w-mxI¾vžtBúMœ%/Q¹{>ðô»' x{ôÜMÔ>^gX¨SŸý{4íâW©ôGŸ@€IʲíXGSŽrWæcèì1jyÛŸ»L¯¬+rRMòn©.§X‚yE;ŠÖR´ùƒ¯ù)–|Ç#d=‘Óë+wa[ ,tâivæ‡fCùÈñL•ûAèÐ$èh:Ø–{<í!L 0¡¶²[ÖÑ.‡¹9ˆO˜ Z"gqÅb]í‡Ñ÷âraî1ì« „ª ,Q;<°ÉÖu\²b>1“ú`¦Õc×–mж„Ú¦¦µJÂiÍÛ«;j÷_1õÙˆÊÌEQ•H%’Z]'ú¤«ò,ýÌmÙè ˜M?4”—Ê/U¬’µ?"6¡Ž¬ÇJ2Ѻ&Â!· Õ­¥Z²ŽHVsŠUW)âúÑ¿N^»†:¹)(µ(µBÚ•FÅN sõGÄo’¦Ð+ý¿RšN.ý¥k‡·äJ †¢ªI¹:é¦m¥ ;L§„ÂqÇÓÌ×#­åÛEVCí`…˜E`ù½)»aèHºziÍq¹Y]x4½’Š¥?­³ž¡ÁZÔ—ï"‹} ´áôÊFkn 9ô{6Ò.Óä§‹ÄÄéd™ÕìÁüEyöõ¡xÕ¥tÍ& ¢6ÓaÉ qÕN©…ÉíªŽ`sî¢ÿ¦Jã¨î?EÂ&O¥E\°ì.,Ó.ÚÖ67q,ªt¤œÒPÝeW#õ ñ²LôÏQWG£(oÖn('.a¸z¨ÒcÌÑéÇõ}‰òi©‘f'H–JšQTA)’5X`!‹ôLa‘ŽÖFoÙVƒ$Û70ä@á´ƒréšCé\¤s~½Q ­£kÆê (¦Ù.ÑÕ]•ÝB»h;{¦vðL…uÉuªH-w<¹K¸è7µ“g±„Ûœ£®­”Ä¿žpÇ*ñ·¿ý ¹¹¹RBÉyæ™gðôÓOKЉâ¯.]o›¡‘Êx¼¹æm$Jï\šQ¶áqÜ3c1’¾y¼Ëµ]y^´âM)»ì7óqÇ7ÓšqÈÊ]¦‚‹‘fâW]÷S‘áTóŠ¿¢ü …ríç@ÆÃøÛ3›F/ –܈PšétàÛð»{&ÂùçHp®X-âá9ãÝQß g‰$Míä:ÂøµòõàªmË&¤:nƒ·ð=ò_Ô¬øÞ=Ì/ ›Ž ¾Õ„£Çé.ç}’ftÒ³FG/ßIE8Z¯Ç1Jܽ}hiÇq=ŽÒyóqZH¿äߟü6öGlü¥8p²¿œôÁ¡ãÞHT¬–'±¿±±nXŸOT7O»“Nª»ˆIß»Ì%¥kQÖF”Ó:/ÿ yÐã§žBç$—éåÒ[ê…¥P6ù·Ë¥©Y9¨¥Ï£ìÚBj¡¡Ã:h¸d™+\µ{*kIaÚ…U¤„íÞSn7f HH–ã-ÏBAi%*K °x•°AкÚHX­†Òx±69ÑÄÇÓP' dH”Îi­Œ¬4ÚKŸ$šJ•½‡èó,¥´ÁM>-¦1iI¡MÃ’:¬Z¼¥•‡PL›Ñ¬ÚMw´á–27?‘kK‹pHßB»ÀÊ嚌M4Ùˆ%¹U’G“ø¶‡[®™öãÈ£õ£YØEІ–æ’Ë×8YA屄%yPAò’ÖVîÁ²L¡P’ŠÖ$÷Ê“ó¨ÅîÒC–Žë@³& z]!²·ìA-­=Ûµa9ò w£Ò<À— 7ÿ±ÕÏE»ëⓤ¶Þ½<;h0»gË2¬£u€òðÖ&ÇNôIWå *•­úáŽr꯴JÖaRq¼¦Ò"m@<† TË‘SPŒCôb gñ*émòð‘Š.$hÀšÌ”Ö’¹kVŠþ”@Ãw¯Ýz™"·GÖÚTª¤ Nc·¸Öh}¬r¥»Ó â%Ë~–Sÿ/'yÊwäДhqó•I…D‘ÖÓR†-»Ê¥~–•o®jw—}Ây:š -]sÕÒõXI×£`°<¯î ê΀Äx¹6ÕùK‘“ƒÅ‹·Z{ÿxȳúÉê¹|=ès½f+váÝD§vÈÀDSñKwa-­ë”K¤ºÚtŒ¦’uô½àlº~Ís€mÂ,íL”7±Éœÿ6Òg…*©ÿΛ—…b³õÌ×EÿíüýÇþìŠeGmk‘O˜A5­AÎŽRú”U%6зn©g"Á<ë xøHz@ëÄKêhz÷H‡÷ZW÷%EžUYè^K ã²%’ÅYb/6‘ºt6î©D}-•OmZ¸›–NØ JME÷EI`Ñ.^oJ õÝò5³xíéùT“ÑÕ¥æuU¾ÑÕuißö¢vÝuJ¡”µòìí¨ßÅNžÅbÈU#½ííƒ0:qÚo¥ùOg %¨£ÿ¶Š£8þóŸÿŒFš")ËmÛ¶ÙýWÊo—'Ä@Õ}ûÐ8ÏŸþ÷Äð7ß÷_Ojk(ÅÛ3¯ÂUWÑÿñÏâ»jƒ$—áØwxáNÙüÌg1çΙØp€¾YMVÃ;ÞƒYþæàá ¤4¥›Þ–󡼿¬øNŠÓÙøíä§)çó?Þ\±#Ëæ¢à°Qæ¦TXùuƒ§ÈÛ]×N7óWÒÙ–³páBI¡TÂlùOä"9ÏÜ€ž>âÓ>0r=> ͵u®ãEûÐÿ;_Ø€jõÃÌó6V¼=Sf>s.6múw™ãÍÝ$·ÉMs©½æ`æxsú99¨6×å@þ‡Pò½jæÛ8 ò¥°=æZÚñá·7¡¡­sÇß-Ué×ß…Mæ>b[õ±mý·y°®VäU»|©”¥³_ñB ýª¥j1V t}J–~ˆ’ß§Ï&}Ö!Âe9Ù¸äÃ͸cÛvÜñÙJD/øÆŠcú?|îƒ8Ñz .Z³ 7ælFºÙÔ;OÂ9W,+É3páf Í\†ôOCc–Áû®7pãgÿBÈG2Ùû ž:éùäibu:ÿ`û×bœ§£7ªöN9§_ñ$ ŽÉÉ´Á øW-1OÓ…ÑfÉöɤ3sÛ—éiL­Í*Áš¥FÌzbŠeA iï £?Aó±„þ 癌 ó›çi)ÈZžO½%r‰ºp¤ÒŽ”~´OR hc âeS¼ü5Ëåx´káèŒ óôUú6­%,ÜZ…˜xùv|b$òé–Ò†.wŒ¦µk[‰ýy®CPˆ?m–Ð$­¥K‹^¥'=ýhhÀk¶àIÒ¹þÝ‘:9Ù´V1OaDje"M­P+Ë WB~E>¶®Z‚­”^G›˜hhƒœjZSgœ,O#‘ó¶–«”¥ü†“RŽ Z«Jœu÷ÎrYר1ÓR›…|Ú…r‰ŒPÚ±6­ÝÖŒrî:¹²Ä×i»S_ž9#YY¹TÑÖ„„hPW§‘d‘>vÒaŸnß'–GýL8we›–‘Œ%Ë -Œ5¢ƒ ץ󨙍_¼’6Ÿ¡u²R\ÚTdt†´n´¼XæBWÖ˜û‰˜ž>Yô7êî^»1ã¦atS6mR“s·¥]‹GKëM¤0‰ÕîVá@nmÔ8d¤Ôcy~ õ[¹ÿ‹1{“Y1ŠHƒÈ‚åd¡Z…"J/3×¼ºìQÎÓi#ÆP™µTf!–›¯G±;í´4Ù~e+æ  ÓT—…Ý4w¹šÖNC££^"6vWL&̘ ã’U¨hª¦ëPN68SÄöІZ饌,-ÅN‰è|úomæChÉy‰á:½¢‡iÚØ|]–KŠT m\V ]]Ü ¤©«œt­ÄÐË,$W‹±AÞЧ¥ê™@IDAT¾†6H’?mb—ÄÅ=Õ/Tëòþ#ò‘-…JŽÁíîÁÎXji墫¶Ur”~ýtÒË#5~ÙVZß*n*ä"“'cœeÝ@F$ÑšgÚj”£e¤€º¸/5=13RMÈÎ-¢{-õ&Ú]6HCo†i*¿xU3fÚdÔR[Z§2‹¶iê¿|s”¢¿~Q ´š°{ÄznjgW×›©œ^DšÜ_— Ÿ6®[Bÿ‰¬TgYítQ~p¨‹ë²ýýÇZ"½ ï¢ëTÌzP?{]=·ä*;{Ó¦^¥âEV2íK몥~Kjº¢›yñgÄ ¼#÷Ö[oáþûï—¢}ýõ×–è®Ò:ûò¾Ãˆ²êѺ²Ïæ-@È´Eˆ÷m@öõw£ìéwñÅ’A8÷7Ü}ë{Xûõd¬J¿Ûo~yKFÒ>…ó‘ñçïqqs3ÉP‹}ÛeË ¨Asã.”‘ãÎwp÷_~Ââõ_P¾?bîØ»ð÷øux Ðýø â×â1qO¶q?}EÏÈ‹ÂÈÃ÷fàÞ5E3k¸¤ü^æ_’Güsƒ©MÖ.»2¯>úHzà¨ÀÆÃô¼ºù!ôJˆüIf!‰È\—âùr0²ŸkîÍÀ­/`ÛÓýP¶e)?¶y_„cÃÌIøË‚›éÅëЕ}„I÷¾ˆ)×¼K”Q{Æódœ¹6´K~Gé߀¯o¦>™‰Wr¾@J¿fäÌ‹g?NÅÂø\üß‹_ZâÏ›û,£Ù:ÏããŒùxeù«H Ñu ke£G\\ù)mãìWäÛÞÉ~š¾¿Å]+ûúÒ’ñ<ì_î m`8|~Ù†êW äáI4NªÃ/o¾ÓÈ? ×•cÐ?q4>4®Ð‘ÿ¢wpâ¢ñèwÃuˆ¾k5ê6ïCØ qÐoÎAÃÑXô͸=î‚#YEð¿’–CýüjÊÉd/¥c¹íã8;ó"ßQœ¥îRÜè ’5.@Þŧ“¹ŸJzÍ5§Q)’•mÐëéŸ4ŠváÜç" û “zÚÍ4ˆi/—k™í3rq&Ê õVZ¿ió$ÕÕ@Çæ™¼.Ú Ĉ¾Ëçš‘Jfšî¬7óÎf_´ë3‰!ÕÏ_®ŸëÈö¡íÚ“,8kIŽË#ä¾°gå|äV„«^X(ùt®O¶+OÉÆÁ¯ó¸*ÆÒ’ö‚µô0/‰JÅS„å[v&jC±ôΦȨÆÅRWeº}íªú›Rv§•|„¬¥9x} ÷ÒK#aÕN–ÕIßrÑ'\¦£»€«û„\²üפ¯GS íºJk볤=iɘ£ó6oZf›ÚþØ@y™H¡pvméÉzÚBÊkp÷'û\iP~¨¯/ÍÇØ{ŸÀpœ]$Wý×}vYZN\¤WÚÖ|O²$qvà"þ–ů¢Ð˜„ï/9•,æû’–f:ä|]‹QSÒÌ/ë‘Cå”…Óg‡¦·dÖÑu#"çÌúÊ<òPš¥?¸“N*„®K½‹ëÒy>®ÚÏ"¾ãL'p×WÅZJFß´ž·å1éx”¾s-m$bû,ÖÓR šö=x⃘à`¹ˆ»%s<+1ìk¡o>ÿ÷¿ÿÅ!C¬.Ž®¹æ)ô«¯¾r pïdÕZŒž:“u²o ;õ?aËöï‘úÊj<}E#î}7)k³qC?`7nÆ‚¥ßã­uÿÀ_ÒŸÄóŸ¯Çpß6xy5#ûªëÑöþg˜â›Kùya )ž¾TŸªUÓq_ó3x×Ò¿ë0ûÁÈ. ,X@Jé[Ø:¥¬Sñ¿zD¨RŠkÄ¿h*cæ¥âõ qøéÓûñZîükËëèwd¥”ïÖmðñM£ÑüÆg¸=V™£¤oÿ»wï^ 6ŒÞ×k¨^^v?¾ SëǶÙK7ÿ®ÿ#ðÙ¶Ùh>ü#*ªÄ߉¾“‹~¼Çšÿ„»õ¯à›{eÅÑ,GWîº^./V+ψPêàè×Î_0zQÙwqî&ýW6zß>Íéï÷©7:Èò:Ĭyݼ~BSÉ9œ”ÊÖ#Q™{ ¿z5êŸÃ{€¾™ëinãçø~ÖëôÆï ^x=ê_™‡n~^_¼†ýsåýÞÛ„߯ñý«Ív飖nB a vNÿ Ã6ÞãÊY¨x¿™ì½jh‡kò¶:ì›ö1ÛŸ9ï´uF|´Ò`Öó¢N%½P†Žª$qÄ Ûçna+«–6Ù±µÙ†‰c? wve;Š«ö³=vU’mLûcñI£p²ì9V(E\Wý×}ö¥*g.ÒÛ´­Û寃øúÒx_|z†:E|úÕ°VÉb¾/™è35UUEXþz%â雯õ4KB¬5N¤ÏSÙºŽ®7aì8lX”‹åc Ì–p'TN×¥ó|\µŸm ;`ê –^*ÖæÄRŒiw*²Ð«n»ÖÑÀI3cX¡ô€wÇI:»fÏÝøêx­Òšµx<ýÆsˆ"%P(R÷|õ"nùs>îßr1Ù÷h“üáqˆô1â¤×xîéfÒN–b¹O+)À&S+¼½[¤x¤eдBJA3Ä¥é…tn ™Oð2ÛHHqŒD3Y.o~î9ü–^*EYHÝ‚,vŠšÑ\ù2ëBpsßZ|¹ùCnF>ÆÊ]Çp_¹LÅ>£L¥ì˜¼{1Ô;Juë­·Ú)•î¦ïÞsð÷\úÓ0ô%ž²3bÝ ©Xûd6žz›µƒIRò´ÒÎwÍÄU²ÍÂ@mÔÒBí Ò6¤—^Í'$Ë­h/±t#$ÀGò¬Z}Ä@à'«ó1aòSdõ} 7¸ÎÅkdˆˆøJyÍõGq´™–È…Ò¦:ôO”×êݱղéÐyl©”¤”­Ó¢!—í¯c‹ŸŸîw?íFù¯Iñ-†^"öÖbaÿ&'Nañì.€‹ô&zù)çO›ï R<G®Åœþ§ü}œ8 =žŠ¡S•8–Uwˆ9–[ʺÃ?§}Me‡p&pÞˆÂôï@Jb¼´Óf­)I4%cvÚ ó¦¤Î <&ƒ“hê©ù"ç‘ðEõ IÀàD±ã2»Îˆ7ÓÆ´ŸÂÛ™<ÎÕ¸Z¿`²‡apÊdLJðì-†˜öüàŒt$F£¶º¦Pún0m4–6ȃü†"ƒ¾e Ú¡—š}‡xtmpdݵ܃f/Ó·mÍŸý²úóQW|wþ+å9‹ûòË/#%%E‰&ÛÇA¤ œh• a)íG~ÍhóíIkôijs[_\1j.5à//æÂ§o$~Kþ™kwJÊKCí Oç>’âBu?¢Ž´cí.ÌûGYk€žQ”ÓöRôMQ£.‡áË¿`e­|„Vév|­dýTäß»êešú5 >ú(fÍzÏ?‡µ/m¡×´²“âŠÃNò4'wú£ÈàÎo-¹xðÁíòr'ˆÓwÔÝO1{ÞZã*ú„‘¦µ.ÙÈðûáE›|­]€Ò:ÆZäf“×`€¹4¥éÝ ,Â|||Q—ùvkeñ¥ØM}Dô•ãv݇I©XúfÝq;®ý«!5㥥ˆlí‰éä¿ýŽ1ö-Ù_úK3ßøQ˜5ä6ÜvãZò !Ë!ýõ÷AÏ+þ„—nwÜpœ îvdÿ9 ¾:·ãøDÉ.Ût¼NVáë€'—ÒwÀIVê9j0ïQì8:› î.ùËÆ<KY€Sû«”×Q.ûö퓾=)¦ kÿ›o¾‰ÿûßnËÒæ…Wþ/Oy“Ö=g..¾µ £È â:ïÿÄC¿+‡…LÀ{KÇP{–ÒyüÍŠ¤` ^Hr«”.à?xh’’þ·Rz_Ú¯a ~Ôkþ%å3„rßß_£Ðóð—ß>€{ƧH9âŠû±â7èD#f‘~ûØmi¸oÉzÜâÆTc9ç…¬²%ÝyG!JÛ8ûuœ'mz$ ðáWãÚ—®¶É–¦ÿ>²¬“dŸ¬òˆ a«ô’ek!^𦦔MÝ@ ûÇ. ÑôûP9k!Y!ÓqüÑÑèw÷óè9à¿ôª·ß‰˜kBѸb™9O‘ÀµSêã:–ãÐshM¥cÙ— 0&À˜`L kˆA£²¦R|^Â7fÌ§ÑÆŽ¥uƤT ÷ꫯJ¿Ê¹tB¼½½¥ÿ¶J¥â§ bŹ˜²ÚFSù|ÌʧGøûùy!÷î18öçõÈ eB„ÝàVÆ5ÿzÛ­xêH§Â‚•J5M>gL€ 0&À˜À¯€À© xÎz¹}NˆOhœÊg4N¿„g¶„SaÁJå™m+. 0&À˜`ç ÅvNÀ< Bpûœ¨ª,•Ox¨¼µ§ÊÎÇž`¥Òjœ† 0&À˜`ç1eC±9 »3G@¬!»™vä¸}:"ä:Ü]ÎÞ^´1MËðøµ;±žRã-ï®ë V*=¡Æi˜`L€ 0&pŸŸhhh@PPÐy\‹óOôÆÆFt£V;rÜ>rî.çÀ~á8þÓ!t£eíî)•‘càïTzŒŽ2&À˜`Làü" ¦TеzâÛ€ÿûßÿ$§GnYÏίšž[ÒŠOfüòË/ÊÎðáÃ¥ï2JŸDQ)3Ü>§Önå\_¼ŸÞ5^dA! ²¯÷¯O¹l&KmõÏfÚaybíÓ5@ú¼Œø\Lg+•¡Åq™`L€ 0&pžJ¥ø/>4_VV†úzù‚çyµÎiñµZ-‚ƒƒ?±‹-M;v6õ˜ÛÇó¦ô„³þp5vÎ}GKKajÒ{^øyšRë€ÞƒâqéãO! _?—}ÓUY©tE‡Ã˜`L€ 0&pÖ0Å"&q,œò{U÷¬WG±øˆ_E™ÇŠ¿Z@n5÷ΞÌÙ=^"Vg™¹Ê™•JWt8Œ 0&À˜` EqQ~/À*žsUxÛÿ®TÚEùu—Ãì Ø2V”&ûÖ3…¯òk ùuu†™32¬T:#ÃþL€ 0&À˜¸À ˆÁ4»3G #%G- ·šˆ{çÌÙ=N¶±:ËÌ6­8f¥RM„Ï™`L€ 0&À˜`LÀmüq"·QqD&À˜`L€ 0&À˜P`¥RM„Ï™`L€ 0&À˜`LÀm¬TºŠ#2&À˜`L€ 0&À˜€š€VíÑÙóúXèÑšŒFé›GMÏñ™`L€ 0&À˜`LàÌŸ¹ñóõEï>} Ñh<à”6ê åÁª* (0Š`Là\ PN÷¦˜ÈÈsA– 0&`G€ïOv8ø„ 0³H …¾U[ßЀƦ&DFEIßRõDœSÒkCà`ô¤ÿ¬Pz‚ŸÓ0&À˜`L€ 0&ÀΡÅôèà  Ô‘nç©;%¥òĉìÞÝÓ²9`L€ 0&À˜`L€ œeþþ8Nº§î””Ja.ópÙ1&À˜`L€ 0&À˜ÀùI@X,M´´ÑSÇ¡§ä8`L€ 0&À˜`L€ €•JîL€ 0&À˜`L€ 0&à1V*=FÇ ™`L€ 0&À˜`L€•JîL€ 0&À˜`L€ 0&à1V*=FÇ ™`L€ 0&À˜`L€•JîL€ 0&À˜`L€ 0&à1V*=FÇ ™`L€ 0&À˜`L€•JîL€ 0&À˜`L€ 0&à1­Ç)ÝLh<¼Ë× !;íw¸(М°±˲נ¹ßȘx)tnæÇј`§ƒÀ±›°¼  ˆGÆ7¢×é(„ódL€ tšÀalüp ë~×NøDe åFFF#ŒM§“GY?lZÕ;›1îÎÛpY?y¹A£0&à&Ó®T‚”Ç…ÙÙ’8Ŷn”H¡l¾‘ý1?·°Réfsq4&ÀNFäÍN)\èÈk1u¸dü— 0³I ±(O¿#£PÐ×}8ÑÍñF¬žy^.‹GÖb¨®ß>»9T™À´‰¬TžÍFå²™ÀHàôOõµ˜m~Ë‹eŒ|O'XñŠŽ`LÀÃxìPŠh|´ËUlcL€ œ1ß|´ÀZVÙ|[k=íèÈW`ù"PŠâ–õë±.'wÇwÂÚÙQ!Θ §_©Ta^8s>ªU~Òiã¬~uÆ]y%’Åÿ´;ñÖ†Ò´ ãM¸ÿÎø+MÿxÎæð9ø¢h'–ÍI3ŸÏÀ'EÖ;mõÎ ¸?ò¹Nä7Ë 8*•ý˜`سé#; µ¹+ðƒí ©Æ"¼4C¹×ÌÁê Ò=éN¼´é€œÎxŸ¼ú|/¢û×ÝsVà€mz»Üù„ 0&à&Æø`³mÜ:¼ÿe©‡‘î_bªeìôV‹ñŽñݳfbá÷"ê÷xŠîWÝPŠï>š§æ,A)M¥ÎX[dW™ï]?˜ßýØô6¦ÞùÞZ±O‰ñ…»óUì1‡7(À_g˜Çd6õ¡¡¤•sç¿L€ üšœA¥2ƒBÚ\<µæMð·µO6bÙï2ðòÇtç¼a6^|f6Օჿ݋եbd¦Ç·eßcý;s±ðG_$Š|ê6ãÑ™÷âÍ¡HŒ'ºïñṲܱ̈o#ýÞ¿áÛºxŒ¿9‰tC}ãá ¼µÓªtRì˜`Dà0V¿#¼ðä¢Å¸/^@ÙNë+›éÆKcg"çû:š®?ããÄË{‡îIe(­m–Ò¿5ižûx;HŤâQ´yn½îÿÛ;¸¬ªüÿÙF` q ? &6.©ÍdÎKñ‡&¥ÖŒæ’Ëh–Kj™Njn© ê¸4bj6j.i?3~¢¿\¦Ð\JFqåP" 8ð@ùÿž{ïóðˆ(Èò9/}î½çžå{Þçr_÷s¿çœ»7Œ¸! ªH;¹š„ žŽm+†kE\Xò‰ååüåO&`Ôœw%MSyÞéNámyÞ™w"~¾Máa®´©/:úº"óÜa\¸x—3EÊ˲qýDxN„‡Ü»ú7Õî]C{OÑ_ŠåžCBâ)l^µ ɽ(ee&îÁ¨7cä…2f>?û.&bàôXôr/$œzC{GXl3WÍ- @à Ps¢²Ëp,‹\ Q½°d!‰WÑr³“°¿µ‹üë¦B®tᨥVmFh:ÑlÀ¦ Œ¼ÁXwèlú Ý´$¹² PŽ­Ñç \0¯Ÿ‚×fwÑÎn^s\ó|š‹ã–H€r.Ä`Ÿ†¡?žìнé÷‹}[Nj÷‹‚äsÚ$ ?ö~°óV~€eý5å©å*·õ›Eo"`8æÎ˜‚IS¦£·vf Ži/Å´þ ÀÈÁÑ-´<ýÆôB›®}g=ˆU÷ñF®^¢^ˆ5ÅÜwcÞŒ·°+rº¼h€Ã­æòæJ<¬²cñÊ·.oä­_ç'Þ€ rÖ£×ì]ù&æmعÚíïÖW/Õ\Tf¾‰í’ã{“µcü SA.̯ésEŸúu ?ý7X«câ/ 44Õ¿P™¨Ü„<ü`]ÿ•·G¼ŠstÏ€~ZûËð×W®ÂõpV^ðôÔoVMÅë)ibƒŸ‚¾š'zË3VŠt0¥à¨Qôî9#Œ‡A£@ѧJ¢Jm $@$  ðÅÎw Ç1mŠ<,:¥_Ts—ÈCœùÅ–qÿ‘³®æÇ)ñs~uLOŸ¸C‹i%ðŽcà†HàŽ XÍõÞ·f*L;Lˆ5 Y/ó¾ŸÞT^¤ëÁËC²qí0@^´0b‹ÇàË"°å?üxzÏEŽx$TT¥Ü“•'ÓxXêÝA‰æè¨={%ˆ2upl‡e+&cæ[«pèÝ9ò_¯²ÛË+°¬ŸQ?7$@ ‰@Íy* ª§Doñ­0$`柕  ƲáLì!¼Qì(Nh~¶³<䟲ì9x¢›‘÷ͪ¬X|~èC,[ Ã3þ\â-%wH€&œ‹Vó•~À…S‡‘ÐT§/<Ìs—Ìïöe(˜¦%ÓñÏsÅo¿<ºêìºÌÆgr¿9{T¼‘·ö ðˆ‡9oÃÄËV“ TÀ?c¬^RÉ C§d˜ªq{Òæ}[ž‰ä½¼±Ÿ“ð‰¶6ÅȈ“%*¶JjoL¬4g–3—Ïé+–y¸ß»ôÕ~DIº0G@2üðâ[â³£‡dhîtýeÿ»Sq4½XÌZUÆ] zN úEeé;™£ŸÌ[z¹Vs"2SñÅþ5²¶žÄÑ|3+•£üCWF‰™{¦bÒšØ1³hcù1²»ŸUZó3šU”C.æOŠW&Œ,qï²)eøëF™¿©î“›çLÅ8m¦ šø¡a3ƒ^/I€ê/F·$TµyIIIðoÕªªÙËæ“7]j~€«ã=º#©òä5ƒƒ+îU‘ef @m$ôÍ7÷æþ$Ãó'õXyØzgC÷4!êÍÁ˜ø(¤Å|99âoÂé…«Xh @- pÏîOæ¶ÜåóÎÝÜ» Œû´Ì½Á- ÔYÚ½Éß¿Jö×ÜB=•1OnHÅg•Ép›4ª<Þän‰§I€*$àè‹‘âˆ}÷^Ø£8iðd      ZDÀßß¿JÖØU)—U&ÿV­¬Ž¸K$@µƒ@Ò7߀÷§ÚÑ´‚H $ÞŸJòà @í  îMU þZUrÌG$@$@$@$@$@$ŠJ^$@$@$@$@$@$@U&@QYetÌH$@$@$@$@$@$@QÉk€H€H€H€H€H€H Ê(*«ŒŽI€H€H€H€H€H€(*y T™À]R¤Ê53# @…îâsâ–[O6jÔ¨.šM›I A ¨lÝÌF’ Ô%JLšŠŠp-5ÿùÏPXXX—Ì¿§¶ÚÛÛã—¿ü%|¼½á`gŠË{Š—…‘À=!@QyO0²   ¸74A)"òRb"¼¼¼àëë ‡{Sx,Åd2áÆ¸”€¶p‘IaY;’&×k5&*×mØPä¸1cÊÄ1‚H€H€H€2%*¯¥¥i‚²E‹ …Öv%¨•¸VAqyÈLJ¢R£Á¨=îûB=Ÿ=Z{hÐ   ¸” Tÿóòòàááq-©}U7kÖL lfTû,¤E$Ðp ÜwQ™på (,îÈ–“ ”$ DÓO?ý„Æ—<ÑÀ”ÇRÍ-U|H€jû.* ËÚuQÐ     ¨,›SYžAœWYÆ“ Ô$‚ä|œÒCžô¼ãj“c>Àå€Aø/?Ç;Ì›ƒ´tÀËÓõó19 Ô÷UT–^¼‡³ö\´„tL{~b_ÞŒ!¸sQéàá ×;Õ“ pò'xúyàóØP•ì «ØZ   ÚJྊÊÚ …v‘ 4,GßšÌà¦hgcÅþ‚ô“øø\ÏíÄúO¼:~8dÿ@ Ú מêP « QŸ\€ƒã9,ßpÝÆLÁä|ú1ìKijš´_ì8¯Ðlšó®–wÚXöBWä\ˆÁò%[Ü4¯Í˜‚ŽžŽ¸!q«WnA|ñüøñïPJôf^À¦5p(1½aâ ¡h&¥$ŸÄ_WnÀ…\É7Hlpí€ð®ž(H?‡õkÖ 6è&ö¿(öSÐw!÷H 6øùçŸk“9´…H€Ê%P+æT–kO @5È9i™ºÙò‰£Lek+HÆò…s:˹`Ö„8äø–-Ž '`_r2OnÁÙt•7®šƒÍé]±.r LK&àÄ 3ë/fe›°åd½ô’¸^x¾{°æµ »ýD`ñOŒ8É9 :vüÆG`Ùôî˜?a ¾È±61óúEB×1xgÅløíœƒÕ'¥ž‚Œz~*LOÇâ)]±zÎB¬NŒ90jà¤wøc.öÚ‘`] ÷I€jó*§•ÝΟ?Æ CNN޶˜Ê§öUœ:W™r~üñGü˜Ÿ_©´•)ï^§©EÝCSH€¬ÐSiƒ»$@ Œ@Î9Œ›Ž]±OÂ5yL.6\•òÑmþ‘Ù5Pà Bàœ‹!ÞG/àÙ¦¢ÓrMpPù´¬JXöÅ;ãŸÔ¼…/NF”äWç¬? àà!.žèø“:ú¹"áƒO€`™ß¥ ÍÑCÒŸBÔ•±”ó«åœß ½°k÷>HR«à‚?üªÖ” ‘ðƒhÓ„tä¸GBðlÕ½¨ìÇ(½ÊK™Ðt:6ŠýÊ;9wóËøíˆ“ÈˆÅZÕÀ] ûG@ ²Ê†·Þz û÷ï×’¿üòËX##T/#´ÿêøÍ7ßT›2!'!ã_x‰æ3M{aÍûo¢ƒŒ˜¨ÞP€ÝßDî›1x!w¢êeÍÒI úPTV[–L$PË \þäMVúæ¯ÝÅ‹â=fz|ˆÅOù•°ÜÃUSŒâTò 0‰óQ©2µÑŤÚуC@€E ÉõÁj”eÎ`lM¹"Šÿ²<äåâМç±Ú¡/æEGѤɲä>LR‚¯µŒµzP hª JU­£ƒŠWµ1 ÔFw2üU GsPûJXª˜h‘‰š°´Yfz B_˜‹§ÞúBáXƒØã1~Àt|øÙ øV«®üróUÚ¦mæFqK$P« pøk­îG$Pü|€½»wcñ[xgA/xôZ€W»‹²»›iÒ<‡¥‹H8|Q“o9ɇ±ß¨QKôyé%­úsK‡`­Ç_pT¦ ˜.|‚Þcç¢×ë“xï\›ŒÝ‡ŽßÇÀI[ñÄ?¦á»µ#1wOw|x(™'Ñûù!ð Ø‹Ì!#‘8k=ŽnDò¡…ùÜß±÷óÑZ")mئâ @ Pc¢’Ÿ ©WM$LÀÑo06ù•àø6ñŽØûùáÄêûÐFÛk‡M´3Îm^0Ç»bæþX¼VPGG«qdް)Ö(Cr…¿µ᳠ćè(éôb¿gÆKœ8KäÕN»âÙ•±ègUnJøƒ¬üzΟIÙ®RÚoõ!²ºØmú&ÎÈ¢C¶Ë³˜Î Z@À,ð*kJEéÕ9[ç5'¡ª@Î[?Ui d‘¸ºâ7“>Åö+×pòÐn?ð¾$–•¤å·PN¼¼¼?Z:Û£Q—¾—d?˜^AâÖ0lýdøH<œŸÀÿFGËTï0Nò8\ü Ÿü cùsÒåè0RrFëõË‘-û$šH àð×:ÐI4‘H ~(+ m´KÔ¤YPŸUq†Ê,Ž´ìYŸSûŽžÁx$}Žx=»¡S·xådYQÖÏ’^äi…åY%ä. À}$ D^eÿ«U[my)•ùj¬:§ÒØ*¯©ŒrØs ¡ä¹ÿ\ÆûöÅÁä|_ö †¼±Ùö­0ìåÉR¢ }P"Uö~þù?(**ÂO7ÿ#Ç2CâÕÆÅþgüôÓOÚ¹ìŒ dæÈ0ZIßó7­ÑªUK<,+ÒΚõ†K­J¶UKÌ ZG€¢²Öu " »%à*^ÏX|~ô>;tgöÏ€_ùšôn+c~ j"`ö.Vf;yòd\¹rÅbIëÖ­¡þ›ƒ:§Ò”-Ë¿›ôppþz$J,ÊÊ9ÿ»|º,cö ºø"å ðÌüièûÄopK¾k^¡Ììá4—©êºuËþXûîaü(B± ó+üiøpœÌqO¦Ü[â±ßýÊÇÜE`ßÔ^÷Tªz+ñ_ÕÁ@$PûÔØð×Ú×tZD$@õ›€££« ¯gýn3[Gõ‰€Y• =ôET*1¹bÅ -ëÔ©S-ñ*Mé2Õ±}«0ü÷ßò0nÒì]`ÔØ´Þùh‘Øåž=©>•SMƒ‚ä7K÷\É)K~Y A%6å8èÕ÷ñÌð?¡_·µÂ›ôÂ||Ðgëb¼4l(ºÏÓ¢1tñVøHú¯ä𖽈KÙg ¨›Ép•ÿ‚“’’àߪUÝl9­&¨×’¾ù†÷§zÝÃl Ô]ÝŸÔc™6z>>¾„§±2­ˆˆÀÕ«W±|ùr¸¸¸hYrssñꫯB Ê3fØ,¦‘L¬üÅ/~¡ý7Izµ>µ‹|É}ºÅ s^µâëºuë´xsœ%‘±£âÕ%êÿò—ø¥Ä«y’f‘§׸Q Å!â±±œ/2ò©b”VéÍu¨­~·nJYzù*¨Z¼š‡iä7‹JužH n ¨¬›ýF«I€H€H€ê9³@«‰fVTWEç¬m³N§D¦­`+Þ:Ÿ­<Œ#¨ý(*kÑB   H€b«v:›Lu”W­TÇ™ðõÞ¿céÚ½HUÓ* ¦4ìY»‘[Ž#¯¢tµð\^^žZ$¼Ö…ëçbéòµø2­ú¬3IÛóª¥xóµ³ – "›¦Ä§mAjÚDFmÓ®åˆÜq¦ü~0‰¥ ½7ý–†ÈñC16â°¥nSÚqŒ:‹§TùšÈŠ?ŒHé»=gÒª\3’ @C% †“òI õZ`»I ¶ ¨¬Tâbôœ>¬ÂÛd(ÌÂÑ'p,z’-"â6yjÁé¼ø-õ⋘°%¾XSÒ„Œ8aöâo ¿d¾JåÅcº´}Ô”-Õð"À|íDáXŠ~A¤ÆnÁôBxµï÷¬K8g´M»vÎâØñK°}™åaÛ±óÅ)¸h\[÷ªßòÎGãX6à×-H–gÐC¼\Ã…ÎA¾•FY:aêÉ­8&}·ýLVéS<& ¸ å©äÿ’ nƒŒ§I€îÕü2æÇèòzÁÎMä\¶ú¹M°äŸi•'ìvUiyoÿs‹Ò*svvÓ·²¤wu…ªÚììV}6Öû¬‰›Ñw÷–€~혯‹<œÞ{V« ´{kù~t\qeæk§\;НC{g=Ûíû­2ÔMø<êˆè‰þ½ {R°çˆHJ·püÖUli¥÷-ÅŠ°BØ»5«t&$ ÐWOUóÕÂ6 :ÅþŸSÈ™H êª]TšRcVD4Ü;uB NsITíÛbüôp$ï^—Ä'cï‰!Óg ÔùŒ¤|ñhF¨çæäÃk±hwÎÀë½l{LþAÍ5Z×eXÞšÕ[Ÿ­|Böhßs4&ŒîwƒåŽ˱xçYÍcÔ3 ö7²ÑlÚzýçÏÇS¾b¥)KgFàzëáX4ZgÅ©Ôó{±rÅN¤hî'Oô8 #»ÙnËõøƒb×N‹]ÃÇaüàn ‡þ¾›èž,{ÏŒ=½ƒÜńØ>w7¼ §>ªì<Z¾ÿ€yóG£ð¸â˜‚GÅ•q$ñ »[GÌ\ü*œÏ,Ǭ­º¸I‹^±Ç[ãõ¥¯"Ð.%Ú"åÌÛïN£1ot'˜Ò¤îF¡o˜ôÙÓpÎKÄÒ¹+jß ¯K½Þ·±9Blî>AYÑØ|˜»~š§ìÅâ%;‘¦llÛ¡öÊ_f„¼l[½ŸÆ]Ó"ìÝÚbÄĉs[Û¼”3X³öï8}M/KåûøÑèë.^ʹ[¡ ÎLß ãã©©óÑ?ÀFã¥à ûþ¥Õ˜7Çú´Æu;‘öž²6H†¸~®™‚G}å C¢õIcÿæØíÑzŸ´4 ³Âœµkìët•&KÇGËÖžø¿¸«Z&K¿-~1 ÿ†Dw/ا‰Te°Ç“£¦cB¯ é¯3xo‹H¯î;¼›þŽ#ïþû’$ yÁF³óâÈWÈ€öƒ»ÃY®ïH¹¾“ÝCÜìœP‰åœ²ëiwD¾±‰…î2c6“?¯¼Äƒ˜µ"JþæGclë8,Ý›ˆÎÃ_ÅÐörR ñ]ý7l?«÷ŸWÛ0L™:~6q˵³v6õÙûtÄø—Æá·êoŽH€ê9µjë?þˆ_ýêWõ¼¥•o^NN~)«Ó2 Ô>Õþú«Ðt)Ùé8$ 1iMड़N…—°fÑ”r¬¼P…騾h9RÝ|aŸ´¸­ø\{ÊÏBÌîÈ–¸æ^å{:qܨ!ƒmÔ„›oHˆØT(ö­Ã‹oÔ†?¦ŽÀ|CPªó©"ÂÎ[éÓ+È›3Œ!ˆJÀ¥¦g#%îJ™a‹×ϬÅTH)…nhßQêAp`õLDžÉ(s5äÅï»t¡k¶ëtÔjŒŠ8.ieèãø)Ø ¥½:‡x ¶8lX4{ó ¤f#õ†Å(d¤]CöµxmÈ®fsö5–T϶PÒ·0û,æË:84‡W3ó*Bλu[¸—óÜîÝ:Hëxa’*edœ9"ý«úl·6$Ó”r§…EšxuÝ*a³ä=µQ¥.~ŠÒ ]Pºµ A@Úˆ3Ã/Äþ…35AéÕ1c†õïñ%ÁѲ8‹ùÊðÖ)3Wh‚2¨ç é"m¿„íK–ãR¡š{š¯­B¸{µ†_y—o×÷:ó³Ø ‚R]ÂÎêz· ¦ô+Ðf(ʵ®½ÀÞ*”H&ý²=:A>ú Šó;a¿ —mÞÜ­Øÿí&¢Ñ³eÙ~k’‡Äôt¤]’a´q7Ñ>ÄGJ/ı‹°46 …ÚpÛ8¹НÕÔØ(m˜kßðŽš-ÒJ|¾ÛÚs©Ê”ëûÒ ”i%íJk†`w¹öÒE˜Æê ùâi»ô+|¼—/ç®áhœê ™·ùâëš tó Aç¶nbg´ =^‹ëFÍŹv¦Éõ®¥\ïíÛÊõ~í,VÍ|‡ªqnmqýÜ# ûG@}bã×-[âßÿþ7®_¿®}ï±!ƒUßµT222à-\ø}Êûwm²f(@µ{*-Û?Õk^oåa }}£<æzb¦x¥uNÄëCçɃv¡ˆ¥ üá 7,;‘gRл{b4=Ñ¡Aå¨K²S™: óó÷h-Wûa‹1KyõDt¼þâ"¤\ÛŠ£iÝexˆ:O\Š×»ÉØ¿ŒX ²ÚJ0–ZYZL“ÒñR×q¿Ièˆaóñǰ×ImÑäÚ%|½×µŽtCh'iC†9ïmln;P”zj]„„†…™½æ‰cQêi‚?ÌžˆÔåëp^<\Ë”(–`ïÓKæiû6ÜpóR¬ Ï®u˜çiŠUŽnj/ ¼æ³6·Ìåôý†+AY~°ö¼k‚Ò:©[7c8¨3íä‰GŒ?¨æ ïF¤E¨•×ožaxÔ0À;¨›\q(T]Ü<ý[±’á°J´z……YþÆRc庤ŞK£Örìrê‰ör-Ï>ƒ=ojÃfᥓ¬j“zö¼~”}ó_×_¶˜O›{Àrln—\¥ÞF‡ž÷›SpK$@õ—€Mj.¥“““&,ÍŸ1oëoË˶L±PÁÌDq1Ç•MÍ ûEà5V±ED•~|,iƒo7͆ô#Ø¥<(öèP2QyG•¬C´ƒnÞTÎ*ÜÀ×¢ÍTP#ó´óéÆ=‰Ì»‚£æçz-•þóõ%]µåeÈpXe©ßHdï‹`cŠå3³"ñѶmعX¼kÃ0b°ò™š’ˆ„DñØXå·Ø%Ôy#Gbèøµ2hÖ\¦É"`3 ²Šùí@Úù+zœ)Cæ¹é»Ö¿öR«DU$z¬ó”Þì$ž) i²ª§Ê:|8þÐVœž²ŠéyB¼ÁÖ…‹§5Ke`Ëf/ñÚ™C¡!j?—Põ‡/Ž›[‰Rag§bÅúõX1k"Ú ßÂkGðÞqý„¹ëm^ün¬AYè)âsý6|´~–öbÃ’Æ@R¦ÿ, ÊîTÜ÷>ð6¨-›U‹Qýd*çÄëi6©Iï·‘Éœ ¼2Òc!£¢µ%CP-—‚) ɉ‰HNÓÿE­Bÿžf¡)s™Ã\Âs©¥Ÿríò’üêBOǧ[huu~¶S¯wˆQ‡Ï ¬—¿…¶­Ç’éã0bØ0tnn¬´ù[HÁõbí¬¹HÍ×ÎÅ ñÇ¡#YA_›Må–H€ê2%šÌ"ªqãÆ0ÿW»†ößÜvµ5 JŠÊº|uÓöúJÀ¬EjQûÜÑw`bd¸žä´³µ@¹kKEuħ«Ïâ´Ìm\šœ/‹*×¾'zø6G–xâ>UCTW¼„‡!ë¸>ç¬tÕ);#y#‰G ¯K“Ò)œñ¸”u@ÊútI vCÆñ2~W˧IDATçPU5 Ou¼‚y3‰ µ—j6•°+RìRõªuкZxáIÈg) O`úâE º‰Æ0ÝPm!]@†ðÎ[›{ù Gù2«´rlˆ¹´è%˜’ÒS¦Ö†ØÚH)n­N²xÎFÄhJÅ EºwUyI‚Aa²¸‹ÊнB›Ä»¥£n=‹ ›Þ‰ì+ð:Âx#Ö­Î6‘>[Ùózb˜,vd¯{ßäØÛËPï*Yyáf’¯œÇ×G¶lD™)q&íК’…)Ó®à“¦¢÷mƒ©¸ïm¸<íͳ"Åé광¤<ëµxó ÅS¦¡ÇÀIâ¥YK¿Míj俆ùâ™°f8¥_Ÿ¾a2gòJ¦+¯}6nê)+¼ÊÅ(+¼v6œãH‹-㹬Рãd@Oapd«q䃾m›—ÉæÜº+|¥ÏS®íÄ¢åÙømó ™;ªVµÇÌîݱgÂLæ„ÎZ±A¥®€l|*ó‰UÚGCÌÆ–©‚$@$Po˜…eCôN–׉“å‘a< Ü5ç©,#¸J7¾ØõâÝ=ÜâIzòYc…ÊÒÉmW²ŽÝ^ÅÜajQ’BY$&JDž|òÀó ,Œ­yïüžž1e¼«œ?&çÏËüO]²è6†G{m7Ç” lÛVÓ&%M2ÒJY“{Šà*¼&âR”^‡!r´xm$‰®—U]€µ]Z½âõs ÇŠqä¬&DÎBgOR×NàSy«eUÏyÛÞáÑV¯3þ„¬ÜÙ¤­¾(R)ËôZu%~œe~bg­‘…²xJ<,#BK¤2ÈØj jH¤(#ïöf1ㆾjè«*¶ÙH¤q0ï;ø>…£ôcRä» 1¢#ÌzQ ²K§ŠwÒi2DzñYlIDˆoωb5Ï\–y«†göõ‘–gÇaÍ’%Ø.Î\½?Ó‘|Cü…ξ2ÔYïƒlYp&ÍÚSf.ÄØÞIߗʪõ„C@'**A†Š&WP­¼j¼kgc1BYüêËô<1½¼~sƒo³+øT¥r»… ü§„µq4³ÇÍxýeJÌÕ^Hº‹Ñº0ìkñ\–¶¤ä±ùzrð• :TØw,^EÖ’Z%t•ˆ—NDì§œ6¥¬üDŸ‡÷ޤÔ88VH$@$@$@$@$@$Ô9Q {{K¿ywê u”}tVZ°p‡H€H€H€H€H€jŒ€]MÔdÊˆÇæu«s)[«Î«mÆNŽ`ù6»)å0fEDýS'´H;®§±o‹ñÓѼ{\*ì=1dú ôj^Ò\w_ôU“ÏÓ¡·×½ýŒ|ÉÊxD$@$@$@$@$@$Pš@õ{*óâ1}Ê"M,Úû„ ½§=Ò.Ecþ„7` M7’ŽóG¢“Ö^ÊõXx k-A)ÇnQ˜Ží‹–ké!S• hŽö]Ý´½´,ã„vÄ      š Pí¢2!j Ò¤%ömGaãÛ30kY$†ø¨¦]ÃöãêŒìŸÀê5˰rñ(C0zbæú5X¹fd椄BŠntŒ­·ah³ëʋW¬ÊÒÎð‡H€H€H€H€H€H º T»¨´4 YsèƒSñh7MU"5=ËrÍÜ¡ÉÄf¾ðR±ž Exá·žÅÉÔžƒ^P‰È¬lz*Ká Ô•†Ø»yÓÒœ„KéÚ¾»Új–Ów(›èøyÊM      %Pí õö|öQ«Qxv5^_{Á7Ïã@œŽnÚ3H;{W N3ªŸ¯> ö® cf      ;"PýžÊæÝ9k˜6¤5åD4œ/¥½ÆÌ[ŠGõi‘ºÁ†Ç±|ë­¼š–Diø\¨žxÔ˺0Kî T#F·$Tµü¤¤$ø·jUéìyyú×$ï4¥ìŰ™;ŸQØúv/cÎf¥ÍaB zL é›oîèþTQ°i$@µŒïOµ¬Ch €F@»7ùûW‰Fµµ¶ê^‰Is™_lA)á™ÑÝ((ÍP¸%     $P£¢ò^·+`Ð,Ì…;‚õebïuñ,H€H€H€H€H€Hà6ê´¨ô‚÷mÈÓ$@$@$@$@$@$@ÕG úê©>ÛY2 À}&@QyŸ;€Õ“ @]&@QY—{¶“ À}&@QyŸ;€Õ“ @]&@QY—{¶“ À}&Ðxž„ªÚðïÿ¸¹U5û=Ê—3Q»ðÑáÏ‘œí€‡j ¥”“ÏEaëGÑ8—œ¿‡B“ÛÉçüxlÚú5Z?úñÍË\DïØ‹¢ÖAh&k䥯aûG{pü\2|üñ`;䧞ÎvKÜ54ñ D ©ÄVº -JÅžöâä×q8s.¦ñkw)+å¶îüGÏ&ÃÉïa)ßlpI;¾‹Áö]Ñ8“”¥]¿2'+Q 9;¾*BÈCÍüÅ.ÍñÁÃÍJ¥R‡Åå»§JÙ' Ò¦¹tzTQŠ4ŠÝ¦3hÝ! Ä÷BË«÷Ûs±ûNáë çðõUnë [Ë_‰Ù/ŠüØLÙ-ý|Púùêç&ÒÏÍq­œvÙê[ º…wÞ߃¬­p=z36žÈÀ£þØô·÷qòß-ðX›f¶²•Š+kW9]R*«›À¿ü±ÜŸª»•,ŸH .àý©.öm&úO@»7=ð@•ZçŸÿµû“Ö Y§÷aé"¨>ÁæèkøÝÐçà—{‘»â-pŠòó-ûæ=ªߥ}"#2ùàû8•”€|-â*VoÞ‡¢ èᛋO×oÅ·E*.é…^Aùøxý.‘g6Ò™+1o³âñ¯T;têÖ ];„àA7‘S"ÊVn‹†ûÏ!Ì_•¿G$”¬í(J9ˆ÷÷Å¡]¯0øåŸÆûïǘK-µ;>>«ßhñ7®"KkG‘¥}æ Öåçg¦Š`Ó èí6§*ÞÚLS”o¯gk‰Š¬2–Woò9éïø]çÇðh›–e¥æ[7¬Â¶ÓIÈ*Ðí¹¢ú9Þýú÷@þé=Ø— ›åÛìÃþ¢"£?õce‡KçÁè牣ל1ôOá°»‡ïÝ;cLx=‘´M·À(Cm¬âlÙeIi•ÎÇ    ¨gl9ˆêPóq%!¾}úˆçÊ ~ýâ°ìÜ÷ø—]<~ §´Lø…Ž@×–-E¤`ëªm¸ªé¬xö•1h‡ËØù1R2šÜ8P8â±Ûï·Æ’MÊσÛ#üx8Á‡OoÆ·¢¡üÚöBÿÇD|¤\“D™¢5l¤Ë~íb”/›\®RKÖ÷IpjÙíÄK™w²ß8¹>¿Ç„ÎÞR„Rväçåãmü®ÍC€¤?µþ{dI2w•Ö*DoØ»Îp6ä‹RbÖaþ>]d>Ôk†=&_ø,U>ì\ðÓµüeñ>±pöÔÁJŠ>[iúÙ‰½)Xû×ÅÈÓ2âO“Ÿ“âlÕëŒäLI䛊ï2[¢Ýc¾%ËWípvÆÃž€Sl rÍŠÎÉ=Ÿ Gk1ûŠðD›å‡Øè)òÌ®UØ/׊V|`ÂÏašt|Ú6Ì?­Ec[ä @½†}ñð๭8rMÏÓñÙWЯþ±u]‰¸‡mØ¥¼ª¥ÓõkcuèÕñ—H€H€H€H€ê:î©!éÝ)'NàFV öÄ\ÃOâÊÍ/@æWûp4>»¶mÆšöìÀUï0Ì9Ïøgâã}ñ¸°o¾óí§ÅMèåÉ,2ì† ƒGØ„¡øµ’Üê¿S&Ná$CE7lF¶[gtòxÏ x É×aÑ6Q%^>p±•Î¥¹YYÈRÿE!%'|/JU¼lÙÙˆÞ¹¾L•xñKfžÆ®Ø8ücÏNñ¬ž“JËÚá4‰2½ŒµëOís7¸K{µ²¥|%Àn|¹ _;õÁK¿÷–¶èï dp­”çפí³FtÆÕû成åÃN)8ñ¾2S˜ …KR4ö\ÎG¾Ùþ,)ÇVšÿ+”~‚_ŸW$ßttqJÀÖO.KI6êͽ†oEËåg‹w3þ V-¯¯XjÍÈÎý!têЋ׬)[‡Àï¼Å»x Ne­[ºØ.ßÎF¿ä_Æ…,LPíôä%Ä£eŸ1xÊÇÞabó«ap„&¿6“Ã|ÐØ+ ýLjxl‰É’gr?œý8Y—÷”‰ûµ »òm¤S$H€H€H€H€H >¨ãžJ ÝàqøvÇN¬]—€Ý£±“ˆ y‚oÙãE¼ô¸Ì‰»ú æï<ŠDgQ2y1X¡un™‚‹ß‹ Ôúµ™ˆÇÃq’|³øÜZ¢õ¹¸R‡¬ÏÎáá>à”ú%Vn>Œ"ŸÎxmL¨»2Ö­ûŒÃÜß_ÆÊåãäÇѵ d:;5ßpÌÛ“Z\ÚöÇ”þ“ÑÎNÇæ½"*D'¢E/üyäc²sËï¾ÿ޵iGÖ—;ðþá$øv„—B}%C]ÿ)’®1‚ÃCqåpšuÎÂáØ|Ÿ‹3)þçÀñ±Jð~mÇáòî²ílå-Î9k }ñ;GœÌþч·à|®ÔàòÆ>a#Íÿ‘‚Ýðhîëñ„?Î|™"iféz³ŠB0mzt¿Ç¦Å‘øöûìQs¥‘ÎÌ‚RíÝHý.Þm0QDÞ¿¢VaçÁ/ñŒ¸sK—Ÿy]ú¥I©~É}»ŸÀÚˆ¿ÂÉI¼¤†GÚrñk;ú‘Ñ5ø6A”+²±:"B³¥±£7â.©!¾Öq^¸ø©þ%íz°eÙ¼ßÊ5ÙZs?kÅñ‡H€H€H€H€ê Ësu]mÑ¿d‘ž¬À¡˜=ØvýÑá.Ã'o\KDTÞø^ð½áßò[\u~ÓÂEˆÅGaÇU;ÅaO\‚¬¸8mXêmž@—läfçj~¾¢üL™x»DP¶–!ýaŒEߟÀû;³Å“5îNNZZ%W—J§ãÄ™ºHRŒÿ±n1.<2B¯7’¯Š×RλxÈxÎÌkâ} .7’'òÏóáÎèâ^ÒŽI±ùp¦ ݉vÆhJ—6á˜=3\ï>ñ>žü®³ˆ³<)Ku­ˆ12«<®Ê;—‹‡¤üs¸,úºw ´ó’åg7Òâ_ù}ÐÎIÊJ*€KȯРô×kv;Ta%Ó´U édãë˹h-|¾þ2IÔ¡GQ…¥ëmw3‹Ö¤âOÂíײhÑ Éiç\’‘Q•Õ¦G·nÆßÇK¹‹0;¥ J–.¿m¶ô˦’ýòÓ7qDºyòÌ× #š¿Y‰¾Ò¡Xª3¿~Hú$ÉžÜGDy<6lMAP >‘^w‰mÆéRvÙÊû eià<&   ¨'ä±¼n¿_âúæGK;û`Äkmà'ú ™xçËÜ@Q”ø¯¡hïáØå;1ÿŸª½Žè24í<úãï˜Ó©ü²‚kÐcð×4  ÉŒ¿&C:Cáž°K$dm$Îÿ$jxöÕèâ‰U‹˱øédž^»ë_áˆì—L7Nš–DûéÖÿ!¹óªC5·S¼ .Aèâ± 5ÊRs;>"“QiŠíÈÝ÷W9.ÀžÈ|¬ìyž¯É°\C_ŠÊj†®¡âEU¹.gãÊé@„5Ù $XÊ÷í1ÝõÕÒY—ï•´KåÄžå‹ñ±ì9úôÀ0Ãûh$–ºdJ¥iㆠûãÊé…Gõð äîQ^á’õ¶ój‰,ŸH¼o´µåoú£“¥’¼TÐ/P'ôÜY3Zƒù‡U7ü׋âÕ=ª&C–,¿}Û–H?U²_~ûˆŠ®2ˆŽ,mqRÊTéÉÆæ?;؉hu ê‡._DbÑ⯴ôÞânÔ²L\ÿP§2v95 )“®‚&jåó‡H€H€H€H€ê*F·$TÕø¤¤$ø·jUÕì÷0Ÿ/™—èâRòÑ=_âÔb.f¹ *ÌÏž¤+'Ë¿:‰·±JAæ4Jn'w–;ßFEù¹2dÔåŽËªtÍ•¶UVI•áššG°ÜÂm§±Õ.µZjFRA¾Ý½áf³|uæËÊ´Z›d‘Ÿ"“•é2[}b+ÎV»m¥+'OÜsIß|SKîO÷¼i,H Žàý©Žw Í'zJ@»7ùûW©u•y®®RÁ5›É®Œ Tõ;‰ ,œJ O-]U¥Ê¬„QéJ*qlKÄÚ©ù •È[å$•¶Uxßê·Uñwß}wß™ÜÏ>QLÔËŽëׯC]¯<ð…¥­ …q$@$PÇpNeë0šK$@¶¨tõ?77-Z´¸ï‚Ò–Œ#E@½ìPרºVÍ×-É Ôm•u»ÿh= X¨ôŸþ™‚ÒB„;µ•€–Êc©®Y  ºO€¢²î÷![@$@> óB¨kxÍÖµ£½$@$`›E¥m.Œ%     ¨ŠÊJ@b      Û(*msa, @%PTV“ Ø&@Qi› cI€H€H€H€H€H€*A€¢²˜„H€H€H€H€H€HÀ6ŠJÛ\K$@$@$@$@$@$P ••€Ä$$@$@$@$@$@$@¶ üÖ&;â£ÒIEND®B`‚graphene-mongo-0.4.1/graphene_mongo/tests/models.py000066400000000000000000000137471453504055300224310ustar00rootroot00000000000000from datetime import datetime import mongoengine import mongomock from mongomock import gridfs gridfs.enable_gridfs_integration() mongoengine.connect( "graphene-mongo-test", mongo_client_class=mongomock.MongoClient, alias="default" ) # mongoengine.connect('graphene-mongo-test', host='mongodb://localhost/graphene-mongo-dev') class Publisher(mongoengine.Document): meta = {"collection": "test_publisher"} name = mongoengine.StringField() @property def legal_name(self): return self.name + " Inc." def bad_field(self): return None class Editor(mongoengine.Document): """ An Editor of a publication. """ meta = {"collection": "test_editor"} id = mongoengine.StringField(primary_key=True) first_name = mongoengine.StringField( required=True, help_text="Editor's first name.", db_field="fname" ) last_name = mongoengine.StringField(required=True, help_text="Editor's last name.") metadata = mongoengine.MapField( field=mongoengine.StringField(), help_text="Arbitrary metadata." ) company = mongoengine.LazyReferenceField(Publisher) avatar = mongoengine.FileField() seq = mongoengine.SequenceField() class Article(mongoengine.Document): meta = {"collection": "test_article"} headline = mongoengine.StringField(required=True, help_text="The article headline.") pub_date = mongoengine.DateTimeField( default=datetime.now, verbose_name="publication date", help_text="The date of first press.", ) editor = mongoengine.ReferenceField(Editor) reporter = mongoengine.ReferenceField("Reporter") # Will not convert these fields cause no choices # generic_reference = mongoengine.GenericReferenceField() # generic_embedded_document = mongoengine.GenericEmbeddedDocumentField() class EmbeddedArticle(mongoengine.EmbeddedDocument): meta = {"collection": "test_embedded_article"} headline = mongoengine.StringField(required=True) pub_date = mongoengine.DateTimeField(default=datetime.now) editor = mongoengine.ReferenceField(Editor) reporter = mongoengine.ReferenceField("Reporter") class EmbeddedFoo(mongoengine.EmbeddedDocument): meta = {"collection": "test_embedded_foo"} bar = mongoengine.StringField() class Reporter(mongoengine.Document): meta = {"collection": "test_reporter"} id = mongoengine.StringField(primary_key=True) first_name = mongoengine.StringField(required=True) last_name = mongoengine.StringField(required=True) email = mongoengine.EmailField() awards = mongoengine.ListField(mongoengine.StringField()) articles = mongoengine.ListField(mongoengine.ReferenceField(Article)) embedded_articles = mongoengine.ListField( mongoengine.EmbeddedDocumentField(EmbeddedArticle), ) embedded_list_articles = mongoengine.EmbeddedDocumentListField(EmbeddedArticle) generic_reference = mongoengine.GenericReferenceField(choices=[Article, Editor], required=True) generic_embedded_document = mongoengine.GenericEmbeddedDocumentField( choices=[EmbeddedArticle, EmbeddedFoo] ) generic_references = mongoengine.ListField( mongoengine.GenericReferenceField(choices=[Article, Editor]) ) class Player(mongoengine.Document): meta = {"collection": "test_player"} first_name = mongoengine.StringField(required=True) last_name = mongoengine.StringField(required=True) opponent = mongoengine.ReferenceField("Player") players = mongoengine.ListField(mongoengine.ReferenceField("Player")) articles = mongoengine.ListField(mongoengine.ReferenceField("Article")) embedded_list_articles = mongoengine.EmbeddedDocumentListField(EmbeddedArticle) class Parent(mongoengine.Document): meta = {"collection": "test_parent", "allow_inheritance": True} bar = mongoengine.StringField() loc = mongoengine.MultiPolygonField() class CellTower(mongoengine.Document): meta = {"collection": "test_cell_tower"} code = mongoengine.StringField() base = mongoengine.PolygonField() coverage_area = mongoengine.MultiPolygonField() class Child(Parent): meta = {"collection": "test_parent"} baz = mongoengine.StringField() loc = mongoengine.PointField() class AnotherChild(Parent): meta = {"collection": "test_parent"} qux = mongoengine.StringField() loc = mongoengine.PointField() class ProfessorMetadata(mongoengine.EmbeddedDocument): meta = {"collection": "test_professor_metadata"} id = mongoengine.StringField(primary_key=False) first_name = mongoengine.StringField() last_name = mongoengine.StringField() departments = mongoengine.ListField(mongoengine.StringField()) class ProfessorVector(mongoengine.Document): meta = {"collection": "test_professor_vector"} vec = mongoengine.ListField(mongoengine.FloatField()) metadata = mongoengine.EmbeddedDocumentField(ProfessorMetadata) class ParentWithRelationship(mongoengine.Document): meta = {"collection": "test_parent_reference"} before_child = mongoengine.ListField( mongoengine.ReferenceField("ChildRegisteredBefore"), ) after_child = mongoengine.ListField( mongoengine.ReferenceField("ChildRegisteredAfter"), ) name = mongoengine.StringField() class ChildRegisteredBefore(mongoengine.Document): meta = {"collection": "test_child_before_reference"} parent = mongoengine.ReferenceField(ParentWithRelationship) name = mongoengine.StringField() class ChildRegisteredAfter(mongoengine.Document): meta = {"collection": "test_child_after_reference"} parent = mongoengine.ReferenceField(ParentWithRelationship) name = mongoengine.StringField() class ErroneousModel(mongoengine.Document): meta = {"collection": "test_colliding_objects_model"} objects = mongoengine.ListField(mongoengine.StringField()) class Bar(mongoengine.EmbeddedDocument): some_list_field = mongoengine.ListField(mongoengine.StringField(), required=True) class Foo(mongoengine.Document): bars = mongoengine.EmbeddedDocumentListField(Bar) graphene-mongo-0.4.1/graphene_mongo/tests/nodes.py000066400000000000000000000045451453504055300222520ustar00rootroot00000000000000import graphene from graphene.relay import Node from . import models from . import types # noqa: F401 from .models import ProfessorMetadata from ..types import MongoengineObjectType class PublisherNode(MongoengineObjectType): legal_name = graphene.String() bad_field = graphene.String() class Meta: model = models.Publisher only_fields = ("id", "name") interfaces = (Node,) class ArticleNode(MongoengineObjectType): class Meta: model = models.Article interfaces = (Node,) class EditorNode(MongoengineObjectType): class Meta: model = models.Editor interfaces = (Node,) class EmbeddedArticleNode(MongoengineObjectType): class Meta: model = models.EmbeddedArticle interfaces = (Node,) class PlayerNode(MongoengineObjectType): class Meta: model = models.Player interfaces = (Node,) filter_fields = {"first_name": ["istartswith", "in"]} class ReporterNode(MongoengineObjectType): class Meta: model = models.Reporter interfaces = (Node,) class ParentNode(MongoengineObjectType): class Meta: model = models.Parent interfaces = (Node,) class ChildNode(MongoengineObjectType): class Meta: model = models.Child interfaces = (Node,) class ChildRegisteredBeforeNode(MongoengineObjectType): class Meta: model = models.ChildRegisteredBefore interfaces = (Node,) class ChildRegisteredAfterNode(MongoengineObjectType): class Meta: model = models.ChildRegisteredAfter interfaces = (Node,) class ParentWithRelationshipNode(MongoengineObjectType): class Meta: model = models.ParentWithRelationship interfaces = (Node,) class ProfessorMetadataNode(MongoengineObjectType): class Meta: model = ProfessorMetadata interfaces = (graphene.Node,) class ProfessorVectorNode(MongoengineObjectType): class Meta: model = models.ProfessorVector interfaces = (Node,) class ErroneousModelNode(MongoengineObjectType): class Meta: model = models.ErroneousModel interfaces = (Node,) class BarNode(MongoengineObjectType): class Meta: model = models.Bar interfaces = (Node,) class FooNode(MongoengineObjectType): class Meta: model = models.Foo interfaces = (Node,) graphene-mongo-0.4.1/graphene_mongo/tests/nodes_async.py000066400000000000000000000050201453504055300234340ustar00rootroot00000000000000import graphene from graphene.relay import Node from . import models from . import types # noqa: F401 from .models import ProfessorMetadata from ..types_async import AsyncMongoengineObjectType class PublisherAsyncNode(AsyncMongoengineObjectType): legal_name = graphene.String() bad_field = graphene.String() class Meta: model = models.Publisher only_fields = ("id", "name") interfaces = (Node,) class ArticleAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Article interfaces = (Node,) class EditorAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Editor interfaces = (Node,) class EmbeddedArticleAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.EmbeddedArticle interfaces = (Node,) class PlayerAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Player interfaces = (Node,) filter_fields = {"first_name": ["istartswith", "in"]} class ReporterAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Reporter interfaces = (Node,) class ParentAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Parent interfaces = (Node,) class ChildAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Child interfaces = (Node,) class ChildRegisteredBeforeAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.ChildRegisteredBefore interfaces = (Node,) class ChildRegisteredAfterAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.ChildRegisteredAfter interfaces = (Node,) class ParentWithRelationshipAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.ParentWithRelationship interfaces = (Node,) class ProfessorMetadataAsyncNode(AsyncMongoengineObjectType): class Meta: model = ProfessorMetadata interfaces = (graphene.Node,) class ProfessorVectorAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.ProfessorVector interfaces = (Node,) class ErroneousModelAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.ErroneousModel interfaces = (Node,) class BarAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Bar interfaces = (Node,) class FooAsyncNode(AsyncMongoengineObjectType): class Meta: model = models.Foo interfaces = (Node,) graphene-mongo-0.4.1/graphene_mongo/tests/test_converter.py000066400000000000000000000274171453504055300242130ustar00rootroot00000000000000import graphene import mongoengine from pytest import raises from .models import ( Article, Editor, EmbeddedArticle, EmbeddedFoo, Player, Reporter, ProfessorMetadata, ProfessorVector, Publisher, ) from .. import registry from .. import advanced_types from ..converter import convert_mongoengine_field from ..fields import MongoengineConnectionField from ..types import MongoengineObjectType def assert_conversion(mongoengine_field, graphene_field, *args, **kwargs): field = mongoengine_field(*args, **kwargs) graphene_type = convert_mongoengine_field(field) assert isinstance(graphene_type, graphene_field) field = graphene_type.Field() return field def test_should_unknown_mongoengine_field_raise_exception(): with raises(Exception) as excinfo: convert_mongoengine_field(None) assert "Don't know how to convert the MongoEngine field" in str(excinfo) def test_should_email_convert_string(): assert_conversion(mongoengine.EmailField, graphene.String) def test_should_string_convert_string(): assert_conversion(mongoengine.StringField, graphene.String) def test_should_url_convert_string(): assert_conversion(mongoengine.URLField, graphene.String) def test_should_uuid_convert_id(): assert_conversion(mongoengine.UUIDField, graphene.ID) def test_sould_int_convert_int(): assert_conversion(mongoengine.IntField, graphene.Int) def test_sould_long_convert_int(): assert_conversion(mongoengine.LongField, graphene.Int) def test_sould_sequence_convert_field(): assert_conversion(mongoengine.SequenceField, graphene.Int) def test_should_object_id_convert_id(): assert_conversion(mongoengine.ObjectIdField, graphene.ID) def test_should_boolean_convert_boolean(): assert_conversion(mongoengine.BooleanField, graphene.Boolean) def test_should_decimal_convert_decimal(): assert_conversion(mongoengine.DecimalField, graphene.Decimal) def test_should_float_convert_float(): assert_conversion(mongoengine.FloatField, graphene.Float) def test_should_decimal128_convert_decimal(): assert_conversion(mongoengine.Decimal128Field, graphene.Decimal) def test_should_datetime_convert_datetime(): assert_conversion(mongoengine.DateTimeField, graphene.DateTime) def test_should_dict_convert_json(): assert_conversion(mongoengine.DictField, graphene.JSONString) def test_should_map_convert_json(): assert_conversion(mongoengine.MapField, graphene.JSONString, field=mongoengine.StringField()) def test_should_point_convert_field(): graphene_type = convert_mongoengine_field(mongoengine.PointField()) assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == advanced_types.PointFieldType assert isinstance(graphene_type.type.type, graphene.String) assert isinstance(graphene_type.type.coordinates, graphene.List) def test_should_polygon_covert_field(): graphene_type = convert_mongoengine_field(mongoengine.PolygonField()) assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == advanced_types.PolygonFieldType assert isinstance(graphene_type.type.type, graphene.String) assert isinstance(graphene_type.type.coordinates, graphene.List) def test_should_multipolygon_convert_field(): graphene_type = convert_mongoengine_field(mongoengine.MultiPolygonField()) assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == advanced_types.MultiPolygonFieldType assert isinstance(graphene_type.type.type, graphene.String) assert isinstance(graphene_type.type.coordinates, graphene.List) def test_should_file_convert_field(): graphene_type = convert_mongoengine_field(mongoengine.FileField()) assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == advanced_types.FileFieldType def test_should_field_convert_list(): assert_conversion(mongoengine.ListField, graphene.List, field=mongoengine.StringField()) def test_should_geo_convert_list(): assert_conversion(mongoengine.GeoPointField, graphene.List, field=mongoengine.FloatField()) def test_should_reference_convert_dynamic(): class E(MongoengineObjectType): class Meta: model = Editor interfaces = (graphene.Node,) dynamic_field = convert_mongoengine_field(EmbeddedArticle._fields["editor"], E._meta.registry) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == E def test_should_lazy_reference_convert_dynamic(): class P(MongoengineObjectType): class Meta: model = Publisher interfaces = (graphene.Node,) dynamic_field = convert_mongoengine_field(Editor._fields["company"], P._meta.registry) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == P def test_should_embedded_convert_dynamic(): class PM(MongoengineObjectType): class Meta: model = ProfessorMetadata interfaces = (graphene.Node,) dynamic_field = convert_mongoengine_field( ProfessorVector._fields["metadata"], PM._meta.registry ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == PM def test_should_convert_none(): registry.reset_global_registry() dynamic_field = convert_mongoengine_field( EmbeddedArticle._fields["editor"], registry.get_global_registry() ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert graphene_type is None def test_should_convert_none_lazily(): registry.reset_global_registry() dynamic_field = convert_mongoengine_field( Editor._fields["company"], registry.get_global_registry() ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert graphene_type is None def test_should_list_of_reference_convert_list(): class A(MongoengineObjectType): class Meta: model = Article graphene_field = convert_mongoengine_field(Reporter._fields["articles"], A._meta.registry) assert isinstance(graphene_field, graphene.List) dynamic_field = graphene_field.get_type() assert dynamic_field._of_type == A def test_should_list_of_generic_reference_covert_list(): class A(MongoengineObjectType): class Meta: model = Article class E(MongoengineObjectType): class Meta: model = Editor class R(MongoengineObjectType): class Meta: model = Reporter generic_references_field = convert_mongoengine_field( Reporter._fields["generic_references"], registry.get_global_registry() ) assert isinstance(generic_references_field, graphene.List) field = generic_references_field.get_type() assert field._of_type._meta.types == (A, E) def test_should_list_of_embedded_convert_list(): class E(MongoengineObjectType): class Meta: model = EmbeddedArticle graphene_field = convert_mongoengine_field( Reporter._fields["embedded_articles"], E._meta.registry ) assert isinstance(graphene_field, graphene.List) dynamic_field = graphene_field.get_type() assert dynamic_field._of_type == E def test_should_embedded_list_convert_list(): class E(MongoengineObjectType): class Meta: model = EmbeddedArticle graphene_field = convert_mongoengine_field( Reporter._fields["embedded_list_articles"], E._meta.registry ) assert isinstance(graphene_field, graphene.List) dynamic_field = graphene_field.get_type() assert dynamic_field._of_type == E def test_should_self_reference_convert_dynamic(): class P(MongoengineObjectType): class Meta: model = Player interfaces = (graphene.Node,) dynamic_field = convert_mongoengine_field(Player._fields["opponent"], P._meta.registry) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() assert isinstance(graphene_type, graphene.Field) assert graphene_type.type == P graphene_field = convert_mongoengine_field(Player._fields["players"], P._meta.registry) assert isinstance(graphene_field, MongoengineConnectionField) def test_should_list_of_self_reference_convert_list(): class A(MongoengineObjectType): class Meta: model = Article class P(MongoengineObjectType): class Meta: model = Player graphene_field = convert_mongoengine_field(Player._fields["players"], P._meta.registry) assert isinstance(graphene_field, graphene.List) dynamic_field = graphene_field.get_type() assert dynamic_field._of_type == P def test_should_description_convert_common_metadata(): class A(MongoengineObjectType): class Meta: model = Article headline_field = convert_mongoengine_field(Article._fields["headline"], A._meta.registry) assert headline_field.kwargs["description"] == "The article headline." pubDate_field = convert_mongoengine_field(Article._fields["pub_date"], A._meta.registry) assert pubDate_field.kwargs["description"] == "Publication Date\nThe date of first press." firstName_field = convert_mongoengine_field(Editor._fields["first_name"], A._meta.registry) assert firstName_field.kwargs["description"] == "Editor's first name.\n(fname)" metadata_field = convert_mongoengine_field(Editor._fields["metadata"], A._meta.registry) assert metadata_field.kwargs["description"] == "Arbitrary metadata." def test_should_description_convert_reference_metadata(): class A(MongoengineObjectType): class Meta: model = Article class E(MongoengineObjectType): class Meta: model = Editor editor_field = convert_mongoengine_field(Article._fields["editor"], A._meta.registry).get_type() assert editor_field.description == "An Editor of a publication." def test_should_generic_reference_convert_union(): class A(MongoengineObjectType): class Meta: model = Article class E(MongoengineObjectType): class Meta: model = Editor class R(MongoengineObjectType): class Meta: model = Reporter generic_reference_field = convert_mongoengine_field( Reporter._fields["generic_reference"], registry.get_global_registry() ) assert isinstance(generic_reference_field, graphene.Field) if not Reporter._fields["generic_reference"].required: assert isinstance(generic_reference_field.type(), graphene.Union) assert generic_reference_field.type()._meta.types == (A, E) else: assert issubclass(generic_reference_field.type.of_type, graphene.Union) assert generic_reference_field.type.of_type._meta.types == (A, E) def test_should_generic_embedded_document_convert_union(): class D(MongoengineObjectType): class Meta: model = EmbeddedArticle class F(MongoengineObjectType): class Meta: model = EmbeddedFoo class A(MongoengineObjectType): class Meta: model = Article class E(MongoengineObjectType): class Meta: model = Editor class R(MongoengineObjectType): class Meta: model = Reporter generic_embedded_document = convert_mongoengine_field( Reporter._fields["generic_embedded_document"], registry.get_global_registry() ) assert isinstance(generic_embedded_document, graphene.Field) assert isinstance(generic_embedded_document.type(), graphene.Union) assert generic_embedded_document.type()._meta.types == (D, F) graphene-mongo-0.4.1/graphene_mongo/tests/test_fields.py000066400000000000000000000037601453504055300234450ustar00rootroot00000000000000import pytest from . import nodes, nodes_async from .. import AsyncMongoengineConnectionField from ..fields import MongoengineConnectionField def test_article_field_args(): field = MongoengineConnectionField(nodes.ArticleNode) field_args = {"id", "headline", "pub_date"} assert set(field.field_args.keys()) == field_args reference_args = {"editor", "reporter"} assert all(item in set(field.advance_args.keys()) for item in reference_args) default_args = {"after", "last", "first", "before"} args = field_args | reference_args | default_args assert set(field.args) == args def test_reporter_field_args(): field = MongoengineConnectionField(nodes.ReporterNode) field_args = {"id", "first_name", "last_name", "email", "awards"} assert set(field.field_args.keys()) == field_args def test_editor_field_args(): field = MongoengineConnectionField(nodes.EditorNode) field_args = {"id", "first_name", "last_name", "metadata", "seq"} assert set(field.field_args.keys()) == field_args def test_field_args_with_property(): field = MongoengineConnectionField(nodes.PublisherNode) field_args = ["id", "name"] assert set(field.field_args.keys()) == set(field_args) def test_field_args_with_unconverted_field(): field = MongoengineConnectionField(nodes.PublisherNode) field_args = ["id", "name"] assert set(field.field_args.keys()) == set(field_args) @pytest.mark.asyncio async def test_default_resolver_with_colliding_objects_field_async(): field = AsyncMongoengineConnectionField(nodes_async.ErroneousModelAsyncNode) connection = await field.default_resolver(None, {}) assert 0 == len(connection.iterable) @pytest.mark.asyncio async def test_default_resolver_connection_list_length_async(fixtures): field = AsyncMongoengineConnectionField(nodes_async.ArticleAsyncNode) connection = await field.default_resolver(None, {}, **{"first": 1}) assert hasattr(connection, "list_length") assert connection.list_length == 1 graphene-mongo-0.4.1/graphene_mongo/tests/test_inputs.py000066400000000000000000000050541453504055300235170ustar00rootroot00000000000000import graphene import pytest from graphene.relay import Node from .models import Article, Editor from .nodes import ArticleNode, EditorNode from .types import ArticleInput, EditorInput @pytest.mark.asyncio async def test_should_create(fixtures): class CreateArticle(graphene.Mutation): class Arguments: article = ArticleInput(required=True) article = graphene.Field(ArticleNode) async def mutate(self, info, article): article = Article(**article) article.save() return CreateArticle(article=article) class Query(graphene.ObjectType): node = Node.Field() class Mutation(graphene.ObjectType): create_article = CreateArticle.Field() query = """ mutation ArticleCreator { createArticle( article: {headline: "My Article"} ) { article { headline } } } """ expected = {"createArticle": {"article": {"headline": "My Article"}}} schema = graphene.Schema(query=Query, mutation=Mutation) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_update(fixtures): class UpdateEditor(graphene.Mutation): class Arguments: id = graphene.ID(required=True) editor = EditorInput(required=True) editor = graphene.Field(EditorNode) async def mutate(self, info, id, editor): editor_to_update = Editor.objects.get(id=id) for key, value in editor.items(): if value: setattr(editor_to_update, key, value) editor_to_update.save() return UpdateEditor(editor=editor_to_update) class Query(graphene.ObjectType): node = Node.Field() class Mutation(graphene.ObjectType): update_editor = UpdateEditor.Field() query = """ mutation EditorUpdater { updateEditor( id: "1" editor: { lastName: "Lane" } ) { editor { firstName lastName } } } """ expected = {"updateEditor": {"editor": {"firstName": "Penny", "lastName": "Lane"}}} schema = graphene.Schema(query=Query, mutation=Mutation) result = await schema.execute_async(query) # print(result.data) assert not result.errors assert result.data == expected graphene-mongo-0.4.1/graphene_mongo/tests/test_mutation.py000066400000000000000000000044201453504055300240310ustar00rootroot00000000000000import graphene import pytest from graphene.relay import Node from .models import Article, Editor from .nodes import ArticleNode, EditorNode @pytest.mark.asyncio async def test_should_create(fixtures): class CreateArticle(graphene.Mutation): class Arguments: headline = graphene.String() article = graphene.Field(ArticleNode) async def mutate(self, info, headline): article = Article(headline=headline) article.save() return CreateArticle(article=article) class Query(graphene.ObjectType): node = Node.Field() class Mutation(graphene.ObjectType): create_article = CreateArticle.Field() query = """ mutation ArticleCreator { createArticle( headline: "My Article" ) { article { headline } } } """ expected = {"createArticle": {"article": {"headline": "My Article"}}} schema = graphene.Schema(query=Query, mutation=Mutation) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_update(fixtures): class UpdateEditor(graphene.Mutation): class Arguments: id = graphene.ID() first_name = graphene.String() editor = graphene.Field(EditorNode) async def mutate(self, info, id, first_name): editor = Editor.objects.get(id=id) editor.first_name = first_name editor.save() return UpdateEditor(editor=editor) class Query(graphene.ObjectType): node = Node.Field() class Mutation(graphene.ObjectType): update_editor = UpdateEditor.Field() query = """ mutation EditorUpdater { updateEditor( id: "1" firstName: "Tony" ) { editor { firstName } } } """ expected = {"updateEditor": {"editor": {"firstName": "Tony"}}} schema = graphene.Schema(query=Query, mutation=Mutation) result = await schema.execute_async(query) # print(result.data) assert not result.errors assert result.data == expected graphene-mongo-0.4.1/graphene_mongo/tests/test_query.py000066400000000000000000000267611453504055300233520ustar00rootroot00000000000000import base64 import os import json import graphene import pytest from . import models from . import types @pytest.mark.asyncio async def test_should_query_editor(fixtures, fixtures_dirname): class Query(graphene.ObjectType): editor = graphene.Field(types.EditorType) editors = graphene.List(types.EditorType) async def resolve_editor(self, *args, **kwargs): return models.Editor.objects.first() async def resolve_editors(self, *args, **kwargs): return list(models.Editor.objects.all()) query = """ query EditorQuery { editor { firstName, metadata, company { name }, avatar { contentType, chunkSize, length, data } } editors { firstName, lastName } } """ avator_filename = os.path.join(fixtures_dirname, "image.jpg") with open(avator_filename, "rb") as f: data = base64.b64encode(f.read()) expected = { "editor": { "firstName": "Penny", "company": {"name": "Newsco"}, "avatar": { "contentType": "image/jpeg", "chunkSize": 261120, "length": 46928, "data": data.decode("utf-8"), }, }, "editors": [ {"firstName": "Penny", "lastName": "Hardaway"}, {"firstName": "Grant", "lastName": "Hill"}, {"firstName": "Dennis", "lastName": "Rodman"}, ], } expected_metadata = {"age": "20", "nickname": "$1"} schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors metadata = result.data["editor"].pop("metadata") assert json.loads(metadata) == expected_metadata assert result.data == expected @pytest.mark.asyncio async def test_should_query_reporter(fixtures): class Query(graphene.ObjectType): reporter = graphene.Field(types.ReporterType) async def resolve_reporter(self, *args, **kwargs): return models.Reporter.objects.first() query = """ query ReporterQuery { reporter { firstName, lastName, email, articles { headline }, embeddedArticles { headline }, embeddedListArticles { headline }, awards } } """ expected = { "reporter": { "firstName": "Allen", "lastName": "Iverson", "email": "ai@gmail.com", "articles": [{"headline": "Hello"}, {"headline": "World"}], "embeddedArticles": [{"headline": "Real"}, {"headline": "World"}], "embeddedListArticles": [{"headline": "World"}, {"headline": "Real"}], "awards": ["2010-mvp"], } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_custom_kwargs(fixtures): class Query(graphene.ObjectType): editors = graphene.List(types.EditorType, first=graphene.Int()) async def resolve_editors(self, *args, **kwargs): editors = models.Editor.objects() if "first" in kwargs: editors = editors[: kwargs["first"]] return list(editors) query = """ query EditorQuery { editors(first: 2) { firstName, lastName } } """ expected = { "editors": [ {"firstName": "Penny", "lastName": "Hardaway"}, {"firstName": "Grant", "lastName": "Hill"}, ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_self_reference(fixtures): class Query(graphene.ObjectType): all_players = graphene.List(types.PlayerType) async def resolve_all_players(self, *args, **kwargs): return models.Player.objects.all() query = """ query PlayersQuery { allPlayers { firstName, opponent { firstName }, players { firstName } } } """ expected = { "allPlayers": [ { "firstName": "Michael", "opponent": None, "players": [{"firstName": "Magic"}], }, { "firstName": "Magic", "opponent": {"firstName": "Michael"}, "players": [{"firstName": "Michael"}], }, { "firstName": "Larry", "opponent": None, "players": [{"firstName": "Michael"}, {"firstName": "Magic"}], }, {"firstName": "Chris", "opponent": None, "players": []}, ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_with_embedded_document(fixtures): class Query(graphene.ObjectType): professor_vector = graphene.Field(types.ProfessorVectorType, id=graphene.String()) async def resolve_professor_vector(self, info, id): return models.ProfessorVector.objects(metadata__id=id).first() query = """ query { professorVector(id: "5e06aa20-6805-4eef-a144-5615dedbe32b") { vec metadata { firstName } } } """ expected = {"professorVector": {"vec": [1.0, 2.3], "metadata": {"firstName": "Steven"}}} schema = graphene.Schema(query=Query, types=[types.ProfessorVectorType]) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_child(fixtures): class Query(graphene.ObjectType): children = graphene.List(types.ChildType) async def resolve_children(self, *args, **kwargs): return list(models.Child.objects.all()) query = """ query Query { children { bar, baz, loc { type, coordinates } } } """ expected = { "children": [ {"bar": "BAR", "baz": "BAZ", "loc": None}, { "bar": "bar", "baz": "baz", "loc": {"type": "Point", "coordinates": [10.0, 20.0]}, }, ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_other_childs(fixtures): class Query(graphene.ObjectType): children = graphene.List(types.AnotherChildType) async def resolve_children(self, *args, **kwargs): return list(models.AnotherChild.objects.all()) query = """ query Query { children { bar, qux, loc { type, coordinates } } } """ expected = { "children": [ {"bar": "BAR", "qux": "QUX", "loc": None}, { "bar": "bar", "qux": "qux", "loc": {"type": "Point", "coordinates": [20, 10]}, }, ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_all_childs(fixtures): class Query(graphene.ObjectType): children = graphene.List(types.ChildUnionType) async def resolve_children(self, *args, **kwargs): return list(models.Parent.objects.all()) query = """ query Query { children { ... on ParentInterface { bar } ... on ChildType{ baz loc { type, coordinates } } ... on AnotherChildType { qux loc { type, coordinates } } } } """ expected = { "children": [ {"bar": "BAR", "baz": "BAZ", "loc": None}, { "bar": "bar", "baz": "baz", "loc": {"type": "Point", "coordinates": [10.0, 20.0]}, }, {"bar": "BAR", "qux": "QUX", "loc": None}, { "bar": "bar", "qux": "qux", "loc": {"type": "Point", "coordinates": [20, 10]}, }, ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_cell_tower(fixtures): class Query(graphene.ObjectType): cell_towers = graphene.List(types.CellTowerType) async def resolve_cell_towers(self, *args, **kwargs): return list(models.CellTower.objects.all()) query = """ query Query { cellTowers { code, base { type, coordinates }, coverageArea { type, coordinates } } } """ expected = { "cellTowers": [ { "code": "bar", "base": { "type": "Polygon", "coordinates": [ [ [-43.36556, -22.99669], [-43.36539, -23.01928], [-43.26583, -23.01802], [-43.36717, -22.98855], [-43.36636, -22.99351], [-43.36556, -22.99669], ] ], }, "coverageArea": { "type": "MultiPolygon", "coordinates": [ [ [ [-43.36556, -22.99669], [-43.36539, -23.01928], [-43.26583, -23.01802], [-43.36717, -22.98855], [-43.36636, -22.99351], [-43.36556, -22.99669], ] ] ], }, } ] } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected graphene-mongo-0.4.1/graphene_mongo/tests/test_relay_query.py000066400000000000000000000732361453504055300245450ustar00rootroot00000000000000import base64 import json import os import graphene import pytest from graphene.relay import Node from graphql_relay.node.node import to_global_id from . import models from . import nodes from ..fields import MongoengineConnectionField from ..types import MongoengineObjectType @pytest.mark.asyncio async def test_should_query_reporter(fixtures): class Query(graphene.ObjectType): reporter = graphene.Field(nodes.ReporterNode) async def resolve_reporter(self, *args, **kwargs): return models.Reporter.objects.no_dereference().first() query = """ query ReporterQuery { reporter { firstName, lastName, email, awards, articles { edges { node { headline } } }, embeddedArticles { edges { node { headline } } }, embeddedListArticles { edges { node { headline } } }, genericReference { __typename ... on ArticleNode { headline } } } } """ expected = { "reporter": { "firstName": "Allen", "lastName": "Iverson", "email": "ai@gmail.com", "awards": ["2010-mvp"], "articles": { "edges": [ {"node": {"headline": "Hello"}}, {"node": {"headline": "World"}}, ] }, "embeddedArticles": { "edges": [ {"node": {"headline": "Real"}}, {"node": {"headline": "World"}}, ] }, "embeddedListArticles": { "edges": [ {"node": {"headline": "World"}}, {"node": {"headline": "Real"}}, ] }, "genericReference": {"__typename": "ArticleNode", "headline": "Hello"}, } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_reporters_with_nested_document(fixtures): class Query(graphene.ObjectType): reporters = MongoengineConnectionField(nodes.ReporterNode) query = """ query ReporterQuery { reporters(firstName: "Allen") { edges { node { firstName, lastName, email, articles(headline: "Hello") { edges { node { headline } } } } } } } """ expected = { "reporters": { "edges": [ { "node": { "firstName": "Allen", "lastName": "Iverson", "email": "ai@gmail.com", "articles": {"edges": [{"node": {"headline": "Hello"}}]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_all_editors(fixtures, fixtures_dirname): class Query(graphene.ObjectType): editors = MongoengineConnectionField(nodes.EditorNode) query = """ query EditorQuery { editors { edges { node { id, firstName, lastName, avatar { contentType, length, data } } } } } """ avator_filename = os.path.join(fixtures_dirname, "image.jpg") with open(avator_filename, "rb") as f: data = base64.b64encode(f.read()) expected = { "editors": { "edges": [ { "node": { "id": "RWRpdG9yTm9kZTox", "firstName": "Penny", "lastName": "Hardaway", "avatar": { "contentType": "image/jpeg", "length": 46928, "data": data.decode("utf-8"), }, } }, { "node": { "id": "RWRpdG9yTm9kZToy", "firstName": "Grant", "lastName": "Hill", "avatar": {"contentType": None, "length": 0, "data": None}, } }, { "node": { "id": "RWRpdG9yTm9kZToz", "firstName": "Dennis", "lastName": "Rodman", "avatar": {"contentType": None, "length": 0, "data": None}, } }, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_editors_with_dataloader(fixtures): from promise import Promise from promise.dataloader import DataLoader class ArticleLoader(DataLoader): def batch_load_fn(self, instances): queryset = models.Article.objects(editor__in=instances) return Promise.resolve( [[a for a in queryset if a.editor.id == instance.id] for instance in instances] ) article_loader = ArticleLoader() class _EditorNode(MongoengineObjectType): class Meta: model = models.Editor interfaces = (graphene.Node,) articles = MongoengineConnectionField(nodes.ArticleNode) async def resolve_articles(self, *args, **kwargs): return article_loader.load(self) class Query(graphene.ObjectType): editors = MongoengineConnectionField(_EditorNode) query = """ query EditorPromiseConnectionQuery { editors(first: 1) { edges { node { firstName, articles(first: 1) { edges { node { headline } } } } } } } """ expected = { "editors": { "edges": [ { "node": { "firstName": "Penny", "articles": {"edges": [{"node": {"headline": "Hello"}}]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_editors_by_id(fixtures): class Query(graphene.ObjectType): editors = MongoengineConnectionField(nodes.EditorNode) query = """ query EditorQuery { editors(id: "RWRpdG9yTm9kZToy") { edges { node { id, firstName, lastName } } } } """ expected = { "editors": { "edges": [ { "node": { "id": "RWRpdG9yTm9kZToy", "firstName": "Grant", "lastName": "Hill", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter(fixtures): class Query(graphene.ObjectType): articles = MongoengineConnectionField(nodes.ArticleNode) query = """ query ArticlesQuery { articles(headline: "World") { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_reference_field(fixtures): class Query(graphene.ObjectType): articles = MongoengineConnectionField(nodes.ArticleNode) query = """ query ArticlesQuery { articles(editor: "RWRpdG9yTm9kZTox") { edges { node { headline, editor { firstName } } } } } """ expected = { "articles": {"edges": [{"node": {"headline": "Hello", "editor": {"firstName": "Penny"}}}]} } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_through_inheritance(fixtures): class Query(graphene.ObjectType): node = Node.Field() children = MongoengineConnectionField(nodes.ChildNode) query = """ query ChildrenQuery { children(bar: "bar") { edges { node { bar, baz, loc { type, coordinates } } } } } """ expected = { "children": { "edges": [ { "node": { "bar": "bar", "baz": "baz", "loc": {"type": "Point", "coordinates": [10.0, 20.0]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_list_contains(fixtures): # Notes: https://goo.gl/hMNRgs class Query(graphene.ObjectType): reporters = MongoengineConnectionField(nodes.ReporterNode) query = """ query ReportersQuery { reporters (awards: "2010-mvp") { edges { node { id, firstName, awards, genericReferences { __typename ... on ArticleNode { headline } } } } } } """ expected = { "reporters": { "edges": [ { "node": { "id": "UmVwb3J0ZXJOb2RlOjE=", "firstName": "Allen", "awards": ["2010-mvp"], "genericReferences": [ { "__typename": "ArticleNode", "headline": "Hello", } ], } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_id(fixtures): # Notes: https://goo.gl/hMNRgs class Query(graphene.ObjectType): reporter = Node.Field(nodes.ReporterNode) query = """ query ReporterQuery { reporter (id: "UmVwb3J0ZXJOb2RlOjE=") { id, firstName, awards } } """ expected = { "reporter": { "id": "UmVwb3J0ZXJOb2RlOjE=", "firstName": "Allen", "awards": ["2010-mvp"], } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_first_n(fixtures): class Query(graphene.ObjectType): editors = MongoengineConnectionField(nodes.EditorNode) query = """ query EditorQuery { editors(first: 2) { edges { cursor, node { firstName } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } } """ expected = { "editors": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjA=", "node": {"firstName": "Penny"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Grant"}}, ], "pageInfo": { "hasNextPage": True, "hasPreviousPage": False, "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", "endCursor": "YXJyYXljb25uZWN0aW9uOjE=", }, } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_after(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query EditorQuery { players(after: "YXJyYXljb25uZWN0aW9uOjA=") { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Magic"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjI=", "node": {"firstName": "Larry"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjM=", "node": {"firstName": "Chris"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_before(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query EditorQuery { players(before: "YXJyYXljb25uZWN0aW9uOjI=") { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ { "cursor": "YXJyYXljb25uZWN0aW9uOjA=", "node": {"firstName": "Michael"}, }, {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_last_n(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query PlayerQuery { players(last: 2) { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjI=", "node": {"firstName": "Larry"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjM=", "node": {"firstName": "Chris"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_self_reference(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query PlayersQuery { players { edges { node { firstName, players { edges { node { firstName } } }, embeddedListArticles { edges { node { headline } } } } } } } """ expected = { "players": { "edges": [ { "node": { "firstName": "Michael", "players": {"edges": [{"node": {"firstName": "Magic"}}]}, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Magic", "players": {"edges": [{"node": {"firstName": "Michael"}}]}, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Larry", "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] }, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Chris", "players": {"edges": []}, "embeddedListArticles": {"edges": []}, } }, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_lazy_reference(fixtures): class Query(graphene.ObjectType): node = Node.Field() parents = MongoengineConnectionField(nodes.ParentWithRelationshipNode) schema = graphene.Schema(query=Query) query = """ query { parents { edges { node { beforeChild { edges { node { name, parent { name } } } }, afterChild { edges { node { name, parent { name } } } } } } } } """ expected = { "parents": { "edges": [ { "node": { "beforeChild": { "edges": [{"node": {"name": "Akari", "parent": {"name": "Yui"}}}] }, "afterChild": { "edges": [{"node": {"name": "Kyouko", "parent": {"name": "Yui"}}}] }, } } ] } } result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_with_embedded_document(fixtures): class Query(graphene.ObjectType): professors = MongoengineConnectionField(nodes.ProfessorVectorNode) query = """ query { professors { edges { node { vec, metadata { firstName } } } } } """ expected = { "professors": { "edges": [{"node": {"vec": [1.0, 2.3], "metadata": {"firstName": "Steven"}}}] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_get_queryset_returns_dict_filters(fixtures): class Query(graphene.ObjectType): node = Node.Field() articles = MongoengineConnectionField( nodes.ArticleNode, get_queryset=lambda *_, **__: {"headline": "World"} ) query = """ query ArticlesQuery { articles { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_get_queryset_returns_qs_filters(fixtures): def get_queryset(model, info, **args): return model.objects(headline="World") class Query(graphene.ObjectType): node = Node.Field() articles = MongoengineConnectionField(nodes.ArticleNode, get_queryset=get_queryset) query = """ query ArticlesQuery { articles { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query players { players(firstName_Istartswith: "M") { edges { node { firstName } } } } """ expected = { "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) @pytest.mark.asyncio async def test_should_query_document_with_embedded(fixtures): class Query(graphene.ObjectType): foos = MongoengineConnectionField(nodes.FooNode) async def resolve_multiple_foos(self, *args, **kwargs): return list(models.Foo.objects.all()) query = """ query { foos { edges { node { bars { edges { node { someListField } } } } } } } """ schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset_with_list(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query players { players(firstName_In: ["Michael", "Magic"]) { edges { node { firstName } } } } """ expected = { "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) @pytest.mark.asyncio async def test_should_get_correct_list_of_documents(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) query = """ query players { players(firstName: "Michael") { edges { node { firstName, articles(first: 3) { edges { node { headline } } } } } } } """ expected = { "players": { "edges": [ { "node": { "firstName": "Michael", "articles": { "edges": [ { "node": { "headline": "Hello", } }, { "node": { "headline": "World", } }, ] }, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset_by_id_and_other_fields(fixtures): class Query(graphene.ObjectType): players = MongoengineConnectionField(nodes.PlayerNode) larry = models.Player.objects.get(first_name="Larry") larry_relay_id = to_global_id("PlayerNode", larry.id) # "Larry" id && firstName == "Michael" should return nothing query = """ query players {{ players( id: "{larry_relay_id}", firstName: "Michael" ) {{ edges {{ node {{ id firstName }} }} }} }} """.format(larry_relay_id=larry_relay_id) expected = { "players": { "edges": [], } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) graphene-mongo-0.4.1/graphene_mongo/tests/test_relay_query_async.py000066400000000000000000000742661453504055300257460ustar00rootroot00000000000000import base64 import json import os import graphene import pytest from graphene.relay import Node from graphql_relay.node.node import to_global_id from . import models from . import nodes_async from .. import AsyncMongoengineConnectionField, AsyncMongoengineObjectType @pytest.mark.asyncio async def test_should_query_reporter_async(fixtures): class Query(graphene.ObjectType): reporter = graphene.Field(nodes_async.ReporterAsyncNode) async def resolve_reporter(self, *args, **kwargs): return models.Reporter.objects.no_dereference().first() query = """ query ReporterQuery { reporter { firstName, lastName, email, awards, articles { edges { node { headline } } }, embeddedArticles { edges { node { headline } } }, embeddedListArticles { edges { node { headline } } }, genericReference { __typename ... on ArticleAsyncNode { headline } } } } """ expected = { "reporter": { "firstName": "Allen", "lastName": "Iverson", "email": "ai@gmail.com", "awards": ["2010-mvp"], "articles": { "edges": [ {"node": {"headline": "Hello"}}, {"node": {"headline": "World"}}, ] }, "embeddedArticles": { "edges": [ {"node": {"headline": "Real"}}, {"node": {"headline": "World"}}, ] }, "embeddedListArticles": { "edges": [ {"node": {"headline": "World"}}, {"node": {"headline": "Real"}}, ] }, "genericReference": {"__typename": "ArticleAsyncNode", "headline": "Hello"}, } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_reporters_with_nested_document_async(fixtures): class Query(graphene.ObjectType): reporters = AsyncMongoengineConnectionField(nodes_async.ReporterAsyncNode) query = """ query ReporterQuery { reporters(firstName: "Allen") { edges { node { firstName, lastName, email, articles(headline: "Hello") { edges { node { headline } } } } } } } """ expected = { "reporters": { "edges": [ { "node": { "firstName": "Allen", "lastName": "Iverson", "email": "ai@gmail.com", "articles": {"edges": [{"node": {"headline": "Hello"}}]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_all_editors_async(fixtures, fixtures_dirname): class Query(graphene.ObjectType): editors = AsyncMongoengineConnectionField(nodes_async.EditorAsyncNode) query = """ query EditorQuery { editors { edges { node { id, firstName, lastName, avatar { contentType, length, data } } } } } """ avator_filename = os.path.join(fixtures_dirname, "image.jpg") with open(avator_filename, "rb") as f: data = base64.b64encode(f.read()) expected = { "editors": { "edges": [ { "node": { "id": "RWRpdG9yQXN5bmNOb2RlOjE=", "firstName": "Penny", "lastName": "Hardaway", "avatar": { "contentType": "image/jpeg", "length": 46928, "data": data.decode("utf-8"), }, } }, { "node": { "id": "RWRpdG9yQXN5bmNOb2RlOjI=", "firstName": "Grant", "lastName": "Hill", "avatar": {"contentType": None, "length": 0, "data": None}, } }, { "node": { "id": "RWRpdG9yQXN5bmNOb2RlOjM=", "firstName": "Dennis", "lastName": "Rodman", "avatar": {"contentType": None, "length": 0, "data": None}, } }, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_editors_with_dataloader_async(fixtures): from promise import Promise from promise.dataloader import DataLoader class ArticleLoader(DataLoader): def batch_load_fn(self, instances): queryset = models.Article.objects(editor__in=instances) return Promise.resolve( [[a for a in queryset if a.editor.id == instance.id] for instance in instances] ) article_loader = ArticleLoader() class _EditorNode(AsyncMongoengineObjectType): class Meta: model = models.Editor interfaces = (graphene.Node,) articles = AsyncMongoengineConnectionField(nodes_async.ArticleAsyncNode) async def resolve_articles(self, *args, **kwargs): return article_loader.load(self) class Query(graphene.ObjectType): editors = AsyncMongoengineConnectionField(_EditorNode) query = """ query EditorPromiseConnectionQuery { editors(first: 1) { edges { node { firstName, articles(first: 1) { edges { node { headline } } } } } } } """ expected = { "editors": { "edges": [ { "node": { "firstName": "Penny", "articles": {"edges": [{"node": {"headline": "Hello"}}]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_editors_by_id_async(fixtures): class Query(graphene.ObjectType): editors = AsyncMongoengineConnectionField(nodes_async.EditorAsyncNode) query = """ query EditorQuery { editors(id: "RWRpdG9yQXN5bmNOb2RlOjI=") { edges { node { id, firstName, lastName } } } } """ expected = { "editors": { "edges": [ { "node": { "id": "RWRpdG9yQXN5bmNOb2RlOjI=", "firstName": "Grant", "lastName": "Hill", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_async(fixtures): class Query(graphene.ObjectType): articles = AsyncMongoengineConnectionField(nodes_async.ArticleAsyncNode) query = """ query ArticlesQuery { articles(headline: "World") { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_reference_field_async(fixtures): class Query(graphene.ObjectType): articles = AsyncMongoengineConnectionField(nodes_async.ArticleAsyncNode) query = """ query ArticlesQuery { articles(editor: "RWRpdG9yTm9kZTox") { edges { node { headline, editor { firstName } } } } } """ expected = { "articles": {"edges": [{"node": {"headline": "Hello", "editor": {"firstName": "Penny"}}}]} } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_through_inheritance_async(fixtures): class Query(graphene.ObjectType): node = Node.Field() children = AsyncMongoengineConnectionField(nodes_async.ChildAsyncNode) query = """ query ChildrenQuery { children(bar: "bar") { edges { node { bar, baz, loc { type, coordinates } } } } } """ expected = { "children": { "edges": [ { "node": { "bar": "bar", "baz": "baz", "loc": {"type": "Point", "coordinates": [10.0, 20.0]}, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_list_contains_async(fixtures): # Notes: https://goo.gl/hMNRgs class Query(graphene.ObjectType): reporters = AsyncMongoengineConnectionField(nodes_async.ReporterAsyncNode) query = """ query ReportersQuery { reporters (awards: "2010-mvp") { edges { node { id, firstName, awards, genericReferences { __typename ... on ArticleAsyncNode { headline } } } } } } """ expected = { "reporters": { "edges": [ { "node": { "id": "UmVwb3J0ZXJBc3luY05vZGU6MQ==", "firstName": "Allen", "awards": ["2010-mvp"], "genericReferences": [ {"__typename": "ArticleAsyncNode", "headline": "Hello"} ], } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_by_id_async(fixtures): # Notes: https://goo.gl/hMNRgs class Query(graphene.ObjectType): reporter = Node.Field(nodes_async.ReporterAsyncNode) query = """ query ReporterQuery { reporter (id: "UmVwb3J0ZXJBc3luY05vZGU6MQ==") { id, firstName, awards } } """ expected = { "reporter": { "id": "UmVwb3J0ZXJBc3luY05vZGU6MQ==", "firstName": "Allen", "awards": ["2010-mvp"], } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_first_n_async(fixtures): class Query(graphene.ObjectType): editors = AsyncMongoengineConnectionField(nodes_async.EditorAsyncNode) query = """ query EditorQuery { editors(first: 2) { edges { cursor, node { firstName } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } } """ expected = { "editors": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjA=", "node": {"firstName": "Penny"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Grant"}}, ], "pageInfo": { "hasNextPage": True, "hasPreviousPage": False, "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", "endCursor": "YXJyYXljb25uZWN0aW9uOjE=", }, } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_after_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query EditorQuery { players(after: "YXJyYXljb25uZWN0aW9uOjA=") { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Magic"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjI=", "node": {"firstName": "Larry"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjM=", "node": {"firstName": "Chris"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_before_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query EditorQuery { players(before: "YXJyYXljb25uZWN0aW9uOjI=") { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ { "cursor": "YXJyYXljb25uZWN0aW9uOjA=", "node": {"firstName": "Michael"}, }, {"cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_last_n_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query PlayerQuery { players(last: 2) { edges { cursor, node { firstName } } } } """ expected = { "players": { "edges": [ {"cursor": "YXJyYXljb25uZWN0aW9uOjI=", "node": {"firstName": "Larry"}}, {"cursor": "YXJyYXljb25uZWN0aW9uOjM=", "node": {"firstName": "Chris"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_self_reference_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query PlayersQuery { players { edges { node { firstName, players { edges { node { firstName } } }, embeddedListArticles { edges { node { headline } } } } } } } """ expected = { "players": { "edges": [ { "node": { "firstName": "Michael", "players": {"edges": [{"node": {"firstName": "Magic"}}]}, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Magic", "players": {"edges": [{"node": {"firstName": "Michael"}}]}, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Larry", "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] }, "embeddedListArticles": {"edges": []}, } }, { "node": { "firstName": "Chris", "players": {"edges": []}, "embeddedListArticles": {"edges": []}, } }, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_lazy_reference_async(fixtures): class Query(graphene.ObjectType): node = Node.Field() parents = AsyncMongoengineConnectionField(nodes_async.ParentWithRelationshipAsyncNode) schema = graphene.Schema(query=Query) print(schema) query = """ query { parents { edges { node { beforeChild { edges { node { name, parent { name } } } }, afterChild { edges { node { name, parent { name } } } } } } } } """ expected = { "parents": { "edges": [ { "node": { "beforeChild": { "edges": [{"node": {"name": "Akari", "parent": {"name": "Yui"}}}] }, "afterChild": { "edges": [{"node": {"name": "Kyouko", "parent": {"name": "Yui"}}}] }, } } ] } } result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_query_with_embedded_document_async(fixtures): class Query(graphene.ObjectType): professors = AsyncMongoengineConnectionField(nodes_async.ProfessorVectorAsyncNode) query = """ query { professors { edges { node { vec, metadata { firstName } } } } } """ expected = { "professors": { "edges": [{"node": {"vec": [1.0, 2.3], "metadata": {"firstName": "Steven"}}}] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_get_queryset_returns_dict_filters_async(fixtures): class Query(graphene.ObjectType): node = Node.Field() articles = AsyncMongoengineConnectionField( nodes_async.ArticleAsyncNode, get_queryset=lambda *_, **__: {"headline": "World"}, ) query = """ query ArticlesQuery { articles { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_get_queryset_returns_qs_filters_async(fixtures): def get_queryset(model, info, **args): return model.objects(headline="World") class Query(graphene.ObjectType): node = Node.Field() articles = AsyncMongoengineConnectionField( nodes_async.ArticleAsyncNode, get_queryset=get_queryset ) query = """ query ArticlesQuery { articles { edges { node { headline, pubDate, editor { firstName } } } } } """ expected = { "articles": { "edges": [ { "node": { "headline": "World", "editor": {"firstName": "Grant"}, "pubDate": "2020-01-01T00:00:00", } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query players { players(firstName_Istartswith: "M") { edges { node { firstName } } } } """ expected = { "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) @pytest.mark.asyncio async def test_should_query_document_with_embedded_async(fixtures): class Query(graphene.ObjectType): foos = AsyncMongoengineConnectionField(nodes_async.FooAsyncNode) async def resolve_multiple_foos(self, *args, **kwargs): return list(models.Foo.objects.all()) query = """ query { foos { edges { node { bars { edges { node { someListField } } } } } } } """ schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset_with_list_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query players { players(firstName_In: ["Michael", "Magic"]) { edges { node { firstName } } } } """ expected = { "players": { "edges": [ {"node": {"firstName": "Michael"}}, {"node": {"firstName": "Magic"}}, ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) @pytest.mark.asyncio async def test_should_get_correct_list_of_documents_async(fixtures): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) query = """ query players { players(firstName: "Michael") { edges { node { firstName, articles(first: 3) { edges { node { headline } } } } } } } """ expected = { "players": { "edges": [ { "node": { "firstName": "Michael", "articles": { "edges": [ { "node": { "headline": "Hello", } }, { "node": { "headline": "World", } }, ] }, } } ] } } schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert result.data == expected @pytest.mark.asyncio async def test_should_filter_mongoengine_queryset_by_id_and_other_fields_async( fixtures, ): class Query(graphene.ObjectType): players = AsyncMongoengineConnectionField(nodes_async.PlayerAsyncNode) larry = models.Player.objects.get(first_name="Larry") larry_relay_id = to_global_id("PlayerAsyncNode", larry.id) # "Larry" id && firstName == "Michael" should return nothing query = """ query players {{ players( id: "{larry_relay_id}", firstName: "Michael" ) {{ edges {{ node {{ id firstName }} }} }} }} """.format(larry_relay_id=larry_relay_id) expected = {"players": {"edges": []}} schema = graphene.Schema(query=Query) result = await schema.execute_async(query) assert not result.errors assert json.dumps(result.data, sort_keys=True) == json.dumps(expected, sort_keys=True) graphene-mongo-0.4.1/graphene_mongo/tests/test_types.py000066400000000000000000000110611453504055300233340ustar00rootroot00000000000000from pytest import raises from graphene import Field, Int, Interface, ObjectType from graphene.relay import Node, is_node from .. import registry from ..types import MongoengineObjectType, MongoengineObjectTypeOptions from .models import Article, EmbeddedArticle, Reporter from .models import Parent, Child from .utils import with_local_registry registry.reset_global_registry() class Human(MongoengineObjectType): pub_date = Int() class Meta: model = Article registry = registry.get_global_registry() interfaces = (Node,) class Being(MongoengineObjectType): class Meta: model = EmbeddedArticle interfaces = (Node,) class Character(MongoengineObjectType): class Meta: model = Reporter registry = registry.get_global_registry() class Dad(MongoengineObjectType): class Meta: model = Parent registry = registry.get_global_registry() class Son(MongoengineObjectType): class Meta: model = Child registry = registry.get_global_registry() def test_mongoengine_interface(): assert issubclass(Node, Interface) assert issubclass(Node, Node) def test_objecttype_registered(): assert issubclass(Character, ObjectType) assert Character._meta.model == Reporter assert set(Character._meta.fields.keys()) == set( [ "id", "first_name", "last_name", "email", "embedded_articles", "embedded_list_articles", "articles", "awards", "generic_reference", "generic_embedded_document", "generic_references", ] ) def test_mongoengine_inheritance(): assert issubclass(Son._meta.model, Dad._meta.model) def test_node_replacedfield(): idfield = Human._meta.fields["pub_date"] assert isinstance(idfield, Field) assert idfield.type == Int def test_object_type(): assert issubclass(Human, ObjectType) assert set(Human._meta.fields.keys()) == set( ["id", "headline", "pub_date", "editor", "reporter"] ) assert is_node(Human) def test_should_raise_if_no_model(): with raises(Exception) as excinfo: class Human1(MongoengineObjectType): pass assert "valid Mongoengine Model" in str(excinfo.value) def test_should_raise_if_model_is_invalid(): with raises(Exception) as excinfo: class Human2(MongoengineObjectType): class Meta: model = 1 assert "valid Mongoengine Model" in str(excinfo.value) @with_local_registry def test_mongoengine_objecttype_only_fields(): class A(MongoengineObjectType): class Meta: model = Article only_fields = "headline" fields = set(A._meta.fields.keys()) assert fields == set(["headline"]) @with_local_registry def test_mongoengine_objecttype_exclude_fields(): class A(MongoengineObjectType): class Meta: model = Article exclude_fields = "headline" assert "headline" not in list(A._meta.fields.keys()) @with_local_registry def test_mongoengine_objecttype_order_by(): class A(MongoengineObjectType): class Meta: model = Article order_by = "some_order_by_statement" assert "some_order_by_statement" not in list(A._meta.fields.keys()) @with_local_registry def test_passing_meta_when_subclassing_mongoengine_objecttype(): class TypeSubclassWithBadOptions(MongoengineObjectType): class Meta: abstract = True @classmethod def __init_subclass_with_meta__(cls, **kwargs): _meta = ["hi"] super(TypeSubclassWithBadOptions, cls).__init_subclass_with_meta__( _meta=_meta, **kwargs ) with raises(Exception) as einfo: class A(TypeSubclassWithBadOptions): class Meta: model = Article assert "instance of MongoengineGenericObjectTypeOptions" in str(einfo.value) class TypeSubclass(MongoengineObjectType): class Meta: abstract = True @classmethod def __init_subclass_with_meta__(cls, some_subclass_attr=None, **kwargs): _meta = MongoengineObjectTypeOptions(cls) _meta.some_subclass_attr = some_subclass_attr super(TypeSubclass, cls).__init_subclass_with_meta__(_meta=_meta, **kwargs) class B(TypeSubclass): class Meta: model = Article some_subclass_attr = "someval" assert hasattr(B._meta, "some_subclass_attr") assert B._meta.some_subclass_attr == "someval" graphene-mongo-0.4.1/graphene_mongo/tests/test_utils.py000066400000000000000000000054021453504055300233320ustar00rootroot00000000000000import graphene from . import types from .models import Article, Child, Reporter from ..utils import get_model_fields, get_query_fields, is_valid_mongoengine_model def test_get_model_fields_no_duplication(): reporter_fields = get_model_fields(Reporter) reporter_name_set = set(reporter_fields) assert len(reporter_fields) == len(reporter_name_set) def test_get_model_fields_excluding(): reporter_fields = get_model_fields(Reporter, excluding=["first_name", "last_name"]) reporter_name_set = set(reporter_fields) assert all( field in reporter_name_set for field in [ "id", "email", "articles", "embedded_articles", "embedded_list_articles", "awards", ] ) def test_get_model_relation_fields(): article_fields = get_model_fields(Article) assert all(field in set(article_fields) for field in ["editor", "reporter"]) def test_get_base_model_fields(): child_fields = get_model_fields(Child) assert all(field in set(child_fields) for field in ["bar", "baz"]) def test_is_valid_mongoengine_mode(): assert is_valid_mongoengine_model(Reporter) def test_get_query_fields(): # Grab ResolveInfo objects from resolvers and set as nonlocal variables outside # Can't assert within resolvers, as the resolvers may not be run if there is an exception class Query(graphene.ObjectType): child = graphene.Field(types.ChildType) children = graphene.List(types.ChildUnionType) def resolve_child(self, info, *args, **kwargs): test_get_query_fields.child_info = info def resolve_children(self, info, *args, **kwargs): test_get_query_fields.children_info = info query = """ query Query { child { bar ...testFragment } children { ... on ChildType{ baz ...testFragment } ... on AnotherChildType { qux } } } fragment testFragment on ChildType { loc { type coordinates } } """ schema = graphene.Schema(query=Query) schema.execute(query) assert get_query_fields(test_get_query_fields.child_info) == { "bar": {}, "loc": { "type": {}, "coordinates": {}, }, } assert get_query_fields(test_get_query_fields.children_info) == { "ChildType": { "baz": {}, "loc": { "type": {}, "coordinates": {}, }, }, "AnotherChildType": { "qux": {}, }, } graphene-mongo-0.4.1/graphene_mongo/tests/types.py000066400000000000000000000037761453504055300223130ustar00rootroot00000000000000from . import models from ..types import ( MongoengineObjectType, MongoengineInterfaceType, MongoengineInputType, ) from graphene.types.union import Union class PublisherType(MongoengineObjectType): class Meta: model = models.Publisher class EditorType(MongoengineObjectType): class Meta: model = models.Editor class ArticleType(MongoengineObjectType): class Meta: model = models.Article class EmbeddedArticleType(MongoengineObjectType): class Meta: model = models.EmbeddedArticle class PlayerType(MongoengineObjectType): class Meta: model = models.Player class ReporterType(MongoengineObjectType): class Meta: model = models.Reporter class ParentType(MongoengineObjectType): class Meta: model = models.Parent class ParentInterface(MongoengineInterfaceType): class Meta: model = models.Parent exclude_fields = ["loc"] class ChildType(MongoengineObjectType): class Meta: model = models.Child interfaces = (ParentInterface,) class AnotherChildType(MongoengineObjectType): class Meta: model = models.AnotherChild interfaces = (ParentInterface,) class ChildUnionType(Union): class Meta: types = (ChildType, AnotherChildType) interfaces = (ParentInterface,) class CellTowerType(MongoengineObjectType): class Meta: model = models.CellTower class ProfessorMetadataType(MongoengineObjectType): class Meta: model = models.ProfessorMetadata class ProfessorVectorType(MongoengineObjectType): class Meta: model = models.ProfessorVector class ArticleInput(MongoengineInputType): class Meta: model = models.Article only_fields = ["headline"] class EditorInput(MongoengineInputType): class Meta: model = models.Editor only_fields = ["first_name", "last_name"] # allow providing only one of those ! Even None... non_required_fields = ["first_name", "last_name"] graphene-mongo-0.4.1/graphene_mongo/tests/utils.py000066400000000000000000000006341453504055300222750ustar00rootroot00000000000000from .. import registry def with_local_registry(func): def inner(*args, **kwargs): old = registry.get_global_registry() registry.reset_global_registry() try: retval = func(*args, **kwargs) except Exception as e: registry.registry = old raise e else: registry.registry = old return retval return inner graphene-mongo-0.4.1/graphene_mongo/types.py000066400000000000000000000243471453504055300211460ustar00rootroot00000000000000from collections import OrderedDict import graphene import mongoengine from graphene.relay import Connection, Node from graphene.types.inputobjecttype import InputObjectType, InputObjectTypeOptions from graphene.types.interface import Interface, InterfaceOptions from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.utils import yank_fields_from_attrs from graphene.utils.str_converters import to_snake_case from graphene_mongo import MongoengineConnectionField from .converter import convert_mongoengine_field from .registry import Registry, get_global_registry, get_inputs_registry from .utils import ( ExecutorEnum, get_model_fields, get_query_fields, is_valid_mongoengine_model, sync_to_async, ) def construct_fields( model, registry, only_fields, exclude_fields, non_required_fields, executor: ExecutorEnum = ExecutorEnum.SYNC, ): """ Args: model (mongoengine.Document): registry (.registry.Registry): only_fields ([str]): exclude_fields ([str]): executor : ExecutorEnum Returns: (OrderedDict, OrderedDict): converted fields and self reference fields. """ _model_fields = get_model_fields(model) fields = OrderedDict() self_referenced = OrderedDict() for name, field in _model_fields.items(): is_not_in_only = only_fields and name not in only_fields is_excluded = name in exclude_fields if is_not_in_only or is_excluded: # We skip this field if we specify required_fields and is not # in there. Or when we exclude this field in exclude_fields continue if isinstance(field, mongoengine.ListField): if not field.field: continue # Take care of list of self-reference. document_type_obj = field.field.__dict__.get("document_type_obj", None) if ( document_type_obj == model._class_name or isinstance(document_type_obj, model) or document_type_obj == model ): self_referenced[name] = field continue converted = convert_mongoengine_field(field, registry, executor) if not converted: continue else: if name in non_required_fields and "required" in converted.kwargs: converted.kwargs["required"] = False fields[name] = converted return fields, self_referenced def construct_self_referenced_fields(self_referenced, registry, executor=ExecutorEnum.SYNC): fields = OrderedDict() for name, field in self_referenced.items(): converted = convert_mongoengine_field(field, registry, executor) if not converted: continue fields[name] = converted return fields def create_graphene_generic_class(object_type, option_type): class MongoengineGenericObjectTypeOptions(option_type): model = None registry = None # type: Registry connection = None filter_fields = () non_required_fields = () order_by = None class GrapheneMongoengineGenericType(object_type): @classmethod def __init_subclass_with_meta__( cls, model=None, registry=None, skip_registry=False, only_fields=(), required_fields=(), exclude_fields=(), non_required_fields=(), filter_fields=None, non_filter_fields=(), connection=None, connection_class=None, use_connection=None, connection_field_class=None, interfaces=(), _meta=None, order_by=None, **options, ): assert is_valid_mongoengine_model(model), ( "The attribute model in {}.Meta must be a valid Mongoengine Model. " 'Received "{}" instead.' ).format(cls.__name__, type(model)) if not registry: # input objects shall be registred in a separated registry if issubclass(cls, InputObjectType): registry = get_inputs_registry() else: registry = get_global_registry() assert isinstance(registry, Registry), ( "The attribute registry in {}.Meta needs to be an instance of " 'Registry({}), received "{}".' ).format(object_type, cls.__name__, registry) converted_fields, self_referenced = construct_fields( model, registry, only_fields, exclude_fields, non_required_fields ) mongoengine_fields = yank_fields_from_attrs(converted_fields, _as=graphene.Field) if use_connection is None and interfaces: use_connection = any((issubclass(interface, Node) for interface in interfaces)) if use_connection and not connection: # We create the connection automatically if not connection_class: connection_class = Connection connection = connection_class.create_type( "{}Connection".format(options.get("name") or cls.__name__), node=cls ) if connection is not None: assert issubclass(connection, Connection), ( "The attribute connection in {}.Meta must be of type Connection. " 'Received "{}" instead.' ).format(cls.__name__, type(connection)) if connection_field_class is not None: assert issubclass(connection_field_class, graphene.ConnectionField), ( "The attribute connection_field_class in {}.Meta must be of type graphene.ConnectionField. " 'Received "{}" instead.' ).format(cls.__name__, type(connection_field_class)) else: connection_field_class = MongoengineConnectionField if _meta: assert isinstance(_meta, MongoengineGenericObjectTypeOptions), ( "_meta must be an instance of MongoengineGenericObjectTypeOptions, " "received {}" ).format(_meta.__class__) else: _meta = MongoengineGenericObjectTypeOptions(option_type) _meta.model = model _meta.registry = registry _meta.fields = mongoengine_fields _meta.filter_fields = filter_fields _meta.non_filter_fields = non_filter_fields _meta.connection = connection _meta.connection_field_class = connection_field_class # Save them for later _meta.only_fields = only_fields _meta.required_fields = required_fields _meta.exclude_fields = exclude_fields _meta.non_required_fields = non_required_fields _meta.order_by = order_by super(GrapheneMongoengineGenericType, cls).__init_subclass_with_meta__( _meta=_meta, interfaces=interfaces, **options ) if not skip_registry: registry.register(cls) # Notes: Take care list of self-reference fields. converted_fields = construct_self_referenced_fields(self_referenced, registry) if converted_fields: mongoengine_fields = yank_fields_from_attrs( converted_fields, _as=graphene.Field ) cls._meta.fields.update(mongoengine_fields) registry.register(cls) @classmethod def rescan_fields(cls): """Attempts to rescan fields and will insert any not converted initially""" converted_fields, self_referenced = construct_fields( cls._meta.model, cls._meta.registry, cls._meta.only_fields, cls._meta.exclude_fields, cls._meta.non_required_fields, ) mongoengine_fields = yank_fields_from_attrs(converted_fields, _as=graphene.Field) # The initial scan should take precedence for field in mongoengine_fields: if field not in cls._meta.fields: cls._meta.fields.update({field: mongoengine_fields[field]}) # Self-referenced fields can't change between scans! @classmethod def is_type_of(cls, root, info): if isinstance(root, cls): return True # XXX: Take care FileField if isinstance(root, mongoengine.GridFSProxy): return True if not is_valid_mongoengine_model(type(root)): raise Exception(('Received incompatible instance "{}".').format(root)) return isinstance(root, cls._meta.model) @classmethod async def get_node(cls, info, id): required_fields = list() for field in cls._meta.required_fields: if field in cls._meta.model._fields_ordered: required_fields.append(field) queried_fields = get_query_fields(info) if cls._meta.name in queried_fields: queried_fields = queried_fields[cls._meta.name] for field in queried_fields: if to_snake_case(field) in cls._meta.model._fields_ordered: required_fields.append(to_snake_case(field)) required_fields = list(set(required_fields)) return await sync_to_async( cls._meta.model.objects.no_dereference().only(*required_fields).get, )(pk=id) def resolve_id(self, info): return str(self.id) return GrapheneMongoengineGenericType, MongoengineGenericObjectTypeOptions MongoengineObjectType, MongoengineObjectTypeOptions = create_graphene_generic_class( ObjectType, ObjectTypeOptions ) MongoengineInterfaceType, MongoengineInterfaceTypeOptions = create_graphene_generic_class( Interface, InterfaceOptions ) MongoengineInputType, MongoengineInputTypeOptions = create_graphene_generic_class( InputObjectType, InputObjectTypeOptions ) GrapheneMongoengineObjectTypes = ( MongoengineObjectType, MongoengineInputType, MongoengineInterfaceType, ) graphene-mongo-0.4.1/graphene_mongo/types_async.py000066400000000000000000000203631453504055300223350ustar00rootroot00000000000000import graphene import mongoengine from graphene import InputObjectType from graphene.relay import Connection, Node from graphene.types.interface import Interface, InterfaceOptions from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.utils import yank_fields_from_attrs from graphene.utils.str_converters import to_snake_case from graphene_mongo import AsyncMongoengineConnectionField from .registry import Registry, get_global_async_registry, get_inputs_async_registry from .types import construct_fields, construct_self_referenced_fields from .utils import ExecutorEnum, get_query_fields, is_valid_mongoengine_model, sync_to_async def create_graphene_generic_class_async(object_type, option_type): class AsyncMongoengineGenericObjectTypeOptions(option_type): model = None registry = None # type: Registry connection = None filter_fields = () non_required_fields = () order_by = None class AsyncGrapheneMongoengineGenericType(object_type): @classmethod def __init_subclass_with_meta__( cls, model=None, registry=None, skip_registry=False, only_fields=(), required_fields=(), exclude_fields=(), non_required_fields=(), filter_fields=None, non_filter_fields=(), connection=None, connection_class=None, use_connection=None, connection_field_class=None, interfaces=(), _meta=None, order_by=None, **options, ): assert is_valid_mongoengine_model(model), ( "The attribute model in {}.Meta must be a valid Mongoengine Model. " 'Received "{}" instead.' ).format(cls.__name__, type(model)) if not registry: # input objects shall be registred in a separated registry if issubclass(cls, InputObjectType): registry = get_inputs_async_registry() else: registry = get_global_async_registry() assert isinstance(registry, Registry), ( "The attribute registry in {}.Meta needs to be an instance of " 'Registry({}), received "{}".' ).format(object_type, cls.__name__, registry) converted_fields, self_referenced = construct_fields( model, registry, only_fields, exclude_fields, non_required_fields, ExecutorEnum.ASYNC, ) mongoengine_fields = yank_fields_from_attrs(converted_fields, _as=graphene.Field) if use_connection is None and interfaces: use_connection = any((issubclass(interface, Node) for interface in interfaces)) if use_connection and not connection: # We create the connection automatically if not connection_class: connection_class = Connection connection = connection_class.create_type( "{}Connection".format(options.get("name") or cls.__name__), node=cls ) if connection is not None: assert issubclass(connection, Connection), ( "The attribute connection in {}.Meta must be of type Connection. " 'Received "{}" instead.' ).format(cls.__name__, type(connection)) if connection_field_class is not None: assert issubclass(connection_field_class, graphene.ConnectionField), ( "The attribute connection_field_class in {}.Meta must be of type graphene.ConnectionField. " 'Received "{}" instead.' ).format(cls.__name__, type(connection_field_class)) else: connection_field_class = AsyncMongoengineConnectionField if _meta: assert isinstance(_meta, AsyncMongoengineGenericObjectTypeOptions), ( "_meta must be an instance of AsyncMongoengineGenericObjectTypeOptions, " "received {}" ).format(_meta.__class__) else: _meta = AsyncMongoengineGenericObjectTypeOptions(option_type) _meta.model = model _meta.registry = registry _meta.fields = mongoengine_fields _meta.filter_fields = filter_fields _meta.non_filter_fields = non_filter_fields _meta.connection = connection _meta.connection_field_class = connection_field_class # Save them for later _meta.only_fields = only_fields _meta.required_fields = required_fields _meta.exclude_fields = exclude_fields _meta.non_required_fields = non_required_fields _meta.order_by = order_by super(AsyncGrapheneMongoengineGenericType, cls).__init_subclass_with_meta__( _meta=_meta, interfaces=interfaces, **options ) if not skip_registry: registry.register(cls) # Notes: Take care list of self-reference fields. converted_fields = construct_self_referenced_fields( self_referenced, registry, ExecutorEnum.ASYNC ) if converted_fields: mongoengine_fields = yank_fields_from_attrs( converted_fields, _as=graphene.Field ) cls._meta.fields.update(mongoengine_fields) registry.register(cls) @classmethod def rescan_fields(cls): """Attempts to rescan fields and will insert any not converted initially""" converted_fields, self_referenced = construct_fields( cls._meta.model, cls._meta.registry, cls._meta.only_fields, cls._meta.exclude_fields, cls._meta.non_required_fields, ExecutorEnum.ASYNC, ) mongoengine_fields = yank_fields_from_attrs(converted_fields, _as=graphene.Field) # The initial scan should take precedence for field in mongoengine_fields: if field not in cls._meta.fields: cls._meta.fields.update({field: mongoengine_fields[field]}) # Self-referenced fields can't change between scans! @classmethod def is_type_of(cls, root, info): if isinstance(root, cls): return True # XXX: Take care FileField if isinstance(root, mongoengine.GridFSProxy): return True if not is_valid_mongoengine_model(type(root)): raise Exception(('Received incompatible instance "{}".').format(root)) return isinstance(root, cls._meta.model) @classmethod async def get_node(cls, info, id): required_fields = list() for field in cls._meta.required_fields: if field in cls._meta.model._fields_ordered: required_fields.append(field) queried_fields = get_query_fields(info) if cls._meta.name in queried_fields: queried_fields = queried_fields[cls._meta.name] for field in queried_fields: if to_snake_case(field) in cls._meta.model._fields_ordered: required_fields.append(to_snake_case(field)) required_fields = list(set(required_fields)) return await sync_to_async( cls._meta.model.objects.no_dereference().only(*required_fields).get )(pk=id) def resolve_id(self, info): return str(self.id) return AsyncGrapheneMongoengineGenericType, AsyncMongoengineGenericObjectTypeOptions ( AsyncMongoengineObjectType, AsyncMongoengineObjectTypeOptions, ) = create_graphene_generic_class_async(ObjectType, ObjectTypeOptions) ( AsyncMongoengineInterfaceType, MongoengineInterfaceTypeOptions, ) = create_graphene_generic_class_async(Interface, InterfaceOptions) AsyncGrapheneMongoengineObjectTypes = ( AsyncMongoengineObjectType, AsyncMongoengineInterfaceType, ) graphene-mongo-0.4.1/graphene_mongo/utils.py000066400000000000000000000213241453504055300211320ustar00rootroot00000000000000from __future__ import unicode_literals import enum import inspect from collections import OrderedDict from concurrent.futures import ThreadPoolExecutor from typing import Any, Callable, Union import mongoengine from asgiref.sync import sync_to_async as asgiref_sync_to_async from asgiref.sync import SyncToAsync from graphene import Node from graphene.utils.trim_docstring import trim_docstring from graphql import FieldNode from graphql_relay.connection.array_connection import offset_to_cursor class ExecutorEnum(enum.Enum): ASYNC = enum.auto() SYNC = enum.auto() def get_model_fields(model, excluding=None): excluding = excluding or [] attributes = dict() for attr_name, attr in model._fields.items(): if attr_name in excluding: continue attributes[attr_name] = attr return OrderedDict(sorted(attributes.items())) def get_model_reference_fields(model, excluding=None): excluding = excluding or [] attributes = dict() for attr_name, attr in model._fields.items(): if attr_name in excluding or not isinstance( attr, (mongoengine.fields.ReferenceField, mongoengine.fields.LazyReferenceField), ): continue attributes[attr_name] = attr return attributes def is_valid_mongoengine_model(model): return inspect.isclass(model) and ( issubclass(model, mongoengine.Document) or issubclass(model, mongoengine.EmbeddedDocument) ) def import_single_dispatch(): try: from functools import singledispatch except ImportError: singledispatch = None if not singledispatch: try: from singledispatch import singledispatch except ImportError: pass if not singledispatch: raise Exception( "It seems your python version does not include " "functools.singledispatch. Please install the 'singledispatch' " "package. More information here: " "https://pypi.python.org/pypi/singledispatch" ) return singledispatch # noqa def get_type_for_document(schema, document): types = schema.types.values() for _type in types: type_document = hasattr(_type, "_meta") and getattr(_type._meta, "document", None) if document == type_document: return _type def get_field_description(field, registry=None): """ Common metadata includes verbose_name and help_text. http://docs.mongoengine.org/apireference.html#fields """ parts = [] if hasattr(field, "document_type"): doc = trim_docstring(field.document_type.__doc__) if doc: parts.append(doc) if hasattr(field, "verbose_name"): parts.append(field.verbose_name.title()) if hasattr(field, "help_text"): parts.append(field.help_text) if field.db_field != field.name: name_format = "(%s)" if parts else "%s" parts.append(name_format % field.db_field) return "\n".join(parts) def get_node_from_global_id(node, info, global_id): try: for interface in node._meta.interfaces: if issubclass(interface, Node): return interface.get_node_from_global_id(info, global_id) except AttributeError: return Node.get_node_from_global_id(info, global_id) def collect_query_fields(node, fragments): """Recursively collects fields from the AST Args: node (dict): A node in the AST fragments (dict): Fragment definitions Returns: A dict mapping each field found, along with their sub fields. { 'name': {}, 'image': { 'id': {}, 'name': {}, 'description': {} }, 'slug': {} } """ field = {} selection_set = None if isinstance(node, dict): selection_set = node.get("selection_set") else: selection_set = node.selection_set if selection_set: for leaf in selection_set.selections: if leaf.kind == "field": field.update({leaf.name.value: collect_query_fields(leaf, fragments)}) elif leaf.kind == "fragment_spread": field.update(collect_query_fields(fragments[leaf.name.value], fragments)) elif leaf.kind == "inline_fragment": field.update( {leaf.type_condition.name.value: collect_query_fields(leaf, fragments)} ) return field def get_query_fields(info): """A convenience function to call collect_query_fields with info Args: info (ResolveInfo) Returns: dict: Returned from collect_query_fields """ fragments = {} node = ast_to_dict(info.field_nodes[0]) for name, value in info.fragments.items(): fragments[name] = ast_to_dict(value) query = collect_query_fields(node, fragments) if "edges" in query: return query["edges"]["node"].keys() return query def has_page_info(info): """A convenience function to call collect_query_fields with info for retrieving if page_info details are required Args: info (ResolveInfo) Returns: bool: True if it received pageinfo """ fragments = {} if not info: return True # Returning True if invalid info is provided node = ast_to_dict(info.field_nodes[0]) for name, value in info.fragments.items(): fragments[name] = ast_to_dict(value) query = collect_query_fields(node, fragments) return next((True for x in query.keys() if x.lower() == "pageinfo"), False) def ast_to_dict(node, include_loc=False): if isinstance(node, FieldNode): d = {"kind": node.__class__.__name__} if hasattr(node, "keys"): for field in node.keys: d[field] = ast_to_dict(getattr(node, field), include_loc) if include_loc and hasattr(node, "loc") and node.loc: d["loc"] = {"start": node.loc.start, "end": node.loc.end} return d elif isinstance(node, list): return [ast_to_dict(item, include_loc) for item in node] return node def find_skip_and_limit(first, last, after, before, count=None): reverse = False skip = 0 limit = None if first is not None and after is not None: skip = after + 1 limit = first elif first is not None and before is not None: if first >= before: limit = before - 1 else: limit = first elif first is not None: skip = 0 limit = first elif last is not None and before is not None: reverse = False if last >= before: limit = before else: limit = last skip = before - last elif last is not None and after is not None: if not count: raise ValueError("Count Missing") reverse = True if last + after < count: limit = last else: limit = count - after - 1 elif last is not None: skip = 0 limit = last reverse = True elif after is not None: skip = after + 1 elif before is not None: limit = before return skip, limit, reverse def connection_from_iterables( edges, start_offset, has_previous_page, has_next_page, connection_type, edge_type, pageinfo_type, ): edges_items = [ edge_type( node=node, cursor=offset_to_cursor((0 if start_offset is None else start_offset) + i), ) for i, node in enumerate(edges) ] first_edge_cursor = edges_items[0].cursor if edges_items else None last_edge_cursor = edges_items[-1].cursor if edges_items else None return connection_type( edges=edges_items, page_info=pageinfo_type( start_cursor=first_edge_cursor, end_cursor=last_edge_cursor, has_previous_page=has_previous_page, has_next_page=has_next_page, ), ) def sync_to_async( func: Callable = None, thread_sensitive: bool = False, executor: Any = None, # noqa ) -> Union[SyncToAsync, Callable[[Callable[..., Any]], SyncToAsync]]: """ Wrapper over sync_to_async from asgiref.sync Defaults to thread insensitive with ThreadPoolExecutor of n workers Args: func: Function to be converted to coroutine thread_sensitive: If the operation is thread sensitive and should run in synchronous thread executor: Threadpool executor, if thread_sensitive=False Returns: coroutine version of func """ if executor is None: executor = ThreadPoolExecutor() return asgiref_sync_to_async(func=func, thread_sensitive=thread_sensitive, executor=executor) graphene-mongo-0.4.1/poetry.lock000066400000000000000000001133541453504055300166310ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aniso8601" version = "9.0.1" description = "A library for parsing ISO 8601 strings." optional = false python-versions = "*" files = [ {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, ] [package.extras] dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] [[package]] name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" files = [ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, ] [package.dependencies] typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "dnspython" version = "2.4.2" description = "DNS toolkit" optional = false python-versions = ">=3.8,<4.0" files = [ {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, ] [package.extras] dnssec = ["cryptography (>=2.6,<42.0)"] doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] doq = ["aioquic (>=0.9.20)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.23)"] wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "graphene" version = "3.3" description = "GraphQL Framework for Python" optional = false python-versions = "*" files = [ {file = "graphene-3.3-py2.py3-none-any.whl", hash = "sha256:bb3810be33b54cb3e6969506671eb72319e8d7ba0d5ca9c8066472f75bf35a38"}, {file = "graphene-3.3.tar.gz", hash = "sha256:529bf40c2a698954217d3713c6041d69d3f719ad0080857d7ee31327112446b0"}, ] [package.dependencies] aniso8601 = ">=8,<10" graphql-core = ">=3.1,<3.3" graphql-relay = ">=3.1,<3.3" [package.extras] dev = ["black (==22.3.0)", "coveralls (>=3.3,<4)", "flake8 (>=4,<5)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] test = ["coveralls (>=3.3,<4)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] [[package]] name = "graphql-core" version = "3.2.3" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = false python-versions = ">=3.6,<4" files = [ {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, ] [[package]] name = "graphql-relay" version = "3.2.0" description = "Relay library for graphql-core" optional = false python-versions = ">=3.6,<4" files = [ {file = "graphql-relay-3.2.0.tar.gz", hash = "sha256:1ff1c51298356e481a0be009ccdff249832ce53f30559c1338f22a0e0d17250c"}, {file = "graphql_relay-3.2.0-py3-none-any.whl", hash = "sha256:c9b22bd28b170ba1fe674c74384a8ff30a76c8e26f88ac3aa1584dd3179953e5"}, ] [package.dependencies] graphql-core = ">=3.2,<3.3" [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "mock" version = "5.1.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" files = [ {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, ] [package.extras] build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] [[package]] name = "mongoengine" version = "0.27.0" description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB." optional = false python-versions = ">=3.7" files = [ {file = "mongoengine-0.27.0-py3-none-any.whl", hash = "sha256:c3523b8f886052f3deb200b3218bcc13e4b781661e3bea38587cc936c80ea358"}, {file = "mongoengine-0.27.0.tar.gz", hash = "sha256:8f38df7834dc4b192d89f2668dcf3091748d12f74d55648ce77b919167a4a49b"}, ] [package.dependencies] pymongo = ">=3.4,<5.0" [[package]] name = "mongomock" version = "4.1.2" description = "Fake pymongo stub for testing simple MongoDB-dependent code" optional = false python-versions = "*" files = [ {file = "mongomock-4.1.2-py2.py3-none-any.whl", hash = "sha256:08a24938a05c80c69b6b8b19a09888d38d8c6e7328547f94d46cadb7f47209f2"}, {file = "mongomock-4.1.2.tar.gz", hash = "sha256:f06cd62afb8ae3ef63ba31349abd220a657ef0dd4f0243a29587c5213f931b7d"}, ] [package.dependencies] packaging = "*" sentinels = "*" [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "promise" version = "2.3" description = "Promises/A+ implementation for Python" optional = false python-versions = "*" files = [ {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, ] [package.dependencies] six = "*" [package.extras] test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"] [[package]] name = "pymongo" version = "4.6.1" description = "Python driver for MongoDB " optional = false python-versions = ">=3.7" files = [ {file = "pymongo-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4344c30025210b9fa80ec257b0e0aab5aa1d5cca91daa70d82ab97b482cc038e"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:1c5654bb8bb2bdb10e7a0bc3c193dd8b49a960b9eebc4381ff5a2043f4c3c441"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:eaf2f65190c506def2581219572b9c70b8250615dc918b3b7c218361a51ec42e"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:262356ea5fcb13d35fb2ab6009d3927bafb9504ef02339338634fffd8a9f1ae4"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:2dd2f6960ee3c9360bed7fb3c678be0ca2d00f877068556785ec2eb6b73d2414"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:ff925f1cca42e933376d09ddc254598f8c5fcd36efc5cac0118bb36c36217c41"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:3cadf7f4c8e94d8a77874b54a63c80af01f4d48c4b669c8b6867f86a07ba994f"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55dac73316e7e8c2616ba2e6f62b750918e9e0ae0b2053699d66ca27a7790105"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:154b361dcb358ad377d5d40df41ee35f1cc14c8691b50511547c12404f89b5cb"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2940aa20e9cc328e8ddeacea8b9a6f5ddafe0b087fedad928912e787c65b4909"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:010bc9aa90fd06e5cc52c8fac2c2fd4ef1b5f990d9638548dde178005770a5e8"}, {file = "pymongo-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e470fa4bace5f50076c32f4b3cc182b31303b4fefb9b87f990144515d572820b"}, {file = "pymongo-4.6.1-cp310-cp310-win32.whl", hash = "sha256:da08ea09eefa6b960c2dd9a68ec47949235485c623621eb1d6c02b46765322ac"}, {file = "pymongo-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:13d613c866f9f07d51180f9a7da54ef491d130f169e999c27e7633abe8619ec9"}, {file = "pymongo-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a0ae7a48a6ef82ceb98a366948874834b86c84e288dbd55600c1abfc3ac1d88"}, {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd94c503271e79917b27c6e77f7c5474da6930b3fb9e70a12e68c2dff386b9a"}, {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d4ccac3053b84a09251da8f5350bb684cbbf8c8c01eda6b5418417d0a8ab198"}, {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:349093675a2d3759e4fb42b596afffa2b2518c890492563d7905fac503b20daa"}, {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88beb444fb438385e53dc9110852910ec2a22f0eab7dd489e827038fdc19ed8d"}, {file = "pymongo-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8e62d06e90f60ea2a3d463ae51401475568b995bafaffd81767d208d84d7bb1"}, {file = "pymongo-4.6.1-cp311-cp311-win32.whl", hash = "sha256:5556e306713e2522e460287615d26c0af0fe5ed9d4f431dad35c6624c5d277e9"}, {file = "pymongo-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:b10d8cda9fc2fcdcfa4a000aa10413a2bf8b575852cd07cb8a595ed09689ca98"}, {file = "pymongo-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b435b13bb8e36be11b75f7384a34eefe487fe87a6267172964628e2b14ecf0a7"}, {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e438417ce1dc5b758742e12661d800482200b042d03512a8f31f6aaa9137ad40"}, {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b47ebd89e69fbf33d1c2df79759d7162fc80c7652dacfec136dae1c9b3afac7"}, {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbed8cccebe1169d45cedf00461b2842652d476d2897fd1c42cf41b635d88746"}, {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30a9e06041fbd7a7590693ec5e407aa8737ad91912a1e70176aff92e5c99d20"}, {file = "pymongo-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8729dbf25eb32ad0dc0b9bd5e6a0d0b7e5c2dc8ec06ad171088e1896b522a74"}, {file = "pymongo-4.6.1-cp312-cp312-win32.whl", hash = "sha256:3177f783ae7e08aaf7b2802e0df4e4b13903520e8380915e6337cdc7a6ff01d8"}, {file = "pymongo-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:00c199e1c593e2c8b033136d7a08f0c376452bac8a896c923fcd6f419e07bdd2"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:13552ca505366df74e3e2f0a4f27c363928f3dff0eef9f281eb81af7f29bc3c5"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:77e0df59b1a4994ad30c6d746992ae887f9756a43fc25dec2db515d94cf0222d"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3a7f02a58a0c2912734105e05dedbee4f7507e6f1bd132ebad520be0b11d46fd"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:026a24a36394dc8930cbcb1d19d5eb35205ef3c838a7e619e04bd170713972e7"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:3b287e814a01deddb59b88549c1e0c87cefacd798d4afc0c8bd6042d1c3d48aa"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:9a710c184ba845afb05a6f876edac8f27783ba70e52d5eaf939f121fc13b2f59"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:30b2c9caf3e55c2e323565d1f3b7e7881ab87db16997dc0cbca7c52885ed2347"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff62ba8ff70f01ab4fe0ae36b2cb0b5d1f42e73dfc81ddf0758cd9f77331ad25"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:547dc5d7f834b1deefda51aedb11a7af9c51c45e689e44e14aa85d44147c7657"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1de3c6faf948f3edd4e738abdb4b76572b4f4fdfc1fed4dad02427e70c5a6219"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2831e05ce0a4df10c4ac5399ef50b9a621f90894c2a4d2945dc5658765514ed"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:144a31391a39a390efce0c5ebcaf4bf112114af4384c90163f402cec5ede476b"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33bb16a07d3cc4e0aea37b242097cd5f7a156312012455c2fa8ca396953b11c4"}, {file = "pymongo-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b7b1a83ce514700276a46af3d9e481ec381f05b64939effc9065afe18456a6b9"}, {file = "pymongo-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:3071ec998cc3d7b4944377e5f1217c2c44b811fae16f9a495c7a1ce9b42fb038"}, {file = "pymongo-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2346450a075625c4d6166b40a013b605a38b6b6168ce2232b192a37fb200d588"}, {file = "pymongo-4.6.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:061598cbc6abe2f382ab64c9caa83faa2f4c51256f732cdd890bcc6e63bfb67e"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d483793a384c550c2d12cb794ede294d303b42beff75f3b3081f57196660edaf"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f9756f1d25454ba6a3c2f1ef8b7ddec23e5cdeae3dc3c3377243ae37a383db00"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ed23b0e2dac6f84f44c8494fbceefe6eb5c35db5c1099f56ab78fc0d94ab3af"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3d18a9b9b858ee140c15c5bfcb3e66e47e2a70a03272c2e72adda2482f76a6ad"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:c258dbacfff1224f13576147df16ce3c02024a0d792fd0323ac01bed5d3c545d"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:f7acc03a4f1154ba2643edeb13658d08598fe6e490c3dd96a241b94f09801626"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:76013fef1c9cd1cd00d55efde516c154aa169f2bf059b197c263a255ba8a9ddf"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0e6a6c807fa887a0c51cc24fe7ea51bb9e496fe88f00d7930063372c3664c3"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd1fa413f8b9ba30140de198e4f408ffbba6396864c7554e0867aa7363eb58b2"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d219b4508f71d762368caec1fc180960569766049bbc4d38174f05e8ef2fe5b"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b81ecf18031998ad7db53b960d1347f8f29e8b7cb5ea7b4394726468e4295e"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56816e43c92c2fa8c11dc2a686f0ca248bea7902f4a067fa6cbc77853b0f041e"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef801027629c5b511cf2ba13b9be29bfee36ae834b2d95d9877818479cdc99ea"}, {file = "pymongo-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d4c2be9760b112b1caf649b4977b81b69893d75aa86caf4f0f398447be871f3c"}, {file = "pymongo-4.6.1-cp38-cp38-win32.whl", hash = "sha256:39d77d8bbb392fa443831e6d4ae534237b1f4eee6aa186f0cdb4e334ba89536e"}, {file = "pymongo-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:4497d49d785482cc1a44a0ddf8830b036a468c088e72a05217f5b60a9e025012"}, {file = "pymongo-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:69247f7a2835fc0984bbf0892e6022e9a36aec70e187fcfe6cae6a373eb8c4de"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7bb0e9049e81def6829d09558ad12d16d0454c26cabe6efc3658e544460688d9"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a1810c2cbde714decf40f811d1edc0dae45506eb37298fd9d4247b8801509fe"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e2aced6fb2f5261b47d267cb40060b73b6527e64afe54f6497844c9affed5fd0"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d0355cff58a4ed6d5e5f6b9c3693f52de0784aa0c17119394e2a8e376ce489d4"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:3c74f4725485f0a7a3862cfd374cc1b740cebe4c133e0c1425984bcdcce0f4bb"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:9c79d597fb3a7c93d7c26924db7497eba06d58f88f58e586aa69b2ad89fee0f8"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8ec75f35f62571a43e31e7bd11749d974c1b5cd5ea4a8388725d579263c0fdf6"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e641f931c5cd95b376fd3c59db52770e17bec2bf86ef16cc83b3906c054845"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aafd036f6f2e5ad109aec92f8dbfcbe76cff16bad683eb6dd18013739c0b3ae"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f2b856518bfcfa316c8dae3d7b412aecacf2e8ba30b149f5eb3b63128d703b9"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec31adc2e988fd7db3ab509954791bbc5a452a03c85e45b804b4bfc31fa221d"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9167e735379ec43d8eafa3fd675bfbb12e2c0464f98960586e9447d2cf2c7a83"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1461199b07903fc1424709efafe379205bf5f738144b1a50a08b0396357b5abf"}, {file = "pymongo-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3094c7d2f820eecabadae76bfec02669567bbdd1730eabce10a5764778564f7b"}, {file = "pymongo-4.6.1-cp39-cp39-win32.whl", hash = "sha256:c91ea3915425bd4111cb1b74511cdc56d1d16a683a48bf2a5a96b6a6c0f297f7"}, {file = "pymongo-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef102a67ede70e1721fe27f75073b5314911dbb9bc27cde0a1c402a11531e7bd"}, {file = "pymongo-4.6.1.tar.gz", hash = "sha256:31dab1f3e1d0cdd57e8df01b645f52d43cc1b653ed3afd535d2891f4fc4f9712"}, ] [package.dependencies] dnspython = ">=1.16.0,<3.0.0" [package.extras] aws = ["pymongo-auth-aws (<2.0.0)"] encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] test = ["pytest (>=7)"] zstd = ["zstandard"] [[package]] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" files = [ {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, ] [package.dependencies] pytest = ">=7.0.0" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "ruff" version = "0.1.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f80496854fdc65b6659c271d2c26e90d4d401e6a4a31908e7e334fab4645aac"}, {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1ea109bdb23c2a4413f397ebd8ac32cb498bee234d4191ae1a310af760e5d287"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0c2de9dd9daf5e07624c24add25c3a490dbf74b0e9bca4145c632457b3b42a"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69a4bed13bc1d5dabf3902522b5a2aadfebe28226c6269694283c3b0cecb45fd"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de02ca331f2143195a712983a57137c5ec0f10acc4aa81f7c1f86519e52b92a1"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45b38c3f8788a65e6a2cab02e0f7adfa88872696839d9882c13b7e2f35d64c5f"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c64cb67b2025b1ac6d58e5ffca8f7b3f7fd921f35e78198411237e4f0db8e73"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dcc6bb2f4df59cb5b4b40ff14be7d57012179d69c6565c1da0d1f013d29951b"}, {file = "ruff-0.1.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2bb4bb6bbe921f6b4f5b6fdd8d8468c940731cb9406f274ae8c5ed7a78c478"}, {file = "ruff-0.1.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:276a89bcb149b3d8c1b11d91aa81898fe698900ed553a08129b38d9d6570e717"}, {file = "ruff-0.1.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:90c958fe950735041f1c80d21b42184f1072cc3975d05e736e8d66fc377119ea"}, {file = "ruff-0.1.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b05e3b123f93bb4146a761b7a7d57af8cb7384ccb2502d29d736eaade0db519"}, {file = "ruff-0.1.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:290ecab680dce94affebefe0bbca2322a6277e83d4f29234627e0f8f6b4fa9ce"}, {file = "ruff-0.1.7-py3-none-win32.whl", hash = "sha256:416dfd0bd45d1a2baa3b1b07b1b9758e7d993c256d3e51dc6e03a5e7901c7d80"}, {file = "ruff-0.1.7-py3-none-win_amd64.whl", hash = "sha256:4af95fd1d3b001fc41325064336db36e3d27d2004cdb6d21fd617d45a172dd96"}, {file = "ruff-0.1.7-py3-none-win_arm64.whl", hash = "sha256:0683b7bfbb95e6df3c7c04fe9d78f631f8e8ba4868dfc932d43d690698057e2e"}, {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, ] [[package]] name = "sentinels" version = "1.0.0" description = "Various objects to denote special meanings in python" optional = false python-versions = "*" files = [ {file = "sentinels-1.0.0.tar.gz", hash = "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1"}, ] [[package]] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "singledispatch" version = "4.1.0" description = "Backport functools.singledispatch to older Pythons." optional = false python-versions = ">=3.8" files = [ {file = "singledispatch-4.1.0-py2.py3-none-any.whl", hash = "sha256:6061bd291204beaeac90cdbc342b68d213b7a6efb44ae6c5e6422a78be351c8a"}, {file = "singledispatch-4.1.0.tar.gz", hash = "sha256:f3430b886d5b4213d07d715096a75da5e4a8105284c497b9aee6d6d48bfe90cb"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" content-hash = "3abff70f60eafa1b72442b3a5cb3dce4e313ed9029de12d87ea8e29060a44952" graphene-mongo-0.4.1/pyproject.toml000066400000000000000000000026071453504055300173470ustar00rootroot00000000000000[tool.poetry] name = "graphene-mongo" packages = [{ include = "graphene_mongo" }] version = "0.4.1" description = "Graphene Mongoengine integration" authors = [ "Abaw Chen ", ] license = "MIT" readme = "README.md" homepage = "https://github.com/graphql-python/graphene-mongo" repository = "https://github.com/graphql-python/graphene-mongo" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: MIT License", ] keywords = [ "graphene-mongo", "graphql", "api", "graphql", "protocol", "relay", "graphene", "mongo", "mongoengine" ] [tool.poetry.dependencies] python = ">=3.8,<4" graphene = ">=3.1.1" promise = ">=2.3" mongoengine = ">=0.27" singledispatch = ">=4.1.0" asgiref = "^3.7.2" [tool.poetry.group.dev.dependencies] pytest = "*" mongomock = ">=4.1.2" mock = ">=5.0.1" pytest-cov = "*" pytest-asyncio = "^0.21.0" ruff = "^0.1.6" setuptools = "^69.0.2" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.ruff] line-length = 100graphene-mongo-0.4.1/setup.cfg000066400000000000000000000004201453504055300162430ustar00rootroot00000000000000[metadata] description_file = README.md [flake8] exclude = setup.py,docs/*,examples/* max-line-length = 150 [coverage:run] omit = */tests/* [isort] known_first_party=graphene,graphene_mongo [aliases] test=pytest [tool:pytest] python_files = graphene_mongo/tests/*.py