Full Code of ramses-tech/ramses for AI

master ea2e1e896325 cached
54 files
230.7 KB
54.8k tokens
349 symbols
1 requests
Download .txt
Showing preview only (246K chars total). Download the full file or copy to clipboard to get everything.
Repository: ramses-tech/ramses
Branch: master
Commit: ea2e1e896325
Files: 54
Total size: 230.7 KB

Directory structure:
gitextract_t7nlq2go/

├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── VERSION
├── docs/
│   ├── Makefile
│   └── source/
│       ├── changelog.rst
│       ├── conf.py
│       ├── event_handlers.rst
│       ├── field_processors.rst
│       ├── fields.rst
│       ├── getting_started.rst
│       ├── index.rst
│       ├── raml.rst
│       ├── relationships.rst
│       └── schemas.rst
├── ramses/
│   ├── __init__.py
│   ├── acl.py
│   ├── auth.py
│   ├── generators.py
│   ├── models.py
│   ├── registry.py
│   ├── scaffolds/
│   │   ├── __init__.py
│   │   └── ramses_starter/
│   │       ├── +package+/
│   │       │   ├── __init__.py
│   │       │   └── tests/
│   │       │       ├── __init__.py
│   │       │       ├── api.raml
│   │       │       ├── items.json
│   │       │       ├── requirements.txt
│   │       │       └── test_api.py_tmpl
│   │       ├── .gitignore_tmpl
│   │       ├── README.md
│   │       ├── api.raml_tmpl
│   │       ├── items.json
│   │       ├── local.ini_tmpl
│   │       ├── requirements.txt
│   │       └── setup.py_tmpl
│   ├── scripts/
│   │   ├── __init__.py
│   │   └── scaffold_test.py
│   ├── utils.py
│   └── views.py
├── requirements.dev
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── fixtures.py
│   ├── test_acl.py
│   ├── test_auth.py
│   ├── test_generators.py
│   ├── test_models.py
│   ├── test_registry.py
│   ├── test_utils.py
│   └── test_views.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
venv
.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/


================================================
FILE: .travis.yml
================================================
# Config file for automatic testing at travis-ci.org
language: python
env:
  - TOXENV=py27
  - TOXENV=py33
  - TOXENV=py34
  - TOXENV=py35
python: 3.5
install:
  - pip install tox
script: tox
services:
  - elasticsearch
  - mongodb
before_script:
  - travis_retry curl -XDELETE 'http://localhost:9200/ramses_starter/'
  - mongo ramses_starter --eval 'db.dropDatabase();'


================================================
FILE: CONTRIBUTING.md
================================================
## Team members

In alphabetical order:

* [Artem Kostiuk](https://github.com/postatum)
* [Chris Hart](https://github.com/chrstphrhrt)
* [Jonathan Stoikovitch](https://github.com/jstoiko)

## Pull-requests

Pull-requests are welcomed!

## Testing

1. Install dev requirements by running `pip install -r requirements.dev`
2. Run tests using `py.test --cov ramses tests`


================================================
FILE: LICENSE
================================================
Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.



================================================
FILE: MANIFEST.in
================================================
include README.md
include VERSION
recursive-include ramses/scaffolds *

================================================
FILE: README.md
================================================
# `Ramses`
[![Build Status](https://travis-ci.org/ramses-tech/ramses.svg?branch=master)](https://travis-ci.org/ramses-tech/ramses)
[![Documentation](https://readthedocs.org/projects/ramses/badge/?version=stable)](http://ramses.readthedocs.org)
[![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)

Ramses 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™ -powered views.

Looking to get started quickly? You can take a look at the ["Getting Started" guide](https://ramses.readthedocs.org/en/stable/getting_started.html).


================================================
FILE: VERSION
================================================
0.5.3

================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = build

# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(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/)
endif

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source

.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext

help:
	@echo "Please use \`make <target>' where <target> is one of"
	@echo "  html       to make standalone HTML files"
	@echo "  dirhtml    to make HTML files named index.html in directories"
	@echo "  singlehtml to make a single large HTML file"
	@echo "  pickle     to make pickle files"
	@echo "  json       to make JSON files"
	@echo "  htmlhelp   to make HTML files and a HTML help project"
	@echo "  qthelp     to make HTML files and a qthelp project"
	@echo "  applehelp  to make an Apple Help Book"
	@echo "  devhelp    to make HTML files and a Devhelp project"
	@echo "  epub       to make an epub"
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
	@echo "  text       to make text files"
	@echo "  man        to make manual pages"
	@echo "  texinfo    to make Texinfo files"
	@echo "  info       to make Texinfo files and run them through makeinfo"
	@echo "  gettext    to make PO message catalogs"
	@echo "  changes    to make an overview of all changed/added/deprecated items"
	@echo "  xml        to make Docutils-native XML files"
	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
	@echo "  linkcheck  to check all external links for integrity"
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
	@echo "  coverage   to run coverage check of the documentation (if enabled)"

clean:
	rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

singlehtml:
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
	@echo
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Nefertari.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Nefertari.qhc"

applehelp:
	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
	@echo
	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
	@echo "N.B. You won't be able to view it unless you put it in" \
	      "~/Library/Documentation/Help or install it in your application" \
	      "bundle."

devhelp:
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
	@echo
	@echo "Build finished."
	@echo "To view the help file:"
	@echo "# mkdir -p $$HOME/.local/share/devhelp/Nefertari"
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Nefertari"
	@echo "# devhelp"

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
	@echo
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
	      "(use \`make latexpdf' here to do that automatically)."

latexpdf:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through pdflatex..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

latexpdfja:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through platex and dvipdfmx..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

text:
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
	@echo
	@echo "Build finished. The text files are in $(BUILDDIR)/text."

man:
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
	@echo
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

texinfo:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo
	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
	@echo "Run \`make' in that directory to run these through makeinfo" \
	      "(use \`make info' here to do that automatically)."

info:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo "Running Texinfo files through makeinfo..."
	make -C $(BUILDDIR)/texinfo info
	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."

gettext:
	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
	@echo
	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."

coverage:
	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
	@echo "Testing of coverage in the sources finished, look at the " \
	      "results in $(BUILDDIR)/coverage/python.txt."

xml:
	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
	@echo
	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."

pseudoxml:
	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
	@echo
	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."


================================================
FILE: docs/source/changelog.rst
================================================
Changelog
=========

* :release:`0.5.3 <2016-05-17>`
* :bug:`107` Fixed issue with hyphens in resource paths

* :release:`0.5.2 <2016-05-17>`
* :support:`99 backported` Use ACL mixin from nefertari-guards (if enabled)
* :support:`- backported` Scaffold defaults to Pyramid 1.6.1

* :release:`0.5.1 <2015-11-18>`
* :bug:`88` Reworked the creation of related/auth_model models, order does not matter anymore

* :release:`0.5.0 <2015-10-07>`
* :bug:`- major` Fixed a bug using 'required' '_db_settings' property on 'relationship' field
* :support:`-` Added support for `'nefertari-guards' <https://nefertari-guards.readthedocs.org/>`_
* :support:`-` Added support for Nefertari '_hidden_fields'
* :support:`-` Added support for Nefertari event handlers
* :support:`-` Simplified field processors, '_before_processors' is now called '_processors', removed '_after_processors'
* :support:`-` ACL permission names in RAML now match real permission names instead of http methods
* :support:`-` Added support for the property '_nesting_depth' in schemas

* :release:`0.4.1 <2015-09-02>`
* :bug:`-` Simplified ACLs (refactoring)

* :release:`0.4.0 <2015-08-19>`
* :support:`-` Added support for JSON schema draft 04
* :support:`-` RAML is now parsed using ramlfications instead of pyraml-parser
* :feature:`-` Boolean values in RAML don't have to be strings anymore (previous limitation of pyraml-parser)
* :feature:`-` Renamed setting 'ramses.auth' to 'auth'
* :feature:`-` Renamed setting 'debug' to 'enable_get_tunneling'
* :feature:`-` Field name and request object are now passed to field processors under 'field' and 'request' kwargs respectively
* :feature:`-` Added support for relationship processors and backref relationship processors ('backref_after_validation'/'backref_before_validation')
* :feature:`-` Renamed schema's 'args' property to '_db_settings'
* :feature:`-` Properties 'type' and 'required' are now under '_db_settings'
* :feature:`-` Prefixed all Ramses schema properties by an underscore: '_auth_fields', '_public_fields', '_nested_relationships', '_auth_model', '_db_settings'
* :feature:`-` Error response bodies are now returned as JSON
* :bug:`- major` Fixed processors not applied on fields of type 'list' and type 'dict'
* :bug:`- major` Fixed a limitation preventing collection names to use nouns that do not have plural forms

* :release:`0.3.1 <2015-07-07>`
* :support:`- backported` Added support for callables in 'default' field argument
* :support:`- backported` Added support for 'onupdate' field argument

* :release:`0.3.0 <2015-06-14>`
* :support:`-` Added python3 support

* :release:`0.2.3 <2015-06-05>`
* :bug:`-` Forward compatibility with nefertari releases

* :release:`0.2.2 <2015-06-03>`
* :bug:`-` Fixed password minimum length support by adding before and after validation processors
* :bug:`-` Fixed race condition in Elasticsearch indexing

* :release:`0.2.1 <2015-05-27>`
* :bug:`-` Fixed limiting fields to be searched
* :bug:`-` Fixed login issue
* :bug:`-` Fixed custom processors

* :release:`0.2.0 <2015-05-18>`
* :feature:`-` Added support for securitySchemes, authentication (Pyramid 'auth ticket') and ACLs
* :support:`-` Added several display options to schemas
* :support:`-` Added unit tests
* :support:`-` Improved docs
* :feature:`-` Add support for processors in schema definition
* :feature:`-` Add support for custom auth model
* :support:`-` ES views now read from ES on update/delete_many

* :release:`0.1.1 <2015-04-21>`
* :bug:`-` Ramses could not be used in an existing Pyramid project

* :release:`0.1.0 <2015-04-08>`
* :support:`-` Initial release!


================================================
FILE: docs/source/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Nefertari documentation build configuration file, created by
# sphinx-quickstart on Fri Mar 27 11:16:31 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys
import os
import shlex

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    # 'sphinxcontrib.fulltoc',
    'releases'
]

releases_github_path = 'ramses-tech/ramses'
releases_debug = True

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Ramses'
copyright = u'Ramses Tech'
author = u'Ramses Tech, Inc.'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# version = '0.1'
# The full version, including alpha/beta/rc tags.
# release = '0.1.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False


# -- Options for HTML output ----------------------------------------------

# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

if not on_rtd:  # only import and set the theme if we're building docs locally
    import sphinx_rtd_theme
    html_theme = 'sphinx_rtd_theme'
    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# otherwise, readthedocs.org uses their theme by default, so no need to specify it

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
# html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}

# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'

# Output file base name for HTML help builder.
htmlhelp_basename = 'Ramsesdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',

# Latex figure (float) alignment
#'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  (master_doc, 'Ramses.tex', u'Ramses Documentation',
   author, 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'ramses', u'Ramses Documentation',
     [author], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  (master_doc, 'Ramses', u'Ramses Documentation',
   author, 'Ramses', 'API generator for Pyramid using RAML',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False


================================================
FILE: docs/source/event_handlers.rst
================================================
Event Handlers
==============

Ramses 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.


Setup
-----


Writing Event Handlers
^^^^^^^^^^^^^^^^^^^^^^

You 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.

Example:

.. code-block:: python


    import logging
    from ramses import registry


    log = logging.getLogger('foo')

    @registry.add
    def log_changed_fields(event):
        changed = ['{}: {}'.format(name, field.new_value)
                   for name, field in event.fields.items()]
        logger.debug('Changed fields: ' + ', '.join(changed))


Connecting Event Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^

When 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:

**type**
    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>`_.

**action**
    Exact name of Nefertari view method that processes the request (action) and special names for authentication actions.

Complete list of actions:
    * **index** - Collection GET
    * **create** - Collection POST
    * **update_many** - Collection PATCH/PUT
    * **delete_many** - Collection DELETE
    * **collection_options** - Collection OPTIONS
    * **show** - Item GET
    * **update** - Item PATCH
    * **replace** - Item PUT
    * **delete** - Item DELETE
    * **item_options** - Item OPTIONS
    * **login** - User login (POST /auth/login)
    * **logout** - User logout (POST /auth/logout)
    * **register** - User register (POST /auth/register)
    * **set** - triggers on all the following actions: **create**, **update**, **replace**, **update_many** and **register**.


Example
-------

We will use the following handler to demonstrate how to connect handlers to events. This handler logs ``request`` to the console.

.. code-block:: python

    import logging
    from ramses import registry


    log = logging.getLogger('foo')

    @registry.add
    def log_request(event):
        log.debug(event.view.request)


Assuming 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:

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "_event_handlers": {
            "after_index": ["log_request"]
        },
        ...
    }


Other Things You Can Do
-----------------------

You can update another field's value, for example, increment a counter:

.. code-block:: python

    from ramses import registry


    @registry.add
    def increment_count(event):
        instance = event.instance or event.response
        counter = instance.counter
        incremented = counter + 1
        event.set_field_value('counter', incremented)


You can update other collections (or filtered collections), for example, mark sub-tasks as completed whenever a task is completed:

.. code-block:: python

    from ramses import registry
    from nefertari import engine

    @registry.add
    def mark_subtasks_completed(event):
        if 'task' not in event.fields:
            return

        completed = event.fields['task'].new_value
        instance = event.instance or event.response

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            subtasks = subtask_model.get_collection(task_id=instance.id)
            subtask_model._update_many(subtasks, {'completed': True})


You can perform more complex queries using Elasticsearch:

.. code-block:: python

    from ramses import registry
    from nefertari import engine
    from nefertari.elasticsearch import ES


    @registry.add
    def mark_subtasks_after_2015_completed(event):
        if 'task' not in event.fields:
            return

        completed = event.fields['task'].new_value
        instance = event.instance or event.response

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            es_query = 'task_id:{} AND created_at:[2015 TO *]'.format(instance.id)
            subtasks_es = ES(subtask_model.__name__).get_collection(_raw_terms=es_query)
            subtasks_db = subtask_model.filter_objects(subtasks_es)
            subtask_model._update_many(subtasks_db, {'completed': True})


================================================
FILE: docs/source/field_processors.rst
================================================
Field processors
================

Ramses 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.


Setup
-----

To 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``.

.. code-block:: json

    "username": {
        ...
        "_processors": ["lowercase"]
    },
    ...


You 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.


Example
-------

If we had following processors defined:

.. code-block:: python

    from .my_helpers import get_stories_by_ids


    @registry.add
    def lowercase(**kwargs):
        """ Make :new_value: lowercase """
        return (kwargs['new_value'] or '').lower()

    @registry.add
    def validate_stories_exist(**kwargs):
        """ Make sure added stories exist. """
        story_ids = kwargs['new_value']
        if story_ids:
            # Get stories by ids
            stories = get_stories_by_ids(story_ids)
            if not stories or len(stories) < len(story_ids):
                raise Exception("Some of provided stories do not exist")
        return story_ids


.. code-block:: json

    # User model json
    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "properties": {
            "stories": {
                "_db_settings": {
                    "type": "relationship",
                    "document": "Story",
                    "backref_name": "owner"
                },
                "_processors": ["validate_stories_exist"],
                "_backref_processors": ["lowercase"]
            },
            ...
        }
    }

Notes:
    * ``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.
    * ``lowercase`` processor will be run when request changes ``Story.owner`` field. The processor will lowercase new value of the ``Story.owner`` field.


================================================
FILE: docs/source/fields.rst
================================================
Fields
======

Types
-----

You can set a field's type by setting the ``type`` property under ``_db_settings``.

.. code-block:: json

    "created_at": {
        (...)
        "_db_settings": {
            "type": "datetime"
        }
    }

This is a list of all available types:

* biginteger
* binary
* boolean
* choice
* date
* datetime
* decimal
* dict
* float
* foreign_key
* id_field
* integer
* interval
* list
* pickle
* relationship
* smallinteger
* string
* text
* time
* unicode
* unicodetext


Required Fields
---------------

You can set a field as required by setting the ``required`` property under ``_db_settings``.

.. code-block:: json

    "password": {
        (...)
        "_db_settings": {
            (...)
            "required": true
        }
    }


Primary Key
-----------

You can use an ``id_field`` in lieu of primary key.

.. code-block:: json

    "id": {
        (...)
        "_db_settings": {
            (...)
            "primary_key": true
        }
    }

You 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``

.. code-block:: json

    "username": {
        (...)
        "_db_settings": {
            (...)
            "primary_key": true
        }
    }

Constraints
-----------

You 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.

.. code-block:: json

    "field": {
        (...)
        "_db_settings": {
            (...)
            "unique": true,
            "min_length": 5,
            "max_length": 50
        }
    }


Default Value
-------------

You can set a default value for you field by setting the ``default`` property under ``_db_settings``.

.. code-block:: json

    "field": {
        (...)
        "_db_settings": {
            (...)
            "default": "default value"
        }
    },

The ``default`` value can also be set to a Python callable, e.g.

.. code-block:: json

    "datetime_field": {
        (...)
        "_db_settings": {
            (...)
            "default": "{{datetime.datetime.utcnow}}"
        }
    },


Update Default Value
--------------------

You 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.

.. code-block:: json

    "datetime_field": {
        (...)
        "_db_settings": {
            (...)
            "onupdate": "{{datetime.datetime.utcnow}}"
        }
    },


List Fields
-----------

You can list the accepted values of any ``list`` or ``choice`` fields by setting the ``choices`` property under ``_db_settings``.

.. code-block:: json

    "field": {
        (...)
        "_db_settings": {
            "type": "choice",
            "choices": ["choice1", "choice2", "choice3"],
            "default": "choice1"
        }
    }

You can also provide the list/choice items' ``item_type``.

.. code-block:: json

    "field": {
        (...)
        "_db_settings": {
            "type": "list",
            "item_type": "string"
        }
    }

Other ``_db_settings``
----------------------

Note that you can pass any engine-specific arguments to your fields by defining such arguments in ``_db_settings``.


================================================
FILE: docs/source/getting_started.rst
================================================
Getting started
===============

1. Create your project in a virtualenv directory (see the `virtualenv documentation <https://virtualenv.pypa.io>`_)

.. code-block:: shell

    $ virtualenv my_project
    $ source my_project/bin/activate
    $ pip install ramses
    $ pcreate -s ramses_starter my_project
    $ cd my_project
    $ pserve local.ini

2. Tada! Start editing api.raml to modify the API and items.json for the schema.


Requirements
------------

* Python 2.7, 3.3 or 3.4
* Elasticsearch (data is automatically indexed for near real-time search)
* Postgres or Mongodb or Your Data Store™


Examples
--------

- 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>`_.
- 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.


Tutorials
---------

- `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/>`_
- `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>`_


================================================
FILE: docs/source/index.rst
================================================
Ramses
======

Ramses 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.

Website:
    `<http://ramses.tech>`_
Source code:
    `<http://github.com/ramses-tech/ramses>`_


Table of Contents
=================

.. toctree::
   :maxdepth: 2

   getting_started
   raml
   schemas
   fields
   event_handlers
   field_processors
   relationships
   changelog

.. image:: ramses.jpg

Image credit: Wikipedia


================================================
FILE: docs/source/raml.rst
================================================
RAML Configuration
==================

You can read the full RAML specs `here <http://raml.org/spec.html>`_.


Authentication
--------------

In order to enable authentication, add the ``auth`` parameter to your .ini file:

.. code-block:: ini

    auth = true

In 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.

.. code-block:: yaml

    securitySchemes:
        - x_ticket_auth:
            description: Standard Pyramid Auth Ticket policy
            type: x-Ticket
            settings:
                secret: auth_tkt_secret
                hashalg: sha512
                cookie_name: ramses_auth_tkt
                http_only: 'true'
    securedBy: [x_ticket_auth]

A few convenience routes will be automatically added:

* POST ``/auth/register``: register a new user
* POST ``/auth/login``: login an existing user
* GET ``/auth/logout``: logout currently logged-in user
* GET ``/users/self``: returns currently logged-in user


ACLs
----

In your ``securitySchemes``, you can add as many ACLs as you need. Then you can reference these ACLs in your resource's ``securedBy``.

.. code-block:: yaml

    securitySchemes:
        (...)
        - read_only_users:
            description: ACL that allows authenticated users to read
            type: x-ACL
            settings:
                collection: |
                    allow admin all
                    allow authenticated view
                item: |
                    allow admin all
                    allow authenticated view
    (...)
    /items:
        securedBy: [read_only_users]


Enabling HTTP Methods
---------------------

Listing an HTTP method in your resource definition is all it takes to enable such method.

.. code-block:: yaml

    /items:
        (...)
        post:
            description: Create an item
        get:
            description: Get multiple items
        patch:
            description: Update multiple items
        delete:
            description: delete multiple items

        /{id}:
            displayName: One item
            get:
                description: Get a particular item
            delete:
                description: Delete a particular item
            patch:
                description: Update a particular item


You can link your schema definition for each resource by adding it to the ``post`` section.

.. code-block:: yaml

    /items:
        (...)
        post:
            (...)
            body:
                application/json:
                    schema: !include schemas/items.json




================================================
FILE: docs/source/relationships.rst
================================================
Relationships
=============


Basics
------

Relationships in Ramses are used to represent One-To-Many(o2m) and One-To-One(o2o) relationships between objects in database.

To 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.


For this tutorial we are going to use the example of users and
stories. In this example we have a OneToMany relationship betweed ``User``
and ``Story``. One user may have many stories but each story has only one
owner.  Check the end of the tutorial for the complete example RAML
file and schemas.

Example code is the very minimum needed to explain the subject. We will be referring to the examples along all the tutorial.


Field "type": "relationship"
----------------------------

Must be defined on the *One* side of OneToOne or OneToMany
relationship (``User`` in our example). Relationships are created as
OneToMany by default.

Example of using ``relationship`` field (defined on ``User`` model in our example):

.. code-block:: json

    "stories": {
        "_db_settings": {
            "type": "relationship",
            "document": "Story",
            "backref_name": "owner"
        }
    }

**Required params:**

*type*
    String. Just ``relationship``.

*document*
    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``.

*backref_name*
    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.**


Field "type": "foreign_key"
---------------------------

This represents a Foreign Key constraint in SQL and is only required
when using ``nefertari_sqla`` engine. It is used in conjunction with
the relationship field, but is used on the model that ``relationship``
refers to. For example, if the ``User`` model contained the
``relationship`` field, than the ``Story`` model would need a
``foreign_key`` field.

**Notes:**

    * This field is not required and is ignored when using nefertari_mongodb engine.
    * Name of the ``foreign_key`` field does not depend on relationship params in any way.
    * This field **MUST NOT** be used to change relationships. This field only exists because it is required by SQLAlchemy.


Example of using ``foreign_key`` field (defined on ``Story`` model in our example):

.. code-block:: json

    "owner_id": {
        "_db_settings": {
            "type": "foreign_key",
            "ref_document": "User",
            "ref_column": "user.username",
            "ref_column_type": "string"
        }
    }

**Required params:**

*type*
    String. Just ``foreign_key``.

*ref_document*
    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``.

*ref_column*
    String. Dotted name/path to ``ref_document`` model's primary key
    column. ``ref_column`` is the lowercased name of model we refer to in
    ``ref_document`` joined by a dot with the exact name of its primary key column. In our example this is ``"user.username"``.

**ref_column_type**
    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"``.


One to One relationship
-----------------------

To 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.

E.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``:

.. code-block:: json

    "user_id": {
        "_db_settings": {
            "type": "foreign_key",
            "ref_document": "User",
            "ref_column": "user.username",
            "ref_column_type": "string"
        }
    }

and ``relationship`` field with ``"uselist": false`` on ``User``:

.. code-block:: json

    "profile": {
        "_db_settings": {
            "type": "relationship",
            "document": "Profile",
            "backref_name": "user",
            "uselist": false
        }
    }


This 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``.


Multiple relationships
----------------------

**Note: This part is only valid(required) for nefertari_sqla engine, as nefertari_mongodb engine does not use foreign_key fields.**

If we were to define multiple relationships from model A to model B,
each relationship must have a corresponding ``foreign_key``
defined. Also you must use a ``foreign_keys`` parameter on each
``relationship`` field to specify which ``foreign_key`` each
``relationship`` uses.

E.g. if we were to add new relationship field ``User.assigned_stories``, relationship fields on ``User`` would have to be defined like this:

.. code-block:: json

    "stories": {
        "_db_settings": {
            "type": "relationship",
            "document": "Story",
            "backref_name": "owner",
            "foreign_keys": "Story.owner_id"
        }
    },
    "assigned_stories": {
        "_db_settings": {
            "type": "relationship",
            "document": "Story",
            "backref_name": "assignee",
            "foreign_keys": "Story.assignee_id"
        }
    }

And fields on ``Story`` like so:

.. code-block:: json

    "owner_id": {
        "_db_settings": {
            "type": "foreign_key",
            "ref_document": "User",
            "ref_column": "user.username",
            "ref_column_type": "string"
        }
    },
    "assignee_id": {
        "_db_settings": {
            "type": "foreign_key",
            "ref_document": "User",
            "ref_column": "user.username",
            "ref_column_type": "string"
        }
    }


Complete example
----------------

**example.raml**

.. code-block:: yaml

    #%RAML 0.8
    ---
    title: Example REST API
    documentation:
        - title: Home
          content: |
            Welcome to the example API.
    baseUri: http://{host}:{port}/{version}
    version: v1

    /stories:
        displayName: All stories
        get:
            description: Get all stories
        post:
            description: Create a new story
            body:
                application/json:
                    schema: !include story.json
        /{id}:
            displayName: One story
            get:
                description: Get a particular story

    /users:
        displayName: All users
        get:
            description: Get all users
        post:
            description: Create a new user
            body:
                application/json:
                    schema: !include user.json
        /{username}:
            displayName: One user
            get:
                description: Get a particular user


**user.json**

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "required": ["username"],
        "properties": {
            "username": {
                "_db_settings": {
                    "type": "string",
                    "primary_key": true
                }
            },
            "stories": {
                "_db_settings": {
                    "type": "relationship",
                    "document": "Story",
                    "backref_name": "owner"
                }
            }
        }
    }


**story.json**

.. code-block:: json

    {
        "type": "object",
        "title": "Story schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "properties": {
            "id": {
                "_db_settings": {
                    "type": "id_field",
                    "primary_key": true
                }
            },
            "owner_id": {
                "_db_settings": {
                    "type": "foreign_key",
                    "ref_document": "User",
                    "ref_column": "user.username",
                    "ref_column_type": "string"
                }
            }
        }
    }


================================================
FILE: docs/source/schemas.rst
================================================
Defining Schemas
================

JSON Schema
-----------

Ramses supports JSON Schema Draft 3 and Draft 4. You can read the official `JSON Schema documentation here <http://json-schema.org/documentation.html>`_.

.. code-block:: json

    {
        "type": "object",
        "title": "Item schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        (...)
    }

All Ramses-specific properties are prefixed with an underscore.

Showing Fields
--------------

If 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``.

.. code-block:: json

    {
        (...)
        "_auth_fields": ["id", "name", "description"],
        "_public_fields": ["name"],
        "_hidden_fields": ["token"],
        (...)
    }

Nested Documents
----------------

If 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.

.. code-block:: json

    {
        (...)
        "_nested_relationships": ["relationship_field_name"],
        "_nesting_depth": 2
        (...)
    }

Custom "user" Model
-------------------

When 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.

.. code-block:: json

    {
        (...)
        "_auth_model": true,
        (...)
    }


================================================
FILE: ramses/__init__.py
================================================
import logging

import ramlfications
from nefertari.acl import RootACL as NefertariRootACL
from nefertari.utils import dictset


log = logging.getLogger(__name__)


def includeme(config):
    from .generators import generate_server, generate_models
    Settings = dictset(config.registry.settings)
    config.include('nefertari.engine')

    config.registry.database_acls = Settings.asbool('database_acls')
    if config.registry.database_acls:
        config.include('nefertari_guards')

    config.include('nefertari')
    config.include('nefertari.view')
    config.include('nefertari.json_httpexceptions')

    # Process nefertari settings
    if Settings.asbool('enable_get_tunneling'):
        config.add_tween('nefertari.tweens.get_tunneling')

    if Settings.asbool('cors.enable'):
        config.add_tween('nefertari.tweens.cors')

    if Settings.asbool('ssl_middleware.enable'):
        config.add_tween('nefertari.tweens.ssl')

    if Settings.asbool('request_timing.enable'):
        config.add_tween('nefertari.tweens.request_timing')

    # Set root factory
    config.root_factory = NefertariRootACL

    # Process auth settings
    root = config.get_root_resource()
    root_auth = getattr(root, 'auth', False)

    log.info('Parsing RAML')
    raml_root = ramlfications.parse(Settings['ramses.raml_schema'])

    log.info('Starting models generation')
    generate_models(config, raml_resources=raml_root.resources)

    if root_auth:
        from .auth import setup_auth_policies, get_authuser_model
        if getattr(config.registry, 'auth_model', None) is None:
            config.registry.auth_model = get_authuser_model()
        setup_auth_policies(config, raml_root)

    config.include('nefertari.elasticsearch')

    log.info('Starting server generation')
    generate_server(raml_root, config)

    log.info('Running nefertari.engine.setup_database')
    from nefertari.engine import setup_database
    setup_database(config)

    from nefertari.elasticsearch import ES
    ES.setup_mappings()

    if root_auth:
        config.include('ramses.auth')

    log.info('Server succesfully generated\n')


================================================
FILE: ramses/acl.py
================================================
import logging

import six
from pyramid.security import (
    Allow, Deny,
    Everyone, Authenticated,
    ALL_PERMISSIONS)
from nefertari.acl import CollectionACL
from nefertari.resource import PERMISSIONS
from nefertari.elasticsearch import ES

from .utils import resolve_to_callable, is_callable_tag


log = logging.getLogger(__name__)


actions = {
    'allow': Allow,
    'deny': Deny,
}
special_principals = {
    'everyone': Everyone,
    'authenticated': Authenticated,
}
ALLOW_ALL = (Allow, Everyone, ALL_PERMISSIONS)


def validate_permissions(perms):
    """ Validate :perms: contains valid permissions.

    :param perms: List of permission names or ALL_PERMISSIONS.
    """
    if not isinstance(perms, (list, tuple)):
        perms = [perms]
    valid_perms = set(PERMISSIONS.values())
    if ALL_PERMISSIONS in perms:
        return perms
    if set(perms) - valid_perms:
        raise ValueError(
            'Invalid ACL permission names. Valid permissions '
            'are: {}'.format(', '.join(valid_perms)))
    return perms


def parse_permissions(perms):
    """ Parse permissions ("perms") which are either exact permission
    names or the keyword 'all'.

    :param perms: List or comma-separated string of nefertari permission
        names, or 'all'
    """
    if isinstance(perms, six.string_types):
        perms = perms.split(',')
    perms = [perm.strip().lower() for perm in perms]
    if 'all' in perms:
        return ALL_PERMISSIONS
    return validate_permissions(perms)


def parse_acl(acl_string):
    """ Parse raw string :acl_string: of RAML-defined ACLs.

    If :acl_string: is blank or None, all permissions are given.
    Values of ACL action and principal are parsed using `actions` and
    `special_principals` maps and are looked up after `strip()` and
    `lower()`.

    ACEs in :acl_string: may be separated by newlines or semicolons.
    Action, principal and permission lists must be separated by spaces.
    Permissions must be comma-separated.
    E.g. 'allow everyone view,create,update' and 'deny authenticated delete'

    :param acl_string: Raw RAML string containing defined ACEs.
    """
    if not acl_string:
        return [ALLOW_ALL]

    aces_list = acl_string.replace('\n', ';').split(';')
    aces_list = [ace.strip().split(' ', 2) for ace in aces_list if ace]
    aces_list = [(a, b, c.split(',')) for a, b, c in aces_list]
    result_acl = []

    for action_str, princ_str, perms in aces_list:
        # Process action
        action_str = action_str.strip().lower()
        action = actions.get(action_str)
        if action is None:
            raise ValueError(
                'Unknown ACL action: {}. Valid actions: {}'.format(
                    action_str, list(actions.keys())))

        # Process principal
        princ_str = princ_str.strip().lower()
        if princ_str in special_principals:
            principal = special_principals[princ_str]
        elif is_callable_tag(princ_str):
            principal = resolve_to_callable(princ_str)
        else:
            principal = princ_str

        # Process permissions
        permissions = parse_permissions(perms)

        result_acl.append((action, principal, permissions))

    return result_acl


class BaseACL(CollectionACL):
    """ ACL Base class. """

    es_based = False
    _collection_acl = (ALLOW_ALL, )
    _item_acl = (ALLOW_ALL, )

    def _apply_callables(self, acl, obj=None):
        """ Iterate over ACEs from :acl: and apply callable principals
        if any.

        Principals are passed 3 arguments on call:
            :ace: Single ACE object that looks like (action, callable,
                permission or [permission])
            :request: Current request object
            :obj: Object instance to be accessed via the ACL
        Principals must return a single ACE or a list of ACEs.

        :param acl: Sequence of valid Pyramid ACEs which will be processed
        :param obj: Object to be accessed via the ACL
        """
        new_acl = []
        for i, ace in enumerate(acl):
            principal = ace[1]
            if six.callable(principal):
                ace = principal(ace=ace, request=self.request, obj=obj)
                if not ace:
                    continue
                if not isinstance(ace[0], (list, tuple)):
                    ace = [ace]
                ace = [(a, b, validate_permissions(c)) for a, b, c in ace]
            else:
                ace = [ace]
            new_acl += ace
        return tuple(new_acl)

    def __acl__(self):
        """ Apply callables to `self._collection_acl` and return result. """
        return self._apply_callables(acl=self._collection_acl)

    def generate_item_acl(self, item):
        acl = self._apply_callables(
            acl=self._item_acl,
            obj=item)
        if acl is None:
            acl = self.__acl__()
        return acl

    def item_acl(self, item):
        """ Apply callables to `self._item_acl` and return result. """
        return self.generate_item_acl(item)

    def item_db_id(self, key):
        # ``self`` can be used for current authenticated user key
        if key != 'self':
            return key
        user = getattr(self.request, 'user', None)
        if user is None or not isinstance(user, self.item_model):
            return key
        return getattr(user, user.pk_field())

    def __getitem__(self, key):
        """ Get item using method depending on value of `self.es_based` """
        if not self.es_based:
            return super(BaseACL, self).__getitem__(key)
        return self.getitem_es(self.item_db_id(key))

    def getitem_es(self, key):
        es = ES(self.item_model.__name__)
        obj = es.get_item(id=key)
        obj.__acl__ = self.item_acl(obj)
        obj.__parent__ = self
        obj.__name__ = key
        return obj


class DatabaseACLMixin(object):
    """ Mixin to be used when ACLs are stored in database. """

    def item_acl(self, item):
        """ Objectify ACL if ES is used or call item.get_acl() if
        db is used.
        """
        if self.es_based:
            from nefertari_guards.elasticsearch import get_es_item_acl
            return get_es_item_acl(item)
        return super(DatabaseACLMixin, self).item_acl(item)

    def getitem_es(self, key):
        """ Override to support ACL filtering.

        To do so: passes `self.request` to `get_item` and uses
        `ACLFilterES`.
        """
        from nefertari_guards.elasticsearch import ACLFilterES
        es = ACLFilterES(self.item_model.__name__)
        params = {
            'id': key,
            'request': self.request,
        }
        obj = es.get_item(**params)
        obj.__acl__ = self.item_acl(obj)
        obj.__parent__ = self
        obj.__name__ = key
        return obj


def generate_acl(config, model_cls, raml_resource, es_based=True):
    """ Generate an ACL.

    Generated ACL class has a `item_model` attribute set to
    :model_cls:.

    ACLs used for collection and item access control are generated from a
    first security scheme with type `x-ACL`.
    If :raml_resource: has no x-ACL security schemes defined then ALLOW_ALL
    ACL is used.
    If the `collection` or `item` settings are empty, then ALLOW_ALL ACL
    is used.

    :param model_cls: Generated model class
    :param raml_resource: Instance of ramlfications.raml.ResourceNode
        for which ACL is being generated
    :param es_based: Boolean inidicating whether ACL should query ES or
        not when getting an object
    """
    schemes = raml_resource.security_schemes or []
    schemes = [sch for sch in schemes if sch.type == 'x-ACL']

    if not schemes:
        collection_acl = item_acl = []
        log.debug('No ACL scheme applied. Using ACL: {}'.format(item_acl))
    else:
        sec_scheme = schemes[0]
        log.debug('{} ACL scheme applied'.format(sec_scheme.name))
        settings = sec_scheme.settings or {}
        collection_acl = parse_acl(acl_string=settings.get('collection'))
        item_acl = parse_acl(acl_string=settings.get('item'))

    class GeneratedACLBase(object):
        item_model = model_cls

        def __init__(self, request, es_based=es_based):
            super(GeneratedACLBase, self).__init__(request=request)
            self.es_based = es_based
            self._collection_acl = collection_acl
            self._item_acl = item_acl

    bases = [GeneratedACLBase]
    if config.registry.database_acls:
        from nefertari_guards.acl import DatabaseACLMixin as GuardsMixin
        bases += [DatabaseACLMixin, GuardsMixin]
    bases.append(BaseACL)

    return type('GeneratedACL', tuple(bases), {})


================================================
FILE: ramses/auth.py
================================================
"""
Auth module that contains all code needed for authentication/authorization
policies setup.

In particular:
    :includeme: Function that actually creates routes listed above and
        connects view to them
    :create_system_user: Function that creates system/admin user
    :_setup_ticket_policy: Setup Pyramid AuthTktAuthenticationPolicy
    :_setup_apikey_policy: Setup nefertari.ApiKeyAuthenticationPolicy
    :setup_auth_policies: Runs generation of particular auth policy
"""
import logging

import transaction
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.security import Allow, ALL_PERMISSIONS
import cryptacular.bcrypt

from nefertari.utils import dictset
from nefertari.json_httpexceptions import *
from nefertari.authentication.policies import ApiKeyAuthenticationPolicy

log = logging.getLogger(__name__)


class ACLAssignRegisterMixin(object):
    """ Mixin that sets ``User._acl`` field after user is registered. """
    def register(self, *args, **kwargs):
        response = super(ACLAssignRegisterMixin, self).register(
            *args, **kwargs)

        user = self.request._user
        mapping = self.request.registry._model_collections
        if not user._acl and self.Model.__name__ in mapping:
            from nefertari_guards import engine as guards_engine
            factory = mapping[self.Model.__name__].view._factory
            acl = factory(self.request).generate_item_acl(user)
            acl = guards_engine.ACLField.stringify_acl(acl)
            user.update({'_acl': acl})

        return response


def _setup_ticket_policy(config, params):
    """ Setup Pyramid AuthTktAuthenticationPolicy.

    Notes:
      * Initial `secret` params value is considered to be a name of config
        param that represents a cookie name.
      * `auth_model.get_groups_by_userid` is used as a `callback`.
      * Also connects basic routes to perform authentication actions.

    :param config: Pyramid Configurator instance.
    :param params: Nefertari dictset which contains security scheme
        `settings`.
    """
    from nefertari.authentication.views import (
        TicketAuthRegisterView, TicketAuthLoginView,
        TicketAuthLogoutView)

    log.info('Configuring Pyramid Ticket Authn policy')
    if 'secret' not in params:
        raise ValueError(
            'Missing required security scheme settings: secret')
    params['secret'] = config.registry.settings[params['secret']]

    auth_model = config.registry.auth_model
    params['callback'] = auth_model.get_groups_by_userid

    config.add_request_method(
        auth_model.get_authuser_by_userid, 'user', reify=True)

    policy = AuthTktAuthenticationPolicy(**params)

    RegisterViewBase = TicketAuthRegisterView
    if config.registry.database_acls:
        class RegisterViewBase(ACLAssignRegisterMixin,
                               TicketAuthRegisterView):
            pass

    class RamsesTicketAuthRegisterView(RegisterViewBase):
        Model = config.registry.auth_model

    class RamsesTicketAuthLoginView(TicketAuthLoginView):
        Model = config.registry.auth_model

    class RamsesTicketAuthLogoutView(TicketAuthLogoutView):
        Model = config.registry.auth_model

    common_kw = {
        'prefix': 'auth',
        'factory': 'nefertari.acl.AuthenticationACL',
    }

    root = config.get_root_resource()
    root.add('register', view=RamsesTicketAuthRegisterView, **common_kw)
    root.add('login', view=RamsesTicketAuthLoginView, **common_kw)
    root.add('logout', view=RamsesTicketAuthLogoutView, **common_kw)

    return policy


def _setup_apikey_policy(config, params):
    """ Setup `nefertari.ApiKeyAuthenticationPolicy`.

    Notes:
      * User may provide model name in :params['user_model']: do define
        the name of the user model.
      * `auth_model.get_groups_by_token` is used to perform username and
        token check
      * `auth_model.get_token_credentials` is used to get username and
        token from userid
      * Also connects basic routes to perform authentication actions.

    Arguments:
        :config: Pyramid Configurator instance.
        :params: Nefertari dictset which contains security scheme `settings`.
    """
    from nefertari.authentication.views import (
        TokenAuthRegisterView, TokenAuthClaimView,
        TokenAuthResetView)
    log.info('Configuring ApiKey Authn policy')

    auth_model = config.registry.auth_model
    params['check'] = auth_model.get_groups_by_token
    params['credentials_callback'] = auth_model.get_token_credentials
    params['user_model'] = auth_model
    config.add_request_method(
        auth_model.get_authuser_by_name, 'user', reify=True)

    policy = ApiKeyAuthenticationPolicy(**params)

    RegisterViewBase = TokenAuthRegisterView
    if config.registry.database_acls:
        class RegisterViewBase(ACLAssignRegisterMixin,
                               TokenAuthRegisterView):
            pass

    class RamsesTokenAuthRegisterView(RegisterViewBase):
        Model = auth_model

    class RamsesTokenAuthClaimView(TokenAuthClaimView):
        Model = auth_model

    class RamsesTokenAuthResetView(TokenAuthResetView):
        Model = auth_model

    common_kw = {
        'prefix': 'auth',
        'factory': 'nefertari.acl.AuthenticationACL',
    }

    root = config.get_root_resource()
    root.add('register', view=RamsesTokenAuthRegisterView, **common_kw)
    root.add('token', view=RamsesTokenAuthClaimView, **common_kw)
    root.add('reset_token', view=RamsesTokenAuthResetView, **common_kw)

    return policy


""" Map of `security_scheme_type`: `generator_function`, where:

  * `security_scheme_type`: String that represents RAML security scheme type
    name that should be used to apply a particular authentication system.
  * `generator_function`: Function that receives instance of Pyramid
    Configurator instance and dictset of security scheme settings and returns
    generated Pyramid authentication policy instance.

"""
AUTHENTICATION_POLICIES = {
    'x-ApiKey': _setup_apikey_policy,
    'x-Ticket': _setup_ticket_policy,
}


def setup_auth_policies(config, raml_root):
    """ Setup authentication, authorization policies.

    Performs basic validation to check all the required values are present
    and performs authentication, authorization policies generation using
    generator functions from `AUTHENTICATION_POLICIES`.

    :param config: Pyramid Configurator instance.
    :param raml_root: Instance of ramlfications.raml.RootNode.
    """
    log.info('Configuring auth policies')
    secured_by_all = raml_root.secured_by or []
    secured_by = [item for item in secured_by_all if item]
    if not secured_by:
        log.info('API is not secured. `secured_by` attribute '
                 'value missing.')
        return
    secured_by = secured_by[0]

    schemes = {scheme.name: scheme
               for scheme in raml_root.security_schemes}
    if secured_by not in schemes:
        raise ValueError(
            'Undefined security scheme used in `secured_by`: {}'.format(
                secured_by))

    scheme = schemes[secured_by]
    if scheme.type not in AUTHENTICATION_POLICIES:
        raise ValueError('Unsupported security scheme type: {}'.format(
            scheme.type))

    # Setup Authentication policy
    policy_generator = AUTHENTICATION_POLICIES[scheme.type]
    params = dictset(scheme.settings or {})
    authn_policy = policy_generator(config, params)
    config.set_authentication_policy(authn_policy)

    # Setup Authorization policy
    authz_policy = ACLAuthorizationPolicy()
    config.set_authorization_policy(authz_policy)


def create_system_user(config):
    log.info('Creating system user')
    crypt = cryptacular.bcrypt.BCRYPTPasswordManager()
    settings = config.registry.settings
    try:
        auth_model = config.registry.auth_model
        s_user = settings['system.user']
        s_pass = str(crypt.encode(settings['system.password']))
        s_email = settings['system.email']
        defaults = dict(
            password=s_pass,
            email=s_email,
            groups=['admin'],
        )
        if config.registry.database_acls:
            defaults['_acl'] = [(Allow, 'g:admin', ALL_PERMISSIONS)]

        user, created = auth_model.get_or_create(
            username=s_user, defaults=defaults)
        if created:
            transaction.commit()
    except KeyError as e:
        log.error('Failed to create system user. Missing config: %s' % e)


def get_authuser_model():
    """ Define and return AuthUser model using nefertari base classes """
    from nefertari.authentication.models import AuthUserMixin
    from nefertari import engine

    class AuthUser(AuthUserMixin, engine.BaseDocument):
        __tablename__ = 'ramses_authuser'

    return AuthUser


def includeme(config):
    create_system_user(config)


================================================
FILE: ramses/generators.py
================================================
import logging

from inflection import singularize

from .views import generate_rest_view
from .acl import generate_acl
from .utils import (
    is_dynamic_uri,
    resource_view_attrs,
    generate_model_name,
    dynamic_part_name,
    attr_subresource,
    singular_subresource,
    get_static_parent,
    get_route_name,
    get_resource_uri,
)


log = logging.getLogger(__name__)


def _get_nefertari_parent_resource(
        raml_resource, generated_resources, default):
    parent_raml_res = get_static_parent(raml_resource)
    if parent_raml_res is not None:
        parent_resource = generated_resources.get(parent_raml_res.path)
        return parent_resource or default
    return default


def generate_resource(config, raml_resource, parent_resource):
    """ Perform complete one resource configuration process

    This function generates: ACL, view, route, resource, database
    model for a given `raml_resource`. New nefertari resource is
    attached to `parent_resource` class which is an instance of
    `nefertari.resource.Resource`.

    Things to consider:
      * Top-level resources must be collection names.
      * No resources are explicitly created for dynamic (ending with '}')
        RAML resources as they are implicitly processed by parent collection
        resources.
      * Resource nesting must look like collection/id/collection/id/...
      * Only part of resource path after last '/' is taken into account,
        thus each level of resource nesting should add one more path
        element. E.g. /stories -> /stories/{id} and not
        /stories -> /stories/mystories/{id}. Latter route will be generated
        at /stories/{id}.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    :param parent_resource: Parent nefertari resource object.
    """
    from .models import get_existing_model

    # Don't generate resources for dynamic routes as they are already
    # generated by their parent
    resource_uri = get_resource_uri(raml_resource)
    if is_dynamic_uri(resource_uri):
        if parent_resource.is_root:
            raise Exception("Top-level resources can't be dynamic and must "
                            "represent collections instead")
        return

    route_name = get_route_name(resource_uri)
    log.info('Configuring resource: `{}`. Parent: `{}`'.format(
        route_name, parent_resource.uid or 'root'))

    # Get DB model. If this is an attribute or singular resource,
    # we don't need to get model
    is_singular = singular_subresource(raml_resource, route_name)
    is_attr_res = attr_subresource(raml_resource, route_name)
    if not parent_resource.is_root and (is_attr_res or is_singular):
        model_cls = parent_resource.view.Model
    else:
        model_name = generate_model_name(raml_resource)
        model_cls = get_existing_model(model_name)

    resource_kwargs = {}

    # Generate ACL
    log.info('Generating ACL for `{}`'.format(route_name))
    resource_kwargs['factory'] = generate_acl(
        config,
        model_cls=model_cls,
        raml_resource=raml_resource)

    # Generate dynamic part name
    if not is_singular:
        resource_kwargs['id_name'] = dynamic_part_name(
            raml_resource=raml_resource,
            route_name=route_name,
            pk_field=model_cls.pk_field())

    # Generate REST view
    log.info('Generating view for `{}`'.format(route_name))
    view_attrs = resource_view_attrs(raml_resource, is_singular)
    resource_kwargs['view'] = generate_rest_view(
        config,
        model_cls=model_cls,
        attrs=view_attrs,
        attr_view=is_attr_res,
        singular=is_singular,
    )

    # In case of singular resource, model still needs to be generated,
    # but we store it on a different view attribute
    if is_singular:
        model_name = generate_model_name(raml_resource)
        view_cls = resource_kwargs['view']
        view_cls._parent_model = view_cls.Model
        view_cls.Model = get_existing_model(model_name)

    # Create new nefertari resource
    log.info('Creating new resource for `{}`'.format(route_name))
    clean_uri = resource_uri.strip('/')
    resource_args = (singularize(clean_uri),)
    if not is_singular:
        resource_args += (clean_uri,)

    return parent_resource.add(*resource_args, **resource_kwargs)


def generate_server(raml_root, config):
    """ Handle server generation process.

    :param raml_root: Instance of ramlfications.raml.RootNode.
    :param config: Pyramid Configurator instance.
    """
    log.info('Server generation started')

    if not raml_root.resources:
        return

    root_resource = config.get_root_resource()
    generated_resources = {}

    for raml_resource in raml_root.resources:
        if raml_resource.path in generated_resources:
            continue

        # Get Nefertari parent resource
        parent_resource = _get_nefertari_parent_resource(
            raml_resource, generated_resources, root_resource)

        # Get generated resource and store it
        new_resource = generate_resource(
            config, raml_resource, parent_resource)
        if new_resource is not None:
            generated_resources[raml_resource.path] = new_resource


def generate_models(config, raml_resources):
    """ Generate model for each resource in :raml_resources:

    The DB model name is generated using singular titled version of current
    resource's url. E.g. for resource under url '/stories', model with
    name 'Story' will be generated.

    :param config: Pyramid Configurator instance.
    :param raml_resources: List of ramlfications.raml.ResourceNode.
    """
    from .models import handle_model_generation
    if not raml_resources:
        return
    for raml_resource in raml_resources:
        # No need to generate models for dynamic resource
        if is_dynamic_uri(raml_resource.path):
            continue

        # Since POST resource must define schema use only POST
        # resources to generate models
        if raml_resource.method.upper() != 'POST':
            continue

        # Generate DB model
        # If this is an attribute resource we don't need to generate model
        resource_uri = get_resource_uri(raml_resource)
        route_name = get_route_name(resource_uri)
        if not attr_subresource(raml_resource, route_name):
            log.info('Configuring model for route `{}`'.format(route_name))
            model_cls, is_auth_model = handle_model_generation(
                config, raml_resource)
            if is_auth_model:
                config.registry.auth_model = model_cls


================================================
FILE: ramses/models.py
================================================
import logging

from nefertari import engine
from inflection import pluralize

from .utils import (
    resolve_to_callable, is_callable_tag,
    resource_schema, generate_model_name,
    get_events_map)
from . import registry


log = logging.getLogger(__name__)

"""
Map of RAML types names to nefertari.engine fields.

"""
type_fields = {
    'string':           engine.StringField,
    'float':            engine.FloatField,
    'integer':          engine.IntegerField,
    'boolean':          engine.BooleanField,
    'datetime':         engine.DateTimeField,
    'file':             engine.BinaryField,
    'relationship':     engine.Relationship,
    'dict':             engine.DictField,
    'foreign_key':      engine.ForeignKeyField,
    'big_integer':      engine.BigIntegerField,
    'date':             engine.DateField,
    'choice':           engine.ChoiceField,
    'interval':         engine.IntervalField,
    'decimal':          engine.DecimalField,
    'pickle':           engine.PickleField,
    'small_integer':    engine.SmallIntegerField,
    'text':             engine.TextField,
    'time':             engine.TimeField,
    'unicode':          engine.UnicodeField,
    'unicode_text':     engine.UnicodeTextField,
    'id_field':         engine.IdField,
    'list':             engine.ListField,
}


def get_existing_model(model_name):
    """ Try to find existing model class named `model_name`.

    :param model_name: String name of the model class.
    """
    try:
        model_cls = engine.get_document_cls(model_name)
        log.debug('Model `{}` already exists. Using existing one'.format(
            model_name))
        return model_cls
    except ValueError:
        log.debug('Model `{}` does not exist'.format(model_name))


def prepare_relationship(config, model_name, raml_resource):
    """ Create referenced model if it doesn't exist.

    When preparing a relationship, we check to see if the model that will be
    referenced already exists. If not, it is created so that it will be possible
    to use it in a relationship. Thus the first usage of this model in RAML file
    must provide its schema in POST method resource body schema.

    :param model_name: Name of model which should be generated.
    :param raml_resource: Instance of ramlfications.raml.ResourceNode for
        which :model_name: will be defined.
    """
    if get_existing_model(model_name) is None:
        plural_route = '/' + pluralize(model_name.lower())
        route = '/' + model_name.lower()
        for res in raml_resource.root.resources:
            if res.method.upper() != 'POST':
                continue
            if res.path.endswith(plural_route) or res.path.endswith(route):
                break
        else:
            raise ValueError('Model `{}` used in relationship is not '
                             'defined'.format(model_name))
        setup_data_model(config, res, model_name)


def generate_model_cls(config, schema, model_name, raml_resource,
                       es_based=True):
    """ Generate model class.

    Engine DB field types are determined using `type_fields` and only those
    types may be used.

    :param schema: Model schema dict parsed from RAML.
    :param model_name: String that is used as new model's name.
    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    :param es_based: Boolean indicating if generated model should be a
        subclass of Elasticsearch-based document class or not.
        It True, ESBaseDocument is used; BaseDocument is used otherwise.
        Defaults to True.
    """
    from nefertari.authentication.models import AuthModelMethodsMixin
    base_cls = engine.ESBaseDocument if es_based else engine.BaseDocument
    model_name = str(model_name)
    metaclass = type(base_cls)
    auth_model = schema.get('_auth_model', False)

    bases = []
    if config.registry.database_acls:
        from nefertari_guards import engine as guards_engine
        bases.append(guards_engine.DocumentACLMixin)
    if auth_model:
        bases.append(AuthModelMethodsMixin)
    bases.append(base_cls)

    attrs = {
        '__tablename__': model_name.lower(),
        '_public_fields': schema.get('_public_fields') or [],
        '_auth_fields': schema.get('_auth_fields') or [],
        '_hidden_fields': schema.get('_hidden_fields') or [],
        '_nested_relationships': schema.get('_nested_relationships') or [],
    }
    if '_nesting_depth' in schema:
        attrs['_nesting_depth'] = schema.get('_nesting_depth')

    # Generate fields from properties
    properties = schema.get('properties', {})
    for field_name, props in properties.items():
        if field_name in attrs:
            continue

        db_settings = props.get('_db_settings')
        if db_settings is None:
            continue

        field_kwargs = db_settings.copy()
        field_kwargs['required'] = bool(field_kwargs.get('required'))

        for default_attr_key in ('default', 'onupdate'):
            value = field_kwargs.get(default_attr_key)
            if is_callable_tag(value):
                field_kwargs[default_attr_key] = resolve_to_callable(value)

        type_name = (
            field_kwargs.pop('type', 'string') or 'string').lower()
        if type_name not in type_fields:
            raise ValueError('Unknown type: {}'.format(type_name))

        field_cls = type_fields[type_name]

        if field_cls is engine.Relationship:
            prepare_relationship(
                config, field_kwargs['document'],
                raml_resource)
        if field_cls is engine.ForeignKeyField:
            key = 'ref_column_type'
            field_kwargs[key] = type_fields[field_kwargs[key]]
        if field_cls is engine.ListField:
            key = 'item_type'
            field_kwargs[key] = type_fields[field_kwargs[key]]

        attrs[field_name] = field_cls(**field_kwargs)

    # Update model definition with methods and variables defined in registry
    attrs.update(registry.mget(model_name))

    # Generate new model class
    model_cls = metaclass(model_name, tuple(bases), attrs)
    setup_model_event_subscribers(config, model_cls, schema)
    setup_fields_processors(config, model_cls, schema)
    return model_cls, auth_model


def setup_data_model(config, raml_resource, model_name):
    """ Setup storage/data model and return generated model class.

    Process follows these steps:
      * Resource schema is found and restructured by `resource_schema`.
      * Model class is generated from properties dict using util function
        `generate_model_cls`.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    :param model_name: String representing model name.
    """
    model_cls = get_existing_model(model_name)
    schema = resource_schema(raml_resource)

    if not schema:
        raise Exception('Missing schema for model `{}`'.format(model_name))

    if model_cls is not None:
        return model_cls, schema.get('_auth_model', False)

    log.info('Generating model class `{}`'.format(model_name))
    return generate_model_cls(
        config,
        schema=schema,
        model_name=model_name,
        raml_resource=raml_resource,
    )


def handle_model_generation(config, raml_resource):
    """ Generates model name and runs `setup_data_model` to get
    or generate actual model class.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    model_name = generate_model_name(raml_resource)
    try:
        return setup_data_model(config, raml_resource, model_name)
    except ValueError as ex:
        raise ValueError('{}: {}'.format(model_name, str(ex)))


def setup_model_event_subscribers(config, model_cls, schema):
    """ Set up model event subscribers.

    :param config: Pyramid Configurator instance.
    :param model_cls: Model class for which handlers should be connected.
    :param schema: Dict of model JSON schema.
    """
    events_map = get_events_map()
    model_events = schema.get('_event_handlers', {})
    event_kwargs = {'model': model_cls}

    for event_tag, subscribers in model_events.items():
        type_, action = event_tag.split('_')
        event_objects = events_map[type_][action]

        if not isinstance(event_objects, list):
            event_objects = [event_objects]

        for sub_name in subscribers:
            sub_func = resolve_to_callable(sub_name)
            config.subscribe_to_events(
                sub_func, event_objects, **event_kwargs)


def setup_fields_processors(config, model_cls, schema):
    """ Set up model fields' processors.

    :param config: Pyramid Configurator instance.
    :param model_cls: Model class for field of which processors should be
        set up.
    :param schema: Dict of model JSON schema.
    """
    properties = schema.get('properties', {})
    for field_name, props in properties.items():
        if not props:
            continue

        processors = props.get('_processors')
        backref_processors = props.get('_backref_processors')

        if processors:
            processors = [resolve_to_callable(val) for val in processors]
            setup_kwargs = {'model': model_cls, 'field': field_name}
            config.add_field_processors(processors, **setup_kwargs)

        if backref_processors:
            db_settings = props.get('_db_settings', {})
            is_relationship = db_settings.get('type') == 'relationship'
            document = db_settings.get('document')
            backref_name = db_settings.get('backref_name')
            if not (is_relationship and document and backref_name):
                continue

            backref_processors = [
                resolve_to_callable(val) for val in backref_processors]
            setup_kwargs = {
                'model': engine.get_document_cls(document),
                'field': backref_name
            }
            config.add_field_processors(
                backref_processors, **setup_kwargs)


================================================
FILE: ramses/registry.py
================================================
"""
Naive registry that is just a subclass of a python dictionary.
It is meant to be used to store objects and retrieve them when needed.
The registry is recreated on each app launch and is best suited to store some
dynamic or short-term data.

Storing an object should be performed by using the `add` function, and
retrieving it by using the `get` function.


Examples:

Register a function under a function name::

    from ramses import registry

    @registry.add
    def foo():
        print 'In foo'

    assert registry.get('foo') is foo


Register a function under a different name::

    from ramses import registry

    @registry.add('bar')
    def foo():
        print 'In foo'

    assert registry.get('bar') is foo


Register an arbitrary object::

    from ramses import registry

    myvar = 'my awesome var'
    registry.add('my_stored_var', myvar)
    assert registry.get('my_stored_var') == myvar


Register and get an object by namespace::

    from ramses import registry

    myvar = 'my awesome var'
    registry.add('Foo.my_stored_var', myvar)
    assert registry.mget('Foo') == {'my_stored_var': myvar}

"""
import six


class Registry(dict):
    pass


registry = Registry()


def add(*args):
    def decorator(function):
        registry[name] = function
        return function

    if len(args) == 1 and six.callable(args[0]):
        function = args[0]
        name = function.__name__
        return decorator(function)
    elif len(args) == 2:
        registry[args[0]] = args[1]
    else:
        name = args[0]
        return decorator


def get(name):
    try:
        return registry[name]
    except KeyError:
        raise KeyError(
            "Object named '{}' is not registered in ramses "
            "registry".format(name))


def mget(namespace):
    namespace = namespace.lower() + '.'
    data = {}
    for key, val in registry.items():
        key = key.lower()
        if not key.startswith(namespace):
            continue
        clean_key = key.split(namespace)[-1]
        data[clean_key] = val
    return data


================================================
FILE: ramses/scaffolds/__init__.py
================================================
import os
import subprocess

from six import moves
from pyramid.scaffolds import PyramidTemplate


class RamsesStarterTemplate(PyramidTemplate):
    _template_dir = 'ramses_starter'
    summary = 'Ramses starter'

    def pre(self, command, output_dir, vars):
        dbengine_choices = {'1': 'sqla', '2': 'mongodb'}
        vars['engine'] = dbengine_choices[moves.input("""
        Which database backend would you like to use:

        (1) for SQLAlchemy/PostgreSQL, or
        (2) for MongoEngine/MongoDB?

        [default is '1']: """) or '1']

        if vars['package'] == 'site':
            raise ValueError("""
                "Site" is a reserved keyword in Python.
                 Please use a different project name. """)

    def post(self, command, output_dir, vars):
        os.chdir(str(output_dir))
        subprocess.call('pip install -r requirements.txt', shell=True)
        subprocess.call('pip install nefertari-{}'.format(vars['engine']),
                        shell=True)
        msg = """Goodbye boilerplate! Welcome to Ramses."""
        self.out(msg)


================================================
FILE: ramses/scaffolds/ramses_starter/+package+/__init__.py
================================================
from pyramid.config import Configurator


def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.include('ramses')
    return config.make_wsgi_app()


================================================
FILE: ramses/scaffolds/ramses_starter/+package+/tests/__init__.py
================================================


================================================
FILE: ramses/scaffolds/ramses_starter/+package+/tests/api.raml
================================================
#%RAML 0.8
---
title: Items API
documentation:
    - title: Items REST API
      content: |
        Welcome to the Items API.
baseUri: http://{host}:{port}/{version}
version: api
mediaType: application/json
protocols: [HTTP]

/items:
    displayName: Collection of items
    head:
        description: Head request
        responses:
            200:
                description: Return headers
    get:
        description: Get all item
        responses:
            200:
                description: Returns a list of items
    post:
        description: Create a new item
        body:
            application/json:
                schema: !include items.json
                example: |
                    {
                        "id": "507f191e810c19729de860ea",
                        "name": "Banana",
                        "description": "Tasty"
                    }
        responses:
            201:
                description: Created item
                body:
                    application/json:
                        schema: !include items.json

    /{id}:
        uriParameters:
            id:
                displayName: Item id
                type: string
                example: 507f191e810c19729de860ea
        displayName: Collection-item
        head:
            description: Head request
            responses:
                200:
                    description: Return headers
        get:
            description: Get a particular item
            responses:
                200:
                    body:
                        application/json:
                            schema: !include items.json
        delete:
            description: Delete a particular item
            responses:
                200:
                    description: Deleted item
        patch:
            description: Update a particular item
            body:
                application/json:
                    example: { "name": "Tree" }
            responses:
                200:
                    body:
                        application/json:
                            schema: !include items.json


================================================
FILE: ramses/scaffolds/ramses_starter/+package+/tests/items.json
================================================
{
    "type": "object",
    "title": "Item schema",
    "$schema": "http://json-schema.org/draft-04/schema",
    "required": ["name"],
    "properties": {
        "id": {
            "type": ["string", "null"],
            "_db_settings": {
                "type": "id_field",
                "required": true,
                "primary_key": true
            }
        },
        "name": {
            "type": "string",
            "_db_settings": {
                "type": "string",
                "required": true
            }
        },
        "description": {
            "type": ["string", "null"],
            "_db_settings": {
                "type": "text"
            }
        }
    }
}

================================================
FILE: ramses/scaffolds/ramses_starter/+package+/tests/requirements.txt
================================================
-e git+https://github.com/ramses-tech/ra.git@develop#egg=ra
-e git+https://github.com/ramses-tech/nefertari.git@develop#egg=nefertari
-e git+https://github.com/ramses-tech/nefertari-mongodb.git@develop#egg=nefertari-mongodb


================================================
FILE: ramses/scaffolds/ramses_starter/+package+/tests/test_api.py_tmpl
================================================
import os
import ra
import webtest
import pytest

appdir = os.path.abspath(
    os.path.join(os.path.dirname(__file__), '..', '..'))
ramlfile = os.path.abspath(
    os.path.join(os.path.dirname(__file__), 'api.raml'))
testapp = webtest.TestApp('config:local.ini', relative_to=appdir)


@pytest.fixture(autouse=True)
def setup(req, examples):
    """ Setup database state for tests.

    NOTE: For objects to be created, when using SQLA transaction
    needs to be commited as follows:
        import transaction
        transaction.commit()
    """
    from nefertari import engine
    Item = engine.get_document_cls('Item')

    if req.match(exclude='POST /items'):
        if Item.get_collection(_count=True) == 0:
            example = examples.build('item')
            Item(**example).save()


# ra entry point: instantiate the API test suite
api = ra.api(ramlfile, testapp)
api.autotest()


================================================
FILE: ramses/scaffolds/ramses_starter/.gitignore_tmpl
================================================
venv
.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

local.ini
mock/


================================================
FILE: ramses/scaffolds/ramses_starter/README.md
================================================
## Installation
```
$ pip install -r requirements.txt
```

## Run
```
$ pserve local.ini
```


================================================
FILE: ramses/scaffolds/ramses_starter/api.raml_tmpl
================================================
#%RAML 0.8
---
title: {{package}} API
documentation:
    - title: {{package}} REST API
      content: |
        Welcome to the {{package}} API.
baseUri: http://{host}:{port}/{version}
version: v1
mediaType: application/json
protocols: [HTTP]

/items:
    displayName: Collection of items
    get:
        description: Get all item
    post:
        description: Create a new item
        body:
            application/json:
                schema: !include items.json

    /{id}:
        displayName: Collection-item
        get:
            description: Get a particular item
        delete:
            description: Delete a particular item
        patch:
            description: Update a particular item

================================================
FILE: ramses/scaffolds/ramses_starter/items.json
================================================
{
    "type": "object",
    "title": "Item schema",
    "$schema": "http://json-schema.org/draft-04/schema",
    "required": ["id", "name"],
    "properties": {
        "id": {
            "type": ["integer", "null"],
            "_db_settings": {
                "type": "id_field",
                "required": true,
                "primary_key": true
            }
        },
        "name": {
            "type": "string",
            "_db_settings": {
                "type": "string",
                "required": true
            }
        },
        "description": {
            "type": ["string", "null"],
            "_db_settings": {
                "type": "text"
            }
        }
    }
}

================================================
FILE: ramses/scaffolds/ramses_starter/local.ini_tmpl
================================================
[app:{{package}}]
use = egg:{{package}}

# Ramses
ramses.raml_schema = api.raml
database_acls = false

# Nefertari
nefertari.engine = nefertari_{{engine}}
enable_get_tunneling = true

# SQLA
sqlalchemy.url = postgresql://localhost:5432/{{package}}

# MongoDB settings
mongodb.host = localhost
mongodb.port = 27017
mongodb.db = {{package}}

# Elasticsearch
elasticsearch.hosts = localhost:9200
elasticsearch.sniff = false
elasticsearch.index_name = {{package}}
elasticsearch.index.disable = false
elasticsearch.enable_refresh_query = false
elasticsearch.enable_aggregations = false
elasticsearch.enable_polymorphic_query = false

# {{package}}
host = localhost
base_url = http://%(host)s:6543

# CORS
cors.enable = false
cors.allow_origins = %(base_url)s
cors.allow_credentials = true

[composite:main]
use = egg:Paste#urlmap
/api/ = {{package}}

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
threads = 3

[loggers]
keys = root, {{package}}, nefertari, ramses

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_{{package}}]
level = INFO
handlers =
qualname = {{package}}

[logger_nefertari]
level = INFO
handlers =
qualname = nefertari

[logger_ramses]
level = INFO
handlers =
qualname = ramses

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(module)s.%(funcName)s: %(message)s


================================================
FILE: ramses/scaffolds/ramses_starter/requirements.txt
================================================
nefertari
ramses

Paste==2.0.2
pyramid==1.6.1
waitress==0.8.9

-e .


================================================
FILE: ramses/scaffolds/ramses_starter/setup.py_tmpl
================================================
from setuptools import setup, find_packages

requires = ['pyramid']

setup(name='{{package}}',
    version='0.0.1',
    description='',
    long_description='',
    classifiers=[
        "Programming Language :: Python",
        "Framework :: Pyramid",
        "Topic :: Internet :: WWW/HTTP",
        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
    ],
    author='',
    author_email='',
    url='',
    keywords='web pyramid pylons raml ramses',
    packages=find_packages(),
    include_package_data=True,
    zip_safe=False,
    install_requires=requires,
    tests_require=requires,
    test_suite="{{package}}",
    entry_points="""\
    [paste.app_factory]
        main = {{package}}:main
    """,
)


================================================
FILE: ramses/scripts/__init__.py
================================================


================================================
FILE: ramses/scripts/scaffold_test.py
================================================
from nefertari.scripts.scaffold_test import (
    ScaffoldTestCommand as NefTestCommand)


class ScaffoldTestCommand(NefTestCommand):
    file = __file__


def main(*args, **kwargs):
    ScaffoldTestCommand().run()


if __name__ == '__main__':
    main()


================================================
FILE: ramses/utils.py
================================================
import re
import logging
from contextlib import contextmanager

import six
import inflection


log = logging.getLogger(__name__)


class ContentTypes(object):
    """ ContentType values.

    """
    JSON = 'application/json'
    TEXT_XML = 'text/xml'
    MULTIPART_FORMDATA = 'multipart/form-data'
    FORM_URLENCODED = 'application/x-www-form-urlencoded'


def convert_schema(raml_schema, mime_type):
    """ Restructure `raml_schema` to a dictionary that has 'properties'
    as well as other schema keys/values.

    The resulting dictionary looks like this::

    {
        "properties": {
            "field1": {
                "required": boolean,
                "type": ...,
                ...more field options
            },
            ...more properties
        },
        "public_fields": [...],
        "auth_fields": [...],
        ...more schema options
    }

    :param raml_schema: RAML request body schema.
    :param mime_type: ContentType of the schema as a string from RAML
        file. Only JSON is currently supported.
    """
    if mime_type == ContentTypes.JSON:
        if not isinstance(raml_schema, dict):
            raise TypeError(
                'Schema is not a valid JSON. Please check your '
                'schema syntax.\n{}...'.format(str(raml_schema)[:60]))
        return raml_schema
    if mime_type == ContentTypes.TEXT_XML:
        # Process XML schema
        pass


def is_dynamic_uri(uri):
    """ Determine whether `uri` is a dynamic uri or not.

    Assumes a dynamic uri is one that ends with '}' which is a Pyramid
    way to define dynamic parts in uri.

    :param uri: URI as a string.
    """
    return uri.strip('/').endswith('}')


def clean_dynamic_uri(uri):
    """ Strips /, {, } from dynamic `uri`.

    :param uri: URI as a string.
    """
    return uri.replace('/', '').replace('{', '').replace('}', '')


def generate_model_name(raml_resource):
    """ Generate model name.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    resource_uri = get_resource_uri(raml_resource).strip('/')
    resource_uri = re.sub('\W', ' ', resource_uri)
    model_name = inflection.titleize(resource_uri)
    return inflection.singularize(model_name).replace(' ', '')


def dynamic_part_name(raml_resource, route_name, pk_field):
    """ Generate a dynamic part for a resource :raml_resource:.

    A dynamic part is generated using 2 parts: :route_name: of the
    resource and the dynamic part of first dynamic child resources. If
    :raml_resource: has no dynamic child resources, 'id' is used as the
    2nd part.
    E.g. if your dynamic part on route 'stories' is named 'superId' then
    dynamic part will be 'stories_superId'.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode for
        which dynamic part name is being generated.
    :param route_name: Cleaned name of :raml_resource:
    :param pk_field: Model Primary Key field name.
    """
    subresources = get_resource_children(raml_resource)
    dynamic_uris = [res.path for res in subresources
                    if is_dynamic_uri(res.path)]
    if dynamic_uris:
        dynamic_part = extract_dynamic_part(dynamic_uris[0])
    else:
        dynamic_part = pk_field
    return '_'.join([route_name, dynamic_part])


def extract_dynamic_part(uri):
    """ Extract dynamic url part from :uri: string.

    :param uri: URI string that may contain dynamic part.
    """
    for part in uri.split('/'):
        part = part.strip()
        if part.startswith('{') and part.endswith('}'):
            return clean_dynamic_uri(part)


def resource_view_attrs(raml_resource, singular=False):
    """ Generate view method names needed for `raml_resource` view.

    Collects HTTP method names from resource siblings and dynamic children
    if exist. Collected methods are then translated  to
    `nefertari.view.BaseView` method names, each of which is used to
    process a particular HTTP method request.

    Maps of {HTTP_method: view_method} `collection_methods` and
    `item_methods` are used to convert collection and item methods
    respectively.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode
    :param singular: Boolean indicating if resource is singular or not
    """
    from .views import collection_methods, item_methods
    # Singular resource doesn't have collection methods though
    # it looks like a collection
    if singular:
        collection_methods = item_methods

    siblings = get_resource_siblings(raml_resource)
    http_methods = [sibl.method.lower() for sibl in siblings]
    attrs = [collection_methods.get(method) for method in http_methods]

    # Check if resource has dynamic child resource like collection/{id}
    # If dynamic child resource exists, add its siblings' methods to attrs,
    # as both resources are handled by a single view
    children = get_resource_children(raml_resource)
    http_submethods = [child.method.lower() for child in children
                       if is_dynamic_uri(child.path)]
    attrs += [item_methods.get(method) for method in http_submethods]

    return set(filter(bool, attrs))


def resource_schema(raml_resource):
    """ Get schema properties of RAML resource :raml_resource:.

    Must be called with RAML resource that defines body schema. First
    body that defines schema is used. Schema is converted on return using
    'convert_schema'.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode of
        POST method.
    """
    # NOTE: Must be called with resource that defines body schema
    log.info('Searching for model schema')
    if not raml_resource.body:
        raise ValueError('RAML resource has no body to setup database '
                         'schema from')

    for body in raml_resource.body:
        if body.schema:
            return convert_schema(body.schema, body.mime_type)
    log.debug('No model schema found.')


def is_dynamic_resource(raml_resource):
    """ Determine if :raml_resource: is a dynamic resource.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    return raml_resource and is_dynamic_uri(raml_resource.path)


def get_static_parent(raml_resource, method=None):
    """ Get static parent resource of :raml_resource: with HTTP
    method :method:.

    :param raml_resource:Instance of ramlfications.raml.ResourceNode.
    :param method: HTTP method name which matching static resource
        must have.
    """
    parent = raml_resource.parent
    while is_dynamic_resource(parent):
        parent = parent.parent

    if parent is None:
        return parent

    match_method = method is not None
    if match_method:
        if parent.method.upper() == method.upper():
            return parent
    else:
        return parent

    for res in parent.root.resources:
        if res.path == parent.path:
            if res.method.upper() == method.upper():
                return res


def attr_subresource(raml_resource, route_name):
    """ Determine if :raml_resource: is an attribute subresource.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    :param route_name: Name of the :raml_resource:.
    """
    static_parent = get_static_parent(raml_resource, method='POST')
    if static_parent is None:
        return False
    schema = resource_schema(static_parent) or {}
    properties = schema.get('properties', {})
    if route_name in properties:
        db_settings = properties[route_name].get('_db_settings', {})
        return db_settings.get('type') in ('dict', 'list')
    return False


def singular_subresource(raml_resource, route_name):
    """ Determine if :raml_resource: is a singular subresource.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    :param route_name: Name of the :raml_resource:.
    """
    static_parent = get_static_parent(raml_resource, method='POST')
    if static_parent is None:
        return False
    schema = resource_schema(static_parent) or {}
    properties = schema.get('properties', {})
    if route_name not in properties:
        return False

    db_settings = properties[route_name].get('_db_settings', {})
    is_obj = db_settings.get('type') == 'relationship'
    single_obj = not db_settings.get('uselist', True)
    return is_obj and single_obj


def is_callable_tag(tag):
    """ Determine whether :tag: is a valid callable string tag.

    String is assumed to be valid callable if it starts with '{{'
    and ends with '}}'.

    :param tag: String name of tag.
    """
    return (isinstance(tag, six.string_types) and
            tag.strip().startswith('{{') and
            tag.strip().endswith('}}'))


def resolve_to_callable(callable_name):
    """ Resolve string :callable_name: to a callable.

    :param callable_name: String representing callable name as registered
        in ramses registry or dotted import path of callable. Can be
        wrapped in double curly brackets, e.g. '{{my_callable}}'.
    """
    from . import registry
    clean_callable_name = callable_name.replace(
        '{{', '').replace('}}', '').strip()
    try:
        return registry.get(clean_callable_name)
    except KeyError:
        try:
            from zope.dottedname.resolve import resolve
            return resolve(clean_callable_name)
        except ImportError:
            raise ImportError(
                'Failed to load callable `{}`'.format(clean_callable_name))


def get_resource_siblings(raml_resource):
    """ Get siblings of :raml_resource:.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    path = raml_resource.path
    return [res for res in raml_resource.root.resources
            if res.path == path]


def get_resource_children(raml_resource):
    """ Get children of :raml_resource:.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    path = raml_resource.path
    return [res for res in raml_resource.root.resources
            if res.parent and res.parent.path == path]


def get_events_map():
    """ Prepare map of event subscribers.

    * Extends copies of BEFORE_EVENTS and AFTER_EVENTS maps with
        'set' action.
    * Returns map of {before/after: {action: event class(es)}}
    """
    from nefertari import events
    set_keys = ('create', 'update', 'replace', 'update_many', 'register')
    before_events = events.BEFORE_EVENTS.copy()
    before_events['set'] = [before_events[key] for key in set_keys]
    after_events = events.AFTER_EVENTS.copy()
    after_events['set'] = [after_events[key] for key in set_keys]
    return {
        'before': before_events,
        'after': after_events,
    }


@contextmanager
def patch_view_model(view_cls, model_cls):
    """ Patches view_cls.Model with model_cls.

    :param view_cls: View class "Model" param of which should be
        patched
    :param model_cls: Model class which should be used to patch
        view_cls.Model
    """
    original_model = view_cls.Model
    view_cls.Model = model_cls

    try:
        yield
    finally:
        view_cls.Model = original_model


def get_route_name(resource_uri):
    """ Get route name from RAML resource URI.

    :param resource_uri: String representing RAML resource URI.
    :returns string: String with route name, which is :resource_uri:
        stripped of non-word characters.
    """
    resource_uri = resource_uri.strip('/')
    resource_uri = re.sub('\W', '', resource_uri)
    return resource_uri


def get_resource_uri(raml_resource):
    """ Get cleaned resource URI of RAML resource.

    :param raml_resource: Instance of ramlfications.raml.ResourceNode.
    """
    return raml_resource.path.split('/')[-1].strip()


================================================
FILE: ramses/views.py
================================================
import logging

import six
from nefertari.view import BaseView as NefertariBaseView
from nefertari.json_httpexceptions import JHTTPNotFound

from .utils import patch_view_model


log = logging.getLogger(__name__)

"""
Maps of {HTTP_method: neferteri view method name}

"""
collection_methods = {
    'get':      'index',
    'head':     'index',
    'post':     'create',
    'put':      'update_many',
    'patch':    'update_many',
    'delete':   'delete_many',
    'options':  'collection_options',
}
item_methods = {
    'get':      'show',
    'head':     'show',
    'post':     'create',
    'put':      'replace',
    'patch':    'update',
    'delete':   'delete',
    'options':  'item_options',
}


class SetObjectACLMixin(object):
    def set_object_acl(self, obj):
        """ Set object ACL on creation if not already present. """
        if not obj._acl:
            from nefertari_guards import engine as guards_engine
            acl = self._factory(self.request).generate_item_acl(obj)
            obj._acl = guards_engine.ACLField.stringify_acl(acl)


class BaseView(object):
    """ Base view class for other all views that defines few helper methods.

    Use `self.get_collection` and `self.get_item` to get access to set of
    objects and object respectively which are valid at current level.
    """
    @property
    def clean_id_name(self):
        id_name = self._resource.id_name
        if '_' in id_name:
            return id_name.split('_', 1)[1]
        else:
            return id_name

    def set_object_acl(self, obj):
        pass

    def resolve_kw(self, kwargs):
        """ Resolve :kwargs: like `story_id: 1` to the form of `id: 1`.

        """
        resolved = {}
        for key, value in kwargs.items():
            split = key.split('_', 1)
            if len(split) > 1:
                key = split[1]
            resolved[key] = value
        return resolved

    def _location(self, obj):
        """ Get location of the `obj`

        Arguments:
            :obj: self.Model instance.
        """
        field_name = self.clean_id_name
        return self.request.route_url(
            self._resource.uid,
            **{self._resource.id_name: getattr(obj, field_name)})

    def _parent_queryset(self):
        """ Get queryset of parent view.

        Generated queryset is used to run queries in the current level view.
        """
        parent = self._resource.parent
        if hasattr(parent, 'view'):
            req = self.request.blank(self.request.path)
            req.registry = self.request.registry
            req.matchdict = {
                parent.id_name: self.request.matchdict.get(parent.id_name)}
            parent_view = parent.view(parent.view._factory, req)
            obj = parent_view.get_item(**req.matchdict)
            if isinstance(self, ItemSubresourceBaseView):
                return
            prop = self._resource.collection_name
            return getattr(obj, prop, None)

    def get_collection(self, **kwargs):
        """ Get objects collection taking into account generated queryset
        of parent view.

        This method allows working with nested resources properly. Thus a
        queryset returned by this method will be a subset of its parent
        view's queryset, thus filtering out objects that don't belong to
        the parent object.
        """
        self._query_params.update(kwargs)
        objects = self._parent_queryset()
        if objects is not None:
            return self.Model.filter_objects(
                objects, **self._query_params)
        return self.Model.get_collection(**self._query_params)

    def get_item(self, **kwargs):
        """ Get collection item taking into account generated queryset
        of parent view.

        This method allows working with nested resources properly. Thus an item
        returned by this method will belong to its parent view's queryset, thus
        filtering out objects that don't belong to the parent object.

        Returns an object from the applicable ACL. If ACL wasn't applied, it is
        applied explicitly.
        """
        if six.callable(self.context):
            self.reload_context(es_based=False, **kwargs)

        objects = self._parent_queryset()
        if objects is not None and self.context not in objects:
            raise JHTTPNotFound('{}({}) not found'.format(
                self.Model.__name__,
                self._get_context_key(**kwargs)))

        return self.context

    def _get_context_key(self, **kwargs):
        """ Get value of `self._resource.id_name` from :kwargs: """
        return str(kwargs.get(self._resource.id_name))

    def reload_context(self, es_based, **kwargs):
        """ Reload `self.context` object into a DB or ES object.

        A reload is performed by getting the object ID from :kwargs: and then
        getting a context key item from the new instance of `self._factory`
        which is an ACL class used by the current view.

        Arguments:
            :es_based: Boolean. Whether to init ACL ac es-based or not. This
                affects the backend which will be queried - either DB or ES
            :kwargs: Kwargs that contain value for current resource 'id_name'
                key
        """
        from .acl import BaseACL
        key = self._get_context_key(**kwargs)
        kwargs = {'request': self.request}
        if issubclass(self._factory, BaseACL):
            kwargs['es_based'] = es_based

        acl = self._factory(**kwargs)
        if acl.item_model is None:
            acl.item_model = self.Model

        self.context = acl[key]


class CollectionView(BaseView):
    """ View that works with database and implements handlers for all
    available CRUD operations.

    """
    def index(self, **kwargs):
        return self.get_collection()

    def show(self, **kwargs):
        return self.get_item(**kwargs)

    def create(self, **kwargs):
        obj = self.Model(**self._json_params)
        self.set_object_acl(obj)
        return obj.save(self.request)

    def update(self, **kwargs):
        obj = self.get_item(**kwargs)
        return obj.update(self._json_params, self.request)

    def replace(self, **kwargs):
        return self.update(**kwargs)

    def delete(self, **kwargs):
        obj = self.get_item(**kwargs)
        obj.delete(self.request)

    def delete_many(self, **kwargs):
        objects = self.get_collection()
        return self.Model._delete_many(objects, self.request)

    def update_many(self, **kwargs):
        objects = self.get_collection(**self._query_params)
        return self.Model._update_many(
            objects, self._json_params, self.request)


class ESBaseView(BaseView):
    """ Elasticsearch base view that fetches data from ES.

    Implements analogues of _parent_queryset, get_collection, get_item
    fetching data from ES instead of database.

    Use `self.get_collection_es` and `self.get_item_es` to get access
    to the set of objects and individual object respectively which are
    valid at the current level.
    """
    def _parent_queryset_es(self):
        """ Get queryset (list of object IDs) of parent view.

        The generated queryset is used to run queries in the current level's
        view.
        """
        parent = self._resource.parent
        if hasattr(parent, 'view'):
            req = self.request.blank(self.request.path)
            req.registry = self.request.registry
            req.matchdict = {
                parent.id_name: self.request.matchdict.get(parent.id_name)}
            parent_view = parent.view(parent.view._factory, req)
            obj = parent_view.get_item_es(**req.matchdict)
            prop = self._resource.collection_name
            objects_ids = getattr(obj, prop, None)
            return objects_ids

    def get_es_object_ids(self, objects):
        """ Return IDs of :objects: if they are not IDs already. """
        id_field = self.clean_id_name
        ids = [getattr(obj, id_field, obj) for obj in objects]
        return list(set(str(id_) for id_ in ids))

    def get_collection_es(self):
        """ Get ES objects collection taking into account the generated
        queryset of parent view.

        This method allows working with nested resources properly. Thus a
        queryset returned by this method will be a subset of its parent view's
        queryset, thus filtering out objects that don't belong to the parent
        object.
        """
        objects_ids = self._parent_queryset_es()

        if objects_ids is not None:
            objects_ids = self.get_es_object_ids(objects_ids)
            if not objects_ids:
                return []
            self._query_params['id'] = objects_ids

        return super(ESBaseView, self).get_collection_es()

    def get_item_es(self, **kwargs):
        """ Get ES collection item taking into account generated queryset
        of parent view.

        This method allows working with nested resources properly. Thus an item
        returned by this method will belong to its parent view's queryset, thus
        filtering out objects that don't belong to the parent object.

        Returns an object retrieved from the applicable ACL. If an ACL wasn't
        applied, it is applied explicitly.
        """
        item_id = self._get_context_key(**kwargs)
        objects_ids = self._parent_queryset_es()
        if objects_ids is not None:
            objects_ids = self.get_es_object_ids(objects_ids)

        if six.callable(self.context):
            self.reload_context(es_based=True, **kwargs)

        if (objects_ids is not None) and (item_id not in objects_ids):
            raise JHTTPNotFound('{}(id={}) resource not found'.format(
                self.Model.__name__, item_id))

        return self.context


class ESCollectionView(ESBaseView, CollectionView):
    """ View that reads data from ES.

    Write operations are inherited from :CollectionView:
    """
    def index(self, **kwargs):
        return self.get_collection_es()

    def show(self, **kwargs):
        return self.get_item_es(**kwargs)

    def update(self, **kwargs):
        """ Explicitly reload context with DB usage to get access
        to complete DB object.
        """
        self.reload_context(es_based=False, **kwargs)
        return super(ESCollectionView, self).update(**kwargs)

    def delete(self, **kwargs):
        """ Explicitly reload context with DB usage to get access
        to complete DB object.
        """
        self.reload_context(es_based=False, **kwargs)
        return super(ESCollectionView, self).delete(**kwargs)

    def get_dbcollection_with_es(self, **kwargs):
        """ Get DB objects collection by first querying ES. """
        es_objects = self.get_collection_es()
        db_objects = self.Model.filter_objects(es_objects)
        return db_objects

    def delete_many(self, **kwargs):
        """ Delete multiple objects from collection.

        First ES is queried, then the results are used to query the DB.
        This is done to make sure deleted objects are those filtered
        by ES in the 'index' method (so user deletes what he saw).
        """
        db_objects = self.get_dbcollection_with_es(**kwargs)
        return self.Model._delete_many(db_objects, self.request)

    def update_many(self, **kwargs):
        """ Update multiple objects from collection.

        First ES is queried, then the results are used to query DB.
        This is done to make sure updated objects are those filtered
        by ES in the 'index' method (so user updates what he saw).
        """
        db_objects = self.get_dbcollection_with_es(**kwargs)
        return self.Model._update_many(
            db_objects, self._json_params, self.request)


class ItemSubresourceBaseView(BaseView):
    """ Base class for all subresources of collection item resources which
    don't represent a collection of their own.
    E.g. /users/{id}/profile, where 'profile' is a singular resource or
    /users/{id}/some_action, where the 'some_action' action may be
    performed when requesting this route.

    Subclass ItemSubresourceBaseView in your project when you want to
    define a subroute and view of an item route defined in RAML and
    generated by ramses.
    Use `self.get_item` to get an object on which actions are being
    performed.

    Moved into a separate class so all item subresources have a common
    base class, thus making checks like `isinstance(view, baseClass)` easier.
    Also to override `_get_context_key` to return parent resource's id_name
    and `get_item` to reload context on each access.
    """

    def _get_context_key(self, **kwargs):
        """ Get value of `self._resource.parent.id_name` from :kwargs: """
        return str(kwargs.get(self._resource.parent.id_name))

    def get_item(self, **kwargs):
        """ Reload context on each access. """
        self.reload_context(es_based=False, **kwargs)
        return super(ItemSubresourceBaseView, self).get_item(**kwargs)


class ItemAttributeView(ItemSubresourceBaseView):
    """ View used to work with attribute resources.

    Attribute resources represent field: ListField, DictField.

    You may subclass ItemAttributeView in your project when you want to
    define custom attribute subroute and view of a item route defined in
    RAML and generated by ramses.
    """
    def __init__(self, *args, **kw):
        super(ItemAttributeView, self).__init__(*args, **kw)
        self.attr = self.request.path.split('/')[-1]
        self.value_type = None
        self.unique = True

    def index(self, **kwargs):
        obj = self.get_item(**kwargs)
        return getattr(obj, self.attr)

    def create(self, **kwargs):
        obj = self.get_item(**kwargs)
        obj.update_iterables(
            self._json_params, self.attr,
            unique=self.unique,
            value_type=self.value_type,
            request=self.request)
        return getattr(obj, self.attr, None)


class ItemSingularView(ItemSubresourceBaseView):
    """ View used to work with singular resources.

    Singular resources represent a one-to-one relationship.
    E.g. users/1/profile.

    You may subclass ItemSingularView in your project when you want to define
    a custom singular subroute and view of an item route defined in RAML and
    generated by ramses.
    If you decide to do so, make sure to set `self._singular_model` to a model
    class, instances of which will be processed by this view.
    """
    _parent_model = None

    def __init__(self, *args, **kw):
        super(ItemSingularView, self).__init__(*args, **kw)
        self.attr = self.request.path.split('/')[-1]

    def get_item(self, **kwargs):
        with patch_view_model(self, self._parent_model):
            return super(ItemSingularView, self).get_item(**kwargs)

    def show(self, **kwargs):
        parent_obj = self.get_item(**kwargs)
        return getattr(parent_obj, self.attr)

    def create(self, **kwargs):
        parent_obj = self.get_item(**kwargs)
        obj = self.Model(**self._json_params)
        self.set_object_acl(obj)
        obj = obj.save(self.request)
        parent_obj.update({self.attr: obj}, self.request)
        return obj

    def update(self, **kwargs):
        parent_obj = self.get_item(**kwargs)
        obj = getattr(parent_obj, self.attr)
        obj.update(self._json_params, self.request)
        return obj

    def replace(self, **kwargs):
        return self.update(**kwargs)

    def delete(self, **kwargs):
        parent_obj = self.get_item(**kwargs)
        obj = getattr(parent_obj, self.attr)
        obj.delete(self.request)


def generate_rest_view(config, model_cls, attrs=None, es_based=True,
                       attr_view=False, singular=False):
    """ Generate REST view for a model class.

    :param model_cls: Generated DB model class.
    :param attr: List of strings that represent names of view methods, new
        generated view should support. Not supported methods are replaced
        with property that raises AttributeError to display MethodNotAllowed
        error.
    :param es_based: Boolean indicating if generated view should read from
        elasticsearch. If True - collection reads are performed from
        elasticsearch. Database is used for reads otherwise.
        Defaults to True.
    :param attr_view: Boolean indicating if ItemAttributeView should be
        used as a base class for generated view.
    :param singular: Boolean indicating if ItemSingularView should be
        used as a base class for generated view.
    """
    valid_attrs = (list(collection_methods.values()) +
                   list(item_methods.values()))
    missing_attrs = set(valid_attrs) - set(attrs)

    if singular:
        bases = [ItemSingularView]
    elif attr_view:
        bases = [ItemAttributeView]
    elif es_based:
        bases = [ESCollectionView]
    else:
        bases = [CollectionView]

    if config.registry.database_acls:
        from nefertari_guards.view import ACLFilterViewMixin
        bases = [SetObjectACLMixin] + bases + [ACLFilterViewMixin]
    bases.append(NefertariBaseView)

    RESTView = type('RESTView', tuple(bases), {'Model': model_cls})

    def _attr_error(*args, **kwargs):
        raise AttributeError

    for attr in missing_attrs:
        setattr(RESTView, attr, property(_attr_error))

    return RESTView


================================================
FILE: requirements.dev
================================================
mock
pytest
pytest-cov
releases
sphinx
virtualenv

-e git+https://github.com/ramses-tech/nefertari.git@develop#egg=nefertari
-e git+https://github.com/ramses-tech/nefertari-guards.git@develop#egg=nefertari_guards

-e .


================================================
FILE: setup.py
================================================
import os
from setuptools import setup, find_packages

here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.md')).read()
VERSION = open(os.path.join(here, 'VERSION')).read()

requires = [
    'cryptacular',
    'inflection',
    'nefertari>=0.7.0',
    'pyramid',
    'ramlfications==0.1.8',
    'six',
    'transaction',
]

setup(name='ramses',
      version=VERSION,
      description='Generate a RESTful API for Pyramid using RAML',
      long_description=README,
      classifiers=[
          "Programming Language :: Python",
          "Programming Language :: Python :: 2",
          "Programming Language :: Python :: 2.7",
          "Programming Language :: Python :: 3",
          "Programming Language :: Python :: 3.4",
          "Programming Language :: Python :: 3.5",
          "Framework :: Pyramid",
          "Topic :: Internet :: WWW/HTTP",
          "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
      ],
      author='Ramses Tech',
      author_email='hello@ramses.tech',
      url='https://github.com/ramses-tech/ramses',
      keywords='web pyramid pylons ramses raml',
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      install_requires=requires,
      tests_require=requires,
      test_suite="ramses",
      entry_points="""\
        [pyramid.scaffold]
            ramses_starter = ramses.scaffolds:RamsesStarterTemplate
      """)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/fixtures.py
================================================
import pytest


@pytest.fixture
def clear_registry(request):
    from ramses import registry
    registry.registry.clear()


@pytest.fixture
def engine_mock(request):
    import nefertari
    from mock import Mock

    class BaseDocument(object):
        pass

    class ESBaseDocument(object):
        pass

    original_engine = nefertari.engine
    nefertari.engine = Mock()
    nefertari.engine.BaseDocument = BaseDocument
    nefertari.engine.ESBaseDocument = ESBaseDocument

    def clear():
        nefertari.engine = original_engine
    request.addfinalizer(clear)

    return nefertari.engine


@pytest.fixture
def guards_engine_mock(request):
    import nefertari_guards
    from nefertari_guards import engine
    from mock import Mock

    class DocumentACLMixin(object):
        pass

    original_engine = engine
    nefertari_guards.engine = Mock()
    nefertari_guards.engine.DocumentACLMixin = DocumentACLMixin

    def clear():
        nefertari_guards.engine = original_engine
    request.addfinalizer(clear)

    return nefertari_guards.engine


def config_mock():
    from mock import Mock
    config = Mock()
    config.registry.database_acls = False
    return config


================================================
FILE: tests/test_acl.py
================================================
import pytest
from mock import Mock, patch, call
from pyramid.security import (
    Allow, Deny,
    Everyone, Authenticated,
    ALL_PERMISSIONS)

from ramses import acl

from .fixtures import config_mock


class TestACLHelpers(object):
    def test_validate_permissions_all_perms(self):
        perms = ALL_PERMISSIONS
        assert acl.validate_permissions(perms) == [perms]
        assert acl.validate_permissions([perms]) == [perms]

    def test_validate_permissions_valid(self):
        perms = ['update', 'delete']
        assert acl.validate_permissions(perms) == perms

    def test_validate_permissions_invalid(self):
        with pytest.raises(ValueError) as ex:
            acl.validate_permissions(['foobar'])
        assert 'Invalid ACL permission names' in str(ex.value)

    def test_parse_permissions_all_permissions(self):
        perms = acl.parse_permissions('all,view,create')
        assert perms is ALL_PERMISSIONS

    def test_parse_permissions_invalid_perm_name(self):
        with pytest.raises(ValueError) as ex:
            acl.parse_permissions('foo,create')
        expected = ('Invalid ACL permission names. Valid '
                    'permissions are: ')
        assert expected in str(ex.value)

    def test_parse_permissions(self):
        perms = acl.parse_permissions('view')
        assert perms == ['view']
        perms = acl.parse_permissions('view,create')
        assert sorted(perms) == ['create', 'view']

    def test_parse_acl_no_string(self):
        perms = acl.parse_acl('')
        assert perms == [acl.ALLOW_ALL]

    def test_parse_acl_unknown_action(self):
        with pytest.raises(ValueError) as ex:
            acl.parse_acl('foobar admin all')
        assert 'Unknown ACL action: foobar' in str(ex.value)

    @patch.object(acl, 'parse_permissions')
    def test_parse_acl_multiple_values(self, mock_perms):
        mock_perms.return_value = 'Foo'
        perms = acl.parse_acl(
            'allow everyone read,write;allow authenticated all')
        mock_perms.assert_has_calls([
            call(['read', 'write']),
            call(['all']),
        ])
        assert sorted(perms) == sorted([
            (Allow, Everyone, 'Foo'),
            (Allow, Authenticated, 'Foo'),
        ])

    @patch.object(acl, 'parse_permissions')
    def test_parse_acl_special_principal(self, mock_perms):
        mock_perms.return_value = 'Foo'
        perms = acl.parse_acl('allow everyone all')
        mock_perms.assert_called_once_with(['all'])
        assert perms == [(Allow, Everyone, 'Foo')]

    @patch.object(acl, 'parse_permissions')
    def test_parse_acl_group_principal(self, mock_perms):
        mock_perms.return_value = 'Foo'
        perms = acl.parse_acl('allow g:admin all')
        mock_perms.assert_called_once_with(['all'])
        assert perms == [(Allow, 'g:admin', 'Foo')]

    @patch.object(acl, 'resolve_to_callable')
    @patch.object(acl, 'parse_permissions')
    def test_parse_acl_callable_principal(self, mock_perms, mock_res):
        mock_perms.return_value = 'Foo'
        mock_res.return_value = 'registry callable'
        perms = acl.parse_acl('allow {{my_user}} all')
        mock_perms.assert_called_once_with(['all'])
        mock_res.assert_called_once_with('{{my_user}}')
        assert perms == [(Allow, 'registry callable', 'Foo')]


@patch.object(acl, 'parse_acl')
class TestGenerateACL(object):

    def test_no_security(self, mock_parse):
        config = config_mock()
        acl_cls = acl.generate_acl(
            config, model_cls='Foo',
            raml_resource=Mock(security_schemes=[]),
            es_based=True)
        assert acl_cls.item_model == 'Foo'
        assert issubclass(acl_cls, acl.BaseACL)
        instance = acl_cls(request=None)
        assert instance.es_based
        assert instance._collection_acl == []
        assert instance._item_acl == []
        assert not mock_parse.called

    def test_wrong_security_scheme_type(self, mock_parse):
        raml_resource = Mock(security_schemes=[
            Mock(type='x-Foo', settings={'collection': 4, 'item': 7})
        ])
        config = config_mock()
        acl_cls = acl.generate_acl(
            config, model_cls='Foo',
            raml_resource=raml_resource,
            es_based=False)
        assert not mock_parse.called
        assert acl_cls.item_model == 'Foo'
        assert issubclass(acl_cls, acl.BaseACL)
        instance = acl_cls(request=None)
        assert not instance.es_based
        assert instance._collection_acl == []
        assert instance._item_acl == []

    def test_correct_security_scheme(self, mock_parse):
        raml_resource = Mock(security_schemes=[
            Mock(type='x-ACL', settings={'collection': 4, 'item': 7})
        ])
        config = config_mock()
        acl_cls = acl.generate_acl(
            config, model_cls='Foo',
            raml_resource=raml_resource,
            es_based=False)
        mock_parse.assert_has_calls([
            call(acl_string=4),
            call(acl_string=7),
        ])
        instance = acl_cls(request=None)
        assert instance._collection_acl == mock_parse()
        assert instance._item_acl == mock_parse()
        assert not instance.es_based

    def test_database_acls_option(self, mock_parse):
        raml_resource = Mock(security_schemes=[
            Mock(type='x-ACL', settings={'collection': 4, 'item': 7})
        ])
        kwargs = dict(
            model_cls='Foo',
            raml_resource=raml_resource,
            es_based=False,
        )
        config = config_mock()
        config.registry.database_acls = False
        acl_cls = acl.generate_acl(config, **kwargs)
        assert not issubclass(acl_cls, acl.DatabaseACLMixin)
        config.registry.database_acls = True
        acl_cls = acl.generate_acl(config, **kwargs)
        assert issubclass(acl_cls, acl.DatabaseACLMixin)


class TestBaseACL(object):

    def test_init(self):
        obj = acl.BaseACL(request='Foo')
        assert obj.item_model is None
        assert obj._collection_acl == (acl.ALLOW_ALL,)
        assert obj._item_acl == (acl.ALLOW_ALL,)
        assert obj.request == 'Foo'

    def test_apply_callables_no_callables(self):
        obj = acl.BaseACL('req')
        new_acl = obj._apply_callables(
            acl=[('foo', 'bar', 'baz')],
            obj='obj')
        assert new_acl == (('foo', 'bar', 'baz'),)

    @patch.object(acl, 'validate_permissions')
    def test_apply_callables(self, mock_meth):
        mock_meth.return_value = '123'
        principal = Mock(return_value=(7, 8, 9))
        obj = acl.BaseACL('req')
        new_acl = obj._apply_callables(
            acl=[('foo', principal, 'bar')],
            obj='obj')
        assert new_acl == ((7, 8, '123'),)
        principal.assert_called_once_with(
            ace=('foo', principal, 'bar'),
            request='req',
            obj='obj')
        mock_meth.assert_called_once_with(9)

    @patch.object(acl, 'parse_permissions')
    def test_apply_callables_principal_returns_none(self, mock_meth):
        mock_meth.return_value = '123'
        principal = Mock(return_value=None)
        obj = acl.BaseACL('req')
        new_acl = obj._apply_callables(
            acl=[('foo', principal, 'bar')],
            obj='obj')
        assert new_acl == ()
        principal.assert_called_once_with(
            ace=('foo', principal, 'bar'),
            request='req',
            obj='obj')
        assert not mock_meth.called

    @patch.object(acl, 'validate_permissions')
    def test_apply_callables_principal_returns_list(self, mock_meth):
        mock_meth.return_value = '123'
        principal = Mock(return_value=[(7, 8, 9)])
        obj = acl.BaseACL('req')
        new_acl = obj._apply_callables(
            acl=[('foo', principal, 'bar')],
            obj='obj')
        assert new_acl == ((7, 8, '123'),)
        principal.assert_called_once_with(
            ace=('foo', principal, 'bar'),
            request='req',
            obj='obj')
        mock_meth.assert_called_once_with(9)

    def test_apply_callables_functional(self):
        obj = acl.BaseACL('req')
        principal = lambda ace, request, obj: [(Allow, Everyone, 'view')]
        new_acl = obj._apply_callables(
            acl=[(Deny, principal, ALL_PERMISSIONS)],
        )
        assert new_acl == ((Allow, Everyone, ['view']),)

    def test_magic_acl(self):
        obj = acl.BaseACL('req')
        obj._collection_acl = [(1, 2, 3)]
        obj._apply_callables = Mock()
        result = obj.__acl__()
        obj._apply_callables.assert_called_once_with(
            acl=[(1, 2, 3)],
        )
        assert result == obj._apply_callables()

    def test_item_acl(self):
        obj = acl.BaseACL('req')
        obj._item_acl = [(1, 2, 3)]
        obj._apply_callables = Mock()
        result = obj.item_acl('foobar')
        obj._apply_callables.assert_called_once_with(
            acl=[(1, 2, 3)],
            obj='foobar'
        )
        assert result == obj._apply_callables()

    def test_magic_getitem_es_based(self):
        obj = acl.BaseACL('req')
        obj.item_db_id = Mock(return_value=42)
        obj.getitem_es = Mock()
        obj.es_based = True
        obj.__getitem__(1)
        obj.item_db_id.assert_called_once_with(1)
        obj.getitem_es.assert_called_once_with(42)

    def test_magic_getitem_db_based(self):
        obj = acl.BaseACL('req')
        obj.item_db_id = Mock(return_value=42)
        obj.item_model = Mock()
        obj.item_model.pk_field.return_value = 'id'
        obj.es_based = False
        obj.__getitem__(1)
        obj.item_db_id.assert_called_once_with(1)

    @patch('ramses.acl.ES')
    def test_getitem_es(self, mock_es):
        found_obj = Mock()
        es_obj = Mock()
        es_obj.get_item.return_value = found_obj
        mock_es.return_value = es_obj
        obj = acl.BaseACL('req')
        obj.item_model = Mock(__name__='Foo')
        obj.item_model.pk_field.return_value = 'myname'
        obj.item_acl = Mock()
        value = obj.getitem_es(key='varvar')
        mock_es.assert_called_with('Foo')
        es_obj.get_item.assert_called_once_with(id='varvar')
        obj.item_acl.assert_called_once_with(found_obj)
        assert value.__acl__ == obj.item_acl()
        assert value.__parent__ is obj
        assert value.__name__ == 'varvar'


================================================
FILE: tests/test_auth.py
================================================
import pytest
from mock import Mock, patch

from nefertari.utils import dictset
from pyramid.security import Allow, ALL_PERMISSIONS

from .fixtures import engine_mock, guards_engine_mock


@pytest.mark.usefixtures('engine_mock')
class TestACLAssignRegisterMixin(object):
    def _dummy_view(self):
        from ramses import auth

        class DummyBase(object):
            def register(self, *args, **kwargs):
                return 1

        class DummyView(auth.ACLAssignRegisterMixin, DummyBase):
            def __init__(self, *args, **kwargs):
                super(DummyView, self).__init__(*args, **kwargs)
                self.Model = Mock()
                self.request = Mock(_user=Mock())
                self.request.registry._model_collections = {}
        return DummyView

    def test_register_acl_present(self):
        DummyView = self._dummy_view()
        view = DummyView()
        view.request._user._acl = ['a']
        assert view.register() == 1
        assert view.request._user._acl == ['a']

    def test_register_no_model_collection(self):
        DummyView = self._dummy_view()
        view = DummyView()
        view.Model.__name__ = 'Foo'
        view.request._user._acl = []
        assert view.register() == 1
        assert view.request._user._acl == []

    def test_register_acl_set(self, guards_engine_mock):
        DummyView = self._dummy_view()
        view = DummyView()
        view.Model.__name__ = 'Foo'
        resource = Mock()
        view.request.registry._model_collections['Foo'] = resource
        view.request._user._acl = []
        assert view.register() == 1
        factory = resource.view._factory
        factory.assert_called_once_with(view.request)
        factory().generate_item_acl.assert_called_once_with(
            view.request._user)
        guards_engine_mock.ACLField.stringify_acl.assert_called_once_with(
            factory().generate_item_acl())
        view.request._user.update.assert_called_once_with(
            {'_acl': guards_engine_mock.ACLField.stringify_acl()})


@pytest.mark.usefixtures('engine_mock')
class TestSetupTicketPolicy(object):

    def test_no_secret(self):
        from ramses import auth
        with pytest.raises(ValueError) as ex:
            auth._setup_ticket_policy(config='', params={})
        expected = 'Missing required security scheme settings: secret'
        assert expected == str(ex.value)

    @patch('ramses.auth.AuthTktAuthenticationPolicy')
    def test_params_converted(self, mock_policy):
        from ramses import auth
        params = dictset(
            secure=True,
            include_ip=True,
            http_only=False,
            wild_domain=True,
            debug=True,
            parent_domain=True,
            secret='my_secret_setting'
        )
        auth_model = Mock()
        config = Mock()
        config.registry.settings = {'my_secret_setting': 12345}
        config.registry.auth_model = auth_model
        auth._setup_ticket_policy(config=config, params=params)
        mock_policy.assert_called_once_with(
            include_ip=True, secure=True, parent_domain=True,
            callback=auth_model.get_groups_by_userid, secret=12345,
            wild_domain=True, debug=True, http_only=False
        )

    @patch('ramses.auth.AuthTktAuthenticationPolicy')
    def test_request_method_added(self, mock_policy):
        from ramses import auth
        config = Mock()
        config.registry.settings = {'my_secret': 12345}
        config.registry.auth_model = Mock()
        policy = auth._setup_ticket_policy(
            config=config, params={'secret': 'my_secret'})
        config.add_request_method.assert_called_once_with(
            config.registry.auth_model.get_authuser_by_userid,
            'user', reify=True)
        assert policy == mock_policy()

    @patch('ramses.auth.AuthTktAuthenticationPolicy')
    def test_routes_views_added(self, mock_policy):
        from ramses import auth
        config = Mock()
        config.registry.settings = {'my_secret': 12345}
        config.registry.auth_model = Mock()
        root = Mock()
        config.get_root_resource.return_value = root
        auth._setup_ticket_policy(
            config=config, params={'secret': 'my_secret'})
        assert root.add.call_count == 3
        login, logout, register = root.add.call_args_list
        login_kwargs = login[1]
        assert sorted(login_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert login_kwargs['prefix'] == 'auth'
        assert login_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'

        logout_kwargs = logout[1]
        assert sorted(logout_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert logout_kwargs['prefix'] == 'auth'
        assert logout_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'

        register_kwargs = register[1]
        assert sorted(register_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert register_kwargs['prefix'] == 'auth'
        assert register_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'


@pytest.mark.usefixtures('engine_mock')
class TestSetupApiKeyPolicy(object):

    @patch('ramses.auth.ApiKeyAuthenticationPolicy')
    def test_policy_params(self, mock_policy):
        from ramses import auth
        auth_model = Mock()
        config = Mock()
        config.registry.auth_model = auth_model
        policy = auth._setup_apikey_policy(config, {'foo': 'bar'})
        mock_policy.assert_called_once_with(
            foo='bar', check=auth_model.get_groups_by_token,
            credentials_callback=auth_model.get_token_credentials,
            user_model=auth_model,
        )
        assert policy == mock_policy()

    @patch('ramses.auth.ApiKeyAuthenticationPolicy')
    def test_routes_views_added(self, mock_policy):
        from ramses import auth
        auth_model = Mock()
        config = Mock()
        config.registry.auth_model = auth_model
        root = Mock()
        config.get_root_resource.return_value = root
        auth._setup_apikey_policy(config, {})
        assert root.add.call_count == 3
        token, reset_token, register = root.add.call_args_list
        token_kwargs = token[1]
        assert sorted(token_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert token_kwargs['prefix'] == 'auth'
        assert token_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'

        reset_token_kwargs = reset_token[1]
        assert sorted(reset_token_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert reset_token_kwargs['prefix'] == 'auth'
        assert reset_token_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'

        register_kwargs = register[1]
        assert sorted(register_kwargs.keys()) == sorted([
            'view', 'prefix', 'factory'])
        assert register_kwargs['prefix'] == 'auth'
        assert register_kwargs['factory'] == 'nefertari.acl.AuthenticationACL'


@pytest.mark.usefixtures('engine_mock')
class TestSetupAuthPolicies(object):

    def test_not_secured(self):
        from ramses import auth
        raml_data = Mock(secured_by=[None])
        config = Mock()
        auth.setup_auth_policies(config, raml_data)
        assert not config.set_authentication_policy.called
        assert not config.set_authorization_policy.called

    def test_not_defined_security_scheme(self):
        from ramses import auth
        scheme = Mock()
        scheme.name = 'foo'
        raml_data = Mock(secured_by=['zoo'], security_schemes=[scheme])
        with pytest.raises(ValueError) as ex:
            auth.setup_auth_policies('asd', raml_data)
        expected = 'Undefined security scheme used in `secured_by`: zoo'
        assert expected == str(ex.value)

    def test_not_supported_scheme_type(self):
        from ramses import auth
        scheme = Mock(type='asd123')
        scheme.name = 'foo'
        raml_data = Mock(secured_by=['foo'], security_schemes=[scheme])
        with pytest.raises(ValueError) as ex:
            auth.setup_auth_policies(None, raml_data)
        expected = 'Unsupported security scheme type: asd123'
        assert expected == str(ex.value)

    @patch('ramses.auth.ACLAuthorizationPolicy')
    def test_policies_calls(self, mock_acl):
        from ramses import auth
        scheme = Mock(type='mytype', settings={'name': 'user1'})
        scheme.name = 'foo'
        raml_data = Mock(secured_by=['foo'], security_schemes=[scheme])
        config = Mock()
        mock_setup = Mock()
        with patch.dict(auth.AUTHENTICATION_POLICIES, {'mytype': mock_setup}):
            auth.setup_auth_policies(config, raml_data)
        mock_setup.assert_called_once_with(config, {'name': 'user1'})
        config.set_authentication_policy.assert_called_once_with(
            mock_setup())
        mock_acl.assert_called_once_with()
        config.set_authorization_policy.assert_called_once_with(
            mock_acl())


@pytest.mark.usefixtures('engine_mock')
class TestHelperFunctions(object):

    def test_create_system_user_key_error(self):
        from ramses import auth
        config = Mock()
        config.registry.settings = {}
        auth.create_system_user(config)
        assert not config.registry.auth_model.get_or_create.called

    @patch('ramses.auth.transaction')
    @patch('ramses.auth.cryptacular')
    def test_create_system_user_exists(self, mock_crypt, mock_trans):
        from ramses import auth
        encoder = mock_crypt.bcrypt.BCRYPTPasswordManager()
        encoder.encode.return_value = '654321'
        config = Mock()
        config.registry.settings = {
            'system.user': 'user12',
            'system.password': '123456',
            'system.email': 'user12@example.com',
        }
        config.registry.auth_model.get_or_create.return_value = (1, False)
        auth.create_system_user(config)
        assert not mock_trans.commit.called
        encoder.encode.assert_called_once_with('123456')
        config.registry.auth_model.get_or_create.assert_called_once_with(
            username='user12',
            defaults={
                'password': '654321',
                'email': 'user12@example.com',
                'groups': ['admin'],
                '_acl': [(Allow, 'g:admin', ALL_PERMISSIONS)],
            }
        )

    @patch('ramses.auth.transaction')
    @patch('ramses.auth.cryptacular')
    def test_create_system_user_created(self, mock_crypt, mock_trans):
        from ramses import auth
        encoder = mock_crypt.bcrypt.BCRYPTPasswordManager()
        encoder.encode.return_value = '654321'
        config = Mock()
        config.registry.settings = {
            'system.user': 'user12',
            'system.password': '123456',
            'system.email': 'user12@example.com',
        }
        config.registry.auth_model.get_or_create.return_value = (
            Mock(), True)
        auth.create_system_user(config)
        mock_trans.commit.assert_called_once_with()
        encoder.encode.assert_called_once_with('123456')
        config.registry.auth_model.get_or_create.assert_called_once_with(
            username='user12',
            defaults={
                'password': '654321',
                'email': 'user12@example.com',
                'groups': ['admin'],
                '_acl': [(Allow, 'g:admin', ALL_PERMISSIONS)],
            }
        )

    @patch('ramses.auth.create_system_user')
    def test_includeme(self, mock_create):
        from ramses import auth
        auth.includeme(config=1)
        mock_create.assert_called_once_with(1)


================================================
FILE: tests/test_generators.py
================================================
import pytest
from mock import Mock, patch, call

from ramses import generators
from .fixtures import engine_mock, config_mock


class TestHelperFunctions(object):
    @patch.object(generators, 'get_static_parent')
    def test_get_nefertari_parent_resource_no_parent(self, mock_get):
        mock_get.return_value = None
        assert generators._get_nefertari_parent_resource(1, 2, 3) == 3
        mock_get.assert_called_once_with(1)

    @patch.object(generators, 'get_static_parent')
    def test_get_nefertari_parent_resource_parent_not_defined(
            self, mock_get):
        mock_get.return_value = Mock(path='foo')
        assert generators._get_nefertari_parent_resource(
            1, {}, 3) == 3
        mock_get.assert_called_once_with(1)

    @patch.object(generators, 'get_static_parent')
    def test_get_nefertari_parent_resource_parent_defined(
            self, mock_get):
        mock_get.return_value = Mock(path='foo')
        assert generators._get_nefertari_parent_resource(
            1, {'foo': 'bar'}, 3) == 'bar'
        mock_get.assert_called_once_with(1)

    @patch.object(generators, 'generate_resource')
    def test_generate_server_no_resources(self, mock_gen):
        generators.generate_server(Mock(resources=None), 'foo')
        assert not mock_gen.called

    @patch.object(generators, '_get_nefertari_parent_resource')
    @patch.object(generators, 'generate_resource')
    def test_generate_server_resources_generated(
            self, mock_gen, mock_get):
        config = Mock()
        resources = [
            Mock(path='/foo'),
            Mock(path='/bar'),
        ]
        generators.generate_server(Mock(resources=resources), config)
        assert mock_get.call_count == 2
        mock_gen.assert_has_calls([
            call(config, resources[0], mock_get()),
            call(config, resources[1], mock_get()),
        ])

    @patch.object(generators, '_get_nefertari_parent_resource')
    @patch.object(generators, 'generate_resource')
    def test_generate_server_call_per_path(
            self, mock_gen, mock_get):
        config = Mock()
        resources = [
            Mock(path='/foo'),
            Mock(path='/foo'),
        ]
        generators.generate_server(Mock(resources=resources), config)
        assert mock_get.call_count == 1
        mock_gen.assert_called_once_with(config, resources[0], mock_get())


@pytest.mark.usefixtures('engine_mock')
class TestGenerateModels(object):

    @patch('ramses.generators.is_dynamic_uri')
    def test_no_resources(self, mock_dyn):
        generators.generate_models(config=1, raml_resources=[])
        assert not mock_dyn.called

    @patch('ramses.models.handle_model_generation')
    def test_dynamic_uri(self, mock_handle):
        generators.generate_models(
            config=1, raml_resources=[Mock(path='/{id}')])
        assert not mock_handle.called

    @patch('ramses.models.handle_model_generation')
    def test_no_post_resources(self, mock_handle):
        generators.generate_models(config=1, raml_resources=[
            Mock(path='/stories', method='get'),
            Mock(path='/stories', method='options'),
            Mock(path='/stories', method='patch'),
        ])
        assert not mock_handle.called

    @patch('ramses.generators.attr_subresource')
    @patch('ramses.models.handle_model_generation')
    def test_attr_subresource(self, mock_handle, mock_attr):
        mock_attr.return_value = True
        resource = Mock(path='/stories', method='POST')
        generators.generate_models(config=1, raml_resources=[resource])
        assert not mock_handle.called
        mock_attr.assert_called_once_with(resource, 'stories')

    @patch('ramses.generators.attr_subresource')
    @patch('ramses.models.handle_model_generation')
    def test_non_auth_model(self, mock_handle, mock_attr):
        mock_attr.return_value = False
        mock_handle.return_value = ('Foo', False)
        config = Mock()
        resource = Mock(path='/stories', method='POST')
        generators.generate_models(
            config=config, raml_resources=[resource])
        mock_attr.assert_called_once_with(resource, 'stories')
        mock_handle.assert_called_once_with(config, resource)
        assert config.registry.auth_model != 'Foo'

    @patch('ramses.generators.attr_subresource')
    @patch('ramses.models.handle_model_generation')
    def test_auth_model(self, mock_handle, mock_attr):
        mock_attr.return_value = False
        mock_handle.return_value = ('Foo', True)
        config = Mock()
        resource = Mock(path='/stories', method='POST')
        generators.generate_models(
            config=config, raml_resources=[resource])
        mock_attr.assert_called_once_with(resource, 'stories')
        mock_handle.assert_called_once_with(config, resource)
        assert config.registry.auth_model == 'Foo'


class TestGenerateResource(object):
    def test_dynamic_root_parent(self):
        raml_resource = Mock(path='/foobar/{id}')
        parent_resource = Mock(is_root=True)
        config = config_mock()
        with pytest.raises(Exception) as ex:
            generators.generate_resource(
                config, raml_resource, parent_resource)

        expected = ("Top-level resources can't be dynamic and must "
                    "represent collections instead")
        assert str(ex.value) == expected

    def test_dynamic_not_root_parent(self):
        raml_resource = Mock(path='/foobar/{id}')
        parent_resource = Mock(is_root=False)
        config = config_mock()
        new_resource = generators.generate_resource(
            config, raml_resource, parent_resource)
        assert new_resource is None

    @patch('ramses.generators.dynamic_part_name')
    @patch('ramses.generators.singular_subresource')
    @patch('ramses.generators.attr_subresource')
    @patch('ramses.models.get_existing_model')
    @patch('ramses.generators.generate_acl')
    @patch('ramses.generators.resource_view_attrs')
    @patch('ramses.generators.generate_rest_view')
    def test_full_run(
            self, generate_view, view_attrs, generate_acl, get_model,
            attr_res, singular_res, mock_dyn):
        mock_dyn.return_value = 'fooid'
        model_cls = Mock()
        model_cls.pk_field.return_value = 'my_id'
        attr_res.return_value = False
        singular_res.return_value = False
        get_model.return_value = model_cls
        raml_resource = Mock(path='/stories')
        parent_resource = Mock(is_root=False, uid=1)
        config = config_mock()

        res = generators.generate_resource(
            config, raml_resource, parent_resource)
        get_model.assert_called_once_with('Story')
        generate_acl.assert_called_once_with(
            config, model_cls=model_cls, raml_resource=raml_resource)
        mock_dyn.assert_called_once_with(
            raml_resource=raml_resource,
            route_name='stories', pk_field='my_id')
        view_attrs.assert_called_once_with(raml_resource, False)
        generate_view.assert_called_once_with(
            config,
            model_cls=model_cls,
            attrs=view_attrs(),
            attr_view=False,
            singular=False
        )
        parent_resource.add.assert_called_once_with(
            'story', 'stories',
            id_name='fooid',
            factory=generate_acl(),
            view=generate_view()
        )
        assert res == parent_resource.add()

    @patch('ramses.generators.dynamic_part_name')
    @patch('ramses.generators.singular_subresource')
    @patch('ramses.generators.attr_subresource')
    @patch('ramses.models.get_existing_model')
    @patch('ramses.generators.generate_acl')
    @patch('ramses.generators.resource_view_attrs')
    @patch('ramses.generators.generate_rest_view')
    def test_full_run_singular(
            self, generate_view, view_attrs, generate_acl, get_model,
            attr_res, singular_res, mock_dyn):
        mock_dyn.return_value = 'fooid'
        model_cls = Mock()
        model_cls.pk_field.return_value = 'my_id'
        attr_res.return_value = False
        singular_res.return_value = True
        get_model.return_value = model_cls
        raml_resource = Mock(path='/stories')
        parent_resource = Mock(is_root=False, uid=1)
        parent_resource.view.Model.pk_field.return_value = 'other_id'

        config = config_mock()
        res = generators.generate_resource(
            config, raml_resource, parent_resource)
        get_model.assert_called_once_with('Story')
        generate_acl.assert_called_once_with(
            config, model_cls=parent_resource.view.Model,
            raml_resource=raml_resource)
        assert not mock_dyn.called
        view_attrs.assert_called_once_with(raml_resource, True)
        generate_view.assert_called_once_with(
            config,
            model_cls=parent_resource.view.Model,
            attrs=view_attrs(),
            attr_view=False,
            singular=True
        )
        parent_resource.add.assert_called_once_with(
            'story',
            factory=generate_acl(),
            view=generate_view()
        )
        assert res == parent_resource.add()


================================================
FILE: tests/test_models.py
================================================
import pytest
from mock import Mock, patch, call

from .fixtures import engine_mock, config_mock, guards_engine_mock


@pytest.mark.usefixtures('engine_mock')
class TestHelperFunctions(object):

    @patch('ramses.models.engine')
    def test_get_existing_model_not_found(self, mock_eng):
        from ramses import models
        mock_eng.get_document_cls.side_effect = ValueError
        model_cls = models.get_existing_model('Foo')
        assert model_cls is None
        mock_eng.get_document_cls.assert_called_once_with('Foo')

    @patch('ramses.models.engine')
    def test_get_existing_model_found(self, mock_eng):
        from ramses import models
        mock_eng.get_document_cls.return_value = 1
        model_cls = models.get_existing_model('Foo')
        assert model_cls == 1
        mock_eng.get_document_cls.assert_called_once_with('Foo')

    @patch('ramses.models.setup_data_model')
    @patch('ramses.models.get_existing_model')
    def test_prepare_relationship_model_exists(self, mock_get, mock_set):
        from ramses import models
        config = Mock()
        models.prepare_relationship(
            config, 'Story', 'raml_resource')
        mock_get.assert_called_once_with('Story')
        assert not mock_set.called

    @patch('ramses.models.get_existing_model')
    def test_prepare_relationship_resource_not_found(self, mock_get):
        from ramses import models
        config = Mock()
        resource = Mock(root=Mock(resources=[
            Mock(method='get', path='/stories'),
            Mock(method='options', path='/stories'),
            Mock(method='post', path='/items'),
        ]))
        mock_get.return_value = None
        with pytest.raises(ValueError) as ex:
            models.prepare_relationship(config, 'Story', resource)
        expected = ('Model `Story` used in relationship '
                    'is not defined')
        assert str(ex.value) == expected

    @patch('ramses.models.setup_data_model')
    @patch('ramses.models.get_existing_model')
    def test_prepare_relationship_resource_found(
            self, mock_get, mock_set):
        from ramses import models
        config = Mock()
        matching_res = Mock(method='post', path='/stories')
        resource = Mock(root=Mock(resources=[
            matching_res,
            Mock(method='options', path='/stories'),
            Mock(method='post', path='/items'),
        ]))
        mock_get.return_value = None
        config = config_mock()
        models.prepare_relationship(config, 'Story', resource)
        mock_set.assert_called_once_with(config, matching_res, 'Story')

    @patch('ramses.models.resource_schema')
    @patch('ramses.models.get_existing_model')
    def test_setup_data_model_existing_model(self, mock_get, mock_schema):
        from ramses import models
        config = Mock()
        mock_get.return_value = 1
        mock_schema.return_value = {"foo": "bar"}
        model, auth_model = models.setup_data_model(config, 'foo', 'Bar')
        assert not auth_model
        assert model == 1
        mock_get.assert_called_once_with('Bar')

    @patch('ramses.models.resource_schema')
    @patch('ramses.models.get_existing_model')
    def test_setup_data_model_existing_auth_model(self, mock_get, mock_schema):
        from ramses import models
        config = Mock()
        mock_get.return_value = 1
        mock_schema.return_value = {"_auth_model": True}
        model, auth_model = models.setup_data_model(config, 'foo', 'Bar')
        assert auth_model
        assert model == 1
        mock_get.assert_called_once_with('Bar')

    @patch('ramses.models.resource_schema')
    @patch('ramses.models.get_existing_model')
    def test_setup_data_model_no_schema(self, mock_get, mock_schema):
        from ramses import models
        config = Mock()
        mock_get.return_value = None
        mock_schema.return_value = None
        with pytest.raises(Exception) as ex:
            models.setup_data_model(config, 'foo', 'Bar')
        assert str(ex.value) == 'Missing schema for model `Bar`'
        mock_get.assert_called_once_with('Bar')
        mock_schema.assert_called_once_with('foo')

    @patch('ramses.models.resource_schema')
    @patch('ramses.models.generate_model_cls')
    @patch('ramses.models.get_existing_model')
    def test_setup_data_model_success(self, mock_get, mock_gen, mock_schema):
        from ramses import models
        mock_get.return_value = None
        mock_schema.return_value = {'field1': 'val1'}
        config = config_mock()
        model = models.setup_data_model(config, 'foo', 'Bar')
        mock_get.assert_called_once_with('Bar')
        mock_schema.assert_called_once_with('foo')
        mock_gen.assert_called_once_with(
            config,
            schema={'field1': 'val1'},
            model_name='Bar',
            raml_resource='foo')
        assert model == mock_gen()

    @patch('ramses.models.setup_data_model')
    def test_handle_model_generation_value_err(self, mock_set):
        from ramses import models
        config = Mock()
        mock_set.side_effect = ValueError('strange error')
        config = config_mock()
        with pytest.raises(ValueError) as ex:
            raml_resource = Mock(path='/stories')
            models.handle_model_generation(config, raml_resource)
        assert str(ex.value) == 'Story: strange error'
        mock_set.assert_called_once_with(config, raml_resource, 'Story')

    @patch('ramses.models.setup_data_model')
    def test_handle_model_generation(self, mock_set):
        from ramses import models
        config = Mock()
        mock_set.return_value = ('Foo1', True)
        config = config_mock()
        raml_resource = Mock(path='/stories')
        model, auth_model = models.handle_model_generation(
            config, raml_resource)
        mock_set.assert_called_once_with(config, raml_resource, 'Story')
        assert model == 'Foo1'
        assert auth_model


@patch('ramses.models.setup_fields_processors')
@patch('ramses.models.setup_model_event_subscribers')
@patch('ramses.models.registry')
@pytest.mark.usefixtures('engine_mock')
class TestGenerateModelCls(object):

    def _test_schema(self):
        return {
            'properties': {},
            '_auth_model': False,
            '_public_fields': ['public_field1'],
            '_auth_fields': ['auth_field1'],
            '_hidden_fields': ['hidden_field1'],
            '_nested_relationships': ['nested_field1'],
            '_nesting_depth': 3
        }

    @patch('ramses.models.resolve_to_callable')
    def test_simple_case(
            self, mock_res, mock_reg, mock_subscribers, mock_proc):
        from nefertari.authentication.models import AuthModelMethodsMixin
        from ramses import models
        config = config_mock()
        models.engine.FloatField.reset_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            "_db_settings": {
                "type": "float",
                "required": True,
                "default": 0,
            }
        }
        mock_reg.mget.return_value = {'foo': 'bar'}
        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=None)
        assert not auth_model
        assert model_cls.__name__ == 'Story'
        assert hasattr(model_cls, 'progress')
        assert model_cls.__tablename__ == 'story'
        assert model_cls._public_fields == ['public_field1']
        assert model_cls._nesting_depth == 3
        assert model_cls._auth_fields == ['auth_field1']
        assert model_cls._hidden_fields == ['hidden_field1']
        assert model_cls._nested_relationships == ['nested_field1']
        assert model_cls.foo == 'bar'
        assert issubclass(model_cls, models.engine.ESBaseDocument)
        assert not issubclass(model_cls, AuthModelMethodsMixin)
        models.engine.FloatField.assert_called_once_with(
            default=0, required=True)
        mock_reg.mget.assert_called_once_with('Story')
        mock_subscribers.assert_called_once_with(
            config, model_cls, schema)
        mock_proc.assert_called_once_with(
            config, model_cls, schema)

    @patch('ramses.models.resolve_to_callable')
    def test_callable_default(
            self, mock_res, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        models.engine.FloatField.reset_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            "_db_settings": {
                "type": "float",
                "default": "{{foobar}}",
            }
        }
        mock_res.return_value = 1
        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=None)
        models.engine.FloatField.assert_called_with(
            default=1, required=False)
        mock_res.assert_called_once_with('{{foobar}}')

    def test_auth_model(self, mock_reg, mock_subscribers, mock_proc):
        from nefertari.authentication.models import AuthModelMethodsMixin
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {'_db_settings': {}}
        schema['_auth_model'] = True
        mock_reg.mget.return_value = {'foo': 'bar'}

        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=None)
        assert auth_model
        assert issubclass(model_cls, AuthModelMethodsMixin)

    def test_database_acls_option(
            self, mock_reg, mock_subscribers, mock_proc,
            guards_engine_mock):
        from ramses import models
        schema = self._test_schema()
        schema['properties']['progress'] = {'_db_settings': {}}
        schema['_auth_model'] = True
        mock_reg.mget.return_value = {'foo': 'bar'}
        config = config_mock()

        config.registry.database_acls = False
        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story1',
            raml_resource=None)
        assert not issubclass(model_cls, guards_engine_mock.DocumentACLMixin)

        config.registry.database_acls = True
        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story2',
            raml_resource=None)
        assert issubclass(model_cls, guards_engine_mock.DocumentACLMixin)

    def test_db_based_model(self, mock_reg, mock_subscribers, mock_proc):
        from nefertari.authentication.models import AuthModelMethodsMixin
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {'_db_settings': {}}
        mock_reg.mget.return_value = {'foo': 'bar'}

        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=None, es_based=False)
        assert issubclass(model_cls, models.engine.BaseDocument)
        assert not issubclass(model_cls, models.engine.ESBaseDocument)
        assert not issubclass(model_cls, AuthModelMethodsMixin)

    def test_no_db_settings(self, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {'type': 'pickle'}
        mock_reg.mget.return_value = {'foo': 'bar'}

        model_cls, auth_model = models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=None, es_based=False)
        assert not models.engine.PickleField.called

    def test_unknown_field_type(
            self, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            '_db_settings': {'type': 'foobar'}}
        mock_reg.mget.return_value = {'foo': 'bar'}

        with pytest.raises(ValueError) as ex:
            models.generate_model_cls(
                config, schema=schema, model_name='Story',
                raml_resource=None)
        assert str(ex.value) == 'Unknown type: foobar'

    @patch('ramses.models.prepare_relationship')
    def test_relationship_field(
            self, mock_prep, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = Mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            '_db_settings': {
                'type': 'relationship',
                'document': 'FooBar',
            }
        }
        mock_reg.mget.return_value = {'foo': 'bar'}
        config = config_mock()
        models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=1)
        mock_prep.assert_called_once_with(
            config, 'FooBar', 1)

    def test_foreignkey_field(
            self, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            "_db_settings": {
                "type": "foreign_key",
                "ref_column_type": "string"
            }
        }
        mock_reg.mget.return_value = {'foo': 'bar'}
        models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=1)
        models.engine.ForeignKeyField.assert_called_once_with(
            required=False, ref_column_type=models.engine.StringField)

    def test_list_field(self, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['progress'] = {
            "_db_settings": {
                "type": "list",
                "item_type": "integer"
            }
        }
        mock_reg.mget.return_value = {'foo': 'bar'}
        models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=1)
        models.engine.ListField.assert_called_once_with(
            required=False, item_type=models.engine.IntegerField)

    def test_duplicate_field_name(
            self, mock_reg, mock_subscribers, mock_proc):
        from ramses import models
        config = config_mock()
        schema = self._test_schema()
        schema['properties']['_public_fields'] = {
            '_db_settings': {'type': 'interval'}}
        mock_reg.mget.return_value = {'foo': 'bar'}
        models.generate_model_cls(
            config, schema=schema, model_name='Story',
            raml_resource=1)
        assert not models.engine.IntervalField.called


class TestSubscribersSetup(object):

    @patch('ramses.models.resolve_to_callable')
    @patch('ramses.models.get_events_map')
    def test_setup_model_event_subscribers(self, mock_get, mock_resolve):
        from ramses import models
        mock_get.return_value = {'before': {'index': 'eventcls'}}
        mock_resolve.return_value = 1
        config = Mock()
        model_cls = 'mymodel'
        schema = {
            '_event_handlers': {
                'before_index': ['func1', 'func2']
            }
        }
        models.setup_model_event_subscribers(config, model_cls, schema)
        mock_get.assert_called_once_with()
        mock_resolve.assert_has_calls([call('func1'), call('func2')])
        config.subscribe_to_events.assert_has_calls([
            call(mock_resolve(), ['eventcls'], model='mymodel'),
            call(mock_resolve(), ['eventcls'], model='mymodel'),
        ])

    @patch('ramses.models.resolve_to_callable')
    @patch('ramses.models.engine')
    def test_setup_fields_processors(self, mock_eng, mock_resolve):
        from ramses import models
        config = Mock()
        schema = {
            'properties': {
                'stories': {
                    "_db_settings": {
                        "type": "relationship",
                        "document": "Story",
                        "backref_name": "owner",
                    },
                    "_processors": ["lowercase"],
                    "_backref_processors": ["backref_lowercase"]
                }
            }
        }

        models.setup_fields_processors(config, 'mymodel', schema)

        mock_resolve.assert_has_calls([
            call('lowercase'), call('backref_lowercase')])
        mock_eng.get_document_cls.assert_called_once_with('Story')
        config.add_field_processors.assert_has_calls([
            call([mock_resolve()], model='mymodel', field='stories'),
            call([mock_resolve()], model=mock_eng.get_document_cls(),
                 field='owner'),
        ])

    @patch('ramses.models.resolve_to_callable')
    @patch('ramses.models.engine')
    def test_setup_fields_processors_backref_not_rel(
            self, mock_eng, mock_resolve):
        from ramses import models
        config = Mock()
        schema = {
            'properties': {
                'stories': {
                    "_db_settings": {
                        "type": "wqeqweqwe",
                        "document": "Story",
                        "backref_name": "owner",
                    },
                    "_backref_processors": ["backref_lowercase"]
                }
            }
        }
        models.setup_fields_processors(config, 'mymodel', schema)
        assert not config.add_field_processors.called

    @patch('ramses.models.resolve_to_callable')
    @patch('ramses.models.engine')
    def test_setup_fields_processors_backref_no_doc(
            self, mock_eng, mock_resolve):
        from ramses import models
        config = Mock()
        schema = {
            'properties': {
                'stories': {
                    "_db_settings": {
                        "type": "relationship",
                        "backref_name": "owner",
                    },
                    "_backref_processors": ["backref_lowercase"]
                }
            }
        }
        models.setup_fields_processors(config, 'mymodel', schema)
        assert not config.add_field_processors.called

    @patch('ramses.models.resolve_to_callable')
    @patch('ramses.models.engine')
    def test_setup_fields_processors_backref_no_backname(
            self, mock_eng, mock_resolve):
        from ramses import models
        config = Mock()
        schema = {
            'properties': {
                'stories': {
                    "_db_settings": {
                        "type": "relationship",
                        "document": "Story",
                    },
                    "_backref_processors": ["backref_lowercase"]
                }
            }
        }
        models.setup_fields_processors(config, 'mymodel', schema)
        assert not config.add_field_processors.called


================================================
FILE: tests/test_registry.py
================================================
import pytest

from .fixtures import clear_registry
from ramses import registry


@pytest.mark.usefixtures('clear_registry')
class TestRegistry(object):

    def test_add_decorator(self):
        @registry.add
        def foo(*args, **kwargs):
            return args, kwargs

        assert registry.registry['foo'] is foo
        assert list(registry.registry.keys()) == ['foo']

    def test_add_decorator_with_name(self):
        @registry.add('bar')
        def foo(*args, **kwargs):
            return args, kwargs

        assert registry.registry['bar'] is foo
        assert list(registry.reg
Download .txt
gitextract_t7nlq2go/

├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── VERSION
├── docs/
│   ├── Makefile
│   └── source/
│       ├── changelog.rst
│       ├── conf.py
│       ├── event_handlers.rst
│       ├── field_processors.rst
│       ├── fields.rst
│       ├── getting_started.rst
│       ├── index.rst
│       ├── raml.rst
│       ├── relationships.rst
│       └── schemas.rst
├── ramses/
│   ├── __init__.py
│   ├── acl.py
│   ├── auth.py
│   ├── generators.py
│   ├── models.py
│   ├── registry.py
│   ├── scaffolds/
│   │   ├── __init__.py
│   │   └── ramses_starter/
│   │       ├── +package+/
│   │       │   ├── __init__.py
│   │       │   └── tests/
│   │       │       ├── __init__.py
│   │       │       ├── api.raml
│   │       │       ├── items.json
│   │       │       ├── requirements.txt
│   │       │       └── test_api.py_tmpl
│   │       ├── .gitignore_tmpl
│   │       ├── README.md
│   │       ├── api.raml_tmpl
│   │       ├── items.json
│   │       ├── local.ini_tmpl
│   │       ├── requirements.txt
│   │       └── setup.py_tmpl
│   ├── scripts/
│   │   ├── __init__.py
│   │   └── scaffold_test.py
│   ├── utils.py
│   └── views.py
├── requirements.dev
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── fixtures.py
│   ├── test_acl.py
│   ├── test_auth.py
│   ├── test_generators.py
│   ├── test_models.py
│   ├── test_registry.py
│   ├── test_utils.py
│   └── test_views.py
└── tox.ini
Download .txt
SYMBOL INDEX (349 symbols across 19 files)

FILE: ramses/__init__.py
  function includeme (line 11) | def includeme(config):

FILE: ramses/acl.py
  function validate_permissions (line 29) | def validate_permissions(perms):
  function parse_permissions (line 46) | def parse_permissions(perms):
  function parse_acl (line 61) | def parse_acl(acl_string):
  class BaseACL (line 110) | class BaseACL(CollectionACL):
    method _apply_callables (line 117) | def _apply_callables(self, acl, obj=None):
    method __acl__ (line 146) | def __acl__(self):
    method generate_item_acl (line 150) | def generate_item_acl(self, item):
    method item_acl (line 158) | def item_acl(self, item):
    method item_db_id (line 162) | def item_db_id(self, key):
    method __getitem__ (line 171) | def __getitem__(self, key):
    method getitem_es (line 177) | def getitem_es(self, key):
  class DatabaseACLMixin (line 186) | class DatabaseACLMixin(object):
    method item_acl (line 189) | def item_acl(self, item):
    method getitem_es (line 198) | def getitem_es(self, key):
  function generate_acl (line 217) | def generate_acl(config, model_cls, raml_resource, es_based=True):

FILE: ramses/auth.py
  class ACLAssignRegisterMixin (line 28) | class ACLAssignRegisterMixin(object):
    method register (line 30) | def register(self, *args, **kwargs):
  function _setup_ticket_policy (line 46) | def _setup_ticket_policy(config, params):
  function _setup_apikey_policy (line 105) | def _setup_apikey_policy(config, params):
  function setup_auth_policies (line 178) | def setup_auth_policies(config, raml_root):
  function create_system_user (line 220) | def create_system_user(config):
  function get_authuser_model (line 245) | def get_authuser_model():
  function includeme (line 256) | def includeme(config):

FILE: ramses/generators.py
  function _get_nefertari_parent_resource (line 23) | def _get_nefertari_parent_resource(
  function generate_resource (line 32) | def generate_resource(config, raml_resource, parent_resource):
  function generate_server (line 125) | def generate_server(raml_root, config):
  function generate_models (line 154) | def generate_models(config, raml_resources):

FILE: ramses/models.py
  function get_existing_model (line 45) | def get_existing_model(model_name):
  function prepare_relationship (line 59) | def prepare_relationship(config, model_name, raml_resource):
  function generate_model_cls (line 85) | def generate_model_cls(config, schema, model_name, raml_resource,
  function setup_data_model (line 172) | def setup_data_model(config, raml_resource, model_name):
  function handle_model_generation (line 201) | def handle_model_generation(config, raml_resource):
  function setup_model_event_subscribers (line 214) | def setup_model_event_subscribers(config, model_cls, schema):
  function setup_fields_processors (line 238) | def setup_fields_processors(config, model_cls, schema):

FILE: ramses/registry.py
  class Registry (line 56) | class Registry(dict):
  function add (line 63) | def add(*args):
  function get (line 79) | def get(name):
  function mget (line 88) | def mget(namespace):

FILE: ramses/scaffolds/__init__.py
  class RamsesStarterTemplate (line 8) | class RamsesStarterTemplate(PyramidTemplate):
    method pre (line 12) | def pre(self, command, output_dir, vars):
    method post (line 27) | def post(self, command, output_dir, vars):

FILE: ramses/scaffolds/ramses_starter/+package+/__init__.py
  function main (line 4) | def main(global_config, **settings):

FILE: ramses/scripts/scaffold_test.py
  class ScaffoldTestCommand (line 5) | class ScaffoldTestCommand(NefTestCommand):
  function main (line 9) | def main(*args, **kwargs):

FILE: ramses/utils.py
  class ContentTypes (line 12) | class ContentTypes(object):
  function convert_schema (line 22) | def convert_schema(raml_schema, mime_type):
  function is_dynamic_uri (line 57) | def is_dynamic_uri(uri):
  function clean_dynamic_uri (line 68) | def clean_dynamic_uri(uri):
  function generate_model_name (line 76) | def generate_model_name(raml_resource):
  function dynamic_part_name (line 87) | def dynamic_part_name(raml_resource, route_name, pk_field):
  function extract_dynamic_part (line 112) | def extract_dynamic_part(uri):
  function resource_view_attrs (line 123) | def resource_view_attrs(raml_resource, singular=False):
  function resource_schema (line 159) | def resource_schema(raml_resource):
  function is_dynamic_resource (line 181) | def is_dynamic_resource(raml_resource):
  function get_static_parent (line 189) | def get_static_parent(raml_resource, method=None):
  function attr_subresource (line 217) | def attr_subresource(raml_resource, route_name):
  function singular_subresource (line 234) | def singular_subresource(raml_resource, route_name):
  function is_callable_tag (line 254) | def is_callable_tag(tag):
  function resolve_to_callable (line 267) | def resolve_to_callable(callable_name):
  function get_resource_siblings (line 288) | def get_resource_siblings(raml_resource):
  function get_resource_children (line 298) | def get_resource_children(raml_resource):
  function get_events_map (line 308) | def get_events_map():
  function patch_view_model (line 328) | def patch_view_model(view_cls, model_cls):
  function get_route_name (line 345) | def get_route_name(resource_uri):
  function get_resource_uri (line 357) | def get_resource_uri(raml_resource):

FILE: ramses/views.py
  class SetObjectACLMixin (line 36) | class SetObjectACLMixin(object):
    method set_object_acl (line 37) | def set_object_acl(self, obj):
  class BaseView (line 45) | class BaseView(object):
    method clean_id_name (line 52) | def clean_id_name(self):
    method set_object_acl (line 59) | def set_object_acl(self, obj):
    method resolve_kw (line 62) | def resolve_kw(self, kwargs):
    method _location (line 74) | def _location(self, obj):
    method _parent_queryset (line 85) | def _parent_queryset(self):
    method get_collection (line 103) | def get_collection(self, **kwargs):
    method get_item (line 119) | def get_item(self, **kwargs):
    method _get_context_key (line 141) | def _get_context_key(self, **kwargs):
    method reload_context (line 145) | def reload_context(self, es_based, **kwargs):
  class CollectionView (line 171) | class CollectionView(BaseView):
    method index (line 176) | def index(self, **kwargs):
    method show (line 179) | def show(self, **kwargs):
    method create (line 182) | def create(self, **kwargs):
    method update (line 187) | def update(self, **kwargs):
    method replace (line 191) | def replace(self, **kwargs):
    method delete (line 194) | def delete(self, **kwargs):
    method delete_many (line 198) | def delete_many(self, **kwargs):
    method update_many (line 202) | def update_many(self, **kwargs):
  class ESBaseView (line 208) | class ESBaseView(BaseView):
    method _parent_queryset_es (line 218) | def _parent_queryset_es(self):
    method get_es_object_ids (line 236) | def get_es_object_ids(self, objects):
    method get_collection_es (line 242) | def get_collection_es(self):
    method get_item_es (line 261) | def get_item_es(self, **kwargs):
  class ESCollectionView (line 287) | class ESCollectionView(ESBaseView, CollectionView):
    method index (line 292) | def index(self, **kwargs):
    method show (line 295) | def show(self, **kwargs):
    method update (line 298) | def update(self, **kwargs):
    method delete (line 305) | def delete(self, **kwargs):
    method get_dbcollection_with_es (line 312) | def get_dbcollection_with_es(self, **kwargs):
    method delete_many (line 318) | def delete_many(self, **kwargs):
    method update_many (line 328) | def update_many(self, **kwargs):
  class ItemSubresourceBaseView (line 340) | class ItemSubresourceBaseView(BaseView):
    method _get_context_key (line 359) | def _get_context_key(self, **kwargs):
    method get_item (line 363) | def get_item(self, **kwargs):
  class ItemAttributeView (line 369) | class ItemAttributeView(ItemSubresourceBaseView):
    method __init__ (line 378) | def __init__(self, *args, **kw):
    method index (line 384) | def index(self, **kwargs):
    method create (line 388) | def create(self, **kwargs):
  class ItemSingularView (line 398) | class ItemSingularView(ItemSubresourceBaseView):
    method __init__ (line 412) | def __init__(self, *args, **kw):
    method get_item (line 416) | def get_item(self, **kwargs):
    method show (line 420) | def show(self, **kwargs):
    method create (line 424) | def create(self, **kwargs):
    method update (line 432) | def update(self, **kwargs):
    method replace (line 438) | def replace(self, **kwargs):
    method delete (line 441) | def delete(self, **kwargs):
  function generate_rest_view (line 447) | def generate_rest_view(config, model_cls, attrs=None, es_based=True,

FILE: tests/fixtures.py
  function clear_registry (line 5) | def clear_registry(request):
  function engine_mock (line 11) | def engine_mock(request):
  function guards_engine_mock (line 34) | def guards_engine_mock(request):
  function config_mock (line 53) | def config_mock():

FILE: tests/test_acl.py
  class TestACLHelpers (line 13) | class TestACLHelpers(object):
    method test_validate_permissions_all_perms (line 14) | def test_validate_permissions_all_perms(self):
    method test_validate_permissions_valid (line 19) | def test_validate_permissions_valid(self):
    method test_validate_permissions_invalid (line 23) | def test_validate_permissions_invalid(self):
    method test_parse_permissions_all_permissions (line 28) | def test_parse_permissions_all_permissions(self):
    method test_parse_permissions_invalid_perm_name (line 32) | def test_parse_permissions_invalid_perm_name(self):
    method test_parse_permissions (line 39) | def test_parse_permissions(self):
    method test_parse_acl_no_string (line 45) | def test_parse_acl_no_string(self):
    method test_parse_acl_unknown_action (line 49) | def test_parse_acl_unknown_action(self):
    method test_parse_acl_multiple_values (line 55) | def test_parse_acl_multiple_values(self, mock_perms):
    method test_parse_acl_special_principal (line 69) | def test_parse_acl_special_principal(self, mock_perms):
    method test_parse_acl_group_principal (line 76) | def test_parse_acl_group_principal(self, mock_perms):
    method test_parse_acl_callable_principal (line 84) | def test_parse_acl_callable_principal(self, mock_perms, mock_res):
  class TestGenerateACL (line 94) | class TestGenerateACL(object):
    method test_no_security (line 96) | def test_no_security(self, mock_parse):
    method test_wrong_security_scheme_type (line 110) | def test_wrong_security_scheme_type(self, mock_parse):
    method test_correct_security_scheme (line 127) | def test_correct_security_scheme(self, mock_parse):
    method test_database_acls_option (line 145) | def test_database_acls_option(self, mock_parse):
  class TestBaseACL (line 163) | class TestBaseACL(object):
    method test_init (line 165) | def test_init(self):
    method test_apply_callables_no_callables (line 172) | def test_apply_callables_no_callables(self):
    method test_apply_callables (line 180) | def test_apply_callables(self, mock_meth):
    method test_apply_callables_principal_returns_none (line 195) | def test_apply_callables_principal_returns_none(self, mock_meth):
    method test_apply_callables_principal_returns_list (line 210) | def test_apply_callables_principal_returns_list(self, mock_meth):
    method test_apply_callables_functional (line 224) | def test_apply_callables_functional(self):
    method test_magic_acl (line 232) | def test_magic_acl(self):
    method test_item_acl (line 242) | def test_item_acl(self):
    method test_magic_getitem_es_based (line 253) | def test_magic_getitem_es_based(self):
    method test_magic_getitem_db_based (line 262) | def test_magic_getitem_db_based(self):
    method test_getitem_es (line 272) | def test_getitem_es(self, mock_es):

FILE: tests/test_auth.py
  class TestACLAssignRegisterMixin (line 11) | class TestACLAssignRegisterMixin(object):
    method _dummy_view (line 12) | def _dummy_view(self):
    method test_register_acl_present (line 27) | def test_register_acl_present(self):
    method test_register_no_model_collection (line 34) | def test_register_no_model_collection(self):
    method test_register_acl_set (line 42) | def test_register_acl_set(self, guards_engine_mock):
  class TestSetupTicketPolicy (line 61) | class TestSetupTicketPolicy(object):
    method test_no_secret (line 63) | def test_no_secret(self):
    method test_params_converted (line 71) | def test_params_converted(self, mock_policy):
    method test_request_method_added (line 94) | def test_request_method_added(self, mock_policy):
    method test_routes_views_added (line 107) | def test_routes_views_added(self, mock_policy):
  class TestSetupApiKeyPolicy (line 138) | class TestSetupApiKeyPolicy(object):
    method test_policy_params (line 141) | def test_policy_params(self, mock_policy):
    method test_routes_views_added (line 155) | def test_routes_views_added(self, mock_policy):
  class TestSetupAuthPolicies (line 185) | class TestSetupAuthPolicies(object):
    method test_not_secured (line 187) | def test_not_secured(self):
    method test_not_defined_security_scheme (line 195) | def test_not_defined_security_scheme(self):
    method test_not_supported_scheme_type (line 205) | def test_not_supported_scheme_type(self):
    method test_policies_calls (line 216) | def test_policies_calls(self, mock_acl):
  class TestHelperFunctions (line 234) | class TestHelperFunctions(object):
    method test_create_system_user_key_error (line 236) | def test_create_system_user_key_error(self):
    method test_create_system_user_exists (line 245) | def test_create_system_user_exists(self, mock_crypt, mock_trans):
    method test_create_system_user_created (line 271) | def test_create_system_user_created(self, mock_crypt, mock_trans):
    method test_includeme (line 297) | def test_includeme(self, mock_create):

FILE: tests/test_generators.py
  class TestHelperFunctions (line 8) | class TestHelperFunctions(object):
    method test_get_nefertari_parent_resource_no_parent (line 10) | def test_get_nefertari_parent_resource_no_parent(self, mock_get):
    method test_get_nefertari_parent_resource_parent_not_defined (line 16) | def test_get_nefertari_parent_resource_parent_not_defined(
    method test_get_nefertari_parent_resource_parent_defined (line 24) | def test_get_nefertari_parent_resource_parent_defined(
    method test_generate_server_no_resources (line 32) | def test_generate_server_no_resources(self, mock_gen):
    method test_generate_server_resources_generated (line 38) | def test_generate_server_resources_generated(
    method test_generate_server_call_per_path (line 54) | def test_generate_server_call_per_path(
  class TestGenerateModels (line 67) | class TestGenerateModels(object):
    method test_no_resources (line 70) | def test_no_resources(self, mock_dyn):
    method test_dynamic_uri (line 75) | def test_dynamic_uri(self, mock_handle):
    method test_no_post_resources (line 81) | def test_no_post_resources(self, mock_handle):
    method test_attr_subresource (line 91) | def test_attr_subresource(self, mock_handle, mock_attr):
    method test_non_auth_model (line 100) | def test_non_auth_model(self, mock_handle, mock_attr):
    method test_auth_model (line 113) | def test_auth_model(self, mock_handle, mock_attr):
  class TestGenerateResource (line 125) | class TestGenerateResource(object):
    method test_dynamic_root_parent (line 126) | def test_dynamic_root_parent(self):
    method test_dynamic_not_root_parent (line 138) | def test_dynamic_not_root_parent(self):
    method test_full_run (line 153) | def test_full_run(
    method test_full_run_singular (line 197) | def test_full_run_singular(

FILE: tests/test_models.py
  class TestHelperFunctions (line 8) | class TestHelperFunctions(object):
    method test_get_existing_model_not_found (line 11) | def test_get_existing_model_not_found(self, mock_eng):
    method test_get_existing_model_found (line 19) | def test_get_existing_model_found(self, mock_eng):
    method test_prepare_relationship_model_exists (line 28) | def test_prepare_relationship_model_exists(self, mock_get, mock_set):
    method test_prepare_relationship_resource_not_found (line 37) | def test_prepare_relationship_resource_not_found(self, mock_get):
    method test_prepare_relationship_resource_found (line 54) | def test_prepare_relationship_resource_found(
    method test_setup_data_model_existing_model (line 71) | def test_setup_data_model_existing_model(self, mock_get, mock_schema):
    method test_setup_data_model_existing_auth_model (line 83) | def test_setup_data_model_existing_auth_model(self, mock_get, mock_sch...
    method test_setup_data_model_no_schema (line 95) | def test_setup_data_model_no_schema(self, mock_get, mock_schema):
    method test_setup_data_model_success (line 109) | def test_setup_data_model_success(self, mock_get, mock_gen, mock_schema):
    method test_handle_model_generation_value_err (line 125) | def test_handle_model_generation_value_err(self, mock_set):
    method test_handle_model_generation (line 137) | def test_handle_model_generation(self, mock_set):
  class TestGenerateModelCls (line 154) | class TestGenerateModelCls(object):
    method _test_schema (line 156) | def _test_schema(self):
    method test_simple_case (line 168) | def test_simple_case(
    method test_callable_default (line 207) | def test_callable_default(
    method test_auth_model (line 227) | def test_auth_model(self, mock_reg, mock_subscribers, mock_proc):
    method test_database_acls_option (line 242) | def test_database_acls_option(
    method test_db_based_model (line 264) | def test_db_based_model(self, mock_reg, mock_subscribers, mock_proc):
    method test_no_db_settings (line 279) | def test_no_db_settings(self, mock_reg, mock_subscribers, mock_proc):
    method test_unknown_field_type (line 291) | def test_unknown_field_type(
    method test_relationship_field (line 307) | def test_relationship_field(
    method test_foreignkey_field (line 326) | def test_foreignkey_field(
    method test_list_field (line 344) | def test_list_field(self, mock_reg, mock_subscribers, mock_proc):
    method test_duplicate_field_name (line 361) | def test_duplicate_field_name(
  class TestSubscribersSetup (line 375) | class TestSubscribersSetup(object):
    method test_setup_model_event_subscribers (line 379) | def test_setup_model_event_subscribers(self, mock_get, mock_resolve):
    method test_setup_fields_processors (line 400) | def test_setup_fields_processors(self, mock_eng, mock_resolve):
    method test_setup_fields_processors_backref_not_rel (line 430) | def test_setup_fields_processors_backref_not_rel(
    method test_setup_fields_processors_backref_no_doc (line 451) | def test_setup_fields_processors_backref_no_doc(
    method test_setup_fields_processors_backref_no_backname (line 471) | def test_setup_fields_processors_backref_no_backname(

FILE: tests/test_registry.py
  class TestRegistry (line 8) | class TestRegistry(object):
    method test_add_decorator (line 10) | def test_add_decorator(self):
    method test_add_decorator_with_name (line 18) | def test_add_decorator_with_name(self):
    method test_add_arbitrary_object (line 26) | def test_add_arbitrary_object(self):
    method test_get (line 34) | def test_get(self):
    method test_get_error (line 38) | def test_get_error(self):
    method test_mget (line 44) | def test_mget(self):
    method test_mget_not_existing (line 49) | def test_mget_not_existing(self):

FILE: tests/test_utils.py
  class TestUtils (line 7) | class TestUtils(object):
    method test_contenttypes (line 9) | def test_contenttypes(self):
    method test_convert_schema_json (line 17) | def test_convert_schema_json(self):
    method test_convert_schema_json_error (line 21) | def test_convert_schema_json_error(self):
    method test_convert_schema_xml (line 26) | def test_convert_schema_xml(self):
    method test_is_dynamic_uri (line 29) | def test_is_dynamic_uri(self):
    method test_clean_dynamic_uri (line 33) | def test_clean_dynamic_uri(self):
    method test_generate_model_name (line 37) | def test_generate_model_name(self):
    method test_dynamic_part_name (line 43) | def test_dynamic_part_name(self, get_children):
    method test_dynamic_part_name_no_dynamic (line 53) | def test_dynamic_part_name_no_dynamic(self, get_children):
    method test_dynamic_part_name_no_resources (line 62) | def test_dynamic_part_name_no_resources(self, get_children):
    method test_extract_dynamic_part (line 70) | def test_extract_dynamic_part(self):
    method test_extract_dynamic_part_fail (line 74) | def test_extract_dynamic_part_fail(self):
    method _get_mock_method_resources (line 77) | def _get_mock_method_resources(self, *methods):
    method test_resource_view_attrs_no_dynamic_subres (line 82) | def test_resource_view_attrs_no_dynamic_subres(self, get_sib, get_child):
    method test_resource_view_attrs_dynamic_subres (line 94) | def test_resource_view_attrs_dynamic_subres(self, get_sib, get_child):
    method test_resource_view_attrs_singular (line 110) | def test_resource_view_attrs_singular(self, get_sib, get_child):
    method test_resource_view_attrs_no_subresources (line 122) | def test_resource_view_attrs_no_subresources(self, get_sib, get_child):
    method test_resource_view_attrs_no_methods (line 136) | def test_resource_view_attrs_no_methods(self, get_sib, get_child):
    method test_resource_view_attrs_not_supported_method (line 147) | def test_resource_view_attrs_not_supported_method(
    method test_resource_schema_no_body (line 156) | def test_resource_schema_no_body(self):
    method test_resource_schema_no_schemas (line 163) | def test_resource_schema_no_schemas(self):
    method test_resource_schema_success (line 167) | def test_resource_schema_success(self):
    method test_is_dynamic_resource_no_resource (line 174) | def test_is_dynamic_resource_no_resource(self):
    method test_is_dynamic_resource_dynamic (line 177) | def test_is_dynamic_resource_dynamic(self):
    method test_is_dynamic_resource_not_dynamic (line 181) | def test_is_dynamic_resource_not_dynamic(self):
    method test_get_static_parent (line 185) | def test_get_static_parent(self):
    method test_get_static_parent_none (line 191) | def test_get_static_parent_none(self):
    method test_get_static_parent_wrong_parent_method (line 196) | def test_get_static_parent_wrong_parent_method(self):
    method test_get_static_parent_without_method_parent_present (line 209) | def test_get_static_parent_without_method_parent_present(self):
    method test_get_static_parent_none_found_in_root (line 221) | def test_get_static_parent_none_found_in_root(self):
    method test_attr_subresource_no_static_parent (line 232) | def test_attr_subresource_no_static_parent(self, mock_schema, mock_par):
    method test_attr_subresource_no_schema (line 239) | def test_attr_subresource_no_schema(self, mock_schema, mock_par):
    method test_attr_subresource_not_attr (line 249) | def test_attr_subresource_not_attr(self, mock_schema, mock_par):
    method test_attr_subresource_dict (line 267) | def test_attr_subresource_dict(self, mock_schema, mock_par):
    method test_singular_subresource_no_static_parent (line 291) | def test_singular_subresource_no_static_parent(self, mock_schema, mock...
    method test_singular_subresource_no_schema (line 298) | def test_singular_subresource_no_schema(self, mock_schema, mock_par):
    method test_singular_subresource_not_attr (line 308) | def test_singular_subresource_not_attr(self, mock_schema, mock_par):
    method test_singular_subresource_dict (line 326) | def test_singular_subresource_dict(self, mock_schema, mock_par):
    method test_is_callable_tag_not_str (line 343) | def test_is_callable_tag_not_str(self):
    method test_is_callable_tag_not_tag (line 347) | def test_is_callable_tag_not_tag(self):
    method test_is_callable_tag (line 350) | def test_is_callable_tag(self):
    method test_resolve_to_callable_not_found (line 353) | def test_resolve_to_callable_not_found(self):
    method test_resolve_to_callable_registry (line 358) | def test_resolve_to_callable_registry(self):
    method test_resolve_to_callable_dotted_path (line 370) | def test_resolve_to_callable_dotted_path(self):
    method test_get_events_map (line 377) | def test_get_events_map(self):
    method test_patch_view_model (line 401) | def test_patch_view_model(self):
    method test_get_route_name (line 414) | def test_get_route_name(self):
    method test_get_resource_uri (line 418) | def test_get_resource_uri(self):

FILE: tests/test_views.py
  class ViewTestBase (line 12) | class ViewTestBase(object):
    method _test_view (line 24) | def _test_view(self):
  class TestSetObjectACLMixin (line 32) | class TestSetObjectACLMixin(object):
    method test_set_object_acl (line 33) | def test_set_object_acl(self, guards_engine_mock):
  class TestBaseView (line 47) | class TestBaseView(ViewTestBase):
    method test_init (line 50) | def test_init(self):
    method test_clean_id_name (line 54) | def test_clean_id_name(self):
    method test_resolve_kw (line 61) | def test_resolve_kw(self):
    method test_location (line 66) | def test_location(self):
    method test_location_split_id (line 73) | def test_location_split_id(self):
    method test_get_collection_has_parent (line 80) | def test_get_collection_has_parent(self):
    method test_get_collection_has_parent_empty_queryset (line 89) | def test_get_collection_has_parent_empty_queryset(self):
    method test_get_collection_no_parent (line 98) | def test_get_collection_no_parent(self):
    method test_get_item_no_parent (line 108) | def test_get_item_no_parent(self):
    method test_get_item_not_found_in_parent (line 114) | def test_get_item_not_found_in_parent(self):
    method test_get_item_found_in_parent (line 123) | def test_get_item_found_in_parent(self):
    method test_get_item_found_in_parent_context_callable (line 129) | def test_get_item_found_in_parent_context_callable(self):
    method test_get_context_key (line 139) | def test_get_context_key(self):
    method test_parent_queryset (line 144) | def test_parent_queryset(self):
    method test_reload_context (line 184) | def test_reload_context(self):
  class TestCollectionView (line 199) | class TestCollectionView(ViewTestBase):
    method test_index (line 202) | def test_index(self):
    method test_show (line 209) | def test_show(self):
    method test_create (line 216) | def test_create(self):
    method test_update (line 233) | def test_update(self):
    method test_replace (line 243) | def test_replace(self):
    method test_delete (line 250) | def test_delete(self):
    method test_delete_many (line 259) | def test_delete_many(self):
    method test_update_many (line 270) | def test_update_many(self):
  class TestESBaseView (line 283) | class TestESBaseView(ViewTestBase):
    method test_parent_queryset_es (line 286) | def test_parent_queryset_es(self):
    method test_get_es_object_ids (line 326) | def test_get_es_object_ids(self):
    method test_get_collection_es_no_parent (line 333) | def test_get_collection_es_no_parent(self, mock_es):
    method test_get_collection_es_parent_no_obj_ids (line 344) | def test_get_collection_es_parent_no_obj_ids(self, mock_es):
    method test_get_collection_es_parent_with_ids (line 355) | def test_get_collection_es_parent_with_ids(self, mock_es):
    method test_get_item_es_no_parent (line 366) | def test_get_item_es_no_parent(self):
    method test_get_item_es_matching_id (line 378) | def test_get_item_es_matching_id(self):
    method test_get_item_es_not_matching_id (line 392) | def test_get_item_es_not_matching_id(self):
    method test_get_item_es_callable_context (line 404) | def test_get_item_es_callable_context(self):
  class TestESCollectionView (line 416) | class TestESCollectionView(ViewTestBase):
    method test_index (line 419) | def test_index(self):
    method test_show (line 427) | def test_show(self):
    method test_update (line 434) | def test_update(self):
    method test_replace (line 446) | def test_replace(self):
    method test_get_dbcollection_with_es (line 453) | def test_get_dbcollection_with_es(self):
    method test_delete_many (line 463) | def test_delete_many(self):
    method test_update_many (line 474) | def test_update_many(self):
  class TestItemSubresourceBaseView (line 487) | class TestItemSubresourceBaseView(ViewTestBase):
    method test_get_context_key (line 490) | def test_get_context_key(self):
    method test_get_item (line 498) | def test_get_item(self):
  class TestItemAttributeView (line 508) | class TestItemAttributeView(ViewTestBase):
    method test_init (line 516) | def test_init(self):
    method test_index (line 522) | def test_index(self):
    method test_create (line 529) | def test_create(self):
  class TestItemSingularView (line 542) | class TestItemSingularView(ViewTestBase):
    method test_init (line 551) | def test_init(self):
    method test_show (line 555) | def test_show(self):
    method test_create (line 562) | def test_create(self):
    method test_update (line 581) | def test_update(self):
    method test_replace (line 591) | def test_replace(self):
    method test_delete (line 598) | def test_delete(self):
  class TestRestViewGeneration (line 610) | class TestRestViewGeneration(object):
    method test_only_provided_attrs_are_available (line 613) | def test_only_provided_attrs_are_available(self, run_init):
    method test_singular_view (line 641) | def test_singular_view(self):
    method test_attribute_view (line 649) | def test_attribute_view(self):
    method test_escollection_view (line 657) | def test_escollection_view(self):
    method test_dbcollection_view (line 666) | def test_dbcollection_view(self):
    method test_default_values (line 675) | def test_default_values(self):
    method test_database_acls_option (line 684) | def test_database_acls_option(self):
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (248K chars).
[
  {
    "path": ".gitignore",
    "chars": 691,
    "preview": "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 / pa"
  },
  {
    "path": ".travis.yml",
    "chars": 371,
    "preview": "# Config file for automatic testing at travis-ci.org\nlanguage: python\nenv:\n  - TOXENV=py27\n  - TOXENV=py33\n  - TOXENV=py"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 369,
    "preview": "## Team members\n\nIn alphabetical order:\n\n* [Artem Kostiuk](https://github.com/postatum)\n* [Chris Hart](https://github.co"
  },
  {
    "path": "LICENSE",
    "chars": 11325,
    "preview": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licens"
  },
  {
    "path": "MANIFEST.in",
    "chars": 70,
    "preview": "include README.md\ninclude VERSION\nrecursive-include ramses/scaffolds *"
  },
  {
    "path": "README.md",
    "chars": 815,
    "preview": "# `Ramses`\n[![Build Status](https://travis-ci.org/ramses-tech/ramses.svg?branch=master)](https://travis-ci.org/ramses-te"
  },
  {
    "path": "VERSION",
    "chars": 5,
    "preview": "0.5.3"
  },
  {
    "path": "docs/Makefile",
    "chars": 7430,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/source/changelog.rst",
    "chars": 3620,
    "preview": "Changelog\n=========\n\n* :release:`0.5.3 <2016-05-17>`\n* :bug:`107` Fixed issue with hyphens in resource paths\n\n* :release"
  },
  {
    "path": "docs/source/conf.py",
    "chars": 9735,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Nefertari documentation build configuration file, created by\n# sphinx-quickstart on Fri Mar "
  },
  {
    "path": "docs/source/event_handlers.rst",
    "chars": 5418,
    "preview": "Event Handlers\n==============\n\nRamses supports `Nefertari event handlers <http://nefertari.readthedocs.org/en/stable/eve"
  },
  {
    "path": "docs/source/field_processors.rst",
    "chars": 2748,
    "preview": "Field processors\n================\n\nRamses supports `Nefertari field processors <http://nefertari.readthedocs.org/en/stab"
  },
  {
    "path": "docs/source/fields.rst",
    "chars": 3599,
    "preview": "Fields\n======\n\nTypes\n-----\n\nYou can set a field's type by setting the ``type`` property under ``_db_settings``.\n\n.. code"
  },
  {
    "path": "docs/source/getting_started.rst",
    "chars": 1339,
    "preview": "Getting started\n===============\n\n1. Create your project in a virtualenv directory (see the `virtualenv documentation <ht"
  },
  {
    "path": "docs/source/index.rst",
    "chars": 749,
    "preview": "Ramses\n======\n\nRamses is a framework that generates a RESTful API using `RAML <http://raml.org>`_. It uses Pyramid and `"
  },
  {
    "path": "docs/source/raml.rst",
    "chars": 2682,
    "preview": "RAML Configuration\n==================\n\nYou can read the full RAML specs `here <http://raml.org/spec.html>`_.\n\n\nAuthentic"
  },
  {
    "path": "docs/source/relationships.rst",
    "chars": 8905,
    "preview": "Relationships\n=============\n\n\nBasics\n------\n\nRelationships in Ramses are used to represent One-To-Many(o2m) and One-To-O"
  },
  {
    "path": "docs/source/schemas.rst",
    "chars": 1908,
    "preview": "Defining Schemas\n================\n\nJSON Schema\n-----------\n\nRamses supports JSON Schema Draft 3 and Draft 4. You can rea"
  },
  {
    "path": "ramses/__init__.py",
    "chars": 2129,
    "preview": "import logging\n\nimport ramlfications\nfrom nefertari.acl import RootACL as NefertariRootACL\nfrom nefertari.utils import d"
  },
  {
    "path": "ramses/acl.py",
    "chars": 8678,
    "preview": "import logging\n\nimport six\nfrom pyramid.security import (\n    Allow, Deny,\n    Everyone, Authenticated,\n    ALL_PERMISSI"
  },
  {
    "path": "ramses/auth.py",
    "chars": 8961,
    "preview": "\"\"\"\nAuth module that contains all code needed for authentication/authorization\npolicies setup.\n\nIn particular:\n    :incl"
  },
  {
    "path": "ramses/generators.py",
    "chars": 6624,
    "preview": "import logging\n\nfrom inflection import singularize\n\nfrom .views import generate_rest_view\nfrom .acl import generate_acl\n"
  },
  {
    "path": "ramses/models.py",
    "chars": 10009,
    "preview": "import logging\n\nfrom nefertari import engine\nfrom inflection import pluralize\n\nfrom .utils import (\n    resolve_to_calla"
  },
  {
    "path": "ramses/registry.py",
    "chars": 2063,
    "preview": "\"\"\"\nNaive registry that is just a subclass of a python dictionary.\nIt is meant to be used to store objects and retrieve "
  },
  {
    "path": "ramses/scaffolds/__init__.py",
    "chars": 1082,
    "preview": "import os\nimport subprocess\n\nfrom six import moves\nfrom pyramid.scaffolds import PyramidTemplate\n\n\nclass RamsesStarterTe"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/__init__.py",
    "chars": 187,
    "preview": "from pyramid.config import Configurator\n\n\ndef main(global_config, **settings):\n    config = Configurator(settings=settin"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/api.raml",
    "chars": 2137,
    "preview": "#%RAML 0.8\n---\ntitle: Items API\ndocumentation:\n    - title: Items REST API\n      content: |\n        Welcome to the Items"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/items.json",
    "chars": 699,
    "preview": "{\n    \"type\": \"object\",\n    \"title\": \"Item schema\",\n    \"$schema\": \"http://json-schema.org/draft-04/schema\",\n    \"requir"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/requirements.txt",
    "chars": 224,
    "preview": "-e git+https://github.com/ramses-tech/ra.git@develop#egg=ra\n-e git+https://github.com/ramses-tech/nefertari.git@develop#"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/+package+/tests/test_api.py_tmpl",
    "chars": 895,
    "preview": "import os\nimport ra\nimport webtest\nimport pytest\n\nappdir = os.path.abspath(\n    os.path.join(os.path.dirname(__file__), "
  },
  {
    "path": "ramses/scaffolds/ramses_starter/.gitignore_tmpl",
    "chars": 708,
    "preview": "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 / pa"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/README.md",
    "chars": 93,
    "preview": "## 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",
    "chars": 707,
    "preview": "#%RAML 0.8\n---\ntitle: {{package}} API\ndocumentation:\n    - title: {{package}} REST API\n      content: |\n        Welcome "
  },
  {
    "path": "ramses/scaffolds/ramses_starter/items.json",
    "chars": 706,
    "preview": "{\n    \"type\": \"object\",\n    \"title\": \"Item schema\",\n    \"$schema\": \"http://json-schema.org/draft-04/schema\",\n    \"requir"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/local.ini_tmpl",
    "chars": 1495,
    "preview": "[app:{{package}}]\nuse = egg:{{package}}\n\n# Ramses\nramses.raml_schema = api.raml\ndatabase_acls = false\n\n# Nefertari\nnefer"
  },
  {
    "path": "ramses/scaffolds/ramses_starter/requirements.txt",
    "chars": 68,
    "preview": "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",
    "chars": 719,
    "preview": "from setuptools import setup, find_packages\n\nrequires = ['pyramid']\n\nsetup(name='{{package}}',\n    version='0.0.1',\n    "
  },
  {
    "path": "ramses/scripts/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ramses/scripts/scaffold_test.py",
    "chars": 255,
    "preview": "from nefertari.scripts.scaffold_test import (\n    ScaffoldTestCommand as NefTestCommand)\n\n\nclass ScaffoldTestCommand(Nef"
  },
  {
    "path": "ramses/utils.py",
    "chars": 11739,
    "preview": "import re\nimport logging\nfrom contextlib import contextmanager\n\nimport six\nimport inflection\n\n\nlog = logging.getLogger(_"
  },
  {
    "path": "ramses/views.py",
    "chars": 17457,
    "preview": "import logging\n\nimport six\nfrom nefertari.view import BaseView as NefertariBaseView\nfrom nefertari.json_httpexceptions i"
  },
  {
    "path": "requirements.dev",
    "chars": 219,
    "preview": "mock\npytest\npytest-cov\nreleases\nsphinx\nvirtualenv\n\n-e git+https://github.com/ramses-tech/nefertari.git@develop#egg=nefer"
  },
  {
    "path": "setup.py",
    "chars": 1447,
    "preview": "import os\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(o"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/fixtures.py",
    "chars": 1191,
    "preview": "import pytest\n\n\n@pytest.fixture\ndef clear_registry(request):\n    from ramses import registry\n    registry.registry.clear"
  },
  {
    "path": "tests/test_acl.py",
    "chars": 10360,
    "preview": "import pytest\nfrom mock import Mock, patch, call\nfrom pyramid.security import (\n    Allow, Deny,\n    Everyone, Authentic"
  },
  {
    "path": "tests/test_auth.py",
    "chars": 11663,
    "preview": "import pytest\nfrom mock import Mock, patch\n\nfrom nefertari.utils import dictset\nfrom pyramid.security import Allow, ALL_"
  },
  {
    "path": "tests/test_generators.py",
    "chars": 9191,
    "preview": "import pytest\nfrom mock import Mock, patch, call\n\nfrom ramses import generators\nfrom .fixtures import engine_mock, confi"
  },
  {
    "path": "tests/test_models.py",
    "chars": 19083,
    "preview": "import pytest\nfrom mock import Mock, patch, call\n\nfrom .fixtures import engine_mock, config_mock, guards_engine_mock\n\n\n@"
  },
  {
    "path": "tests/test_registry.py",
    "chars": 1554,
    "preview": "import pytest\n\nfrom .fixtures import clear_registry\nfrom ramses import registry\n\n\n@pytest.mark.usefixtures('clear_regist"
  },
  {
    "path": "tests/test_utils.py",
    "chars": 16368,
    "preview": "import pytest\nfrom mock import Mock, patch\n\nfrom ramses import utils\n\n\nclass TestUtils(object):\n\n    def test_contenttyp"
  },
  {
    "path": "tests/test_views.py",
    "chars": 25488,
    "preview": "import pytest\nfrom mock import Mock, patch\n\nfrom nefertari.json_httpexceptions import (\n    JHTTPNotFound, JHTTPMethodNo"
  },
  {
    "path": "tox.ini",
    "chars": 291,
    "preview": "[tox]\nenvlist =\n    py27,\n    py33,py34,py35,\n\n[testenv]\nsetenv =\n    PYTHONHASHSEED=0\ndeps = -rrequirements.dev\ncommand"
  }
]

About this extraction

This page contains the full source code of the ramses-tech/ramses GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (230.7 KB), approximately 54.8k tokens, and a symbol index with 349 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!