[
  {
    "path": ".gitignore",
    "content": "venv\n.DS_Store\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n"
  },
  {
    "path": ".travis.yml",
    "content": "# Config file for automatic testing at travis-ci.org\nlanguage: python\nenv:\n  - TOXENV=py27\n  - TOXENV=py33\n  - TOXENV=py34\n  - TOXENV=py35\npython: 3.5\ninstall:\n  - pip install tox\nscript: tox\nservices:\n  - elasticsearch\n  - mongodb\nbefore_script:\n  - travis_retry curl -XDELETE 'http://localhost:9200/ramses_starter/'\n  - mongo ramses_starter --eval 'db.dropDatabase();'\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Team members\n\nIn alphabetical order:\n\n* [Artem Kostiuk](https://github.com/postatum)\n* [Chris Hart](https://github.com/chrstphrhrt)\n* [Jonathan Stoikovitch](https://github.com/jstoiko)\n\n## Pull-requests\n\nPull-requests are welcomed!\n\n## Testing\n\n1. Install dev requirements by running `pip install -r requirements.dev`\n2. Run tests using `py.test --cov ramses tests`\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.md\ninclude VERSION\nrecursive-include ramses/scaffolds *"
  },
  {
    "path": "README.md",
    "content": "# `Ramses`\n[![Build Status](https://travis-ci.org/ramses-tech/ramses.svg?branch=master)](https://travis-ci.org/ramses-tech/ramses)\n[![Documentation](https://readthedocs.org/projects/ramses/badge/?version=stable)](http://ramses.readthedocs.org)\n[![Join the chat at https://gitter.im/ramses-tech/ramses](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/ramses-tech/ramses)\n\nRamses is a framework that generates a RESTful API using [RAML](http://raml.org). It uses Pyramid and [Nefertari](https://github.com/ramses-tech/nefertari) which provides Elasticsearch / Posgres / MongoDB / Your Data Store&#8482; -powered views.\n\nLooking to get started quickly? You can take a look at the [\"Getting Started\" guide](https://ramses.readthedocs.org/en/stable/getting_started.html).\n"
  },
  {
    "path": "VERSION",
    "content": "0.5.3"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/Nefertari.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/Nefertari.qhc\"\n\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/Nefertari\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Nefertari\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/source/changelog.rst",
    "content": "Changelog\n=========\n\n* :release:`0.5.3 <2016-05-17>`\n* :bug:`107` Fixed issue with hyphens in resource paths\n\n* :release:`0.5.2 <2016-05-17>`\n* :support:`99 backported` Use ACL mixin from nefertari-guards (if enabled)\n* :support:`- backported` Scaffold defaults to Pyramid 1.6.1\n\n* :release:`0.5.1 <2015-11-18>`\n* :bug:`88` Reworked the creation of related/auth_model models, order does not matter anymore\n\n* :release:`0.5.0 <2015-10-07>`\n* :bug:`- major` Fixed a bug using 'required' '_db_settings' property on 'relationship' field\n* :support:`-` Added support for `'nefertari-guards' <https://nefertari-guards.readthedocs.org/>`_\n* :support:`-` Added support for Nefertari '_hidden_fields'\n* :support:`-` Added support for Nefertari event handlers\n* :support:`-` Simplified field processors, '_before_processors' is now called '_processors', removed '_after_processors'\n* :support:`-` ACL permission names in RAML now match real permission names instead of http methods\n* :support:`-` Added support for the property '_nesting_depth' in schemas\n\n* :release:`0.4.1 <2015-09-02>`\n* :bug:`-` Simplified ACLs (refactoring)\n\n* :release:`0.4.0 <2015-08-19>`\n* :support:`-` Added support for JSON schema draft 04\n* :support:`-` RAML is now parsed using ramlfications instead of pyraml-parser\n* :feature:`-` Boolean values in RAML don't have to be strings anymore (previous limitation of pyraml-parser)\n* :feature:`-` Renamed setting 'ramses.auth' to 'auth'\n* :feature:`-` Renamed setting 'debug' to 'enable_get_tunneling'\n* :feature:`-` Field name and request object are now passed to field processors under 'field' and 'request' kwargs respectively\n* :feature:`-` Added support for relationship processors and backref relationship processors ('backref_after_validation'/'backref_before_validation')\n* :feature:`-` Renamed schema's 'args' property to '_db_settings'\n* :feature:`-` Properties 'type' and 'required' are now under '_db_settings'\n* :feature:`-` Prefixed all Ramses schema properties by an underscore: '_auth_fields', '_public_fields', '_nested_relationships', '_auth_model', '_db_settings'\n* :feature:`-` Error response bodies are now returned as JSON\n* :bug:`- major` Fixed processors not applied on fields of type 'list' and type 'dict'\n* :bug:`- major` Fixed a limitation preventing collection names to use nouns that do not have plural forms\n\n* :release:`0.3.1 <2015-07-07>`\n* :support:`- backported` Added support for callables in 'default' field argument\n* :support:`- backported` Added support for 'onupdate' field argument\n\n* :release:`0.3.0 <2015-06-14>`\n* :support:`-` Added python3 support\n\n* :release:`0.2.3 <2015-06-05>`\n* :bug:`-` Forward compatibility with nefertari releases\n\n* :release:`0.2.2 <2015-06-03>`\n* :bug:`-` Fixed password minimum length support by adding before and after validation processors\n* :bug:`-` Fixed race condition in Elasticsearch indexing\n\n* :release:`0.2.1 <2015-05-27>`\n* :bug:`-` Fixed limiting fields to be searched\n* :bug:`-` Fixed login issue\n* :bug:`-` Fixed custom processors\n\n* :release:`0.2.0 <2015-05-18>`\n* :feature:`-` Added support for securitySchemes, authentication (Pyramid 'auth ticket') and ACLs\n* :support:`-` Added several display options to schemas\n* :support:`-` Added unit tests\n* :support:`-` Improved docs\n* :feature:`-` Add support for processors in schema definition\n* :feature:`-` Add support for custom auth model\n* :support:`-` ES views now read from ES on update/delete_many\n\n* :release:`0.1.1 <2015-04-21>`\n* :bug:`-` Ramses could not be used in an existing Pyramid project\n\n* :release:`0.1.0 <2015-04-08>`\n* :support:`-` Initial release!\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Nefertari documentation build configuration file, created by\n# sphinx-quickstart on Fri Mar 27 11:16:31 2015.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys\nimport os\nimport shlex\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    # 'sphinxcontrib.fulltoc',\n    'releases'\n]\n\nreleases_github_path = 'ramses-tech/ramses'\nreleases_debug = True\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'Ramses'\ncopyright = u'Ramses Tech'\nauthor = u'Ramses Tech, Inc.'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\n# version = '0.1'\n# The full version, including alpha/beta/rc tags.\n# release = '0.1.0'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\n\nif not on_rtd:  # only import and set the theme if we're building docs locally\n    import sphinx_rtd_theme\n    html_theme = 'sphinx_rtd_theme'\n    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n\n# otherwise, readthedocs.org uses their theme by default, so no need to specify it\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n# html_theme = 'alabaster'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'Ramsesdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n\n# Latex figure (float) alignment\n#'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  (master_doc, 'Ramses.tex', u'Ramses Documentation',\n   author, 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'ramses', u'Ramses Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  (master_doc, 'Ramses', u'Ramses Documentation',\n   author, 'Ramses', 'API generator for Pyramid using RAML',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/source/event_handlers.rst",
    "content": "Event Handlers\n==============\n\nRamses supports `Nefertari event handlers <http://nefertari.readthedocs.org/en/stable/event_handlers.html>`_. Ramses event handlers also have access to `Nefertari's wrapper API <http://nefertari.readthedocs.org/en/stable/models.html#wrapper-api>`_ which provides additional helpers.\n\n\nSetup\n-----\n\n\nWriting Event Handlers\n^^^^^^^^^^^^^^^^^^^^^^\n\nYou can write custom functions inside your ``__init__.py`` file, then add the ``@registry.add`` decorator before the functions that you'd like to turn into CRUD event handlers. Ramses CRUD event handlers has the same API as Nefertari CRUD event handlers. Check Nefertari CRUD Events doc for more details on events API.\n\nExample:\n\n.. code-block:: python\n\n\n    import logging\n    from ramses import registry\n\n\n    log = logging.getLogger('foo')\n\n    @registry.add\n    def log_changed_fields(event):\n        changed = ['{}: {}'.format(name, field.new_value)\n                   for name, field in event.fields.items()]\n        logger.debug('Changed fields: ' + ', '.join(changed))\n\n\nConnecting Event Handlers\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen you define event handlers in your ``__init__.py`` as described above, you can apply them on per-model basis. If multiple handlers are listed, they are executed in the order in which they are listed. Handlers should be defined in the root of JSON schema using ``_event_handlers`` property. This property is an object, keys of which are called \"event tags\" and values are lists of handler names. Event tags are composed of two parts: ``<type>_<action>`` whereby:\n\n**type**\n    Is either ``before`` or ``after``, depending on when handler should run - before view method call or after respectively. You can read more about when to use `before vs after event handlers <http://nefertari.readthedocs.org/en/stable/event_handlers.html#before-vs-after>`_.\n\n**action**\n    Exact name of Nefertari view method that processes the request (action) and special names for authentication actions.\n\nComplete list of actions:\n    * **index** - Collection GET\n    * **create** - Collection POST\n    * **update_many** - Collection PATCH/PUT\n    * **delete_many** - Collection DELETE\n    * **collection_options** - Collection OPTIONS\n    * **show** - Item GET\n    * **update** - Item PATCH\n    * **replace** - Item PUT\n    * **delete** - Item DELETE\n    * **item_options** - Item OPTIONS\n    * **login** - User login (POST /auth/login)\n    * **logout** - User logout (POST /auth/logout)\n    * **register** - User register (POST /auth/register)\n    * **set** - triggers on all the following actions: **create**, **update**, **replace**, **update_many** and **register**.\n\n\nExample\n-------\n\nWe will use the following handler to demonstrate how to connect handlers to events. This handler logs ``request`` to the console.\n\n.. code-block:: python\n\n    import logging\n    from ramses import registry\n\n\n    log = logging.getLogger('foo')\n\n    @registry.add\n    def log_request(event):\n        log.debug(event.view.request)\n\n\nAssuming we had a JSON schema representing the model ``User`` and we want to log all collection GET requests on the ``User`` model after they are processed using the ``log_request`` handler, we would register the handler in the JSON schema like this:\n\n.. code-block:: json\n\n    {\n        \"type\": \"object\",\n        \"title\": \"User schema\",\n        \"$schema\": \"http://json-schema.org/draft-04/schema\",\n        \"_event_handlers\": {\n            \"after_index\": [\"log_request\"]\n        },\n        ...\n    }\n\n\nOther Things You Can Do\n-----------------------\n\nYou can update another field's value, for example, increment a counter:\n\n.. code-block:: python\n\n    from ramses import registry\n\n\n    @registry.add\n    def increment_count(event):\n        instance = event.instance or event.response\n        counter = instance.counter\n        incremented = counter + 1\n        event.set_field_value('counter', incremented)\n\n\nYou can update other collections (or filtered collections), for example, mark sub-tasks as completed whenever a task is completed:\n\n.. code-block:: python\n\n    from ramses import registry\n    from nefertari import engine\n\n    @registry.add\n    def mark_subtasks_completed(event):\n        if 'task' not in event.fields:\n            return\n\n        completed = event.fields['task'].new_value\n        instance = event.instance or event.response\n\n        if completed:\n            subtask_model = engine.get_document_cls('Subtask')\n            subtasks = subtask_model.get_collection(task_id=instance.id)\n            subtask_model._update_many(subtasks, {'completed': True})\n\n\nYou can perform more complex queries using Elasticsearch:\n\n.. code-block:: python\n\n    from ramses import registry\n    from nefertari import engine\n    from nefertari.elasticsearch import ES\n\n\n    @registry.add\n    def mark_subtasks_after_2015_completed(event):\n        if 'task' not in event.fields:\n            return\n\n        completed = event.fields['task'].new_value\n        instance = event.instance or event.response\n\n        if completed:\n            subtask_model = engine.get_document_cls('Subtask')\n            es_query = 'task_id:{} AND created_at:[2015 TO *]'.format(instance.id)\n            subtasks_es = ES(subtask_model.__name__).get_collection(_raw_terms=es_query)\n            subtasks_db = subtask_model.filter_objects(subtasks_es)\n            subtask_model._update_many(subtasks_db, {'completed': True})\n"
  },
  {
    "path": "docs/source/field_processors.rst",
    "content": "Field processors\n================\n\nRamses supports `Nefertari field processors <http://nefertari.readthedocs.org/en/stable/field_processors.html>`_. Ramses field processors also have access to `Nefertari's wrapper API <http://nefertari.readthedocs.org/en/stable/models.html#wrapper-api>`_ which provides additional helpers.\n\n\nSetup\n-----\n\nTo setup a field processor, you can define the ``_processors`` property in your field definition (same level as ``_db_settings``). It should be an array of processor names to apply. You can also use the ``_backref_processors`` property to specify processors for backref field. For backref processors to work, ``_db_settings`` must contain the following properties: ``document``, ``type=relationship`` and ``backref_name``.\n\n.. code-block:: json\n\n    \"username\": {\n        ...\n        \"_processors\": [\"lowercase\"]\n    },\n    ...\n\n\nYou can read more about processors in Nefertari's `field processors documentation <http://nefertari.readthedocs.org/en/stable/field_processors.html>`_ including the `list of keyword arguments <http://nefertari.readthedocs.org/en/stable/field_processors.html#keyword-arguments>`_ passed to processors.\n\n\nExample\n-------\n\nIf we had following processors defined:\n\n.. code-block:: python\n\n    from .my_helpers import get_stories_by_ids\n\n\n    @registry.add\n    def lowercase(**kwargs):\n        \"\"\" Make :new_value: lowercase \"\"\"\n        return (kwargs['new_value'] or '').lower()\n\n    @registry.add\n    def validate_stories_exist(**kwargs):\n        \"\"\" Make sure added stories exist. \"\"\"\n        story_ids = kwargs['new_value']\n        if story_ids:\n            # Get stories by ids\n            stories = get_stories_by_ids(story_ids)\n            if not stories or len(stories) < len(story_ids):\n                raise Exception(\"Some of provided stories do not exist\")\n        return story_ids\n\n\n.. code-block:: json\n\n    # User model json\n    {\n        \"type\": \"object\",\n        \"title\": \"User schema\",\n        \"$schema\": \"http://json-schema.org/draft-04/schema\",\n        \"properties\": {\n            \"stories\": {\n                \"_db_settings\": {\n                    \"type\": \"relationship\",\n                    \"document\": \"Story\",\n                    \"backref_name\": \"owner\"\n                },\n                \"_processors\": [\"validate_stories_exist\"],\n                \"_backref_processors\": [\"lowercase\"]\n            },\n            ...\n        }\n    }\n\nNotes:\n    * ``validate_stories_exist`` processor will be run when request changes ``User.stories`` value. The processor will make sure all of story IDs from request exist.\n    * ``lowercase`` processor will be run when request changes ``Story.owner`` field. The processor will lowercase new value of the ``Story.owner`` field.\n"
  },
  {
    "path": "docs/source/fields.rst",
    "content": "Fields\n======\n\nTypes\n-----\n\nYou can set a field's type by setting the ``type`` property under ``_db_settings``.\n\n.. code-block:: json\n\n    \"created_at\": {\n        (...)\n        \"_db_settings\": {\n            \"type\": \"datetime\"\n        }\n    }\n\nThis is a list of all available types:\n\n* biginteger\n* binary\n* boolean\n* choice\n* date\n* datetime\n* decimal\n* dict\n* float\n* foreign_key\n* id_field\n* integer\n* interval\n* list\n* pickle\n* relationship\n* smallinteger\n* string\n* text\n* time\n* unicode\n* unicodetext\n\n\nRequired Fields\n---------------\n\nYou can set a field as required by setting the ``required`` property under ``_db_settings``.\n\n.. code-block:: json\n\n    \"password\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"required\": true\n        }\n    }\n\n\nPrimary Key\n-----------\n\nYou can use an ``id_field`` in lieu of primary key.\n\n.. code-block:: json\n\n    \"id\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"primary_key\": true\n        }\n    }\n\nYou can alternatively elect a field to be the primary key of your model by setting its ``primary_key`` property under ``_db_settings``. For example, if you decide to use ``username`` as the primary key of your `User` model. This will enable resources to refer to that field in their url, e.g. ``/api/users/john``\n\n.. code-block:: json\n\n    \"username\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"primary_key\": true\n        }\n    }\n\nConstraints\n-----------\n\nYou can set a minimum and/or maximum length of your field by setting the ``min_length`` / ``max_length`` properties under ``_db_settings``. You can also add a unique constraint on a field by setting the ``unique`` property.\n\n.. code-block:: json\n\n    \"field\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"unique\": true,\n            \"min_length\": 5,\n            \"max_length\": 50\n        }\n    }\n\n\nDefault Value\n-------------\n\nYou can set a default value for you field by setting the ``default`` property under ``_db_settings``.\n\n.. code-block:: json\n\n    \"field\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"default\": \"default value\"\n        }\n    },\n\nThe ``default`` value can also be set to a Python callable, e.g.\n\n.. code-block:: json\n\n    \"datetime_field\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"default\": \"{{datetime.datetime.utcnow}}\"\n        }\n    },\n\n\nUpdate Default Value\n--------------------\n\nYou can set an update default value for your field by setting the ``onupdate`` property under ``_db_settings``. This is particularly useful to update 'datetime' fields on every updates, e.g.\n\n.. code-block:: json\n\n    \"datetime_field\": {\n        (...)\n        \"_db_settings\": {\n            (...)\n            \"onupdate\": \"{{datetime.datetime.utcnow}}\"\n        }\n    },\n\n\nList Fields\n-----------\n\nYou can list the accepted values of any ``list`` or ``choice`` fields by setting the ``choices`` property under ``_db_settings``.\n\n.. code-block:: json\n\n    \"field\": {\n        (...)\n        \"_db_settings\": {\n            \"type\": \"choice\",\n            \"choices\": [\"choice1\", \"choice2\", \"choice3\"],\n            \"default\": \"choice1\"\n        }\n    }\n\nYou can also provide the list/choice items' ``item_type``.\n\n.. code-block:: json\n\n    \"field\": {\n        (...)\n        \"_db_settings\": {\n            \"type\": \"list\",\n            \"item_type\": \"string\"\n        }\n    }\n\nOther ``_db_settings``\n----------------------\n\nNote that you can pass any engine-specific arguments to your fields by defining such arguments in ``_db_settings``.\n"
  },
  {
    "path": "docs/source/getting_started.rst",
    "content": "Getting started\n===============\n\n1. Create your project in a virtualenv directory (see the `virtualenv documentation <https://virtualenv.pypa.io>`_)\n\n.. code-block:: shell\n\n    $ virtualenv my_project\n    $ source my_project/bin/activate\n    $ pip install ramses\n    $ pcreate -s ramses_starter my_project\n    $ cd my_project\n    $ pserve local.ini\n\n2. Tada! Start editing api.raml to modify the API and items.json for the schema.\n\n\nRequirements\n------------\n\n* Python 2.7, 3.3 or 3.4\n* Elasticsearch (data is automatically indexed for near real-time search)\n* Postgres or Mongodb or Your Data Store™\n\n\nExamples\n--------\n\n- For a more complete example of a Pyramid project using Ramses, you can take a look at the `Example Project <https://github.com/ramses-tech/ramses-example>`_.\n- RAML can be used to generate an end-to-end application, check out `this example <https://github.com/jstoiko/raml-javascript-client>`_ using Ramses on the backend and RAML-javascript-client + BackboneJS on the front-end.\n\n\nTutorials\n---------\n\n- `Create a REST API in Minutes With Pyramid and Ramses <https://realpython.com/blog/python/create-a-rest-api-in-minutes-with-pyramid-and-ramses/>`_\n- `Make an Elasticsearch-powered REST API for any data with Ramses <https://www.elastic.co/blog/make-an-elasticsearch-powered-rest-api-for-any-data-with-ramses>`_\n"
  },
  {
    "path": "docs/source/index.rst",
    "content": "Ramses\n======\n\nRamses is a framework that generates a RESTful API using `RAML <http://raml.org>`_. It uses Pyramid and `Nefertari <https://nefertari.readthedocs.org/>`_ which provides Elasticsearch / Posgres / MongoDB / Your Data Store™ -powered views. Using Elasticsearch enables `Elasticsearch-powered requests <http://nefertari.readthedocs.org/en/stable/making_requests.html>`_ which provides near real-time search.\n\nWebsite:\n    `<http://ramses.tech>`_\nSource code:\n    `<http://github.com/ramses-tech/ramses>`_\n\n\nTable of Contents\n=================\n\n.. toctree::\n   :maxdepth: 2\n\n   getting_started\n   raml\n   schemas\n   fields\n   event_handlers\n   field_processors\n   relationships\n   changelog\n\n.. image:: ramses.jpg\n\nImage credit: Wikipedia\n"
  },
  {
    "path": "docs/source/raml.rst",
    "content": "RAML Configuration\n==================\n\nYou can read the full RAML specs `here <http://raml.org/spec.html>`_.\n\n\nAuthentication\n--------------\n\nIn order to enable authentication, add the ``auth`` parameter to your .ini file:\n\n.. code-block:: ini\n\n    auth = true\n\nIn the root section of your RAML file, you can add a ``securitySchemes``, define the ``x_ticket_auth`` method and list it in your root-level ``securedBy``. This will enable cookie-based authentication.\n\n.. code-block:: yaml\n\n    securitySchemes:\n        - x_ticket_auth:\n            description: Standard Pyramid Auth Ticket policy\n            type: x-Ticket\n            settings:\n                secret: auth_tkt_secret\n                hashalg: sha512\n                cookie_name: ramses_auth_tkt\n                http_only: 'true'\n    securedBy: [x_ticket_auth]\n\nA few convenience routes will be automatically added:\n\n* POST ``/auth/register``: register a new user\n* POST ``/auth/login``: login an existing user\n* GET ``/auth/logout``: logout currently logged-in user\n* GET ``/users/self``: returns currently logged-in user\n\n\nACLs\n----\n\nIn your ``securitySchemes``, you can add as many ACLs as you need. Then you can reference these ACLs in your resource's ``securedBy``.\n\n.. code-block:: yaml\n\n    securitySchemes:\n        (...)\n        - read_only_users:\n            description: ACL that allows authenticated users to read\n            type: x-ACL\n            settings:\n                collection: |\n                    allow admin all\n                    allow authenticated view\n                item: |\n                    allow admin all\n                    allow authenticated view\n    (...)\n    /items:\n        securedBy: [read_only_users]\n\n\nEnabling HTTP Methods\n---------------------\n\nListing an HTTP method in your resource definition is all it takes to enable such method.\n\n.. code-block:: yaml\n\n    /items:\n        (...)\n        post:\n            description: Create an item\n        get:\n            description: Get multiple items\n        patch:\n            description: Update multiple items\n        delete:\n            description: delete multiple items\n\n        /{id}:\n            displayName: One item\n            get:\n                description: Get a particular item\n            delete:\n                description: Delete a particular item\n            patch:\n                description: Update a particular item\n\n\nYou can link your schema definition for each resource by adding it to the ``post`` section.\n\n.. code-block:: yaml\n\n    /items:\n        (...)\n        post:\n            (...)\n            body:\n                application/json:\n                    schema: !include schemas/items.json\n\n\n"
  },
  {
    "path": "docs/source/relationships.rst",
    "content": "Relationships\n=============\n\n\nBasics\n------\n\nRelationships in Ramses are used to represent One-To-Many(o2m) and One-To-One(o2o) relationships between objects in database.\n\nTo set up relationships fields of types ``foreign_key`` and ``relationship`` are used. ``foreign_key`` field is not required when using ``nefertari_mongodb`` engine and is ignored.\n\n\nFor this tutorial we are going to use the example of users and\nstories. In this example we have a OneToMany relationship betweed ``User``\nand ``Story``. One user may have many stories but each story has only one\nowner.  Check the end of the tutorial for the complete example RAML\nfile and schemas.\n\nExample code is the very minimum needed to explain the subject. We will be referring to the examples along all the tutorial.\n\n\nField \"type\": \"relationship\"\n----------------------------\n\nMust be defined on the *One* side of OneToOne or OneToMany\nrelationship (``User`` in our example). Relationships are created as\nOneToMany by default.\n\nExample of using ``relationship`` field (defined on ``User`` model in our example):\n\n.. code-block:: json\n\n    \"stories\": {\n        \"_db_settings\": {\n            \"type\": \"relationship\",\n            \"document\": \"Story\",\n            \"backref_name\": \"owner\"\n        }\n    }\n\n**Required params:**\n\n*type*\n    String. Just ``relationship``.\n\n*document*\n    String. Exact name of model class to which relationship is set up. To find out the name of model use singularized uppercased version of route name. E.g. if we want to set up relationship to objects of ``/stories`` then the ``document`` arg will be ``Story``.\n\n*backref_name*\n    String. Name of *back reference* field. This field will be auto-generated on model we set up relationship to and will hold the instance of model we are defining. In our example, field ``Story.owner`` will be generated and it will hold instance of ``User`` model to which story instance belongs. **Use this field to change relationships between objects.**\n\n\nField \"type\": \"foreign_key\"\n---------------------------\n\nThis represents a Foreign Key constraint in SQL and is only required\nwhen using ``nefertari_sqla`` engine. It is used in conjunction with\nthe relationship field, but is used on the model that ``relationship``\nrefers to. For example, if the ``User`` model contained the\n``relationship`` field, than the ``Story`` model would need a\n``foreign_key`` field.\n\n**Notes:**\n\n    * This field is not required and is ignored when using nefertari_mongodb engine.\n    * Name of the ``foreign_key`` field does not depend on relationship params in any way.\n    * This field **MUST NOT** be used to change relationships. This field only exists because it is required by SQLAlchemy.\n\n\nExample of using ``foreign_key`` field (defined on ``Story`` model in our example):\n\n.. code-block:: json\n\n    \"owner_id\": {\n        \"_db_settings\": {\n            \"type\": \"foreign_key\",\n            \"ref_document\": \"User\",\n            \"ref_column\": \"user.username\",\n            \"ref_column_type\": \"string\"\n        }\n    }\n\n**Required params:**\n\n*type*\n    String. Just ``foreign_key``.\n\n*ref_document*\n    String. Exact name of model class to which foreign key is set up. To find out the name of model use singularized uppercased version of route name. E.g. if we want to set up foreign key to objects of ``/user`` then the ``ref_document`` arg will be ``User``.\n\n*ref_column*\n    String. Dotted name/path to ``ref_document`` model's primary key\n    column. ``ref_column`` is the lowercased name of model we refer to in\n    ``ref_document`` joined by a dot with the exact name of its primary key column. In our example this is ``\"user.username\"``.\n\n**ref_column_type**\n    String. Ramses field type of ``ref_document`` model's primary key column specified in ``ref_column`` parameter. In our example this is ``\"string\"`` because ``User.username`` is ``\"type\": \"string\"``.\n\n\nOne to One relationship\n-----------------------\n\nTo create OneToOne relationships, specify ``\"uselist\": false`` in ``_db_settings`` of ``relationship`` field. When setting up One-to-One relationship, it doesn't matter which side defines the ``relationship`` field.\n\nE.g. if we had ``Profile`` model and we wanted to set up One-to-One relationship between ``Profile`` and ``User``, we would have to define a regular ``foreign_key`` field on ``Profile``:\n\n.. code-block:: json\n\n    \"user_id\": {\n        \"_db_settings\": {\n            \"type\": \"foreign_key\",\n            \"ref_document\": \"User\",\n            \"ref_column\": \"user.username\",\n            \"ref_column_type\": \"string\"\n        }\n    }\n\nand ``relationship`` field with ``\"uselist\": false`` on ``User``:\n\n.. code-block:: json\n\n    \"profile\": {\n        \"_db_settings\": {\n            \"type\": \"relationship\",\n            \"document\": \"Profile\",\n            \"backref_name\": \"user\",\n            \"uselist\": false\n        }\n    }\n\n\nThis relationship could also be defined the other way but with the same result: ``foreign_key`` field on ``User`` and ``relationship`` field on ``Profile`` pointing to ``User``.\n\n\nMultiple relationships\n----------------------\n\n**Note: This part is only valid(required) for nefertari_sqla engine, as nefertari_mongodb engine does not use foreign_key fields.**\n\nIf we were to define multiple relationships from model A to model B,\neach relationship must have a corresponding ``foreign_key``\ndefined. Also you must use a ``foreign_keys`` parameter on each\n``relationship`` field to specify which ``foreign_key`` each\n``relationship`` uses.\n\nE.g. if we were to add new relationship field ``User.assigned_stories``, relationship fields on ``User`` would have to be defined like this:\n\n.. code-block:: json\n\n    \"stories\": {\n        \"_db_settings\": {\n            \"type\": \"relationship\",\n            \"document\": \"Story\",\n            \"backref_name\": \"owner\",\n            \"foreign_keys\": \"Story.owner_id\"\n        }\n    },\n    \"assigned_stories\": {\n        \"_db_settings\": {\n            \"type\": \"relationship\",\n            \"document\": \"Story\",\n            \"backref_name\": \"assignee\",\n            \"foreign_keys\": \"Story.assignee_id\"\n        }\n    }\n\nAnd fields on ``Story`` like so:\n\n.. code-block:: json\n\n    \"owner_id\": {\n        \"_db_settings\": {\n            \"type\": \"foreign_key\",\n            \"ref_document\": \"User\",\n            \"ref_column\": \"user.username\",\n            \"ref_column_type\": \"string\"\n        }\n    },\n    \"assignee_id\": {\n        \"_db_settings\": {\n            \"type\": \"foreign_key\",\n            \"ref_document\": \"User\",\n            \"ref_column\": \"user.username\",\n            \"ref_column_type\": \"string\"\n        }\n    }\n\n\nComplete example\n----------------\n\n**example.raml**\n\n.. code-block:: yaml\n\n    #%RAML 0.8\n    ---\n    title: Example REST API\n    documentation:\n        - title: Home\n          content: |\n            Welcome to the example API.\n    baseUri: http://{host}:{port}/{version}\n    version: v1\n\n    /stories:\n        displayName: All stories\n        get:\n            description: Get all stories\n        post:\n            description: Create a new story\n            body:\n                application/json:\n                    schema: !include story.json\n        /{id}:\n            displayName: One story\n            get:\n                description: Get a particular story\n\n    /users:\n        displayName: All users\n        get:\n            description: Get all users\n        post:\n            description: Create a new user\n            body:\n                application/json:\n                    schema: !include user.json\n        /{username}:\n            displayName: One user\n            get:\n                description: Get a particular user\n\n\n**user.json**\n\n.. code-block:: json\n\n    {\n        \"type\": \"object\",\n        \"title\": \"User schema\",\n        \"$schema\": \"http://json-schema.org/draft-04/schema\",\n        \"required\": [\"username\"],\n        \"properties\": {\n            \"username\": {\n                \"_db_settings\": {\n                    \"type\": \"string\",\n                    \"primary_key\": true\n                }\n            },\n            \"stories\": {\n                \"_db_settings\": {\n                    \"type\": \"relationship\",\n                    \"document\": \"Story\",\n                    \"backref_name\": \"owner\"\n                }\n            }\n        }\n    }\n\n\n**story.json**\n\n.. code-block:: json\n\n    {\n        \"type\": \"object\",\n        \"title\": \"Story schema\",\n        \"$schema\": \"http://json-schema.org/draft-04/schema\",\n        \"properties\": {\n            \"id\": {\n                \"_db_settings\": {\n                    \"type\": \"id_field\",\n                    \"primary_key\": true\n                }\n            },\n            \"owner_id\": {\n                \"_db_settings\": {\n                    \"type\": \"foreign_key\",\n                    \"ref_document\": \"User\",\n                    \"ref_column\": \"user.username\",\n                    \"ref_column_type\": \"string\"\n                }\n            }\n        }\n    }\n"
  },
  {
    "path": "docs/source/schemas.rst",
    "content": "Defining Schemas\n================\n\nJSON Schema\n-----------\n\nRamses supports JSON Schema Draft 3 and Draft 4. You can read the official `JSON Schema documentation here <http://json-schema.org/documentation.html>`_.\n\n.. code-block:: json\n\n    {\n        \"type\": \"object\",\n        \"title\": \"Item schema\",\n        \"$schema\": \"http://json-schema.org/draft-04/schema\",\n        (...)\n    }\n\nAll Ramses-specific properties are prefixed with an underscore.\n\nShowing Fields\n--------------\n\nIf you've enabled authentication, you can list which fields to return to authenticated users in ``_auth_fields`` and to non-authenticated users in ``_public_fields``. Additionaly, you can list fields to be hidden but remain hidden (with proper persmissions) in ``_hidden_fields``.\n\n.. code-block:: json\n\n    {\n        (...)\n        \"_auth_fields\": [\"id\", \"name\", \"description\"],\n        \"_public_fields\": [\"name\"],\n        \"_hidden_fields\": [\"token\"],\n        (...)\n    }\n\nNested Documents\n----------------\n\nIf you use ``Relationship`` fields in your schemas, you can list those fields in ``_nested_relationships``. Your fields will then become nested documents instead of just showing the ``id``. You can control the level of nesting by specifying the ``_nesting_depth`` property, defaul is 1.\n\n.. code-block:: json\n\n    {\n        (...)\n        \"_nested_relationships\": [\"relationship_field_name\"],\n        \"_nesting_depth\": 2\n        (...)\n    }\n\nCustom \"user\" Model\n-------------------\n\nWhen authentication is enabled, a default \"user\" model will be created automatically with 4 fields: \"username\", \"email\", \"groups\" and \"password\". You can extend this default model by defining your own \"user\" schema and by setting ``_auth_model`` to ``true`` on that schema. You can add any additional fields in addition to those 4 default fields.\n\n.. code-block:: json\n\n    {\n        (...)\n        \"_auth_model\": true,\n        (...)\n    }\n"
  },
  {
    "path": "ramses/__init__.py",
    "content": "import logging\n\nimport ramlfications\nfrom nefertari.acl import RootACL as NefertariRootACL\nfrom nefertari.utils import dictset\n\n\nlog = logging.getLogger(__name__)\n\n\ndef includeme(config):\n    from .generators import generate_server, generate_models\n    Settings = dictset(config.registry.settings)\n    config.include('nefertari.engine')\n\n    config.registry.database_acls = Settings.asbool('database_acls')\n    if config.registry.database_acls:\n        config.include('nefertari_guards')\n\n    config.include('nefertari')\n    config.include('nefertari.view')\n    config.include('nefertari.json_httpexceptions')\n\n    # Process nefertari settings\n    if Settings.asbool('enable_get_tunneling'):\n        config.add_tween('nefertari.tweens.get_tunneling')\n\n    if Settings.asbool('cors.enable'):\n        config.add_tween('nefertari.tweens.cors')\n\n    if Settings.asbool('ssl_middleware.enable'):\n        config.add_tween('nefertari.tweens.ssl')\n\n    if Settings.asbool('request_timing.enable'):\n        config.add_tween('nefertari.tweens.request_timing')\n\n    # Set root factory\n    config.root_factory = NefertariRootACL\n\n    # Process auth settings\n    root = config.get_root_resource()\n    root_auth = getattr(root, 'auth', False)\n\n    log.info('Parsing RAML')\n    raml_root = ramlfications.parse(Settings['ramses.raml_schema'])\n\n    log.info('Starting models generation')\n    generate_models(config, raml_resources=raml_root.resources)\n\n    if root_auth:\n        from .auth import setup_auth_policies, get_authuser_model\n        if getattr(config.registry, 'auth_model', None) is None:\n            config.registry.auth_model = get_authuser_model()\n        setup_auth_policies(config, raml_root)\n\n    config.include('nefertari.elasticsearch')\n\n    log.info('Starting server generation')\n    generate_server(raml_root, config)\n\n    log.info('Running nefertari.engine.setup_database')\n    from nefertari.engine import setup_database\n    setup_database(config)\n\n    from nefertari.elasticsearch import ES\n    ES.setup_mappings()\n\n    if root_auth:\n        config.include('ramses.auth')\n\n    log.info('Server succesfully generated\\n')\n"
  },
  {
    "path": "ramses/acl.py",
    "content": "import logging\n\nimport six\nfrom pyramid.security import (\n    Allow, Deny,\n    Everyone, Authenticated,\n    ALL_PERMISSIONS)\nfrom nefertari.acl import CollectionACL\nfrom nefertari.resource import PERMISSIONS\nfrom nefertari.elasticsearch import ES\n\nfrom .utils import resolve_to_callable, is_callable_tag\n\n\nlog = logging.getLogger(__name__)\n\n\nactions = {\n    'allow': Allow,\n    'deny': Deny,\n}\nspecial_principals = {\n    'everyone': Everyone,\n    'authenticated': Authenticated,\n}\nALLOW_ALL = (Allow, Everyone, ALL_PERMISSIONS)\n\n\ndef validate_permissions(perms):\n    \"\"\" Validate :perms: contains valid permissions.\n\n    :param perms: List of permission names or ALL_PERMISSIONS.\n    \"\"\"\n    if not isinstance(perms, (list, tuple)):\n        perms = [perms]\n    valid_perms = set(PERMISSIONS.values())\n    if ALL_PERMISSIONS in perms:\n        return perms\n    if set(perms) - valid_perms:\n        raise ValueError(\n            'Invalid ACL permission names. Valid permissions '\n            'are: {}'.format(', '.join(valid_perms)))\n    return perms\n\n\ndef parse_permissions(perms):\n    \"\"\" Parse permissions (\"perms\") which are either exact permission\n    names or the keyword 'all'.\n\n    :param perms: List or comma-separated string of nefertari permission\n        names, or 'all'\n    \"\"\"\n    if isinstance(perms, six.string_types):\n        perms = perms.split(',')\n    perms = [perm.strip().lower() for perm in perms]\n    if 'all' in perms:\n        return ALL_PERMISSIONS\n    return validate_permissions(perms)\n\n\ndef parse_acl(acl_string):\n    \"\"\" Parse raw string :acl_string: of RAML-defined ACLs.\n\n    If :acl_string: is blank or None, all permissions are given.\n    Values of ACL action and principal are parsed using `actions` and\n    `special_principals` maps and are looked up after `strip()` and\n    `lower()`.\n\n    ACEs in :acl_string: may be separated by newlines or semicolons.\n    Action, principal and permission lists must be separated by spaces.\n    Permissions must be comma-separated.\n    E.g. 'allow everyone view,create,update' and 'deny authenticated delete'\n\n    :param acl_string: Raw RAML string containing defined ACEs.\n    \"\"\"\n    if not acl_string:\n        return [ALLOW_ALL]\n\n    aces_list = acl_string.replace('\\n', ';').split(';')\n    aces_list = [ace.strip().split(' ', 2) for ace in aces_list if ace]\n    aces_list = [(a, b, c.split(',')) for a, b, c in aces_list]\n    result_acl = []\n\n    for action_str, princ_str, perms in aces_list:\n        # Process action\n        action_str = action_str.strip().lower()\n        action = actions.get(action_str)\n        if action is None:\n            raise ValueError(\n                'Unknown ACL action: {}. Valid actions: {}'.format(\n                    action_str, list(actions.keys())))\n\n        # Process principal\n        princ_str = princ_str.strip().lower()\n        if princ_str in special_principals:\n            principal = special_principals[princ_str]\n        elif is_callable_tag(princ_str):\n            principal = resolve_to_callable(princ_str)\n        else:\n            principal = princ_str\n\n        # Process permissions\n        permissions = parse_permissions(perms)\n\n        result_acl.append((action, principal, permissions))\n\n    return result_acl\n\n\nclass BaseACL(CollectionACL):\n    \"\"\" ACL Base class. \"\"\"\n\n    es_based = False\n    _collection_acl = (ALLOW_ALL, )\n    _item_acl = (ALLOW_ALL, )\n\n    def _apply_callables(self, acl, obj=None):\n        \"\"\" Iterate over ACEs from :acl: and apply callable principals\n        if any.\n\n        Principals are passed 3 arguments on call:\n            :ace: Single ACE object that looks like (action, callable,\n                permission or [permission])\n            :request: Current request object\n            :obj: Object instance to be accessed via the ACL\n        Principals must return a single ACE or a list of ACEs.\n\n        :param acl: Sequence of valid Pyramid ACEs which will be processed\n        :param obj: Object to be accessed via the ACL\n        \"\"\"\n        new_acl = []\n        for i, ace in enumerate(acl):\n            principal = ace[1]\n            if six.callable(principal):\n                ace = principal(ace=ace, request=self.request, obj=obj)\n                if not ace:\n                    continue\n                if not isinstance(ace[0], (list, tuple)):\n                    ace = [ace]\n                ace = [(a, b, validate_permissions(c)) for a, b, c in ace]\n            else:\n                ace = [ace]\n            new_acl += ace\n        return tuple(new_acl)\n\n    def __acl__(self):\n        \"\"\" Apply callables to `self._collection_acl` and return result. \"\"\"\n        return self._apply_callables(acl=self._collection_acl)\n\n    def generate_item_acl(self, item):\n        acl = self._apply_callables(\n            acl=self._item_acl,\n            obj=item)\n        if acl is None:\n            acl = self.__acl__()\n        return acl\n\n    def item_acl(self, item):\n        \"\"\" Apply callables to `self._item_acl` and return result. \"\"\"\n        return self.generate_item_acl(item)\n\n    def item_db_id(self, key):\n        # ``self`` can be used for current authenticated user key\n        if key != 'self':\n            return key\n        user = getattr(self.request, 'user', None)\n        if user is None or not isinstance(user, self.item_model):\n            return key\n        return getattr(user, user.pk_field())\n\n    def __getitem__(self, key):\n        \"\"\" Get item using method depending on value of `self.es_based` \"\"\"\n        if not self.es_based:\n            return super(BaseACL, self).__getitem__(key)\n        return self.getitem_es(self.item_db_id(key))\n\n    def getitem_es(self, key):\n        es = ES(self.item_model.__name__)\n        obj = es.get_item(id=key)\n        obj.__acl__ = self.item_acl(obj)\n        obj.__parent__ = self\n        obj.__name__ = key\n        return obj\n\n\nclass DatabaseACLMixin(object):\n    \"\"\" Mixin to be used when ACLs are stored in database. \"\"\"\n\n    def item_acl(self, item):\n        \"\"\" Objectify ACL if ES is used or call item.get_acl() if\n        db is used.\n        \"\"\"\n        if self.es_based:\n            from nefertari_guards.elasticsearch import get_es_item_acl\n            return get_es_item_acl(item)\n        return super(DatabaseACLMixin, self).item_acl(item)\n\n    def getitem_es(self, key):\n        \"\"\" Override to support ACL filtering.\n\n        To do so: passes `self.request` to `get_item` and uses\n        `ACLFilterES`.\n        \"\"\"\n        from nefertari_guards.elasticsearch import ACLFilterES\n        es = ACLFilterES(self.item_model.__name__)\n        params = {\n            'id': key,\n            'request': self.request,\n        }\n        obj = es.get_item(**params)\n        obj.__acl__ = self.item_acl(obj)\n        obj.__parent__ = self\n        obj.__name__ = key\n        return obj\n\n\ndef generate_acl(config, model_cls, raml_resource, es_based=True):\n    \"\"\" Generate an ACL.\n\n    Generated ACL class has a `item_model` attribute set to\n    :model_cls:.\n\n    ACLs used for collection and item access control are generated from a\n    first security scheme with type `x-ACL`.\n    If :raml_resource: has no x-ACL security schemes defined then ALLOW_ALL\n    ACL is used.\n    If the `collection` or `item` settings are empty, then ALLOW_ALL ACL\n    is used.\n\n    :param model_cls: Generated model class\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode\n        for which ACL is being generated\n    :param es_based: Boolean inidicating whether ACL should query ES or\n        not when getting an object\n    \"\"\"\n    schemes = raml_resource.security_schemes or []\n    schemes = [sch for sch in schemes if sch.type == 'x-ACL']\n\n    if not schemes:\n        collection_acl = item_acl = []\n        log.debug('No ACL scheme applied. Using ACL: {}'.format(item_acl))\n    else:\n        sec_scheme = schemes[0]\n        log.debug('{} ACL scheme applied'.format(sec_scheme.name))\n        settings = sec_scheme.settings or {}\n        collection_acl = parse_acl(acl_string=settings.get('collection'))\n        item_acl = parse_acl(acl_string=settings.get('item'))\n\n    class GeneratedACLBase(object):\n        item_model = model_cls\n\n        def __init__(self, request, es_based=es_based):\n            super(GeneratedACLBase, self).__init__(request=request)\n            self.es_based = es_based\n            self._collection_acl = collection_acl\n            self._item_acl = item_acl\n\n    bases = [GeneratedACLBase]\n    if config.registry.database_acls:\n        from nefertari_guards.acl import DatabaseACLMixin as GuardsMixin\n        bases += [DatabaseACLMixin, GuardsMixin]\n    bases.append(BaseACL)\n\n    return type('GeneratedACL', tuple(bases), {})\n"
  },
  {
    "path": "ramses/auth.py",
    "content": "\"\"\"\nAuth module that contains all code needed for authentication/authorization\npolicies setup.\n\nIn particular:\n    :includeme: Function that actually creates routes listed above and\n        connects view to them\n    :create_system_user: Function that creates system/admin user\n    :_setup_ticket_policy: Setup Pyramid AuthTktAuthenticationPolicy\n    :_setup_apikey_policy: Setup nefertari.ApiKeyAuthenticationPolicy\n    :setup_auth_policies: Runs generation of particular auth policy\n\"\"\"\nimport logging\n\nimport transaction\nfrom pyramid.authentication import AuthTktAuthenticationPolicy\nfrom pyramid.authorization import ACLAuthorizationPolicy\nfrom pyramid.security import Allow, ALL_PERMISSIONS\nimport cryptacular.bcrypt\n\nfrom nefertari.utils import dictset\nfrom nefertari.json_httpexceptions import *\nfrom nefertari.authentication.policies import ApiKeyAuthenticationPolicy\n\nlog = logging.getLogger(__name__)\n\n\nclass ACLAssignRegisterMixin(object):\n    \"\"\" Mixin that sets ``User._acl`` field after user is registered. \"\"\"\n    def register(self, *args, **kwargs):\n        response = super(ACLAssignRegisterMixin, self).register(\n            *args, **kwargs)\n\n        user = self.request._user\n        mapping = self.request.registry._model_collections\n        if not user._acl and self.Model.__name__ in mapping:\n            from nefertari_guards import engine as guards_engine\n            factory = mapping[self.Model.__name__].view._factory\n            acl = factory(self.request).generate_item_acl(user)\n            acl = guards_engine.ACLField.stringify_acl(acl)\n            user.update({'_acl': acl})\n\n        return response\n\n\ndef _setup_ticket_policy(config, params):\n    \"\"\" Setup Pyramid AuthTktAuthenticationPolicy.\n\n    Notes:\n      * Initial `secret` params value is considered to be a name of config\n        param that represents a cookie name.\n      * `auth_model.get_groups_by_userid` is used as a `callback`.\n      * Also connects basic routes to perform authentication actions.\n\n    :param config: Pyramid Configurator instance.\n    :param params: Nefertari dictset which contains security scheme\n        `settings`.\n    \"\"\"\n    from nefertari.authentication.views import (\n        TicketAuthRegisterView, TicketAuthLoginView,\n        TicketAuthLogoutView)\n\n    log.info('Configuring Pyramid Ticket Authn policy')\n    if 'secret' not in params:\n        raise ValueError(\n            'Missing required security scheme settings: secret')\n    params['secret'] = config.registry.settings[params['secret']]\n\n    auth_model = config.registry.auth_model\n    params['callback'] = auth_model.get_groups_by_userid\n\n    config.add_request_method(\n        auth_model.get_authuser_by_userid, 'user', reify=True)\n\n    policy = AuthTktAuthenticationPolicy(**params)\n\n    RegisterViewBase = TicketAuthRegisterView\n    if config.registry.database_acls:\n        class RegisterViewBase(ACLAssignRegisterMixin,\n                               TicketAuthRegisterView):\n            pass\n\n    class RamsesTicketAuthRegisterView(RegisterViewBase):\n        Model = config.registry.auth_model\n\n    class RamsesTicketAuthLoginView(TicketAuthLoginView):\n        Model = config.registry.auth_model\n\n    class RamsesTicketAuthLogoutView(TicketAuthLogoutView):\n        Model = config.registry.auth_model\n\n    common_kw = {\n        'prefix': 'auth',\n        'factory': 'nefertari.acl.AuthenticationACL',\n    }\n\n    root = config.get_root_resource()\n    root.add('register', view=RamsesTicketAuthRegisterView, **common_kw)\n    root.add('login', view=RamsesTicketAuthLoginView, **common_kw)\n    root.add('logout', view=RamsesTicketAuthLogoutView, **common_kw)\n\n    return policy\n\n\ndef _setup_apikey_policy(config, params):\n    \"\"\" Setup `nefertari.ApiKeyAuthenticationPolicy`.\n\n    Notes:\n      * User may provide model name in :params['user_model']: do define\n        the name of the user model.\n      * `auth_model.get_groups_by_token` is used to perform username and\n        token check\n      * `auth_model.get_token_credentials` is used to get username and\n        token from userid\n      * Also connects basic routes to perform authentication actions.\n\n    Arguments:\n        :config: Pyramid Configurator instance.\n        :params: Nefertari dictset which contains security scheme `settings`.\n    \"\"\"\n    from nefertari.authentication.views import (\n        TokenAuthRegisterView, TokenAuthClaimView,\n        TokenAuthResetView)\n    log.info('Configuring ApiKey Authn policy')\n\n    auth_model = config.registry.auth_model\n    params['check'] = auth_model.get_groups_by_token\n    params['credentials_callback'] = auth_model.get_token_credentials\n    params['user_model'] = auth_model\n    config.add_request_method(\n        auth_model.get_authuser_by_name, 'user', reify=True)\n\n    policy = ApiKeyAuthenticationPolicy(**params)\n\n    RegisterViewBase = TokenAuthRegisterView\n    if config.registry.database_acls:\n        class RegisterViewBase(ACLAssignRegisterMixin,\n                               TokenAuthRegisterView):\n            pass\n\n    class RamsesTokenAuthRegisterView(RegisterViewBase):\n        Model = auth_model\n\n    class RamsesTokenAuthClaimView(TokenAuthClaimView):\n        Model = auth_model\n\n    class RamsesTokenAuthResetView(TokenAuthResetView):\n        Model = auth_model\n\n    common_kw = {\n        'prefix': 'auth',\n        'factory': 'nefertari.acl.AuthenticationACL',\n    }\n\n    root = config.get_root_resource()\n    root.add('register', view=RamsesTokenAuthRegisterView, **common_kw)\n    root.add('token', view=RamsesTokenAuthClaimView, **common_kw)\n    root.add('reset_token', view=RamsesTokenAuthResetView, **common_kw)\n\n    return policy\n\n\n\"\"\" Map of `security_scheme_type`: `generator_function`, where:\n\n  * `security_scheme_type`: String that represents RAML security scheme type\n    name that should be used to apply a particular authentication system.\n  * `generator_function`: Function that receives instance of Pyramid\n    Configurator instance and dictset of security scheme settings and returns\n    generated Pyramid authentication policy instance.\n\n\"\"\"\nAUTHENTICATION_POLICIES = {\n    'x-ApiKey': _setup_apikey_policy,\n    'x-Ticket': _setup_ticket_policy,\n}\n\n\ndef setup_auth_policies(config, raml_root):\n    \"\"\" Setup authentication, authorization policies.\n\n    Performs basic validation to check all the required values are present\n    and performs authentication, authorization policies generation using\n    generator functions from `AUTHENTICATION_POLICIES`.\n\n    :param config: Pyramid Configurator instance.\n    :param raml_root: Instance of ramlfications.raml.RootNode.\n    \"\"\"\n    log.info('Configuring auth policies')\n    secured_by_all = raml_root.secured_by or []\n    secured_by = [item for item in secured_by_all if item]\n    if not secured_by:\n        log.info('API is not secured. `secured_by` attribute '\n                 'value missing.')\n        return\n    secured_by = secured_by[0]\n\n    schemes = {scheme.name: scheme\n               for scheme in raml_root.security_schemes}\n    if secured_by not in schemes:\n        raise ValueError(\n            'Undefined security scheme used in `secured_by`: {}'.format(\n                secured_by))\n\n    scheme = schemes[secured_by]\n    if scheme.type not in AUTHENTICATION_POLICIES:\n        raise ValueError('Unsupported security scheme type: {}'.format(\n            scheme.type))\n\n    # Setup Authentication policy\n    policy_generator = AUTHENTICATION_POLICIES[scheme.type]\n    params = dictset(scheme.settings or {})\n    authn_policy = policy_generator(config, params)\n    config.set_authentication_policy(authn_policy)\n\n    # Setup Authorization policy\n    authz_policy = ACLAuthorizationPolicy()\n    config.set_authorization_policy(authz_policy)\n\n\ndef create_system_user(config):\n    log.info('Creating system user')\n    crypt = cryptacular.bcrypt.BCRYPTPasswordManager()\n    settings = config.registry.settings\n    try:\n        auth_model = config.registry.auth_model\n        s_user = settings['system.user']\n        s_pass = str(crypt.encode(settings['system.password']))\n        s_email = settings['system.email']\n        defaults = dict(\n            password=s_pass,\n            email=s_email,\n            groups=['admin'],\n        )\n        if config.registry.database_acls:\n            defaults['_acl'] = [(Allow, 'g:admin', ALL_PERMISSIONS)]\n\n        user, created = auth_model.get_or_create(\n            username=s_user, defaults=defaults)\n        if created:\n            transaction.commit()\n    except KeyError as e:\n        log.error('Failed to create system user. Missing config: %s' % e)\n\n\ndef get_authuser_model():\n    \"\"\" Define and return AuthUser model using nefertari base classes \"\"\"\n    from nefertari.authentication.models import AuthUserMixin\n    from nefertari import engine\n\n    class AuthUser(AuthUserMixin, engine.BaseDocument):\n        __tablename__ = 'ramses_authuser'\n\n    return AuthUser\n\n\ndef includeme(config):\n    create_system_user(config)\n"
  },
  {
    "path": "ramses/generators.py",
    "content": "import logging\n\nfrom inflection import singularize\n\nfrom .views import generate_rest_view\nfrom .acl import generate_acl\nfrom .utils import (\n    is_dynamic_uri,\n    resource_view_attrs,\n    generate_model_name,\n    dynamic_part_name,\n    attr_subresource,\n    singular_subresource,\n    get_static_parent,\n    get_route_name,\n    get_resource_uri,\n)\n\n\nlog = logging.getLogger(__name__)\n\n\ndef _get_nefertari_parent_resource(\n        raml_resource, generated_resources, default):\n    parent_raml_res = get_static_parent(raml_resource)\n    if parent_raml_res is not None:\n        parent_resource = generated_resources.get(parent_raml_res.path)\n        return parent_resource or default\n    return default\n\n\ndef generate_resource(config, raml_resource, parent_resource):\n    \"\"\" Perform complete one resource configuration process\n\n    This function generates: ACL, view, route, resource, database\n    model for a given `raml_resource`. New nefertari resource is\n    attached to `parent_resource` class which is an instance of\n    `nefertari.resource.Resource`.\n\n    Things to consider:\n      * Top-level resources must be collection names.\n      * No resources are explicitly created for dynamic (ending with '}')\n        RAML resources as they are implicitly processed by parent collection\n        resources.\n      * Resource nesting must look like collection/id/collection/id/...\n      * Only part of resource path after last '/' is taken into account,\n        thus each level of resource nesting should add one more path\n        element. E.g. /stories -> /stories/{id} and not\n        /stories -> /stories/mystories/{id}. Latter route will be generated\n        at /stories/{id}.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    :param parent_resource: Parent nefertari resource object.\n    \"\"\"\n    from .models import get_existing_model\n\n    # Don't generate resources for dynamic routes as they are already\n    # generated by their parent\n    resource_uri = get_resource_uri(raml_resource)\n    if is_dynamic_uri(resource_uri):\n        if parent_resource.is_root:\n            raise Exception(\"Top-level resources can't be dynamic and must \"\n                            \"represent collections instead\")\n        return\n\n    route_name = get_route_name(resource_uri)\n    log.info('Configuring resource: `{}`. Parent: `{}`'.format(\n        route_name, parent_resource.uid or 'root'))\n\n    # Get DB model. If this is an attribute or singular resource,\n    # we don't need to get model\n    is_singular = singular_subresource(raml_resource, route_name)\n    is_attr_res = attr_subresource(raml_resource, route_name)\n    if not parent_resource.is_root and (is_attr_res or is_singular):\n        model_cls = parent_resource.view.Model\n    else:\n        model_name = generate_model_name(raml_resource)\n        model_cls = get_existing_model(model_name)\n\n    resource_kwargs = {}\n\n    # Generate ACL\n    log.info('Generating ACL for `{}`'.format(route_name))\n    resource_kwargs['factory'] = generate_acl(\n        config,\n        model_cls=model_cls,\n        raml_resource=raml_resource)\n\n    # Generate dynamic part name\n    if not is_singular:\n        resource_kwargs['id_name'] = dynamic_part_name(\n            raml_resource=raml_resource,\n            route_name=route_name,\n            pk_field=model_cls.pk_field())\n\n    # Generate REST view\n    log.info('Generating view for `{}`'.format(route_name))\n    view_attrs = resource_view_attrs(raml_resource, is_singular)\n    resource_kwargs['view'] = generate_rest_view(\n        config,\n        model_cls=model_cls,\n        attrs=view_attrs,\n        attr_view=is_attr_res,\n        singular=is_singular,\n    )\n\n    # In case of singular resource, model still needs to be generated,\n    # but we store it on a different view attribute\n    if is_singular:\n        model_name = generate_model_name(raml_resource)\n        view_cls = resource_kwargs['view']\n        view_cls._parent_model = view_cls.Model\n        view_cls.Model = get_existing_model(model_name)\n\n    # Create new nefertari resource\n    log.info('Creating new resource for `{}`'.format(route_name))\n    clean_uri = resource_uri.strip('/')\n    resource_args = (singularize(clean_uri),)\n    if not is_singular:\n        resource_args += (clean_uri,)\n\n    return parent_resource.add(*resource_args, **resource_kwargs)\n\n\ndef generate_server(raml_root, config):\n    \"\"\" Handle server generation process.\n\n    :param raml_root: Instance of ramlfications.raml.RootNode.\n    :param config: Pyramid Configurator instance.\n    \"\"\"\n    log.info('Server generation started')\n\n    if not raml_root.resources:\n        return\n\n    root_resource = config.get_root_resource()\n    generated_resources = {}\n\n    for raml_resource in raml_root.resources:\n        if raml_resource.path in generated_resources:\n            continue\n\n        # Get Nefertari parent resource\n        parent_resource = _get_nefertari_parent_resource(\n            raml_resource, generated_resources, root_resource)\n\n        # Get generated resource and store it\n        new_resource = generate_resource(\n            config, raml_resource, parent_resource)\n        if new_resource is not None:\n            generated_resources[raml_resource.path] = new_resource\n\n\ndef generate_models(config, raml_resources):\n    \"\"\" Generate model for each resource in :raml_resources:\n\n    The DB model name is generated using singular titled version of current\n    resource's url. E.g. for resource under url '/stories', model with\n    name 'Story' will be generated.\n\n    :param config: Pyramid Configurator instance.\n    :param raml_resources: List of ramlfications.raml.ResourceNode.\n    \"\"\"\n    from .models import handle_model_generation\n    if not raml_resources:\n        return\n    for raml_resource in raml_resources:\n        # No need to generate models for dynamic resource\n        if is_dynamic_uri(raml_resource.path):\n            continue\n\n        # Since POST resource must define schema use only POST\n        # resources to generate models\n        if raml_resource.method.upper() != 'POST':\n            continue\n\n        # Generate DB model\n        # If this is an attribute resource we don't need to generate model\n        resource_uri = get_resource_uri(raml_resource)\n        route_name = get_route_name(resource_uri)\n        if not attr_subresource(raml_resource, route_name):\n            log.info('Configuring model for route `{}`'.format(route_name))\n            model_cls, is_auth_model = handle_model_generation(\n                config, raml_resource)\n            if is_auth_model:\n                config.registry.auth_model = model_cls\n"
  },
  {
    "path": "ramses/models.py",
    "content": "import logging\n\nfrom nefertari import engine\nfrom inflection import pluralize\n\nfrom .utils import (\n    resolve_to_callable, is_callable_tag,\n    resource_schema, generate_model_name,\n    get_events_map)\nfrom . import registry\n\n\nlog = logging.getLogger(__name__)\n\n\"\"\"\nMap of RAML types names to nefertari.engine fields.\n\n\"\"\"\ntype_fields = {\n    'string':           engine.StringField,\n    'float':            engine.FloatField,\n    'integer':          engine.IntegerField,\n    'boolean':          engine.BooleanField,\n    'datetime':         engine.DateTimeField,\n    'file':             engine.BinaryField,\n    'relationship':     engine.Relationship,\n    'dict':             engine.DictField,\n    'foreign_key':      engine.ForeignKeyField,\n    'big_integer':      engine.BigIntegerField,\n    'date':             engine.DateField,\n    'choice':           engine.ChoiceField,\n    'interval':         engine.IntervalField,\n    'decimal':          engine.DecimalField,\n    'pickle':           engine.PickleField,\n    'small_integer':    engine.SmallIntegerField,\n    'text':             engine.TextField,\n    'time':             engine.TimeField,\n    'unicode':          engine.UnicodeField,\n    'unicode_text':     engine.UnicodeTextField,\n    'id_field':         engine.IdField,\n    'list':             engine.ListField,\n}\n\n\ndef get_existing_model(model_name):\n    \"\"\" Try to find existing model class named `model_name`.\n\n    :param model_name: String name of the model class.\n    \"\"\"\n    try:\n        model_cls = engine.get_document_cls(model_name)\n        log.debug('Model `{}` already exists. Using existing one'.format(\n            model_name))\n        return model_cls\n    except ValueError:\n        log.debug('Model `{}` does not exist'.format(model_name))\n\n\ndef prepare_relationship(config, model_name, raml_resource):\n    \"\"\" Create referenced model if it doesn't exist.\n\n    When preparing a relationship, we check to see if the model that will be\n    referenced already exists. If not, it is created so that it will be possible\n    to use it in a relationship. Thus the first usage of this model in RAML file\n    must provide its schema in POST method resource body schema.\n\n    :param model_name: Name of model which should be generated.\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode for\n        which :model_name: will be defined.\n    \"\"\"\n    if get_existing_model(model_name) is None:\n        plural_route = '/' + pluralize(model_name.lower())\n        route = '/' + model_name.lower()\n        for res in raml_resource.root.resources:\n            if res.method.upper() != 'POST':\n                continue\n            if res.path.endswith(plural_route) or res.path.endswith(route):\n                break\n        else:\n            raise ValueError('Model `{}` used in relationship is not '\n                             'defined'.format(model_name))\n        setup_data_model(config, res, model_name)\n\n\ndef generate_model_cls(config, schema, model_name, raml_resource,\n                       es_based=True):\n    \"\"\" Generate model class.\n\n    Engine DB field types are determined using `type_fields` and only those\n    types may be used.\n\n    :param schema: Model schema dict parsed from RAML.\n    :param model_name: String that is used as new model's name.\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    :param es_based: Boolean indicating if generated model should be a\n        subclass of Elasticsearch-based document class or not.\n        It True, ESBaseDocument is used; BaseDocument is used otherwise.\n        Defaults to True.\n    \"\"\"\n    from nefertari.authentication.models import AuthModelMethodsMixin\n    base_cls = engine.ESBaseDocument if es_based else engine.BaseDocument\n    model_name = str(model_name)\n    metaclass = type(base_cls)\n    auth_model = schema.get('_auth_model', False)\n\n    bases = []\n    if config.registry.database_acls:\n        from nefertari_guards import engine as guards_engine\n        bases.append(guards_engine.DocumentACLMixin)\n    if auth_model:\n        bases.append(AuthModelMethodsMixin)\n    bases.append(base_cls)\n\n    attrs = {\n        '__tablename__': model_name.lower(),\n        '_public_fields': schema.get('_public_fields') or [],\n        '_auth_fields': schema.get('_auth_fields') or [],\n        '_hidden_fields': schema.get('_hidden_fields') or [],\n        '_nested_relationships': schema.get('_nested_relationships') or [],\n    }\n    if '_nesting_depth' in schema:\n        attrs['_nesting_depth'] = schema.get('_nesting_depth')\n\n    # Generate fields from properties\n    properties = schema.get('properties', {})\n    for field_name, props in properties.items():\n        if field_name in attrs:\n            continue\n\n        db_settings = props.get('_db_settings')\n        if db_settings is None:\n            continue\n\n        field_kwargs = db_settings.copy()\n        field_kwargs['required'] = bool(field_kwargs.get('required'))\n\n        for default_attr_key in ('default', 'onupdate'):\n            value = field_kwargs.get(default_attr_key)\n            if is_callable_tag(value):\n                field_kwargs[default_attr_key] = resolve_to_callable(value)\n\n        type_name = (\n            field_kwargs.pop('type', 'string') or 'string').lower()\n        if type_name not in type_fields:\n            raise ValueError('Unknown type: {}'.format(type_name))\n\n        field_cls = type_fields[type_name]\n\n        if field_cls is engine.Relationship:\n            prepare_relationship(\n                config, field_kwargs['document'],\n                raml_resource)\n        if field_cls is engine.ForeignKeyField:\n            key = 'ref_column_type'\n            field_kwargs[key] = type_fields[field_kwargs[key]]\n        if field_cls is engine.ListField:\n            key = 'item_type'\n            field_kwargs[key] = type_fields[field_kwargs[key]]\n\n        attrs[field_name] = field_cls(**field_kwargs)\n\n    # Update model definition with methods and variables defined in registry\n    attrs.update(registry.mget(model_name))\n\n    # Generate new model class\n    model_cls = metaclass(model_name, tuple(bases), attrs)\n    setup_model_event_subscribers(config, model_cls, schema)\n    setup_fields_processors(config, model_cls, schema)\n    return model_cls, auth_model\n\n\ndef setup_data_model(config, raml_resource, model_name):\n    \"\"\" Setup storage/data model and return generated model class.\n\n    Process follows these steps:\n      * Resource schema is found and restructured by `resource_schema`.\n      * Model class is generated from properties dict using util function\n        `generate_model_cls`.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    :param model_name: String representing model name.\n    \"\"\"\n    model_cls = get_existing_model(model_name)\n    schema = resource_schema(raml_resource)\n\n    if not schema:\n        raise Exception('Missing schema for model `{}`'.format(model_name))\n\n    if model_cls is not None:\n        return model_cls, schema.get('_auth_model', False)\n\n    log.info('Generating model class `{}`'.format(model_name))\n    return generate_model_cls(\n        config,\n        schema=schema,\n        model_name=model_name,\n        raml_resource=raml_resource,\n    )\n\n\ndef handle_model_generation(config, raml_resource):\n    \"\"\" Generates model name and runs `setup_data_model` to get\n    or generate actual model class.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    model_name = generate_model_name(raml_resource)\n    try:\n        return setup_data_model(config, raml_resource, model_name)\n    except ValueError as ex:\n        raise ValueError('{}: {}'.format(model_name, str(ex)))\n\n\ndef setup_model_event_subscribers(config, model_cls, schema):\n    \"\"\" Set up model event subscribers.\n\n    :param config: Pyramid Configurator instance.\n    :param model_cls: Model class for which handlers should be connected.\n    :param schema: Dict of model JSON schema.\n    \"\"\"\n    events_map = get_events_map()\n    model_events = schema.get('_event_handlers', {})\n    event_kwargs = {'model': model_cls}\n\n    for event_tag, subscribers in model_events.items():\n        type_, action = event_tag.split('_')\n        event_objects = events_map[type_][action]\n\n        if not isinstance(event_objects, list):\n            event_objects = [event_objects]\n\n        for sub_name in subscribers:\n            sub_func = resolve_to_callable(sub_name)\n            config.subscribe_to_events(\n                sub_func, event_objects, **event_kwargs)\n\n\ndef setup_fields_processors(config, model_cls, schema):\n    \"\"\" Set up model fields' processors.\n\n    :param config: Pyramid Configurator instance.\n    :param model_cls: Model class for field of which processors should be\n        set up.\n    :param schema: Dict of model JSON schema.\n    \"\"\"\n    properties = schema.get('properties', {})\n    for field_name, props in properties.items():\n        if not props:\n            continue\n\n        processors = props.get('_processors')\n        backref_processors = props.get('_backref_processors')\n\n        if processors:\n            processors = [resolve_to_callable(val) for val in processors]\n            setup_kwargs = {'model': model_cls, 'field': field_name}\n            config.add_field_processors(processors, **setup_kwargs)\n\n        if backref_processors:\n            db_settings = props.get('_db_settings', {})\n            is_relationship = db_settings.get('type') == 'relationship'\n            document = db_settings.get('document')\n            backref_name = db_settings.get('backref_name')\n            if not (is_relationship and document and backref_name):\n                continue\n\n            backref_processors = [\n                resolve_to_callable(val) for val in backref_processors]\n            setup_kwargs = {\n                'model': engine.get_document_cls(document),\n                'field': backref_name\n            }\n            config.add_field_processors(\n                backref_processors, **setup_kwargs)\n"
  },
  {
    "path": "ramses/registry.py",
    "content": "\"\"\"\nNaive registry that is just a subclass of a python dictionary.\nIt is meant to be used to store objects and retrieve them when needed.\nThe registry is recreated on each app launch and is best suited to store some\ndynamic or short-term data.\n\nStoring an object should be performed by using the `add` function, and\nretrieving it by using the `get` function.\n\n\nExamples:\n\nRegister a function under a function name::\n\n    from ramses import registry\n\n    @registry.add\n    def foo():\n        print 'In foo'\n\n    assert registry.get('foo') is foo\n\n\nRegister a function under a different name::\n\n    from ramses import registry\n\n    @registry.add('bar')\n    def foo():\n        print 'In foo'\n\n    assert registry.get('bar') is foo\n\n\nRegister an arbitrary object::\n\n    from ramses import registry\n\n    myvar = 'my awesome var'\n    registry.add('my_stored_var', myvar)\n    assert registry.get('my_stored_var') == myvar\n\n\nRegister and get an object by namespace::\n\n    from ramses import registry\n\n    myvar = 'my awesome var'\n    registry.add('Foo.my_stored_var', myvar)\n    assert registry.mget('Foo') == {'my_stored_var': myvar}\n\n\"\"\"\nimport six\n\n\nclass Registry(dict):\n    pass\n\n\nregistry = Registry()\n\n\ndef add(*args):\n    def decorator(function):\n        registry[name] = function\n        return function\n\n    if len(args) == 1 and six.callable(args[0]):\n        function = args[0]\n        name = function.__name__\n        return decorator(function)\n    elif len(args) == 2:\n        registry[args[0]] = args[1]\n    else:\n        name = args[0]\n        return decorator\n\n\ndef get(name):\n    try:\n        return registry[name]\n    except KeyError:\n        raise KeyError(\n            \"Object named '{}' is not registered in ramses \"\n            \"registry\".format(name))\n\n\ndef mget(namespace):\n    namespace = namespace.lower() + '.'\n    data = {}\n    for key, val in registry.items():\n        key = key.lower()\n        if not key.startswith(namespace):\n            continue\n        clean_key = key.split(namespace)[-1]\n        data[clean_key] = val\n    return data\n"
  },
  {
    "path": "ramses/scaffolds/__init__.py",
    "content": "import os\nimport subprocess\n\nfrom six import moves\nfrom pyramid.scaffolds import PyramidTemplate\n\n\nclass RamsesStarterTemplate(PyramidTemplate):\n    _template_dir = 'ramses_starter'\n    summary = 'Ramses starter'\n\n    def pre(self, command, output_dir, vars):\n        dbengine_choices = {'1': 'sqla', '2': 'mongodb'}\n        vars['engine'] = dbengine_choices[moves.input(\"\"\"\n        Which database backend would you like to use:\n\n        (1) for SQLAlchemy/PostgreSQL, or\n        (2) for MongoEngine/MongoDB?\n\n        [default is '1']: \"\"\") or '1']\n\n        if vars['package'] == 'site':\n            raise ValueError(\"\"\"\n                \"Site\" is a reserved keyword in Python.\n                 Please use a different project name. \"\"\")\n\n    def post(self, command, output_dir, vars):\n        os.chdir(str(output_dir))\n        subprocess.call('pip install -r requirements.txt', shell=True)\n        subprocess.call('pip install nefertari-{}'.format(vars['engine']),\n                        shell=True)\n        msg = \"\"\"Goodbye boilerplate! Welcome to Ramses.\"\"\"\n        self.out(msg)\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/__init__.py",
    "content": "from pyramid.config import Configurator\n\n\ndef main(global_config, **settings):\n    config = Configurator(settings=settings)\n    config.include('ramses')\n    return config.make_wsgi_app()\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/__init__.py",
    "content": ""
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/api.raml",
    "content": "#%RAML 0.8\n---\ntitle: Items API\ndocumentation:\n    - title: Items REST API\n      content: |\n        Welcome to the Items API.\nbaseUri: http://{host}:{port}/{version}\nversion: api\nmediaType: application/json\nprotocols: [HTTP]\n\n/items:\n    displayName: Collection of items\n    head:\n        description: Head request\n        responses:\n            200:\n                description: Return headers\n    get:\n        description: Get all item\n        responses:\n            200:\n                description: Returns a list of items\n    post:\n        description: Create a new item\n        body:\n            application/json:\n                schema: !include items.json\n                example: |\n                    {\n                        \"id\": \"507f191e810c19729de860ea\",\n                        \"name\": \"Banana\",\n                        \"description\": \"Tasty\"\n                    }\n        responses:\n            201:\n                description: Created item\n                body:\n                    application/json:\n                        schema: !include items.json\n\n    /{id}:\n        uriParameters:\n            id:\n                displayName: Item id\n                type: string\n                example: 507f191e810c19729de860ea\n        displayName: Collection-item\n        head:\n            description: Head request\n            responses:\n                200:\n                    description: Return headers\n        get:\n            description: Get a particular item\n            responses:\n                200:\n                    body:\n                        application/json:\n                            schema: !include items.json\n        delete:\n            description: Delete a particular item\n            responses:\n                200:\n                    description: Deleted item\n        patch:\n            description: Update a particular item\n            body:\n                application/json:\n                    example: { \"name\": \"Tree\" }\n            responses:\n                200:\n                    body:\n                        application/json:\n                            schema: !include items.json\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/items.json",
    "content": "{\n    \"type\": \"object\",\n    \"title\": \"Item schema\",\n    \"$schema\": \"http://json-schema.org/draft-04/schema\",\n    \"required\": [\"name\"],\n    \"properties\": {\n        \"id\": {\n            \"type\": [\"string\", \"null\"],\n            \"_db_settings\": {\n                \"type\": \"id_field\",\n                \"required\": true,\n                \"primary_key\": true\n            }\n        },\n        \"name\": {\n            \"type\": \"string\",\n            \"_db_settings\": {\n                \"type\": \"string\",\n                \"required\": true\n            }\n        },\n        \"description\": {\n            \"type\": [\"string\", \"null\"],\n            \"_db_settings\": {\n                \"type\": \"text\"\n            }\n        }\n    }\n}"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/requirements.txt",
    "content": "-e git+https://github.com/ramses-tech/ra.git@develop#egg=ra\n-e git+https://github.com/ramses-tech/nefertari.git@develop#egg=nefertari\n-e git+https://github.com/ramses-tech/nefertari-mongodb.git@develop#egg=nefertari-mongodb\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/test_api.py_tmpl",
    "content": "import os\nimport ra\nimport webtest\nimport pytest\n\nappdir = os.path.abspath(\n    os.path.join(os.path.dirname(__file__), '..', '..'))\nramlfile = os.path.abspath(\n    os.path.join(os.path.dirname(__file__), 'api.raml'))\ntestapp = webtest.TestApp('config:local.ini', relative_to=appdir)\n\n\n@pytest.fixture(autouse=True)\ndef setup(req, examples):\n    \"\"\" Setup database state for tests.\n\n    NOTE: For objects to be created, when using SQLA transaction\n    needs to be commited as follows:\n        import transaction\n        transaction.commit()\n    \"\"\"\n    from nefertari import engine\n    Item = engine.get_document_cls('Item')\n\n    if req.match(exclude='POST /items'):\n        if Item.get_collection(_count=True) == 0:\n            example = examples.build('item')\n            Item(**example).save()\n\n\n# ra entry point: instantiate the API test suite\napi = ra.api(ramlfile, testapp)\napi.autotest()\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/.gitignore_tmpl",
    "content": "venv\n.DS_Store\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\nlocal.ini\nmock/\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/README.md",
    "content": "## Installation\n```\n$ pip install -r requirements.txt\n```\n\n## Run\n```\n$ pserve local.ini\n```\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/api.raml_tmpl",
    "content": "#%RAML 0.8\n---\ntitle: {{package}} API\ndocumentation:\n    - title: {{package}} REST API\n      content: |\n        Welcome to the {{package}} API.\nbaseUri: http://{host}:{port}/{version}\nversion: v1\nmediaType: application/json\nprotocols: [HTTP]\n\n/items:\n    displayName: Collection of items\n    get:\n        description: Get all item\n    post:\n        description: Create a new item\n        body:\n            application/json:\n                schema: !include items.json\n\n    /{id}:\n        displayName: Collection-item\n        get:\n            description: Get a particular item\n        delete:\n            description: Delete a particular item\n        patch:\n            description: Update a particular item"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/items.json",
    "content": "{\n    \"type\": \"object\",\n    \"title\": \"Item schema\",\n    \"$schema\": \"http://json-schema.org/draft-04/schema\",\n    \"required\": [\"id\", \"name\"],\n    \"properties\": {\n        \"id\": {\n            \"type\": [\"integer\", \"null\"],\n            \"_db_settings\": {\n                \"type\": \"id_field\",\n                \"required\": true,\n                \"primary_key\": true\n            }\n        },\n        \"name\": {\n            \"type\": \"string\",\n            \"_db_settings\": {\n                \"type\": \"string\",\n                \"required\": true\n            }\n        },\n        \"description\": {\n            \"type\": [\"string\", \"null\"],\n            \"_db_settings\": {\n                \"type\": \"text\"\n            }\n        }\n    }\n}"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/local.ini_tmpl",
    "content": "[app:{{package}}]\nuse = egg:{{package}}\n\n# Ramses\nramses.raml_schema = api.raml\ndatabase_acls = false\n\n# Nefertari\nnefertari.engine = nefertari_{{engine}}\nenable_get_tunneling = true\n\n# SQLA\nsqlalchemy.url = postgresql://localhost:5432/{{package}}\n\n# MongoDB settings\nmongodb.host = localhost\nmongodb.port = 27017\nmongodb.db = {{package}}\n\n# Elasticsearch\nelasticsearch.hosts = localhost:9200\nelasticsearch.sniff = false\nelasticsearch.index_name = {{package}}\nelasticsearch.index.disable = false\nelasticsearch.enable_refresh_query = false\nelasticsearch.enable_aggregations = false\nelasticsearch.enable_polymorphic_query = false\n\n# {{package}}\nhost = localhost\nbase_url = http://%(host)s:6543\n\n# CORS\ncors.enable = false\ncors.allow_origins = %(base_url)s\ncors.allow_credentials = true\n\n[composite:main]\nuse = egg:Paste#urlmap\n/api/ = {{package}}\n\n[server:main]\nuse = egg:waitress#main\nhost = 0.0.0.0\nport = 6543\nthreads = 3\n\n[loggers]\nkeys = root, {{package}}, nefertari, ramses\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = INFO\nhandlers = console\n\n[logger_{{package}}]\nlevel = INFO\nhandlers =\nqualname = {{package}}\n\n[logger_nefertari]\nlevel = INFO\nhandlers =\nqualname = nefertari\n\n[logger_ramses]\nlevel = INFO\nhandlers =\nqualname = ramses\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(module)s.%(funcName)s: %(message)s\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/requirements.txt",
    "content": "nefertari\nramses\n\nPaste==2.0.2\npyramid==1.6.1\nwaitress==0.8.9\n\n-e .\n"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/setup.py_tmpl",
    "content": "from setuptools import setup, find_packages\n\nrequires = ['pyramid']\n\nsetup(name='{{package}}',\n    version='0.0.1',\n    description='',\n    long_description='',\n    classifiers=[\n        \"Programming Language :: Python\",\n        \"Framework :: Pyramid\",\n        \"Topic :: Internet :: WWW/HTTP\",\n        \"Topic :: Internet :: WWW/HTTP :: WSGI :: Application\",\n    ],\n    author='',\n    author_email='',\n    url='',\n    keywords='web pyramid pylons raml ramses',\n    packages=find_packages(),\n    include_package_data=True,\n    zip_safe=False,\n    install_requires=requires,\n    tests_require=requires,\n    test_suite=\"{{package}}\",\n    entry_points=\"\"\"\\\n    [paste.app_factory]\n        main = {{package}}:main\n    \"\"\",\n)\n"
  },
  {
    "path": "ramses/scripts/__init__.py",
    "content": ""
  },
  {
    "path": "ramses/scripts/scaffold_test.py",
    "content": "from nefertari.scripts.scaffold_test import (\n    ScaffoldTestCommand as NefTestCommand)\n\n\nclass ScaffoldTestCommand(NefTestCommand):\n    file = __file__\n\n\ndef main(*args, **kwargs):\n    ScaffoldTestCommand().run()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "ramses/utils.py",
    "content": "import re\nimport logging\nfrom contextlib import contextmanager\n\nimport six\nimport inflection\n\n\nlog = logging.getLogger(__name__)\n\n\nclass ContentTypes(object):\n    \"\"\" ContentType values.\n\n    \"\"\"\n    JSON = 'application/json'\n    TEXT_XML = 'text/xml'\n    MULTIPART_FORMDATA = 'multipart/form-data'\n    FORM_URLENCODED = 'application/x-www-form-urlencoded'\n\n\ndef convert_schema(raml_schema, mime_type):\n    \"\"\" Restructure `raml_schema` to a dictionary that has 'properties'\n    as well as other schema keys/values.\n\n    The resulting dictionary looks like this::\n\n    {\n        \"properties\": {\n            \"field1\": {\n                \"required\": boolean,\n                \"type\": ...,\n                ...more field options\n            },\n            ...more properties\n        },\n        \"public_fields\": [...],\n        \"auth_fields\": [...],\n        ...more schema options\n    }\n\n    :param raml_schema: RAML request body schema.\n    :param mime_type: ContentType of the schema as a string from RAML\n        file. Only JSON is currently supported.\n    \"\"\"\n    if mime_type == ContentTypes.JSON:\n        if not isinstance(raml_schema, dict):\n            raise TypeError(\n                'Schema is not a valid JSON. Please check your '\n                'schema syntax.\\n{}...'.format(str(raml_schema)[:60]))\n        return raml_schema\n    if mime_type == ContentTypes.TEXT_XML:\n        # Process XML schema\n        pass\n\n\ndef is_dynamic_uri(uri):\n    \"\"\" Determine whether `uri` is a dynamic uri or not.\n\n    Assumes a dynamic uri is one that ends with '}' which is a Pyramid\n    way to define dynamic parts in uri.\n\n    :param uri: URI as a string.\n    \"\"\"\n    return uri.strip('/').endswith('}')\n\n\ndef clean_dynamic_uri(uri):\n    \"\"\" Strips /, {, } from dynamic `uri`.\n\n    :param uri: URI as a string.\n    \"\"\"\n    return uri.replace('/', '').replace('{', '').replace('}', '')\n\n\ndef generate_model_name(raml_resource):\n    \"\"\" Generate model name.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    resource_uri = get_resource_uri(raml_resource).strip('/')\n    resource_uri = re.sub('\\W', ' ', resource_uri)\n    model_name = inflection.titleize(resource_uri)\n    return inflection.singularize(model_name).replace(' ', '')\n\n\ndef dynamic_part_name(raml_resource, route_name, pk_field):\n    \"\"\" Generate a dynamic part for a resource :raml_resource:.\n\n    A dynamic part is generated using 2 parts: :route_name: of the\n    resource and the dynamic part of first dynamic child resources. If\n    :raml_resource: has no dynamic child resources, 'id' is used as the\n    2nd part.\n    E.g. if your dynamic part on route 'stories' is named 'superId' then\n    dynamic part will be 'stories_superId'.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode for\n        which dynamic part name is being generated.\n    :param route_name: Cleaned name of :raml_resource:\n    :param pk_field: Model Primary Key field name.\n    \"\"\"\n    subresources = get_resource_children(raml_resource)\n    dynamic_uris = [res.path for res in subresources\n                    if is_dynamic_uri(res.path)]\n    if dynamic_uris:\n        dynamic_part = extract_dynamic_part(dynamic_uris[0])\n    else:\n        dynamic_part = pk_field\n    return '_'.join([route_name, dynamic_part])\n\n\ndef extract_dynamic_part(uri):\n    \"\"\" Extract dynamic url part from :uri: string.\n\n    :param uri: URI string that may contain dynamic part.\n    \"\"\"\n    for part in uri.split('/'):\n        part = part.strip()\n        if part.startswith('{') and part.endswith('}'):\n            return clean_dynamic_uri(part)\n\n\ndef resource_view_attrs(raml_resource, singular=False):\n    \"\"\" Generate view method names needed for `raml_resource` view.\n\n    Collects HTTP method names from resource siblings and dynamic children\n    if exist. Collected methods are then translated  to\n    `nefertari.view.BaseView` method names, each of which is used to\n    process a particular HTTP method request.\n\n    Maps of {HTTP_method: view_method} `collection_methods` and\n    `item_methods` are used to convert collection and item methods\n    respectively.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode\n    :param singular: Boolean indicating if resource is singular or not\n    \"\"\"\n    from .views import collection_methods, item_methods\n    # Singular resource doesn't have collection methods though\n    # it looks like a collection\n    if singular:\n        collection_methods = item_methods\n\n    siblings = get_resource_siblings(raml_resource)\n    http_methods = [sibl.method.lower() for sibl in siblings]\n    attrs = [collection_methods.get(method) for method in http_methods]\n\n    # Check if resource has dynamic child resource like collection/{id}\n    # If dynamic child resource exists, add its siblings' methods to attrs,\n    # as both resources are handled by a single view\n    children = get_resource_children(raml_resource)\n    http_submethods = [child.method.lower() for child in children\n                       if is_dynamic_uri(child.path)]\n    attrs += [item_methods.get(method) for method in http_submethods]\n\n    return set(filter(bool, attrs))\n\n\ndef resource_schema(raml_resource):\n    \"\"\" Get schema properties of RAML resource :raml_resource:.\n\n    Must be called with RAML resource that defines body schema. First\n    body that defines schema is used. Schema is converted on return using\n    'convert_schema'.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode of\n        POST method.\n    \"\"\"\n    # NOTE: Must be called with resource that defines body schema\n    log.info('Searching for model schema')\n    if not raml_resource.body:\n        raise ValueError('RAML resource has no body to setup database '\n                         'schema from')\n\n    for body in raml_resource.body:\n        if body.schema:\n            return convert_schema(body.schema, body.mime_type)\n    log.debug('No model schema found.')\n\n\ndef is_dynamic_resource(raml_resource):\n    \"\"\" Determine if :raml_resource: is a dynamic resource.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    return raml_resource and is_dynamic_uri(raml_resource.path)\n\n\ndef get_static_parent(raml_resource, method=None):\n    \"\"\" Get static parent resource of :raml_resource: with HTTP\n    method :method:.\n\n    :param raml_resource:Instance of ramlfications.raml.ResourceNode.\n    :param method: HTTP method name which matching static resource\n        must have.\n    \"\"\"\n    parent = raml_resource.parent\n    while is_dynamic_resource(parent):\n        parent = parent.parent\n\n    if parent is None:\n        return parent\n\n    match_method = method is not None\n    if match_method:\n        if parent.method.upper() == method.upper():\n            return parent\n    else:\n        return parent\n\n    for res in parent.root.resources:\n        if res.path == parent.path:\n            if res.method.upper() == method.upper():\n                return res\n\n\ndef attr_subresource(raml_resource, route_name):\n    \"\"\" Determine if :raml_resource: is an attribute subresource.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    :param route_name: Name of the :raml_resource:.\n    \"\"\"\n    static_parent = get_static_parent(raml_resource, method='POST')\n    if static_parent is None:\n        return False\n    schema = resource_schema(static_parent) or {}\n    properties = schema.get('properties', {})\n    if route_name in properties:\n        db_settings = properties[route_name].get('_db_settings', {})\n        return db_settings.get('type') in ('dict', 'list')\n    return False\n\n\ndef singular_subresource(raml_resource, route_name):\n    \"\"\" Determine if :raml_resource: is a singular subresource.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    :param route_name: Name of the :raml_resource:.\n    \"\"\"\n    static_parent = get_static_parent(raml_resource, method='POST')\n    if static_parent is None:\n        return False\n    schema = resource_schema(static_parent) or {}\n    properties = schema.get('properties', {})\n    if route_name not in properties:\n        return False\n\n    db_settings = properties[route_name].get('_db_settings', {})\n    is_obj = db_settings.get('type') == 'relationship'\n    single_obj = not db_settings.get('uselist', True)\n    return is_obj and single_obj\n\n\ndef is_callable_tag(tag):\n    \"\"\" Determine whether :tag: is a valid callable string tag.\n\n    String is assumed to be valid callable if it starts with '{{'\n    and ends with '}}'.\n\n    :param tag: String name of tag.\n    \"\"\"\n    return (isinstance(tag, six.string_types) and\n            tag.strip().startswith('{{') and\n            tag.strip().endswith('}}'))\n\n\ndef resolve_to_callable(callable_name):\n    \"\"\" Resolve string :callable_name: to a callable.\n\n    :param callable_name: String representing callable name as registered\n        in ramses registry or dotted import path of callable. Can be\n        wrapped in double curly brackets, e.g. '{{my_callable}}'.\n    \"\"\"\n    from . import registry\n    clean_callable_name = callable_name.replace(\n        '{{', '').replace('}}', '').strip()\n    try:\n        return registry.get(clean_callable_name)\n    except KeyError:\n        try:\n            from zope.dottedname.resolve import resolve\n            return resolve(clean_callable_name)\n        except ImportError:\n            raise ImportError(\n                'Failed to load callable `{}`'.format(clean_callable_name))\n\n\ndef get_resource_siblings(raml_resource):\n    \"\"\" Get siblings of :raml_resource:.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    path = raml_resource.path\n    return [res for res in raml_resource.root.resources\n            if res.path == path]\n\n\ndef get_resource_children(raml_resource):\n    \"\"\" Get children of :raml_resource:.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    path = raml_resource.path\n    return [res for res in raml_resource.root.resources\n            if res.parent and res.parent.path == path]\n\n\ndef get_events_map():\n    \"\"\" Prepare map of event subscribers.\n\n    * Extends copies of BEFORE_EVENTS and AFTER_EVENTS maps with\n        'set' action.\n    * Returns map of {before/after: {action: event class(es)}}\n    \"\"\"\n    from nefertari import events\n    set_keys = ('create', 'update', 'replace', 'update_many', 'register')\n    before_events = events.BEFORE_EVENTS.copy()\n    before_events['set'] = [before_events[key] for key in set_keys]\n    after_events = events.AFTER_EVENTS.copy()\n    after_events['set'] = [after_events[key] for key in set_keys]\n    return {\n        'before': before_events,\n        'after': after_events,\n    }\n\n\n@contextmanager\ndef patch_view_model(view_cls, model_cls):\n    \"\"\" Patches view_cls.Model with model_cls.\n\n    :param view_cls: View class \"Model\" param of which should be\n        patched\n    :param model_cls: Model class which should be used to patch\n        view_cls.Model\n    \"\"\"\n    original_model = view_cls.Model\n    view_cls.Model = model_cls\n\n    try:\n        yield\n    finally:\n        view_cls.Model = original_model\n\n\ndef get_route_name(resource_uri):\n    \"\"\" Get route name from RAML resource URI.\n\n    :param resource_uri: String representing RAML resource URI.\n    :returns string: String with route name, which is :resource_uri:\n        stripped of non-word characters.\n    \"\"\"\n    resource_uri = resource_uri.strip('/')\n    resource_uri = re.sub('\\W', '', resource_uri)\n    return resource_uri\n\n\ndef get_resource_uri(raml_resource):\n    \"\"\" Get cleaned resource URI of RAML resource.\n\n    :param raml_resource: Instance of ramlfications.raml.ResourceNode.\n    \"\"\"\n    return raml_resource.path.split('/')[-1].strip()\n"
  },
  {
    "path": "ramses/views.py",
    "content": "import logging\n\nimport six\nfrom nefertari.view import BaseView as NefertariBaseView\nfrom nefertari.json_httpexceptions import JHTTPNotFound\n\nfrom .utils import patch_view_model\n\n\nlog = logging.getLogger(__name__)\n\n\"\"\"\nMaps of {HTTP_method: neferteri view method name}\n\n\"\"\"\ncollection_methods = {\n    'get':      'index',\n    'head':     'index',\n    'post':     'create',\n    'put':      'update_many',\n    'patch':    'update_many',\n    'delete':   'delete_many',\n    'options':  'collection_options',\n}\nitem_methods = {\n    'get':      'show',\n    'head':     'show',\n    'post':     'create',\n    'put':      'replace',\n    'patch':    'update',\n    'delete':   'delete',\n    'options':  'item_options',\n}\n\n\nclass SetObjectACLMixin(object):\n    def set_object_acl(self, obj):\n        \"\"\" Set object ACL on creation if not already present. \"\"\"\n        if not obj._acl:\n            from nefertari_guards import engine as guards_engine\n            acl = self._factory(self.request).generate_item_acl(obj)\n            obj._acl = guards_engine.ACLField.stringify_acl(acl)\n\n\nclass BaseView(object):\n    \"\"\" Base view class for other all views that defines few helper methods.\n\n    Use `self.get_collection` and `self.get_item` to get access to set of\n    objects and object respectively which are valid at current level.\n    \"\"\"\n    @property\n    def clean_id_name(self):\n        id_name = self._resource.id_name\n        if '_' in id_name:\n            return id_name.split('_', 1)[1]\n        else:\n            return id_name\n\n    def set_object_acl(self, obj):\n        pass\n\n    def resolve_kw(self, kwargs):\n        \"\"\" Resolve :kwargs: like `story_id: 1` to the form of `id: 1`.\n\n        \"\"\"\n        resolved = {}\n        for key, value in kwargs.items():\n            split = key.split('_', 1)\n            if len(split) > 1:\n                key = split[1]\n            resolved[key] = value\n        return resolved\n\n    def _location(self, obj):\n        \"\"\" Get location of the `obj`\n\n        Arguments:\n            :obj: self.Model instance.\n        \"\"\"\n        field_name = self.clean_id_name\n        return self.request.route_url(\n            self._resource.uid,\n            **{self._resource.id_name: getattr(obj, field_name)})\n\n    def _parent_queryset(self):\n        \"\"\" Get queryset of parent view.\n\n        Generated queryset is used to run queries in the current level view.\n        \"\"\"\n        parent = self._resource.parent\n        if hasattr(parent, 'view'):\n            req = self.request.blank(self.request.path)\n            req.registry = self.request.registry\n            req.matchdict = {\n                parent.id_name: self.request.matchdict.get(parent.id_name)}\n            parent_view = parent.view(parent.view._factory, req)\n            obj = parent_view.get_item(**req.matchdict)\n            if isinstance(self, ItemSubresourceBaseView):\n                return\n            prop = self._resource.collection_name\n            return getattr(obj, prop, None)\n\n    def get_collection(self, **kwargs):\n        \"\"\" Get objects collection taking into account generated queryset\n        of parent view.\n\n        This method allows working with nested resources properly. Thus a\n        queryset returned by this method will be a subset of its parent\n        view's queryset, thus filtering out objects that don't belong to\n        the parent object.\n        \"\"\"\n        self._query_params.update(kwargs)\n        objects = self._parent_queryset()\n        if objects is not None:\n            return self.Model.filter_objects(\n                objects, **self._query_params)\n        return self.Model.get_collection(**self._query_params)\n\n    def get_item(self, **kwargs):\n        \"\"\" Get collection item taking into account generated queryset\n        of parent view.\n\n        This method allows working with nested resources properly. Thus an item\n        returned by this method will belong to its parent view's queryset, thus\n        filtering out objects that don't belong to the parent object.\n\n        Returns an object from the applicable ACL. If ACL wasn't applied, it is\n        applied explicitly.\n        \"\"\"\n        if six.callable(self.context):\n            self.reload_context(es_based=False, **kwargs)\n\n        objects = self._parent_queryset()\n        if objects is not None and self.context not in objects:\n            raise JHTTPNotFound('{}({}) not found'.format(\n                self.Model.__name__,\n                self._get_context_key(**kwargs)))\n\n        return self.context\n\n    def _get_context_key(self, **kwargs):\n        \"\"\" Get value of `self._resource.id_name` from :kwargs: \"\"\"\n        return str(kwargs.get(self._resource.id_name))\n\n    def reload_context(self, es_based, **kwargs):\n        \"\"\" Reload `self.context` object into a DB or ES object.\n\n        A reload is performed by getting the object ID from :kwargs: and then\n        getting a context key item from the new instance of `self._factory`\n        which is an ACL class used by the current view.\n\n        Arguments:\n            :es_based: Boolean. Whether to init ACL ac es-based or not. This\n                affects the backend which will be queried - either DB or ES\n            :kwargs: Kwargs that contain value for current resource 'id_name'\n                key\n        \"\"\"\n        from .acl import BaseACL\n        key = self._get_context_key(**kwargs)\n        kwargs = {'request': self.request}\n        if issubclass(self._factory, BaseACL):\n            kwargs['es_based'] = es_based\n\n        acl = self._factory(**kwargs)\n        if acl.item_model is None:\n            acl.item_model = self.Model\n\n        self.context = acl[key]\n\n\nclass CollectionView(BaseView):\n    \"\"\" View that works with database and implements handlers for all\n    available CRUD operations.\n\n    \"\"\"\n    def index(self, **kwargs):\n        return self.get_collection()\n\n    def show(self, **kwargs):\n        return self.get_item(**kwargs)\n\n    def create(self, **kwargs):\n        obj = self.Model(**self._json_params)\n        self.set_object_acl(obj)\n        return obj.save(self.request)\n\n    def update(self, **kwargs):\n        obj = self.get_item(**kwargs)\n        return obj.update(self._json_params, self.request)\n\n    def replace(self, **kwargs):\n        return self.update(**kwargs)\n\n    def delete(self, **kwargs):\n        obj = self.get_item(**kwargs)\n        obj.delete(self.request)\n\n    def delete_many(self, **kwargs):\n        objects = self.get_collection()\n        return self.Model._delete_many(objects, self.request)\n\n    def update_many(self, **kwargs):\n        objects = self.get_collection(**self._query_params)\n        return self.Model._update_many(\n            objects, self._json_params, self.request)\n\n\nclass ESBaseView(BaseView):\n    \"\"\" Elasticsearch base view that fetches data from ES.\n\n    Implements analogues of _parent_queryset, get_collection, get_item\n    fetching data from ES instead of database.\n\n    Use `self.get_collection_es` and `self.get_item_es` to get access\n    to the set of objects and individual object respectively which are\n    valid at the current level.\n    \"\"\"\n    def _parent_queryset_es(self):\n        \"\"\" Get queryset (list of object IDs) of parent view.\n\n        The generated queryset is used to run queries in the current level's\n        view.\n        \"\"\"\n        parent = self._resource.parent\n        if hasattr(parent, 'view'):\n            req = self.request.blank(self.request.path)\n            req.registry = self.request.registry\n            req.matchdict = {\n                parent.id_name: self.request.matchdict.get(parent.id_name)}\n            parent_view = parent.view(parent.view._factory, req)\n            obj = parent_view.get_item_es(**req.matchdict)\n            prop = self._resource.collection_name\n            objects_ids = getattr(obj, prop, None)\n            return objects_ids\n\n    def get_es_object_ids(self, objects):\n        \"\"\" Return IDs of :objects: if they are not IDs already. \"\"\"\n        id_field = self.clean_id_name\n        ids = [getattr(obj, id_field, obj) for obj in objects]\n        return list(set(str(id_) for id_ in ids))\n\n    def get_collection_es(self):\n        \"\"\" Get ES objects collection taking into account the generated\n        queryset of parent view.\n\n        This method allows working with nested resources properly. Thus a\n        queryset returned by this method will be a subset of its parent view's\n        queryset, thus filtering out objects that don't belong to the parent\n        object.\n        \"\"\"\n        objects_ids = self._parent_queryset_es()\n\n        if objects_ids is not None:\n            objects_ids = self.get_es_object_ids(objects_ids)\n            if not objects_ids:\n                return []\n            self._query_params['id'] = objects_ids\n\n        return super(ESBaseView, self).get_collection_es()\n\n    def get_item_es(self, **kwargs):\n        \"\"\" Get ES collection item taking into account generated queryset\n        of parent view.\n\n        This method allows working with nested resources properly. Thus an item\n        returned by this method will belong to its parent view's queryset, thus\n        filtering out objects that don't belong to the parent object.\n\n        Returns an object retrieved from the applicable ACL. If an ACL wasn't\n        applied, it is applied explicitly.\n        \"\"\"\n        item_id = self._get_context_key(**kwargs)\n        objects_ids = self._parent_queryset_es()\n        if objects_ids is not None:\n            objects_ids = self.get_es_object_ids(objects_ids)\n\n        if six.callable(self.context):\n            self.reload_context(es_based=True, **kwargs)\n\n        if (objects_ids is not None) and (item_id not in objects_ids):\n            raise JHTTPNotFound('{}(id={}) resource not found'.format(\n                self.Model.__name__, item_id))\n\n        return self.context\n\n\nclass ESCollectionView(ESBaseView, CollectionView):\n    \"\"\" View that reads data from ES.\n\n    Write operations are inherited from :CollectionView:\n    \"\"\"\n    def index(self, **kwargs):\n        return self.get_collection_es()\n\n    def show(self, **kwargs):\n        return self.get_item_es(**kwargs)\n\n    def update(self, **kwargs):\n        \"\"\" Explicitly reload context with DB usage to get access\n        to complete DB object.\n        \"\"\"\n        self.reload_context(es_based=False, **kwargs)\n        return super(ESCollectionView, self).update(**kwargs)\n\n    def delete(self, **kwargs):\n        \"\"\" Explicitly reload context with DB usage to get access\n        to complete DB object.\n        \"\"\"\n        self.reload_context(es_based=False, **kwargs)\n        return super(ESCollectionView, self).delete(**kwargs)\n\n    def get_dbcollection_with_es(self, **kwargs):\n        \"\"\" Get DB objects collection by first querying ES. \"\"\"\n        es_objects = self.get_collection_es()\n        db_objects = self.Model.filter_objects(es_objects)\n        return db_objects\n\n    def delete_many(self, **kwargs):\n        \"\"\" Delete multiple objects from collection.\n\n        First ES is queried, then the results are used to query the DB.\n        This is done to make sure deleted objects are those filtered\n        by ES in the 'index' method (so user deletes what he saw).\n        \"\"\"\n        db_objects = self.get_dbcollection_with_es(**kwargs)\n        return self.Model._delete_many(db_objects, self.request)\n\n    def update_many(self, **kwargs):\n        \"\"\" Update multiple objects from collection.\n\n        First ES is queried, then the results are used to query DB.\n        This is done to make sure updated objects are those filtered\n        by ES in the 'index' method (so user updates what he saw).\n        \"\"\"\n        db_objects = self.get_dbcollection_with_es(**kwargs)\n        return self.Model._update_many(\n            db_objects, self._json_params, self.request)\n\n\nclass ItemSubresourceBaseView(BaseView):\n    \"\"\" Base class for all subresources of collection item resources which\n    don't represent a collection of their own.\n    E.g. /users/{id}/profile, where 'profile' is a singular resource or\n    /users/{id}/some_action, where the 'some_action' action may be\n    performed when requesting this route.\n\n    Subclass ItemSubresourceBaseView in your project when you want to\n    define a subroute and view of an item route defined in RAML and\n    generated by ramses.\n    Use `self.get_item` to get an object on which actions are being\n    performed.\n\n    Moved into a separate class so all item subresources have a common\n    base class, thus making checks like `isinstance(view, baseClass)` easier.\n    Also to override `_get_context_key` to return parent resource's id_name\n    and `get_item` to reload context on each access.\n    \"\"\"\n\n    def _get_context_key(self, **kwargs):\n        \"\"\" Get value of `self._resource.parent.id_name` from :kwargs: \"\"\"\n        return str(kwargs.get(self._resource.parent.id_name))\n\n    def get_item(self, **kwargs):\n        \"\"\" Reload context on each access. \"\"\"\n        self.reload_context(es_based=False, **kwargs)\n        return super(ItemSubresourceBaseView, self).get_item(**kwargs)\n\n\nclass ItemAttributeView(ItemSubresourceBaseView):\n    \"\"\" View used to work with attribute resources.\n\n    Attribute resources represent field: ListField, DictField.\n\n    You may subclass ItemAttributeView in your project when you want to\n    define custom attribute subroute and view of a item route defined in\n    RAML and generated by ramses.\n    \"\"\"\n    def __init__(self, *args, **kw):\n        super(ItemAttributeView, self).__init__(*args, **kw)\n        self.attr = self.request.path.split('/')[-1]\n        self.value_type = None\n        self.unique = True\n\n    def index(self, **kwargs):\n        obj = self.get_item(**kwargs)\n        return getattr(obj, self.attr)\n\n    def create(self, **kwargs):\n        obj = self.get_item(**kwargs)\n        obj.update_iterables(\n            self._json_params, self.attr,\n            unique=self.unique,\n            value_type=self.value_type,\n            request=self.request)\n        return getattr(obj, self.attr, None)\n\n\nclass ItemSingularView(ItemSubresourceBaseView):\n    \"\"\" View used to work with singular resources.\n\n    Singular resources represent a one-to-one relationship.\n    E.g. users/1/profile.\n\n    You may subclass ItemSingularView in your project when you want to define\n    a custom singular subroute and view of an item route defined in RAML and\n    generated by ramses.\n    If you decide to do so, make sure to set `self._singular_model` to a model\n    class, instances of which will be processed by this view.\n    \"\"\"\n    _parent_model = None\n\n    def __init__(self, *args, **kw):\n        super(ItemSingularView, self).__init__(*args, **kw)\n        self.attr = self.request.path.split('/')[-1]\n\n    def get_item(self, **kwargs):\n        with patch_view_model(self, self._parent_model):\n            return super(ItemSingularView, self).get_item(**kwargs)\n\n    def show(self, **kwargs):\n        parent_obj = self.get_item(**kwargs)\n        return getattr(parent_obj, self.attr)\n\n    def create(self, **kwargs):\n        parent_obj = self.get_item(**kwargs)\n        obj = self.Model(**self._json_params)\n        self.set_object_acl(obj)\n        obj = obj.save(self.request)\n        parent_obj.update({self.attr: obj}, self.request)\n        return obj\n\n    def update(self, **kwargs):\n        parent_obj = self.get_item(**kwargs)\n        obj = getattr(parent_obj, self.attr)\n        obj.update(self._json_params, self.request)\n        return obj\n\n    def replace(self, **kwargs):\n        return self.update(**kwargs)\n\n    def delete(self, **kwargs):\n        parent_obj = self.get_item(**kwargs)\n        obj = getattr(parent_obj, self.attr)\n        obj.delete(self.request)\n\n\ndef generate_rest_view(config, model_cls, attrs=None, es_based=True,\n                       attr_view=False, singular=False):\n    \"\"\" Generate REST view for a model class.\n\n    :param model_cls: Generated DB model class.\n    :param attr: List of strings that represent names of view methods, new\n        generated view should support. Not supported methods are replaced\n        with property that raises AttributeError to display MethodNotAllowed\n        error.\n    :param es_based: Boolean indicating if generated view should read from\n        elasticsearch. If True - collection reads are performed from\n        elasticsearch. Database is used for reads otherwise.\n        Defaults to True.\n    :param attr_view: Boolean indicating if ItemAttributeView should be\n        used as a base class for generated view.\n    :param singular: Boolean indicating if ItemSingularView should be\n        used as a base class for generated view.\n    \"\"\"\n    valid_attrs = (list(collection_methods.values()) +\n                   list(item_methods.values()))\n    missing_attrs = set(valid_attrs) - set(attrs)\n\n    if singular:\n        bases = [ItemSingularView]\n    elif attr_view:\n        bases = [ItemAttributeView]\n    elif es_based:\n        bases = [ESCollectionView]\n    else:\n        bases = [CollectionView]\n\n    if config.registry.database_acls:\n        from nefertari_guards.view import ACLFilterViewMixin\n        bases = [SetObjectACLMixin] + bases + [ACLFilterViewMixin]\n    bases.append(NefertariBaseView)\n\n    RESTView = type('RESTView', tuple(bases), {'Model': model_cls})\n\n    def _attr_error(*args, **kwargs):\n        raise AttributeError\n\n    for attr in missing_attrs:\n        setattr(RESTView, attr, property(_attr_error))\n\n    return RESTView\n"
  },
  {
    "path": "requirements.dev",
    "content": "mock\npytest\npytest-cov\nreleases\nsphinx\nvirtualenv\n\n-e git+https://github.com/ramses-tech/nefertari.git@develop#egg=nefertari\n-e git+https://github.com/ramses-tech/nefertari-guards.git@develop#egg=nefertari_guards\n\n-e .\n"
  },
  {
    "path": "setup.py",
    "content": "import os\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.md')).read()\nVERSION = open(os.path.join(here, 'VERSION')).read()\n\nrequires = [\n    'cryptacular',\n    'inflection',\n    'nefertari>=0.7.0',\n    'pyramid',\n    'ramlfications==0.1.8',\n    'six',\n    'transaction',\n]\n\nsetup(name='ramses',\n      version=VERSION,\n      description='Generate a RESTful API for Pyramid using RAML',\n      long_description=README,\n      classifiers=[\n          \"Programming Language :: Python\",\n          \"Programming Language :: Python :: 2\",\n          \"Programming Language :: Python :: 2.7\",\n          \"Programming Language :: Python :: 3\",\n          \"Programming Language :: Python :: 3.4\",\n          \"Programming Language :: Python :: 3.5\",\n          \"Framework :: Pyramid\",\n          \"Topic :: Internet :: WWW/HTTP\",\n          \"Topic :: Internet :: WWW/HTTP :: WSGI :: Application\",\n      ],\n      author='Ramses Tech',\n      author_email='hello@ramses.tech',\n      url='https://github.com/ramses-tech/ramses',\n      keywords='web pyramid pylons ramses raml',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=requires,\n      test_suite=\"ramses\",\n      entry_points=\"\"\"\\\n        [pyramid.scaffold]\n            ramses_starter = ramses.scaffolds:RamsesStarterTemplate\n      \"\"\")\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/fixtures.py",
    "content": "import pytest\n\n\n@pytest.fixture\ndef clear_registry(request):\n    from ramses import registry\n    registry.registry.clear()\n\n\n@pytest.fixture\ndef engine_mock(request):\n    import nefertari\n    from mock import Mock\n\n    class BaseDocument(object):\n        pass\n\n    class ESBaseDocument(object):\n        pass\n\n    original_engine = nefertari.engine\n    nefertari.engine = Mock()\n    nefertari.engine.BaseDocument = BaseDocument\n    nefertari.engine.ESBaseDocument = ESBaseDocument\n\n    def clear():\n        nefertari.engine = original_engine\n    request.addfinalizer(clear)\n\n    return nefertari.engine\n\n\n@pytest.fixture\ndef guards_engine_mock(request):\n    import nefertari_guards\n    from nefertari_guards import engine\n    from mock import Mock\n\n    class DocumentACLMixin(object):\n        pass\n\n    original_engine = engine\n    nefertari_guards.engine = Mock()\n    nefertari_guards.engine.DocumentACLMixin = DocumentACLMixin\n\n    def clear():\n        nefertari_guards.engine = original_engine\n    request.addfinalizer(clear)\n\n    return nefertari_guards.engine\n\n\ndef config_mock():\n    from mock import Mock\n    config = Mock()\n    config.registry.database_acls = False\n    return config\n"
  },
  {
    "path": "tests/test_acl.py",
    "content": "import pytest\nfrom mock import Mock, patch, call\nfrom pyramid.security import (\n    Allow, Deny,\n    Everyone, Authenticated,\n    ALL_PERMISSIONS)\n\nfrom ramses import acl\n\nfrom .fixtures import config_mock\n\n\nclass TestACLHelpers(object):\n    def test_validate_permissions_all_perms(self):\n        perms = ALL_PERMISSIONS\n        assert acl.validate_permissions(perms) == [perms]\n        assert acl.validate_permissions([perms]) == [perms]\n\n    def test_validate_permissions_valid(self):\n        perms = ['update', 'delete']\n        assert acl.validate_permissions(perms) == perms\n\n    def test_validate_permissions_invalid(self):\n        with pytest.raises(ValueError) as ex:\n            acl.validate_permissions(['foobar'])\n        assert 'Invalid ACL permission names' in str(ex.value)\n\n    def test_parse_permissions_all_permissions(self):\n        perms = acl.parse_permissions('all,view,create')\n        assert perms is ALL_PERMISSIONS\n\n    def test_parse_permissions_invalid_perm_name(self):\n        with pytest.raises(ValueError) as ex:\n            acl.parse_permissions('foo,create')\n        expected = ('Invalid ACL permission names. Valid '\n                    'permissions are: ')\n        assert expected in str(ex.value)\n\n    def test_parse_permissions(self):\n        perms = acl.parse_permissions('view')\n        assert perms == ['view']\n        perms = acl.parse_permissions('view,create')\n        assert sorted(perms) == ['create', 'view']\n\n    def test_parse_acl_no_string(self):\n        perms = acl.parse_acl('')\n        assert perms == [acl.ALLOW_ALL]\n\n    def test_parse_acl_unknown_action(self):\n        with pytest.raises(ValueError) as ex:\n            acl.parse_acl('foobar admin all')\n        assert 'Unknown ACL action: foobar' in str(ex.value)\n\n    @patch.object(acl, 'parse_permissions')\n    def test_parse_acl_multiple_values(self, mock_perms):\n        mock_perms.return_value = 'Foo'\n        perms = acl.parse_acl(\n            'allow everyone read,write;allow authenticated all')\n        mock_perms.assert_has_calls([\n            call(['read', 'write']),\n            call(['all']),\n        ])\n        assert sorted(perms) == sorted([\n            (Allow, Everyone, 'Foo'),\n            (Allow, Authenticated, 'Foo'),\n        ])\n\n    @patch.object(acl, 'parse_permissions')\n    def test_parse_acl_special_principal(self, mock_perms):\n        mock_perms.return_value = 'Foo'\n        perms = acl.parse_acl('allow everyone all')\n        mock_perms.assert_called_once_with(['all'])\n        assert perms == [(Allow, Everyone, 'Foo')]\n\n    @patch.object(acl, 'parse_permissions')\n    def test_parse_acl_group_principal(self, mock_perms):\n        mock_perms.return_value = 'Foo'\n        perms = acl.parse_acl('allow g:admin all')\n        mock_perms.assert_called_once_with(['all'])\n        assert perms == [(Allow, 'g:admin', 'Foo')]\n\n    @patch.object(acl, 'resolve_to_callable')\n    @patch.object(acl, 'parse_permissions')\n    def test_parse_acl_callable_principal(self, mock_perms, mock_res):\n        mock_perms.return_value = 'Foo'\n        mock_res.return_value = 'registry callable'\n        perms = acl.parse_acl('allow {{my_user}} all')\n        mock_perms.assert_called_once_with(['all'])\n        mock_res.assert_called_once_with('{{my_user}}')\n        assert perms == [(Allow, 'registry callable', 'Foo')]\n\n\n@patch.object(acl, 'parse_acl')\nclass TestGenerateACL(object):\n\n    def test_no_security(self, mock_parse):\n        config = config_mock()\n        acl_cls = acl.generate_acl(\n            config, model_cls='Foo',\n            raml_resource=Mock(security_schemes=[]),\n            es_based=True)\n        assert acl_cls.item_model == 'Foo'\n        assert issubclass(acl_cls, acl.BaseACL)\n        instance = acl_cls(request=None)\n        assert instance.es_based\n        assert instance._collection_acl == []\n        assert instance._item_acl == []\n        assert not mock_parse.called\n\n    def test_wrong_security_scheme_type(self, mock_parse):\n        raml_resource = Mock(security_schemes=[\n            Mock(type='x-Foo', settings={'collection': 4, 'item': 7})\n        ])\n        config = config_mock()\n        acl_cls = acl.generate_acl(\n            config, model_cls='Foo',\n            raml_resource=raml_resource,\n            es_based=False)\n        assert not mock_parse.called\n        assert acl_cls.item_model == 'Foo'\n        assert issubclass(acl_cls, acl.BaseACL)\n        instance = acl_cls(request=None)\n        assert not instance.es_based\n        assert instance._collection_acl == []\n        assert instance._item_acl == []\n\n    def test_correct_security_scheme(self, mock_parse):\n        raml_resource = Mock(security_schemes=[\n            Mock(type='x-ACL', settings={'collection': 4, 'item': 7})\n        ])\n        config = config_mock()\n        acl_cls = acl.generate_acl(\n            config, model_cls='Foo',\n            raml_resource=raml_resource,\n            es_based=False)\n        mock_parse.assert_has_calls([\n            call(acl_string=4),\n            call(acl_string=7),\n        ])\n        instance = acl_cls(request=None)\n        assert instance._collection_acl == mock_parse()\n        assert instance._item_acl == mock_parse()\n        assert not instance.es_based\n\n    def test_database_acls_option(self, mock_parse):\n        raml_resource = Mock(security_schemes=[\n            Mock(type='x-ACL', settings={'collection': 4, 'item': 7})\n        ])\n        kwargs = dict(\n            model_cls='Foo',\n            raml_resource=raml_resource,\n            es_based=False,\n        )\n        config = config_mock()\n        config.registry.database_acls = False\n        acl_cls = acl.generate_acl(config, **kwargs)\n        assert not issubclass(acl_cls, acl.DatabaseACLMixin)\n        config.registry.database_acls = True\n        acl_cls = acl.generate_acl(config, **kwargs)\n        assert issubclass(acl_cls, acl.DatabaseACLMixin)\n\n\nclass TestBaseACL(object):\n\n    def test_init(self):\n        obj = acl.BaseACL(request='Foo')\n        assert obj.item_model is None\n        assert obj._collection_acl == (acl.ALLOW_ALL,)\n        assert obj._item_acl == (acl.ALLOW_ALL,)\n        assert obj.request == 'Foo'\n\n    def test_apply_callables_no_callables(self):\n        obj = acl.BaseACL('req')\n        new_acl = obj._apply_callables(\n            acl=[('foo', 'bar', 'baz')],\n            obj='obj')\n        assert new_acl == (('foo', 'bar', 'baz'),)\n\n    @patch.object(acl, 'validate_permissions')\n    def test_apply_callables(self, mock_meth):\n        mock_meth.return_value = '123'\n        principal = Mock(return_value=(7, 8, 9))\n        obj = acl.BaseACL('req')\n        new_acl = obj._apply_callables(\n            acl=[('foo', principal, 'bar')],\n            obj='obj')\n        assert new_acl == ((7, 8, '123'),)\n        principal.assert_called_once_with(\n            ace=('foo', principal, 'bar'),\n            request='req',\n            obj='obj')\n        mock_meth.assert_called_once_with(9)\n\n    @patch.object(acl, 'parse_permissions')\n    def test_apply_callables_principal_returns_none(self, mock_meth):\n        mock_meth.return_value = '123'\n        principal = Mock(return_value=None)\n        obj = acl.BaseACL('req')\n        new_acl = obj._apply_callables(\n            acl=[('foo', principal, 'bar')],\n            obj='obj')\n        assert new_acl == ()\n        principal.assert_called_once_with(\n            ace=('foo', principal, 'bar'),\n            request='req',\n            obj='obj')\n        assert not mock_meth.called\n\n    @patch.object(acl, 'validate_permissions')\n    def test_apply_callables_principal_returns_list(self, mock_meth):\n        mock_meth.return_value = '123'\n        principal = Mock(return_value=[(7, 8, 9)])\n        obj = acl.BaseACL('req')\n        new_acl = obj._apply_callables(\n            acl=[('foo', principal, 'bar')],\n            obj='obj')\n        assert new_acl == ((7, 8, '123'),)\n        principal.assert_called_once_with(\n            ace=('foo', principal, 'bar'),\n            request='req',\n            obj='obj')\n        mock_meth.assert_called_once_with(9)\n\n    def test_apply_callables_functional(self):\n        obj = acl.BaseACL('req')\n        principal = lambda ace, request, obj: [(Allow, Everyone, 'view')]\n        new_acl = obj._apply_callables(\n            acl=[(Deny, principal, ALL_PERMISSIONS)],\n        )\n        assert new_acl == ((Allow, Everyone, ['view']),)\n\n    def test_magic_acl(self):\n        obj = acl.BaseACL('req')\n        obj._collection_acl = [(1, 2, 3)]\n        obj._apply_callables = Mock()\n        result = obj.__acl__()\n        obj._apply_callables.assert_called_once_with(\n            acl=[(1, 2, 3)],\n        )\n        assert result == obj._apply_callables()\n\n    def test_item_acl(self):\n        obj = acl.BaseACL('req')\n        obj._item_acl = [(1, 2, 3)]\n        obj._apply_callables = Mock()\n        result = obj.item_acl('foobar')\n        obj._apply_callables.assert_called_once_with(\n            acl=[(1, 2, 3)],\n            obj='foobar'\n        )\n        assert result == obj._apply_callables()\n\n    def test_magic_getitem_es_based(self):\n        obj = acl.BaseACL('req')\n        obj.item_db_id = Mock(return_value=42)\n        obj.getitem_es = Mock()\n        obj.es_based = True\n        obj.__getitem__(1)\n        obj.item_db_id.assert_called_once_with(1)\n        obj.getitem_es.assert_called_once_with(42)\n\n    def test_magic_getitem_db_based(self):\n        obj = acl.BaseACL('req')\n        obj.item_db_id = Mock(return_value=42)\n        obj.item_model = Mock()\n        obj.item_model.pk_field.return_value = 'id'\n        obj.es_based = False\n        obj.__getitem__(1)\n        obj.item_db_id.assert_called_once_with(1)\n\n    @patch('ramses.acl.ES')\n    def test_getitem_es(self, mock_es):\n        found_obj = Mock()\n        es_obj = Mock()\n        es_obj.get_item.return_value = found_obj\n        mock_es.return_value = es_obj\n        obj = acl.BaseACL('req')\n        obj.item_model = Mock(__name__='Foo')\n        obj.item_model.pk_field.return_value = 'myname'\n        obj.item_acl = Mock()\n        value = obj.getitem_es(key='varvar')\n        mock_es.assert_called_with('Foo')\n        es_obj.get_item.assert_called_once_with(id='varvar')\n        obj.item_acl.assert_called_once_with(found_obj)\n        assert value.__acl__ == obj.item_acl()\n        assert value.__parent__ is obj\n        assert value.__name__ == 'varvar'\n"
  },
  {
    "path": "tests/test_auth.py",
    "content": "import pytest\nfrom mock import Mock, patch\n\nfrom nefertari.utils import dictset\nfrom pyramid.security import Allow, ALL_PERMISSIONS\n\nfrom .fixtures import engine_mock, guards_engine_mock\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestACLAssignRegisterMixin(object):\n    def _dummy_view(self):\n        from ramses import auth\n\n        class DummyBase(object):\n            def register(self, *args, **kwargs):\n                return 1\n\n        class DummyView(auth.ACLAssignRegisterMixin, DummyBase):\n            def __init__(self, *args, **kwargs):\n                super(DummyView, self).__init__(*args, **kwargs)\n                self.Model = Mock()\n                self.request = Mock(_user=Mock())\n                self.request.registry._model_collections = {}\n        return DummyView\n\n    def test_register_acl_present(self):\n        DummyView = self._dummy_view()\n        view = DummyView()\n        view.request._user._acl = ['a']\n        assert view.register() == 1\n        assert view.request._user._acl == ['a']\n\n    def test_register_no_model_collection(self):\n        DummyView = self._dummy_view()\n        view = DummyView()\n        view.Model.__name__ = 'Foo'\n        view.request._user._acl = []\n        assert view.register() == 1\n        assert view.request._user._acl == []\n\n    def test_register_acl_set(self, guards_engine_mock):\n        DummyView = self._dummy_view()\n        view = DummyView()\n        view.Model.__name__ = 'Foo'\n        resource = Mock()\n        view.request.registry._model_collections['Foo'] = resource\n        view.request._user._acl = []\n        assert view.register() == 1\n        factory = resource.view._factory\n        factory.assert_called_once_with(view.request)\n        factory().generate_item_acl.assert_called_once_with(\n            view.request._user)\n        guards_engine_mock.ACLField.stringify_acl.assert_called_once_with(\n            factory().generate_item_acl())\n        view.request._user.update.assert_called_once_with(\n            {'_acl': guards_engine_mock.ACLField.stringify_acl()})\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestSetupTicketPolicy(object):\n\n    def test_no_secret(self):\n        from ramses import auth\n        with pytest.raises(ValueError) as ex:\n            auth._setup_ticket_policy(config='', params={})\n        expected = 'Missing required security scheme settings: secret'\n        assert expected == str(ex.value)\n\n    @patch('ramses.auth.AuthTktAuthenticationPolicy')\n    def test_params_converted(self, mock_policy):\n        from ramses import auth\n        params = dictset(\n            secure=True,\n            include_ip=True,\n            http_only=False,\n            wild_domain=True,\n            debug=True,\n            parent_domain=True,\n            secret='my_secret_setting'\n        )\n        auth_model = Mock()\n        config = Mock()\n        config.registry.settings = {'my_secret_setting': 12345}\n        config.registry.auth_model = auth_model\n        auth._setup_ticket_policy(config=config, params=params)\n        mock_policy.assert_called_once_with(\n            include_ip=True, secure=True, parent_domain=True,\n            callback=auth_model.get_groups_by_userid, secret=12345,\n            wild_domain=True, debug=True, http_only=False\n        )\n\n    @patch('ramses.auth.AuthTktAuthenticationPolicy')\n    def test_request_method_added(self, mock_policy):\n        from ramses import auth\n        config = Mock()\n        config.registry.settings = {'my_secret': 12345}\n        config.registry.auth_model = Mock()\n        policy = auth._setup_ticket_policy(\n            config=config, params={'secret': 'my_secret'})\n        config.add_request_method.assert_called_once_with(\n            config.registry.auth_model.get_authuser_by_userid,\n            'user', reify=True)\n        assert policy == mock_policy()\n\n    @patch('ramses.auth.AuthTktAuthenticationPolicy')\n    def test_routes_views_added(self, mock_policy):\n        from ramses import auth\n        config = Mock()\n        config.registry.settings = {'my_secret': 12345}\n        config.registry.auth_model = Mock()\n        root = Mock()\n        config.get_root_resource.return_value = root\n        auth._setup_ticket_policy(\n            config=config, params={'secret': 'my_secret'})\n        assert root.add.call_count == 3\n        login, logout, register = root.add.call_args_list\n        login_kwargs = login[1]\n        assert sorted(login_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert login_kwargs['prefix'] == 'auth'\n        assert login_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n        logout_kwargs = logout[1]\n        assert sorted(logout_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert logout_kwargs['prefix'] == 'auth'\n        assert logout_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n        register_kwargs = register[1]\n        assert sorted(register_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert register_kwargs['prefix'] == 'auth'\n        assert register_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestSetupApiKeyPolicy(object):\n\n    @patch('ramses.auth.ApiKeyAuthenticationPolicy')\n    def test_policy_params(self, mock_policy):\n        from ramses import auth\n        auth_model = Mock()\n        config = Mock()\n        config.registry.auth_model = auth_model\n        policy = auth._setup_apikey_policy(config, {'foo': 'bar'})\n        mock_policy.assert_called_once_with(\n            foo='bar', check=auth_model.get_groups_by_token,\n            credentials_callback=auth_model.get_token_credentials,\n            user_model=auth_model,\n        )\n        assert policy == mock_policy()\n\n    @patch('ramses.auth.ApiKeyAuthenticationPolicy')\n    def test_routes_views_added(self, mock_policy):\n        from ramses import auth\n        auth_model = Mock()\n        config = Mock()\n        config.registry.auth_model = auth_model\n        root = Mock()\n        config.get_root_resource.return_value = root\n        auth._setup_apikey_policy(config, {})\n        assert root.add.call_count == 3\n        token, reset_token, register = root.add.call_args_list\n        token_kwargs = token[1]\n        assert sorted(token_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert token_kwargs['prefix'] == 'auth'\n        assert token_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n        reset_token_kwargs = reset_token[1]\n        assert sorted(reset_token_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert reset_token_kwargs['prefix'] == 'auth'\n        assert reset_token_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n        register_kwargs = register[1]\n        assert sorted(register_kwargs.keys()) == sorted([\n            'view', 'prefix', 'factory'])\n        assert register_kwargs['prefix'] == 'auth'\n        assert register_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestSetupAuthPolicies(object):\n\n    def test_not_secured(self):\n        from ramses import auth\n        raml_data = Mock(secured_by=[None])\n        config = Mock()\n        auth.setup_auth_policies(config, raml_data)\n        assert not config.set_authentication_policy.called\n        assert not config.set_authorization_policy.called\n\n    def test_not_defined_security_scheme(self):\n        from ramses import auth\n        scheme = Mock()\n        scheme.name = 'foo'\n        raml_data = Mock(secured_by=['zoo'], security_schemes=[scheme])\n        with pytest.raises(ValueError) as ex:\n            auth.setup_auth_policies('asd', raml_data)\n        expected = 'Undefined security scheme used in `secured_by`: zoo'\n        assert expected == str(ex.value)\n\n    def test_not_supported_scheme_type(self):\n        from ramses import auth\n        scheme = Mock(type='asd123')\n        scheme.name = 'foo'\n        raml_data = Mock(secured_by=['foo'], security_schemes=[scheme])\n        with pytest.raises(ValueError) as ex:\n            auth.setup_auth_policies(None, raml_data)\n        expected = 'Unsupported security scheme type: asd123'\n        assert expected == str(ex.value)\n\n    @patch('ramses.auth.ACLAuthorizationPolicy')\n    def test_policies_calls(self, mock_acl):\n        from ramses import auth\n        scheme = Mock(type='mytype', settings={'name': 'user1'})\n        scheme.name = 'foo'\n        raml_data = Mock(secured_by=['foo'], security_schemes=[scheme])\n        config = Mock()\n        mock_setup = Mock()\n        with patch.dict(auth.AUTHENTICATION_POLICIES, {'mytype': mock_setup}):\n            auth.setup_auth_policies(config, raml_data)\n        mock_setup.assert_called_once_with(config, {'name': 'user1'})\n        config.set_authentication_policy.assert_called_once_with(\n            mock_setup())\n        mock_acl.assert_called_once_with()\n        config.set_authorization_policy.assert_called_once_with(\n            mock_acl())\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestHelperFunctions(object):\n\n    def test_create_system_user_key_error(self):\n        from ramses import auth\n        config = Mock()\n        config.registry.settings = {}\n        auth.create_system_user(config)\n        assert not config.registry.auth_model.get_or_create.called\n\n    @patch('ramses.auth.transaction')\n    @patch('ramses.auth.cryptacular')\n    def test_create_system_user_exists(self, mock_crypt, mock_trans):\n        from ramses import auth\n        encoder = mock_crypt.bcrypt.BCRYPTPasswordManager()\n        encoder.encode.return_value = '654321'\n        config = Mock()\n        config.registry.settings = {\n            'system.user': 'user12',\n            'system.password': '123456',\n            'system.email': 'user12@example.com',\n        }\n        config.registry.auth_model.get_or_create.return_value = (1, False)\n        auth.create_system_user(config)\n        assert not mock_trans.commit.called\n        encoder.encode.assert_called_once_with('123456')\n        config.registry.auth_model.get_or_create.assert_called_once_with(\n            username='user12',\n            defaults={\n                'password': '654321',\n                'email': 'user12@example.com',\n                'groups': ['admin'],\n                '_acl': [(Allow, 'g:admin', ALL_PERMISSIONS)],\n            }\n        )\n\n    @patch('ramses.auth.transaction')\n    @patch('ramses.auth.cryptacular')\n    def test_create_system_user_created(self, mock_crypt, mock_trans):\n        from ramses import auth\n        encoder = mock_crypt.bcrypt.BCRYPTPasswordManager()\n        encoder.encode.return_value = '654321'\n        config = Mock()\n        config.registry.settings = {\n            'system.user': 'user12',\n            'system.password': '123456',\n            'system.email': 'user12@example.com',\n        }\n        config.registry.auth_model.get_or_create.return_value = (\n            Mock(), True)\n        auth.create_system_user(config)\n        mock_trans.commit.assert_called_once_with()\n        encoder.encode.assert_called_once_with('123456')\n        config.registry.auth_model.get_or_create.assert_called_once_with(\n            username='user12',\n            defaults={\n                'password': '654321',\n                'email': 'user12@example.com',\n                'groups': ['admin'],\n                '_acl': [(Allow, 'g:admin', ALL_PERMISSIONS)],\n            }\n        )\n\n    @patch('ramses.auth.create_system_user')\n    def test_includeme(self, mock_create):\n        from ramses import auth\n        auth.includeme(config=1)\n        mock_create.assert_called_once_with(1)\n"
  },
  {
    "path": "tests/test_generators.py",
    "content": "import pytest\nfrom mock import Mock, patch, call\n\nfrom ramses import generators\nfrom .fixtures import engine_mock, config_mock\n\n\nclass TestHelperFunctions(object):\n    @patch.object(generators, 'get_static_parent')\n    def test_get_nefertari_parent_resource_no_parent(self, mock_get):\n        mock_get.return_value = None\n        assert generators._get_nefertari_parent_resource(1, 2, 3) == 3\n        mock_get.assert_called_once_with(1)\n\n    @patch.object(generators, 'get_static_parent')\n    def test_get_nefertari_parent_resource_parent_not_defined(\n            self, mock_get):\n        mock_get.return_value = Mock(path='foo')\n        assert generators._get_nefertari_parent_resource(\n            1, {}, 3) == 3\n        mock_get.assert_called_once_with(1)\n\n    @patch.object(generators, 'get_static_parent')\n    def test_get_nefertari_parent_resource_parent_defined(\n            self, mock_get):\n        mock_get.return_value = Mock(path='foo')\n        assert generators._get_nefertari_parent_resource(\n            1, {'foo': 'bar'}, 3) == 'bar'\n        mock_get.assert_called_once_with(1)\n\n    @patch.object(generators, 'generate_resource')\n    def test_generate_server_no_resources(self, mock_gen):\n        generators.generate_server(Mock(resources=None), 'foo')\n        assert not mock_gen.called\n\n    @patch.object(generators, '_get_nefertari_parent_resource')\n    @patch.object(generators, 'generate_resource')\n    def test_generate_server_resources_generated(\n            self, mock_gen, mock_get):\n        config = Mock()\n        resources = [\n            Mock(path='/foo'),\n            Mock(path='/bar'),\n        ]\n        generators.generate_server(Mock(resources=resources), config)\n        assert mock_get.call_count == 2\n        mock_gen.assert_has_calls([\n            call(config, resources[0], mock_get()),\n            call(config, resources[1], mock_get()),\n        ])\n\n    @patch.object(generators, '_get_nefertari_parent_resource')\n    @patch.object(generators, 'generate_resource')\n    def test_generate_server_call_per_path(\n            self, mock_gen, mock_get):\n        config = Mock()\n        resources = [\n            Mock(path='/foo'),\n            Mock(path='/foo'),\n        ]\n        generators.generate_server(Mock(resources=resources), config)\n        assert mock_get.call_count == 1\n        mock_gen.assert_called_once_with(config, resources[0], mock_get())\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestGenerateModels(object):\n\n    @patch('ramses.generators.is_dynamic_uri')\n    def test_no_resources(self, mock_dyn):\n        generators.generate_models(config=1, raml_resources=[])\n        assert not mock_dyn.called\n\n    @patch('ramses.models.handle_model_generation')\n    def test_dynamic_uri(self, mock_handle):\n        generators.generate_models(\n            config=1, raml_resources=[Mock(path='/{id}')])\n        assert not mock_handle.called\n\n    @patch('ramses.models.handle_model_generation')\n    def test_no_post_resources(self, mock_handle):\n        generators.generate_models(config=1, raml_resources=[\n            Mock(path='/stories', method='get'),\n            Mock(path='/stories', method='options'),\n            Mock(path='/stories', method='patch'),\n        ])\n        assert not mock_handle.called\n\n    @patch('ramses.generators.attr_subresource')\n    @patch('ramses.models.handle_model_generation')\n    def test_attr_subresource(self, mock_handle, mock_attr):\n        mock_attr.return_value = True\n        resource = Mock(path='/stories', method='POST')\n        generators.generate_models(config=1, raml_resources=[resource])\n        assert not mock_handle.called\n        mock_attr.assert_called_once_with(resource, 'stories')\n\n    @patch('ramses.generators.attr_subresource')\n    @patch('ramses.models.handle_model_generation')\n    def test_non_auth_model(self, mock_handle, mock_attr):\n        mock_attr.return_value = False\n        mock_handle.return_value = ('Foo', False)\n        config = Mock()\n        resource = Mock(path='/stories', method='POST')\n        generators.generate_models(\n            config=config, raml_resources=[resource])\n        mock_attr.assert_called_once_with(resource, 'stories')\n        mock_handle.assert_called_once_with(config, resource)\n        assert config.registry.auth_model != 'Foo'\n\n    @patch('ramses.generators.attr_subresource')\n    @patch('ramses.models.handle_model_generation')\n    def test_auth_model(self, mock_handle, mock_attr):\n        mock_attr.return_value = False\n        mock_handle.return_value = ('Foo', True)\n        config = Mock()\n        resource = Mock(path='/stories', method='POST')\n        generators.generate_models(\n            config=config, raml_resources=[resource])\n        mock_attr.assert_called_once_with(resource, 'stories')\n        mock_handle.assert_called_once_with(config, resource)\n        assert config.registry.auth_model == 'Foo'\n\n\nclass TestGenerateResource(object):\n    def test_dynamic_root_parent(self):\n        raml_resource = Mock(path='/foobar/{id}')\n        parent_resource = Mock(is_root=True)\n        config = config_mock()\n        with pytest.raises(Exception) as ex:\n            generators.generate_resource(\n                config, raml_resource, parent_resource)\n\n        expected = (\"Top-level resources can't be dynamic and must \"\n                    \"represent collections instead\")\n        assert str(ex.value) == expected\n\n    def test_dynamic_not_root_parent(self):\n        raml_resource = Mock(path='/foobar/{id}')\n        parent_resource = Mock(is_root=False)\n        config = config_mock()\n        new_resource = generators.generate_resource(\n            config, raml_resource, parent_resource)\n        assert new_resource is None\n\n    @patch('ramses.generators.dynamic_part_name')\n    @patch('ramses.generators.singular_subresource')\n    @patch('ramses.generators.attr_subresource')\n    @patch('ramses.models.get_existing_model')\n    @patch('ramses.generators.generate_acl')\n    @patch('ramses.generators.resource_view_attrs')\n    @patch('ramses.generators.generate_rest_view')\n    def test_full_run(\n            self, generate_view, view_attrs, generate_acl, get_model,\n            attr_res, singular_res, mock_dyn):\n        mock_dyn.return_value = 'fooid'\n        model_cls = Mock()\n        model_cls.pk_field.return_value = 'my_id'\n        attr_res.return_value = False\n        singular_res.return_value = False\n        get_model.return_value = model_cls\n        raml_resource = Mock(path='/stories')\n        parent_resource = Mock(is_root=False, uid=1)\n        config = config_mock()\n\n        res = generators.generate_resource(\n            config, raml_resource, parent_resource)\n        get_model.assert_called_once_with('Story')\n        generate_acl.assert_called_once_with(\n            config, model_cls=model_cls, raml_resource=raml_resource)\n        mock_dyn.assert_called_once_with(\n            raml_resource=raml_resource,\n            route_name='stories', pk_field='my_id')\n        view_attrs.assert_called_once_with(raml_resource, False)\n        generate_view.assert_called_once_with(\n            config,\n            model_cls=model_cls,\n            attrs=view_attrs(),\n            attr_view=False,\n            singular=False\n        )\n        parent_resource.add.assert_called_once_with(\n            'story', 'stories',\n            id_name='fooid',\n            factory=generate_acl(),\n            view=generate_view()\n        )\n        assert res == parent_resource.add()\n\n    @patch('ramses.generators.dynamic_part_name')\n    @patch('ramses.generators.singular_subresource')\n    @patch('ramses.generators.attr_subresource')\n    @patch('ramses.models.get_existing_model')\n    @patch('ramses.generators.generate_acl')\n    @patch('ramses.generators.resource_view_attrs')\n    @patch('ramses.generators.generate_rest_view')\n    def test_full_run_singular(\n            self, generate_view, view_attrs, generate_acl, get_model,\n            attr_res, singular_res, mock_dyn):\n        mock_dyn.return_value = 'fooid'\n        model_cls = Mock()\n        model_cls.pk_field.return_value = 'my_id'\n        attr_res.return_value = False\n        singular_res.return_value = True\n        get_model.return_value = model_cls\n        raml_resource = Mock(path='/stories')\n        parent_resource = Mock(is_root=False, uid=1)\n        parent_resource.view.Model.pk_field.return_value = 'other_id'\n\n        config = config_mock()\n        res = generators.generate_resource(\n            config, raml_resource, parent_resource)\n        get_model.assert_called_once_with('Story')\n        generate_acl.assert_called_once_with(\n            config, model_cls=parent_resource.view.Model,\n            raml_resource=raml_resource)\n        assert not mock_dyn.called\n        view_attrs.assert_called_once_with(raml_resource, True)\n        generate_view.assert_called_once_with(\n            config,\n            model_cls=parent_resource.view.Model,\n            attrs=view_attrs(),\n            attr_view=False,\n            singular=True\n        )\n        parent_resource.add.assert_called_once_with(\n            'story',\n            factory=generate_acl(),\n            view=generate_view()\n        )\n        assert res == parent_resource.add()\n"
  },
  {
    "path": "tests/test_models.py",
    "content": "import pytest\nfrom mock import Mock, patch, call\n\nfrom .fixtures import engine_mock, config_mock, guards_engine_mock\n\n\n@pytest.mark.usefixtures('engine_mock')\nclass TestHelperFunctions(object):\n\n    @patch('ramses.models.engine')\n    def test_get_existing_model_not_found(self, mock_eng):\n        from ramses import models\n        mock_eng.get_document_cls.side_effect = ValueError\n        model_cls = models.get_existing_model('Foo')\n        assert model_cls is None\n        mock_eng.get_document_cls.assert_called_once_with('Foo')\n\n    @patch('ramses.models.engine')\n    def test_get_existing_model_found(self, mock_eng):\n        from ramses import models\n        mock_eng.get_document_cls.return_value = 1\n        model_cls = models.get_existing_model('Foo')\n        assert model_cls == 1\n        mock_eng.get_document_cls.assert_called_once_with('Foo')\n\n    @patch('ramses.models.setup_data_model')\n    @patch('ramses.models.get_existing_model')\n    def test_prepare_relationship_model_exists(self, mock_get, mock_set):\n        from ramses import models\n        config = Mock()\n        models.prepare_relationship(\n            config, 'Story', 'raml_resource')\n        mock_get.assert_called_once_with('Story')\n        assert not mock_set.called\n\n    @patch('ramses.models.get_existing_model')\n    def test_prepare_relationship_resource_not_found(self, mock_get):\n        from ramses import models\n        config = Mock()\n        resource = Mock(root=Mock(resources=[\n            Mock(method='get', path='/stories'),\n            Mock(method='options', path='/stories'),\n            Mock(method='post', path='/items'),\n        ]))\n        mock_get.return_value = None\n        with pytest.raises(ValueError) as ex:\n            models.prepare_relationship(config, 'Story', resource)\n        expected = ('Model `Story` used in relationship '\n                    'is not defined')\n        assert str(ex.value) == expected\n\n    @patch('ramses.models.setup_data_model')\n    @patch('ramses.models.get_existing_model')\n    def test_prepare_relationship_resource_found(\n            self, mock_get, mock_set):\n        from ramses import models\n        config = Mock()\n        matching_res = Mock(method='post', path='/stories')\n        resource = Mock(root=Mock(resources=[\n            matching_res,\n            Mock(method='options', path='/stories'),\n            Mock(method='post', path='/items'),\n        ]))\n        mock_get.return_value = None\n        config = config_mock()\n        models.prepare_relationship(config, 'Story', resource)\n        mock_set.assert_called_once_with(config, matching_res, 'Story')\n\n    @patch('ramses.models.resource_schema')\n    @patch('ramses.models.get_existing_model')\n    def test_setup_data_model_existing_model(self, mock_get, mock_schema):\n        from ramses import models\n        config = Mock()\n        mock_get.return_value = 1\n        mock_schema.return_value = {\"foo\": \"bar\"}\n        model, auth_model = models.setup_data_model(config, 'foo', 'Bar')\n        assert not auth_model\n        assert model == 1\n        mock_get.assert_called_once_with('Bar')\n\n    @patch('ramses.models.resource_schema')\n    @patch('ramses.models.get_existing_model')\n    def test_setup_data_model_existing_auth_model(self, mock_get, mock_schema):\n        from ramses import models\n        config = Mock()\n        mock_get.return_value = 1\n        mock_schema.return_value = {\"_auth_model\": True}\n        model, auth_model = models.setup_data_model(config, 'foo', 'Bar')\n        assert auth_model\n        assert model == 1\n        mock_get.assert_called_once_with('Bar')\n\n    @patch('ramses.models.resource_schema')\n    @patch('ramses.models.get_existing_model')\n    def test_setup_data_model_no_schema(self, mock_get, mock_schema):\n        from ramses import models\n        config = Mock()\n        mock_get.return_value = None\n        mock_schema.return_value = None\n        with pytest.raises(Exception) as ex:\n            models.setup_data_model(config, 'foo', 'Bar')\n        assert str(ex.value) == 'Missing schema for model `Bar`'\n        mock_get.assert_called_once_with('Bar')\n        mock_schema.assert_called_once_with('foo')\n\n    @patch('ramses.models.resource_schema')\n    @patch('ramses.models.generate_model_cls')\n    @patch('ramses.models.get_existing_model')\n    def test_setup_data_model_success(self, mock_get, mock_gen, mock_schema):\n        from ramses import models\n        mock_get.return_value = None\n        mock_schema.return_value = {'field1': 'val1'}\n        config = config_mock()\n        model = models.setup_data_model(config, 'foo', 'Bar')\n        mock_get.assert_called_once_with('Bar')\n        mock_schema.assert_called_once_with('foo')\n        mock_gen.assert_called_once_with(\n            config,\n            schema={'field1': 'val1'},\n            model_name='Bar',\n            raml_resource='foo')\n        assert model == mock_gen()\n\n    @patch('ramses.models.setup_data_model')\n    def test_handle_model_generation_value_err(self, mock_set):\n        from ramses import models\n        config = Mock()\n        mock_set.side_effect = ValueError('strange error')\n        config = config_mock()\n        with pytest.raises(ValueError) as ex:\n            raml_resource = Mock(path='/stories')\n            models.handle_model_generation(config, raml_resource)\n        assert str(ex.value) == 'Story: strange error'\n        mock_set.assert_called_once_with(config, raml_resource, 'Story')\n\n    @patch('ramses.models.setup_data_model')\n    def test_handle_model_generation(self, mock_set):\n        from ramses import models\n        config = Mock()\n        mock_set.return_value = ('Foo1', True)\n        config = config_mock()\n        raml_resource = Mock(path='/stories')\n        model, auth_model = models.handle_model_generation(\n            config, raml_resource)\n        mock_set.assert_called_once_with(config, raml_resource, 'Story')\n        assert model == 'Foo1'\n        assert auth_model\n\n\n@patch('ramses.models.setup_fields_processors')\n@patch('ramses.models.setup_model_event_subscribers')\n@patch('ramses.models.registry')\n@pytest.mark.usefixtures('engine_mock')\nclass TestGenerateModelCls(object):\n\n    def _test_schema(self):\n        return {\n            'properties': {},\n            '_auth_model': False,\n            '_public_fields': ['public_field1'],\n            '_auth_fields': ['auth_field1'],\n            '_hidden_fields': ['hidden_field1'],\n            '_nested_relationships': ['nested_field1'],\n            '_nesting_depth': 3\n        }\n\n    @patch('ramses.models.resolve_to_callable')\n    def test_simple_case(\n            self, mock_res, mock_reg, mock_subscribers, mock_proc):\n        from nefertari.authentication.models import AuthModelMethodsMixin\n        from ramses import models\n        config = config_mock()\n        models.engine.FloatField.reset_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            \"_db_settings\": {\n                \"type\": \"float\",\n                \"required\": True,\n                \"default\": 0,\n            }\n        }\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=None)\n        assert not auth_model\n        assert model_cls.__name__ == 'Story'\n        assert hasattr(model_cls, 'progress')\n        assert model_cls.__tablename__ == 'story'\n        assert model_cls._public_fields == ['public_field1']\n        assert model_cls._nesting_depth == 3\n        assert model_cls._auth_fields == ['auth_field1']\n        assert model_cls._hidden_fields == ['hidden_field1']\n        assert model_cls._nested_relationships == ['nested_field1']\n        assert model_cls.foo == 'bar'\n        assert issubclass(model_cls, models.engine.ESBaseDocument)\n        assert not issubclass(model_cls, AuthModelMethodsMixin)\n        models.engine.FloatField.assert_called_once_with(\n            default=0, required=True)\n        mock_reg.mget.assert_called_once_with('Story')\n        mock_subscribers.assert_called_once_with(\n            config, model_cls, schema)\n        mock_proc.assert_called_once_with(\n            config, model_cls, schema)\n\n    @patch('ramses.models.resolve_to_callable')\n    def test_callable_default(\n            self, mock_res, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        models.engine.FloatField.reset_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            \"_db_settings\": {\n                \"type\": \"float\",\n                \"default\": \"{{foobar}}\",\n            }\n        }\n        mock_res.return_value = 1\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=None)\n        models.engine.FloatField.assert_called_with(\n            default=1, required=False)\n        mock_res.assert_called_once_with('{{foobar}}')\n\n    def test_auth_model(self, mock_reg, mock_subscribers, mock_proc):\n        from nefertari.authentication.models import AuthModelMethodsMixin\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {'_db_settings': {}}\n        schema['_auth_model'] = True\n        mock_reg.mget.return_value = {'foo': 'bar'}\n\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=None)\n        assert auth_model\n        assert issubclass(model_cls, AuthModelMethodsMixin)\n\n    def test_database_acls_option(\n            self, mock_reg, mock_subscribers, mock_proc,\n            guards_engine_mock):\n        from ramses import models\n        schema = self._test_schema()\n        schema['properties']['progress'] = {'_db_settings': {}}\n        schema['_auth_model'] = True\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        config = config_mock()\n\n        config.registry.database_acls = False\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story1',\n            raml_resource=None)\n        assert not issubclass(model_cls, guards_engine_mock.DocumentACLMixin)\n\n        config.registry.database_acls = True\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story2',\n            raml_resource=None)\n        assert issubclass(model_cls, guards_engine_mock.DocumentACLMixin)\n\n    def test_db_based_model(self, mock_reg, mock_subscribers, mock_proc):\n        from nefertari.authentication.models import AuthModelMethodsMixin\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {'_db_settings': {}}\n        mock_reg.mget.return_value = {'foo': 'bar'}\n\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=None, es_based=False)\n        assert issubclass(model_cls, models.engine.BaseDocument)\n        assert not issubclass(model_cls, models.engine.ESBaseDocument)\n        assert not issubclass(model_cls, AuthModelMethodsMixin)\n\n    def test_no_db_settings(self, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {'type': 'pickle'}\n        mock_reg.mget.return_value = {'foo': 'bar'}\n\n        model_cls, auth_model = models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=None, es_based=False)\n        assert not models.engine.PickleField.called\n\n    def test_unknown_field_type(\n            self, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            '_db_settings': {'type': 'foobar'}}\n        mock_reg.mget.return_value = {'foo': 'bar'}\n\n        with pytest.raises(ValueError) as ex:\n            models.generate_model_cls(\n                config, schema=schema, model_name='Story',\n                raml_resource=None)\n        assert str(ex.value) == 'Unknown type: foobar'\n\n    @patch('ramses.models.prepare_relationship')\n    def test_relationship_field(\n            self, mock_prep, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = Mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            '_db_settings': {\n                'type': 'relationship',\n                'document': 'FooBar',\n            }\n        }\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        config = config_mock()\n        models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=1)\n        mock_prep.assert_called_once_with(\n            config, 'FooBar', 1)\n\n    def test_foreignkey_field(\n            self, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            \"_db_settings\": {\n                \"type\": \"foreign_key\",\n                \"ref_column_type\": \"string\"\n            }\n        }\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=1)\n        models.engine.ForeignKeyField.assert_called_once_with(\n            required=False, ref_column_type=models.engine.StringField)\n\n    def test_list_field(self, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['progress'] = {\n            \"_db_settings\": {\n                \"type\": \"list\",\n                \"item_type\": \"integer\"\n            }\n        }\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=1)\n        models.engine.ListField.assert_called_once_with(\n            required=False, item_type=models.engine.IntegerField)\n\n    def test_duplicate_field_name(\n            self, mock_reg, mock_subscribers, mock_proc):\n        from ramses import models\n        config = config_mock()\n        schema = self._test_schema()\n        schema['properties']['_public_fields'] = {\n            '_db_settings': {'type': 'interval'}}\n        mock_reg.mget.return_value = {'foo': 'bar'}\n        models.generate_model_cls(\n            config, schema=schema, model_name='Story',\n            raml_resource=1)\n        assert not models.engine.IntervalField.called\n\n\nclass TestSubscribersSetup(object):\n\n    @patch('ramses.models.resolve_to_callable')\n    @patch('ramses.models.get_events_map')\n    def test_setup_model_event_subscribers(self, mock_get, mock_resolve):\n        from ramses import models\n        mock_get.return_value = {'before': {'index': 'eventcls'}}\n        mock_resolve.return_value = 1\n        config = Mock()\n        model_cls = 'mymodel'\n        schema = {\n            '_event_handlers': {\n                'before_index': ['func1', 'func2']\n            }\n        }\n        models.setup_model_event_subscribers(config, model_cls, schema)\n        mock_get.assert_called_once_with()\n        mock_resolve.assert_has_calls([call('func1'), call('func2')])\n        config.subscribe_to_events.assert_has_calls([\n            call(mock_resolve(), ['eventcls'], model='mymodel'),\n            call(mock_resolve(), ['eventcls'], model='mymodel'),\n        ])\n\n    @patch('ramses.models.resolve_to_callable')\n    @patch('ramses.models.engine')\n    def test_setup_fields_processors(self, mock_eng, mock_resolve):\n        from ramses import models\n        config = Mock()\n        schema = {\n            'properties': {\n                'stories': {\n                    \"_db_settings\": {\n                        \"type\": \"relationship\",\n                        \"document\": \"Story\",\n                        \"backref_name\": \"owner\",\n                    },\n                    \"_processors\": [\"lowercase\"],\n                    \"_backref_processors\": [\"backref_lowercase\"]\n                }\n            }\n        }\n\n        models.setup_fields_processors(config, 'mymodel', schema)\n\n        mock_resolve.assert_has_calls([\n            call('lowercase'), call('backref_lowercase')])\n        mock_eng.get_document_cls.assert_called_once_with('Story')\n        config.add_field_processors.assert_has_calls([\n            call([mock_resolve()], model='mymodel', field='stories'),\n            call([mock_resolve()], model=mock_eng.get_document_cls(),\n                 field='owner'),\n        ])\n\n    @patch('ramses.models.resolve_to_callable')\n    @patch('ramses.models.engine')\n    def test_setup_fields_processors_backref_not_rel(\n            self, mock_eng, mock_resolve):\n        from ramses import models\n        config = Mock()\n        schema = {\n            'properties': {\n                'stories': {\n                    \"_db_settings\": {\n                        \"type\": \"wqeqweqwe\",\n                        \"document\": \"Story\",\n                        \"backref_name\": \"owner\",\n                    },\n                    \"_backref_processors\": [\"backref_lowercase\"]\n                }\n            }\n        }\n        models.setup_fields_processors(config, 'mymodel', schema)\n        assert not config.add_field_processors.called\n\n    @patch('ramses.models.resolve_to_callable')\n    @patch('ramses.models.engine')\n    def test_setup_fields_processors_backref_no_doc(\n            self, mock_eng, mock_resolve):\n        from ramses import models\n        config = Mock()\n        schema = {\n            'properties': {\n                'stories': {\n                    \"_db_settings\": {\n                        \"type\": \"relationship\",\n                        \"backref_name\": \"owner\",\n                    },\n                    \"_backref_processors\": [\"backref_lowercase\"]\n                }\n            }\n        }\n        models.setup_fields_processors(config, 'mymodel', schema)\n        assert not config.add_field_processors.called\n\n    @patch('ramses.models.resolve_to_callable')\n    @patch('ramses.models.engine')\n    def test_setup_fields_processors_backref_no_backname(\n            self, mock_eng, mock_resolve):\n        from ramses import models\n        config = Mock()\n        schema = {\n            'properties': {\n                'stories': {\n                    \"_db_settings\": {\n                        \"type\": \"relationship\",\n                        \"document\": \"Story\",\n                    },\n                    \"_backref_processors\": [\"backref_lowercase\"]\n                }\n            }\n        }\n        models.setup_fields_processors(config, 'mymodel', schema)\n        assert not config.add_field_processors.called\n"
  },
  {
    "path": "tests/test_registry.py",
    "content": "import pytest\n\nfrom .fixtures import clear_registry\nfrom ramses import registry\n\n\n@pytest.mark.usefixtures('clear_registry')\nclass TestRegistry(object):\n\n    def test_add_decorator(self):\n        @registry.add\n        def foo(*args, **kwargs):\n            return args, kwargs\n\n        assert registry.registry['foo'] is foo\n        assert list(registry.registry.keys()) == ['foo']\n\n    def test_add_decorator_with_name(self):\n        @registry.add('bar')\n        def foo(*args, **kwargs):\n            return args, kwargs\n\n        assert registry.registry['bar'] is foo\n        assert list(registry.registry.keys()) == ['bar']\n\n    def test_add_arbitrary_object(self):\n        registry.add('foo', 1)\n        registry.add('bar', 2)\n\n        assert registry.registry['foo'] == 1\n        assert registry.registry['bar'] == 2\n        assert sorted(registry.registry.keys()) == ['bar', 'foo']\n\n    def test_get(self):\n        registry.registry['foo'] = 1\n        assert registry.get('foo') == 1\n\n    def test_get_error(self):\n        assert not list(registry.registry.keys())\n        with pytest.raises(KeyError) as ex:\n            registry.get('foo')\n        assert 'is not registered in ramses registry' in str(ex.value)\n\n    def test_mget(self):\n        registry.registry['Foo.bar'] = 1\n        registry.registry['Foo.zoo'] = 2\n        assert registry.mget('FoO') == {'bar': 1, 'zoo': 2}\n\n    def test_mget_not_existing(self):\n        registry.registry['Foo.bar'] = 1\n        registry.registry['Foo.zoo'] = 2\n        assert registry.mget('asdasdasd') == {}\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import pytest\nfrom mock import Mock, patch\n\nfrom ramses import utils\n\n\nclass TestUtils(object):\n\n    def test_contenttypes(self):\n        assert utils.ContentTypes.JSON == 'application/json'\n        assert utils.ContentTypes.TEXT_XML == 'text/xml'\n        assert utils.ContentTypes.MULTIPART_FORMDATA == \\\n            'multipart/form-data'\n        assert utils.ContentTypes.FORM_URLENCODED == \\\n            'application/x-www-form-urlencoded'\n\n    def test_convert_schema_json(self):\n        schema = utils.convert_schema({'foo': 'bar'}, 'application/json')\n        assert schema == {'foo': 'bar'}\n\n    def test_convert_schema_json_error(self):\n        with pytest.raises(TypeError) as ex:\n            utils.convert_schema('foo', 'application/json')\n        assert 'Schema is not a valid JSON' in str(ex.value)\n\n    def test_convert_schema_xml(self):\n        assert utils.convert_schema({'foo': 'bar'}, 'text/xml') is None\n\n    def test_is_dynamic_uri(self):\n        assert utils.is_dynamic_uri('/{id}')\n        assert not utils.is_dynamic_uri('/collection')\n\n    def test_clean_dynamic_uri(self):\n        clean = utils.clean_dynamic_uri('/{item_id}')\n        assert clean == 'item_id'\n\n    def test_generate_model_name(self):\n        resource = Mock(path='/zoo/alien-users')\n        model_name = utils.generate_model_name(resource)\n        assert model_name == 'AlienUser'\n\n    @patch.object(utils, 'get_resource_children')\n    def test_dynamic_part_name(self, get_children):\n        get_children.return_value = [\n            Mock(path='/items'), Mock(path='/{myid}')]\n        resource = Mock()\n        part_name = utils.dynamic_part_name(\n            resource, 'stories', 'default_id')\n        assert part_name == 'stories_myid'\n        get_children.assert_called_once_with(resource)\n\n    @patch.object(utils, 'get_resource_children')\n    def test_dynamic_part_name_no_dynamic(self, get_children):\n        get_children.return_value = [Mock(path='/items')]\n        resource = Mock()\n        part_name = utils.dynamic_part_name(\n            resource, 'stories', 'default_id')\n        assert part_name == 'stories_default_id'\n        get_children.assert_called_once_with(resource)\n\n    @patch.object(utils, 'get_resource_children')\n    def test_dynamic_part_name_no_resources(self, get_children):\n        get_children.return_value = []\n        resource = Mock(resources=None)\n        part_name = utils.dynamic_part_name(\n            resource, 'stories', 'default_id')\n        assert part_name == 'stories_default_id'\n        get_children.assert_called_once_with(resource)\n\n    def test_extract_dynamic_part(self):\n        assert utils.extract_dynamic_part('/stories/{id}/foo') == 'id'\n        assert utils.extract_dynamic_part('/stories/{id}') == 'id'\n\n    def test_extract_dynamic_part_fail(self):\n        assert utils.extract_dynamic_part('/stories/id') is None\n\n    def _get_mock_method_resources(self, *methods):\n        return [Mock(method=meth) for meth in methods]\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_no_dynamic_subres(self, get_sib, get_child):\n        get_child.return_value = []\n        get_sib.return_value = self._get_mock_method_resources(\n            'get', 'post', 'put', 'patch', 'delete')\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=False)\n        get_sib.assert_called_once_with(resource)\n        get_child.assert_called_once_with(resource)\n        assert attrs == set(['create', 'delete_many', 'index', 'update_many'])\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_dynamic_subres(self, get_sib, get_child):\n        get_child.return_value = self._get_mock_method_resources(\n            'get', 'put', 'patch', 'delete')\n        get_sib.return_value = self._get_mock_method_resources(\n            'get', 'post', 'put', 'patch', 'delete')\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=False)\n        get_sib.assert_called_once_with(resource)\n        get_child.assert_called_once_with(resource)\n        assert attrs == set([\n            'create', 'delete_many', 'index', 'update_many',\n            'show', 'update', 'delete', 'replace'\n        ])\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_singular(self, get_sib, get_child):\n        get_child.return_value = []\n        get_sib.return_value = self._get_mock_method_resources(\n            'get', 'post', 'put', 'patch', 'delete')\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=True)\n        get_sib.assert_called_once_with(resource)\n        get_child.assert_called_once_with(resource)\n        assert attrs == set(['create', 'delete', 'show', 'update', 'replace'])\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_no_subresources(self, get_sib, get_child):\n        child_res = self._get_mock_method_resources('get')\n        child_res[0].path = '/items'\n        get_child.return_value = child_res\n        get_sib.return_value = self._get_mock_method_resources(\n            'get', 'post', 'put', 'patch', 'delete')\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=False)\n        get_sib.assert_called_once_with(resource)\n        get_child.assert_called_once_with(resource)\n        assert attrs == set(['create', 'delete_many', 'index', 'update_many'])\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_no_methods(self, get_sib, get_child):\n        get_sib.return_value = []\n        get_child.return_value = []\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=False)\n        get_sib.assert_called_once_with(resource)\n        get_child.assert_called_once_with(resource)\n        assert attrs == set()\n\n    @patch.object(utils, 'get_resource_children')\n    @patch.object(utils, 'get_resource_siblings')\n    def test_resource_view_attrs_not_supported_method(\n            self, get_sib, get_child):\n        get_sib.return_value = []\n        get_child.return_value = self._get_mock_method_resources(\n            'nice_method')\n        resource = Mock()\n        attrs = utils.resource_view_attrs(resource, singular=False)\n        assert attrs == set()\n\n    def test_resource_schema_no_body(self):\n        resource = Mock(body=None)\n        with pytest.raises(ValueError) as ex:\n            utils.resource_schema(resource)\n        expected = 'RAML resource has no body to setup database'\n        assert expected in str(ex.value)\n\n    def test_resource_schema_no_schemas(self):\n        resource = Mock(body=[Mock(schema=None), Mock(schema='')])\n        assert utils.resource_schema(resource) is None\n\n    def test_resource_schema_success(self):\n        resource = Mock(body=[\n            Mock(schema={'foo': 'bar'},\n                 mime_type=utils.ContentTypes.JSON)\n        ])\n        assert utils.resource_schema(resource) == {'foo': 'bar'}\n\n    def test_is_dynamic_resource_no_resource(self):\n        assert not utils.is_dynamic_resource(None)\n\n    def test_is_dynamic_resource_dynamic(self):\n        resource = Mock(path='/{id}')\n        assert utils.is_dynamic_resource(resource)\n\n    def test_is_dynamic_resource_not_dynamic(self):\n        resource = Mock(path='/stories')\n        assert not utils.is_dynamic_resource(resource)\n\n    def test_get_static_parent(self):\n        parent = Mock(path='/stories', method='post')\n        resource = Mock(path='/{id}')\n        resource.parent = parent\n        assert utils.get_static_parent(resource, method='post') is parent\n\n    def test_get_static_parent_none(self):\n        resource = Mock(path='/{id}')\n        resource.parent = None\n        assert utils.get_static_parent(resource, method='post') is None\n\n    def test_get_static_parent_wrong_parent_method(self):\n        root = Mock(resources=[\n            Mock(path='/stories', method='options'),\n            Mock(path='/users', method='post'),\n            Mock(path='/stories', method='post'),\n        ])\n        parent = Mock(path='/stories', method='get', root=root)\n        resource = Mock(path='/{id}')\n        resource.parent = parent\n        res = utils.get_static_parent(resource, method='post')\n        assert res.method == 'post'\n        assert res.path == '/stories'\n\n    def test_get_static_parent_without_method_parent_present(self):\n        root = Mock(resources=[\n            Mock(path='/stories', method='options'),\n            Mock(path='/stories', method='post'),\n        ])\n        parent = Mock(path='/stories', method='get', root=root)\n        resource = Mock(path='/{id}')\n        resource.parent = parent\n        res = utils.get_static_parent(resource)\n        assert res.method == 'get'\n        assert res.path == '/stories'\n\n    def test_get_static_parent_none_found_in_root(self):\n        root = Mock(resources=[\n            Mock(path='/stories', method='get'),\n        ])\n        parent = Mock(path='/stories', method='options', root=root)\n        resource = Mock(path='/{id}')\n        resource.parent = parent\n        assert utils.get_static_parent(resource, method='post') is None\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_attr_subresource_no_static_parent(self, mock_schema, mock_par):\n        mock_par.return_value = None\n        assert not utils.attr_subresource('foo', 1)\n        mock_par.assert_called_once_with('foo', method='POST')\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_attr_subresource_no_schema(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = None\n        assert not utils.attr_subresource('foo', 1)\n        mock_par.assert_called_once_with('foo', method='POST')\n        mock_schema.assert_called_once_with(parent)\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_attr_subresource_not_attr(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = {\n            'properties': {\n                'route_name': {\n                    '_db_settings': {\n                        'type': 'string'\n                    }\n                }\n            }\n        }\n        assert not utils.attr_subresource('resource', 'route_name')\n        mock_par.assert_called_once_with('resource', method='POST')\n        mock_schema.assert_called_once_with(parent)\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_attr_subresource_dict(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = {\n            'properties': {\n                'route_name': {\n                    '_db_settings': {\n                        'type': 'dict'\n                    }\n                },\n                'route_name2': {\n                    '_db_settings': {\n                        'type': 'list'\n                    }\n                }\n            }\n        }\n        assert utils.attr_subresource('resource', 'route_name')\n        mock_par.assert_called_once_with('resource', method='POST')\n        mock_schema.assert_called_once_with(parent)\n        assert utils.attr_subresource('resource', 'route_name2')\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_singular_subresource_no_static_parent(self, mock_schema, mock_par):\n        mock_par.return_value = None\n        assert not utils.singular_subresource('foo', 1)\n        mock_par.assert_called_once_with('foo', method='POST')\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_singular_subresource_no_schema(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = None\n        assert not utils.singular_subresource('foo', 1)\n        mock_par.assert_called_once_with('foo', method='POST')\n        mock_schema.assert_called_once_with(parent)\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_singular_subresource_not_attr(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = {\n            'properties': {\n                'route_name': {\n                    '_db_settings': {\n                        'type': 'string'\n                    }\n                }\n            }\n        }\n        assert not utils.singular_subresource('resource', 'route_name')\n        mock_par.assert_called_once_with('resource', method='POST')\n        mock_schema.assert_called_once_with(parent)\n\n    @patch('ramses.utils.get_static_parent')\n    @patch('ramses.utils.resource_schema')\n    def test_singular_subresource_dict(self, mock_schema, mock_par):\n        parent = Mock()\n        mock_par.return_value = parent\n        mock_schema.return_value = {\n            'properties': {\n                'route_name': {\n                    '_db_settings': {\n                        'type': 'relationship',\n                        'uselist': False\n                    }\n                },\n            }\n        }\n        assert utils.singular_subresource('resource', 'route_name')\n        mock_par.assert_called_once_with('resource', method='POST')\n        mock_schema.assert_called_once_with(parent)\n\n    def test_is_callable_tag_not_str(self):\n        assert not utils.is_callable_tag(1)\n        assert not utils.is_callable_tag(None)\n\n    def test_is_callable_tag_not_tag(self):\n        assert not utils.is_callable_tag('foobar')\n\n    def test_is_callable_tag(self):\n        assert utils.is_callable_tag('{{foobar}}')\n\n    def test_resolve_to_callable_not_found(self):\n        with pytest.raises(ImportError) as ex:\n            utils.resolve_to_callable('{{foobar}}')\n        assert str(ex.value) == 'Failed to load callable `foobar`'\n\n    def test_resolve_to_callable_registry(self):\n        from ramses import registry\n\n        @registry.add\n        def foo():\n            pass\n\n        func = utils.resolve_to_callable('{{foo}}')\n        assert func is foo\n        func = utils.resolve_to_callable('foo')\n        assert func is foo\n\n    def test_resolve_to_callable_dotted_path(self):\n        from datetime import datetime\n        func = utils.resolve_to_callable('{{datetime.datetime}}')\n        assert func is datetime\n        func = utils.resolve_to_callable('datetime.datetime')\n        assert func is datetime\n\n    def test_get_events_map(self):\n        from nefertari import events\n        events_map = utils.get_events_map()\n        after, before = events_map['after'], events_map['before']\n        after_set, before_set = after.pop('set'), before.pop('set')\n        assert sorted(events.BEFORE_EVENTS.keys()) == sorted(\n            before.keys())\n        assert sorted(events.AFTER_EVENTS.keys()) == sorted(\n            after.keys())\n        assert after_set == [\n            events.AfterCreate,\n            events.AfterUpdate,\n            events.AfterReplace,\n            events.AfterUpdateMany,\n            events.AfterRegister,\n        ]\n        assert before_set == [\n            events.BeforeCreate,\n            events.BeforeUpdate,\n            events.BeforeReplace,\n            events.BeforeUpdateMany,\n            events.BeforeRegister,\n        ]\n\n    def test_patch_view_model(self):\n        view_cls = Mock()\n        model1 = Mock()\n        model2 = Mock()\n        view_cls.Model = model1\n\n        with utils.patch_view_model(view_cls, model2):\n            view_cls.Model()\n\n        assert view_cls.Model is model1\n        assert not model1.called\n        model2.assert_called_once_with()\n\n    def test_get_route_name(self):\n        resource_uri = '/foo-=-=-=-123'\n        assert utils.get_route_name(resource_uri) == 'foo123'\n\n    def test_get_resource_uri(self):\n        resource = Mock(path='/foobar/zoo ')\n        assert utils.get_resource_uri(resource) == 'zoo'\n"
  },
  {
    "path": "tests/test_views.py",
    "content": "import pytest\nfrom mock import Mock, patch\n\nfrom nefertari.json_httpexceptions import (\n    JHTTPNotFound, JHTTPMethodNotAllowed)\nfrom nefertari.view import BaseView\n\nfrom ramses import views\nfrom .fixtures import config_mock, guards_engine_mock\n\n\nclass ViewTestBase(object):\n    view_cls = None\n    view_kwargs = dict(\n        context={},\n        _query_params={'foo': 'bar'},\n        _json_params={'foo2': 'bar2'},\n    )\n    request_kwargs = dict(\n        method='GET',\n        accept=[''],\n    )\n\n    def _test_view(self):\n        class View(self.view_cls, BaseView):\n            _json_encoder = 'foo'\n\n        request = Mock(**self.request_kwargs)\n        return View(request=request, **self.view_kwargs)\n\n\nclass TestSetObjectACLMixin(object):\n    def test_set_object_acl(self, guards_engine_mock):\n        view = views.SetObjectACLMixin()\n        view.request = 'foo'\n        view._factory = Mock()\n        obj = Mock(_acl=None)\n        view.set_object_acl(obj)\n        view._factory.assert_called_once_with(view.request)\n        view._factory().generate_item_acl.assert_called_once_with(obj)\n        field = guards_engine_mock.ACLField\n        field.stringify_acl.assert_called_once_with(\n            view._factory().generate_item_acl())\n        assert obj._acl == field.stringify_acl()\n\n\nclass TestBaseView(ViewTestBase):\n    view_cls = views.BaseView\n\n    def test_init(self):\n        view = self._test_view()\n        assert view._query_params['_limit'] == 20\n\n    def test_clean_id_name(self):\n        view = self._test_view()\n        view._resource = Mock(id_name='foo')\n        assert view.clean_id_name == 'foo'\n        view._resource = Mock(id_name='foo_bar')\n        assert view.clean_id_name == 'bar'\n\n    def test_resolve_kw(self):\n        view = self._test_view()\n        kwargs = {'foo_bar_qoo': 1, 'arg_val': 4, 'q': 3}\n        assert view.resolve_kw(kwargs) == {'bar_qoo': 1, 'val': 4, 'q': 3}\n\n    def test_location(self):\n        view = self._test_view()\n        view._resource = Mock(id_name='myid', uid='items')\n        view._location(Mock(myid=123))\n        view.request.route_url.assert_called_once_with(\n            'items', myid=123)\n\n    def test_location_split_id(self):\n        view = self._test_view()\n        view._resource = Mock(id_name='items_myid', uid='items')\n        view._location(Mock(myid=123))\n        view.request.route_url.assert_called_once_with(\n            'items', items_myid=123)\n\n    def test_get_collection_has_parent(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=[1, 2, 3])\n        view.Model = Mock()\n        view.get_collection(name='ok')\n        view._parent_queryset.assert_called_once_with()\n        view.Model.filter_objects.assert_called_once_with(\n            [1, 2, 3], _limit=20, foo='bar', name='ok')\n\n    def test_get_collection_has_parent_empty_queryset(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=[])\n        view.Model = Mock()\n        view.get_collection(name='ok')\n        view._parent_queryset.assert_called_once_with()\n        view.Model.filter_objects.assert_called_once_with(\n            [], _limit=20, foo='bar', name='ok')\n\n    def test_get_collection_no_parent(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=None)\n        view.Model = Mock()\n        view.get_collection(name='ok')\n        view._parent_queryset.assert_called_once_with()\n        assert not view.Model.filter_objects.called\n        view.Model.get_collection.assert_called_once_with(\n            _limit=20, foo='bar', name='ok')\n\n    def test_get_item_no_parent(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=None)\n        view.context = 1\n        assert view.get_item(name='wqe') == 1\n\n    def test_get_item_not_found_in_parent(self):\n        view = self._test_view()\n        view.Model = Mock(__name__='foo')\n        view._get_context_key = Mock(return_value='123123')\n        view._parent_queryset = Mock(return_value=[2, 3])\n        view.context = 1\n        with pytest.raises(JHTTPNotFound):\n            view.get_item(name='wqe')\n\n    def test_get_item_found_in_parent(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=[1, 3])\n        view.context = 1\n        assert view.get_item(name='wqe') == 1\n\n    def test_get_item_found_in_parent_context_callable(self):\n        func = lambda x: x\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=[func, 3])\n        view.reload_context = Mock()\n        view.context = func\n        assert view.get_item(name='wqe') is view.context\n        view.reload_context.assert_called_once_with(\n            es_based=False, name='wqe')\n\n    def test_get_context_key(self):\n        view = self._test_view()\n        view._resource = Mock(id_name='foo')\n        assert view._get_context_key(foo='bar') == 'bar'\n\n    def test_parent_queryset(self):\n        from pyramid.config import Configurator\n        from ramses.acl import BaseACL\n        config = Configurator()\n        config.include('nefertari')\n        root = config.get_root_resource()\n\n        class View(self.view_cls, BaseView):\n            _json_encoder = 'foo'\n\n        user = root.add(\n            'user', 'users', id_name='username',\n            view=View, factory=BaseACL)\n        user.add(\n            'story', 'stories', id_name='prof_id',\n            view=View, factory=BaseACL)\n        view_cls = root.resource_map['user:story'].view\n        view_cls._json_encoder = 'foo'\n\n        request = Mock(\n            registry=Mock(),\n            path='/foo/foo',\n            matchdict={'username': 'user12', 'prof_id': 4},\n            accept=[''], method='GET'\n        )\n        request.params.mixed.return_value = {'foo1': 'bar1'}\n        request.blank.return_value = request\n        stories_view = view_cls(\n            request=request,\n            context={},\n            _query_params={'foo1': 'bar1'},\n            _json_params={'foo2': 'bar2'},)\n\n        parent_view = stories_view._resource.parent.view\n        with patch.object(parent_view, 'get_item') as get_item:\n            parent_view.get_item = get_item\n            result = stories_view._parent_queryset()\n            get_item.assert_called_once_with(username='user12')\n            assert result == get_item().stories\n\n    def test_reload_context(self):\n        class Factory(dict):\n            item_model = None\n\n            def __getitem__(self, key):\n                return key\n\n        view = self._test_view()\n        view._factory = Factory\n        view._get_context_key = Mock(return_value='foo')\n        view.reload_context(es_based=False, arg='asd')\n        view._get_context_key.assert_called_once_with(arg='asd')\n        assert view.context == 'foo'\n\n\nclass TestCollectionView(ViewTestBase):\n    view_cls = views.CollectionView\n\n    def test_index(self):\n        view = self._test_view()\n        view.get_collection = Mock()\n        resp = view.index(foo='bar')\n        view.get_collection.assert_called_once_with()\n        assert resp == view.get_collection()\n\n    def test_show(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.show(foo='bar')\n        view.get_item.assert_called_once_with(foo='bar')\n        assert resp == view.get_item()\n\n    def test_create(self):\n        view = self._test_view()\n        view.set_object_acl = Mock()\n        view.request.registry._root_resources = {\n            'foo': Mock(auth=False)\n        }\n        view.Model = Mock()\n        obj = Mock()\n        obj.to_dict.return_value = {'id': 1}\n        view.Model().save.return_value = obj\n        view._location = Mock(return_value='/sadasd')\n        resp = view.create(foo='bar')\n        view.Model.assert_called_with(foo2='bar2')\n        view.Model().save.assert_called_with(view.request)\n        assert view.set_object_acl.call_count == 1\n        assert resp == view.Model().save()\n\n    def test_update(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        view._location = Mock(return_value='/sadasd')\n        resp = view.update(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        view.get_item().update.assert_called_once_with(\n            {'foo2': 'bar2'}, view.request)\n        assert resp == view.get_item().update()\n\n    def test_replace(self):\n        view = self._test_view()\n        view.update = Mock()\n        resp = view.replace(foo=1)\n        view.update.assert_called_once_with(foo=1)\n        assert resp == view.update()\n\n    def test_delete(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.delete(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        view.get_item().delete.assert_called_once_with(\n            view.request)\n        assert resp is None\n\n    def test_delete_many(self):\n        view = self._test_view()\n        view.Model = Mock(__name__='Mock')\n        view.Model._delete_many.return_value = 123\n        view.get_collection = Mock()\n        resp = view.delete_many(foo=1)\n        view.get_collection.assert_called_once_with()\n        view.Model._delete_many.assert_called_once_with(\n            view.get_collection(), view.request)\n        assert resp == 123\n\n    def test_update_many(self):\n        view = self._test_view()\n        view.Model = Mock(__name__='Mock')\n        view.Model._update_many.return_value = 123\n        view.get_collection = Mock()\n        resp = view.update_many(qoo=1)\n        view.get_collection.assert_called_once_with(_limit=20, foo='bar')\n        view.Model._update_many.assert_called_once_with(\n            view.get_collection(), {'foo2': 'bar2'},\n            view.request)\n        assert resp == 123\n\n\nclass TestESBaseView(ViewTestBase):\n    view_cls = views.ESBaseView\n\n    def test_parent_queryset_es(self):\n        from pyramid.config import Configurator\n        from ramses.acl import BaseACL\n\n        class View(self.view_cls, BaseView):\n            _json_encoder = 'foo'\n\n        config = Configurator()\n        config.include('nefertari')\n        root = config.get_root_resource()\n        user = root.add(\n            'user', 'users', id_name='username',\n            view=View, factory=BaseACL)\n        user.add(\n            'story', 'stories', id_name='prof_id',\n            view=View, factory=BaseACL)\n        view_cls = root.resource_map['user:story'].view\n        view_cls._json_encoder = 'foo'\n\n        request = Mock(\n            registry=Mock(),\n            path='/foo/foo',\n            matchdict={'username': 'user12', 'prof_id': 4},\n            accept=[''], method='GET'\n        )\n        request.params.mixed.return_value = {'foo1': 'bar1'}\n        request.blank.return_value = request\n        stories_view = view_cls(\n            request=request,\n            context={},\n            _query_params={'foo1': 'bar1'},\n            _json_params={'foo2': 'bar2'},)\n\n        parent_view = stories_view._resource.parent.view\n        with patch.object(parent_view, 'get_item_es') as get_item_es:\n            parent_view.get_item_es = get_item_es\n            result = stories_view._parent_queryset_es()\n            get_item_es.assert_called_once_with(username='user12')\n            assert result == get_item_es().stories\n\n    def test_get_es_object_ids(self):\n        view = self._test_view()\n        view._resource = Mock(id_name='foobar')\n        objects = [Mock(foobar=4), Mock(foobar=7)]\n        assert sorted(view.get_es_object_ids(objects)) == ['4', '7']\n\n    @patch('nefertari.elasticsearch.ES')\n    def test_get_collection_es_no_parent(self, mock_es):\n        mock_es.settings.asbool.return_value = False\n        view = self._test_view()\n        view._parent_queryset_es = Mock(return_value=None)\n        view.Model = Mock(__name__='Foo')\n        view.get_collection_es()\n        mock_es.assert_called_once_with('Foo')\n        mock_es().get_collection.assert_called_once_with(\n            _limit=20, foo='bar')\n\n    @patch('nefertari.elasticsearch.ES')\n    def test_get_collection_es_parent_no_obj_ids(self, mock_es):\n        mock_es.settings.asbool.return_value = False\n        view = self._test_view()\n        view._parent_queryset_es = Mock(return_value=[1, 2])\n        view.Model = Mock(__name__='Foo')\n        view.get_es_object_ids = Mock(return_value=None)\n        result = view.get_collection_es()\n        assert not mock_es().get_collection.called\n        assert result == []\n\n    @patch('nefertari.elasticsearch.ES')\n    def test_get_collection_es_parent_with_ids(self, mock_es):\n        mock_es.settings.asbool.return_value = False\n        view = self._test_view()\n        view._parent_queryset_es = Mock(return_value=['obj1', 'obj2'])\n        view.Model = Mock(__name__='Foo')\n        view.get_es_object_ids = Mock(return_value=[1, 2])\n        view.get_collection_es()\n        view.get_es_object_ids.assert_called_once_with(['obj1', 'obj2'])\n        mock_es().get_collection.assert_called_once_with(\n            _limit=20, foo='bar', id=[1, 2])\n\n    def test_get_item_es_no_parent(self):\n        view = self._test_view()\n        view._get_context_key = Mock(return_value=1)\n        view._parent_queryset_es = Mock(return_value=None)\n        view.reload_context = Mock()\n        view.context = 'foo'\n        resp = view.get_item_es(a=4)\n        view._get_context_key.assert_called_once_with(a=4)\n        view._parent_queryset_es.assert_called_once_with()\n        assert not view.reload_context.called\n        assert resp == 'foo'\n\n    def test_get_item_es_matching_id(self):\n        view = self._test_view()\n        view._get_context_key = Mock(return_value=1)\n        view._parent_queryset_es = Mock(return_value=['obj1', 'obj2'])\n        view.get_es_object_ids = Mock(return_value=[1, 2])\n        view.reload_context = Mock()\n        view.context = 'foo'\n        resp = view.get_item_es(a=4)\n        view.get_es_object_ids.assert_called_once_with(['obj1', 'obj2'])\n        view._get_context_key.assert_called_once_with(a=4)\n        view._parent_queryset_es.assert_called_once_with()\n        assert not view.reload_context.called\n        assert resp == 'foo'\n\n    def test_get_item_es_not_matching_id(self):\n        view = self._test_view()\n        view._get_context_key = Mock(return_value=1)\n        view._parent_queryset_es = Mock(return_value=['obj1', 'obj2'])\n        view.get_es_object_ids = Mock(return_value=[2, 3])\n        view.reload_context = Mock()\n        view.Model = Mock(__name__='Foo')\n        view.context = 'foo'\n        with pytest.raises(JHTTPNotFound) as ex:\n            view.get_item_es(a=4)\n        assert 'Foo(id=1) resource not found' in str(ex.value)\n\n    def test_get_item_es_callable_context(self):\n        view = self._test_view()\n        view._get_context_key = Mock(return_value=1)\n        view._parent_queryset_es = Mock(return_value=['obj1', 'obj2'])\n        view.get_es_object_ids = Mock(return_value=[1, 2])\n        view.reload_context = Mock()\n        view.context = lambda x: x\n        resp = view.get_item_es(a=4)\n        view.reload_context.assert_called_once_with(es_based=True, a=4)\n        assert resp == view.context\n\n\nclass TestESCollectionView(ViewTestBase):\n    view_cls = views.ESCollectionView\n\n    def test_index(self):\n        view = self._test_view()\n        view.aggregate = Mock(side_effect=KeyError)\n        view.get_collection_es = Mock()\n        resp = view.index(foo=1)\n        view.get_collection_es.assert_called_once_with()\n        assert resp == view.get_collection_es()\n\n    def test_show(self):\n        view = self._test_view()\n        view.get_item_es = Mock()\n        resp = view.show(foo=1)\n        view.get_item_es.assert_called_once_with(foo=1)\n        assert resp == view.get_item_es()\n\n    def test_update(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        view.reload_context = Mock()\n        view._location = Mock(return_value='/sadasd')\n        resp = view.update(foo=1)\n        view.reload_context.assert_called_once_with(es_based=False, foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        view.get_item().update.assert_called_once_with(\n            {'foo2': 'bar2'}, view.request)\n        assert resp == view.get_item().update()\n\n    def test_replace(self):\n        view = self._test_view()\n        view.update = Mock()\n        resp = view.replace(foo=1)\n        view.update.assert_called_once_with(foo=1)\n        assert resp == view.update()\n\n    def test_get_dbcollection_with_es(self):\n        view = self._test_view()\n        view._query_params['_limit'] = 50\n        view.get_collection_es = Mock(return_value=[1, 2])\n        view.Model = Mock()\n        result = view.get_dbcollection_with_es(foo='bar')\n        view.get_collection_es.assert_called_once_with()\n        view.Model.filter_objects.assert_called_once_with([1, 2])\n        assert result == view.Model.filter_objects()\n\n    def test_delete_many(self):\n        view = self._test_view()\n        view.Model = Mock(__name__='Foo')\n        view.Model._delete_many.return_value = 123\n        view.get_dbcollection_with_es = Mock()\n        result = view.delete_many(foo=1)\n        view.get_dbcollection_with_es.assert_called_once_with(foo=1)\n        view.Model._delete_many.assert_called_once_with(\n            view.get_dbcollection_with_es(), view.request)\n        assert result == 123\n\n    def test_update_many(self):\n        view = self._test_view()\n        view.Model = Mock(__name__='Foo')\n        view.Model._update_many.return_value = 123\n        view.get_dbcollection_with_es = Mock()\n        result = view.update_many(foo=1)\n        view.get_dbcollection_with_es.assert_called_once_with(foo=1)\n        view.Model._update_many.assert_called_once_with(\n            view.get_dbcollection_with_es(), {'foo2': 'bar2'},\n            view.request)\n        assert result == 123\n\n\nclass TestItemSubresourceBaseView(ViewTestBase):\n    view_cls = views.ItemSubresourceBaseView\n\n    def test_get_context_key(self):\n        view = self._test_view()\n        parent = Mock(id_name='foobar')\n        resource = Mock()\n        resource.parent = parent\n        view._resource = resource\n        assert view._get_context_key(foobar=1, foo=2) == '1'\n\n    def test_get_item(self):\n        view = self._test_view()\n        view._parent_queryset = Mock(return_value=[1, 2])\n        view.reload_context = Mock()\n        view.context = 1\n        assert view.get_item(foo=4) == 1\n        view._parent_queryset.assert_called_once_with()\n        view.reload_context.assert_called_once_with(es_based=False, foo=4)\n\n\nclass TestItemAttributeView(ViewTestBase):\n    view_cls = views.ItemAttributeView\n    request_kwargs = dict(\n        method='GET',\n        accept=[''],\n        path='user/1/settings'\n    )\n\n    def test_init(self):\n        view = self._test_view()\n        assert view.value_type is None\n        assert view.unique\n        assert view.attr == 'settings'\n\n    def test_index(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.index(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        assert resp == view.get_item().settings\n\n    def test_create(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.create(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        obj = view.get_item()\n        obj.update_iterables.assert_called_once_with(\n            {'foo2': 'bar2'}, 'settings',\n            unique=True, value_type=None,\n            request=view.request)\n        assert resp == obj.settings\n\n\nclass TestItemSingularView(ViewTestBase):\n    view_cls = views.ItemSingularView\n    request_kwargs = dict(\n        method='GET',\n        accept=[''],\n        path='user/1/profile',\n        url='http://example.com',\n    )\n\n    def test_init(self):\n        view = self._test_view()\n        assert view.attr == 'profile'\n\n    def test_show(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.show(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        assert resp == view.get_item().profile\n\n    def test_create(self):\n        view = self._test_view()\n        view.set_object_acl = Mock()\n        view.request.registry._root_resources = {\n            'foo': Mock(auth=False)\n        }\n        view.get_item = Mock()\n        view.Model = Mock()\n        resp = view.create(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        view.Model.assert_called_once_with(foo2='bar2')\n        child = view.Model()\n        child.save.assert_called_once_with(view.request)\n        parent = view.get_item()\n        parent.update.assert_called_once_with(\n            {'profile': child.save()}, view.request)\n        assert view.set_object_acl.call_count == 1\n        assert resp == child.save()\n\n    def test_update(self):\n        view = self._test_view()\n        view.get_item = Mock()\n        resp = view.update(foo=1)\n        view.get_item.assert_called_once_with(foo=1)\n        child = view.get_item().profile\n        child.update.assert_called_once_with(\n            {'foo2': 'bar2'}, view.request)\n        assert resp == child\n\n    def test_replace(self):\n        view = self._test_view()\n        view.update = Mock()\n        resp = view.replace(foo=1)\n        view.update.assert_called_once_with(foo=1)\n        assert resp == view.update()\n\n    def test_delete(self):\n        view = self._test_view()\n        view.attr = 'profile'\n        view.get_item = Mock()\n        resp = view.delete(foo=1)\n        assert resp is None\n        view.get_item.assert_called_once_with(foo=1)\n        parent = view.get_item()\n        parent.profile.delete.assert_called_once_with(\n            view.request)\n\n\nclass TestRestViewGeneration(object):\n\n    @patch('ramses.views.NefertariBaseView._run_init_actions')\n    def test_only_provided_attrs_are_available(self, run_init):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show', 'foobar'],\n            es_based=True, attr_view=False, singular=False)\n        view_cls._json_encoder = 'foo'\n        assert issubclass(view_cls, views.ESCollectionView)\n        request = Mock(**ViewTestBase.request_kwargs)\n        view = view_cls(request=request, **ViewTestBase.view_kwargs)\n        assert not hasattr(view_cls, 'foobar')\n\n        try:\n            view.show()\n        except JHTTPMethodNotAllowed:\n            raise Exception('Unexpected error')\n        except Exception:\n            pass\n        with pytest.raises(JHTTPMethodNotAllowed):\n            view.delete_many()\n        with pytest.raises(JHTTPMethodNotAllowed):\n            view.create()\n        with pytest.raises(JHTTPMethodNotAllowed):\n            view.delete()\n        with pytest.raises(JHTTPMethodNotAllowed):\n            view.update_many()\n        with pytest.raises(JHTTPMethodNotAllowed):\n            view.index()\n\n    def test_singular_view(self):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=True, attr_view=False, singular=True)\n        view_cls._json_encoder = 'foo'\n        assert issubclass(view_cls, views.ItemSingularView)\n\n    def test_attribute_view(self):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=True, attr_view=True, singular=False)\n        view_cls._json_encoder = 'foo'\n        assert issubclass(view_cls, views.ItemAttributeView)\n\n    def test_escollection_view(self):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=True, attr_view=False, singular=False)\n        view_cls._json_encoder = 'foo'\n        assert issubclass(view_cls, views.ESCollectionView)\n        assert issubclass(view_cls, views.CollectionView)\n\n    def test_dbcollection_view(self):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=False, attr_view=False, singular=False)\n        view_cls._json_encoder = 'foo'\n        assert not issubclass(view_cls, views.ESCollectionView)\n        assert issubclass(view_cls, views.CollectionView)\n\n    def test_default_values(self):\n        config = config_mock()\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'])\n        view_cls._json_encoder = 'foo'\n        assert issubclass(view_cls, views.ESCollectionView)\n        assert issubclass(view_cls, views.CollectionView)\n        assert view_cls.Model == 'foo'\n\n    def test_database_acls_option(self):\n        from nefertari_guards.view import ACLFilterViewMixin\n        config = config_mock()\n\n        config.registry.database_acls = False\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=False, attr_view=False, singular=False)\n        assert not issubclass(\n            view_cls, ACLFilterViewMixin)\n        assert not issubclass(\n            view_cls, views.SetObjectACLMixin)\n\n        config.registry.database_acls = True\n        view_cls = views.generate_rest_view(\n            config, model_cls='foo', attrs=['show'],\n            es_based=False, attr_view=False, singular=False)\n        assert issubclass(view_cls, views.SetObjectACLMixin)\n        assert issubclass(view_cls, ACLFilterViewMixin)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist =\n    py27,\n    py33,py34,py35,\n\n[testenv]\nsetenv =\n    PYTHONHASHSEED=0\ndeps = -rrequirements.dev\ncommands =\n    py.test {posargs:--cov ramses tests}\n    python ramses/scripts/scaffold_test.py -s ramses_starter\n\n[testenv:flake8]\ndeps =\n    flake8\ncommands =\n    flake8 ramses\n"
  }
]