Sondra

Author
Jefferson Heard
Copyright
2015 Jefferson Heard
License
Apache 2.0
Development home
https://github.com/JeffHeard/sondra
Development status
pre-alpha
Maintainer
Jefferson Heard
Documentation
http://sondra.readthedocs.org/en/latest/

Sondra is an “ORM” and REST-ful webservice framework for Python 3.x, Flask, and RethinkDB with some unique features. Sondra’s goal is to aid full stack developers by letting them focus on data models and functionality instead of writing workarounds and glue code. It embraces common “shortcuts” developers take in common full-stack web applications, e.g. merging “Model” and “Controller” in the oft-used MVC pattern.

Sondra does not currently support asynchronous access to RethinkDB. The goal is to eventually support Tornado

Features

  • A clear, DRY heirarchical application structure that emphasizes convention over configuration.
  • Authentication via JSON Web Tokens (JWT)
  • JSON-Schema validation for documents.
  • Expose methods on documents, collections, and applications, complete with schemas for call and return.
  • A clear, predictable URL scheme for all manner of API calls, covering a broad set of use-cases.
  • Self documenting APIs with both human-readable help based on docstrings and schemas for every call.
  • Use API idiomatically over HTTP and native Python without writing boilerplate code

Installation

$ python setup.py

Contribute

Contributions are welcome. I haven’t gotten around to posting a “master plan” of any sort yet, but I will get there. If you’re interested in contributing, please email me directly or simply fork the GitHub project and issue a pull request.

Support

For support, please see the project page on GitHub

License

This project is licensed under the Apache license V2. See LICENSE.

Concept

A Sondra API is exposed in Flask as a suite of applications. Each application contains a number of document collections, and each collection contains a number of documents.

Sondra tries to take advantage of all manner of Python idioms in a sane manner. It generates as much as possible, while avoiding “magic tricks” or introducing conventions that are already covered by Python idioms. This means that:

  • Online documentation is generated at every level from reStructuredText or Google style docstrings.
  • Method schemas are generated from annotated function signatures
  • All URL features that urllib.parse recognizes are taken advantage of to create a regular URL scheme that encompasses all manner of calls.

Web Services

Sondra uses a regular URL scheme for its web services:

  • Underscores in Python identifiers are replaced with more web-friendly dashes (slugs)
  • Paths address app / collection / instance (document)
  • The last path element include an optional . (dot), which separates the object being addressed and a method being called.
  • Fragments allow the caller to address sub-documents in JSON responses. Fragments delimted with # are not passed by browsers to the server. The alternative for delimiting fragments is @!
  • Path parameters (on the last element, with a ”;”, see the URL spec or urlparse docs) are used to specify output format or content directives. Currently supported:
    • json or format=json retrieves data in JSON format.
    • geojson or format=geojson retrieves data in GeoJSON feature or feature collection format.
    • help or format=help retrieves HTML autogenerated help.
    • schema or format=schema retrieves a JSON-Schema document for the service call.

Documents

A Document is a single document that conforms to a JSON-Schema object type. That is, it is never a simple type nor an array of items.

Documents may expose methods to the HTTP api. These are similar to instance methods in Python. They operate on an individual document in a collection instead. Document methods might include operations that combine multiple documents to make a third (add, multiply, divide, subtract, or similar) or they might provide specific views of a document. Anything that you would write as an “instance method” in Python.

Collections

A Collection is a RethinkDB table that contains a specific subclass of Document, which is defined by a single JSON-Schema. The collection class defines additionally:

  • The primary key name (defaults to the RethinkDB default of “id”)
  • Indexes
  • Any document properties that require “special treatment” in RethinkDB such as geographical and date/time types.
  • Relations to other Collections
  • The Application class it belongs to.

Collections may expose methods to the HTTP api. These are similar to class methods in Python, as they operate on the collection itself and not the individual documents. Collection methods might provide special filtering, create documents according to a specific template, or set properties on the collection itself. Anything you would write as a “class method” in Python

Applications

An Application is a reusable grouping of collections and a set of optional application methods, which operate a bit like globally available functions. Applications are bound to a single database within RethinkDB.

Applications may expose methods to the HTTP api. These are similar to the functions that are defined at the module level in Python. They are not specific to a particular class or instance, but instead are defined to provide broad functionality for the whole application.

The Suite

A Suite defines the environment of applications, including database connections and provides some basic functionality. Every application is registered with the global Suite object, which itself implements Python’s Mapping protocol to provide dictionary-like lookup of application objects. The “Suite” object determines the base path of all Application APIs. Suites are similar in nature to Django’s settings.py except that they are class-based. There may be only one concrete class of Suite in your Flask app, although it may derive from any number of abstract Suite mixins.

Contents

Web Services Guide

This is a client guide to using Sondra’s automatically generated REST APIs. The primary audience for this guide are application developers who wish to understand the conventions that Sondra uses. Each API has full autogenerated documentation and self-describing schemas that support standard REST access for objects (documents), filtering collections, and “method calls” which implement more complex server-side application logic than simple CRUD operations support, e.g. login and logout support for Authentication.

Making calls

The following section describes conventions for making web-service requests and interpreting their responses.

Requests

Requests are made using standard HTTP and HTTPS calls. Most browsers, jQuery, and as well as cURL and wget should support all manner of calls supported by these. When a body is required (or optional) for a call, the body is assumed to be JSON. All calls return JSON by default, if no format specifier is supplied as part of the call.

Alternative Request Syntax

Query parameters can always be supplied to modify the call being made, even if the call also supplies a JSON object in the body. Additionally, long query strings may be overwhelming or poorly supported on some browsers. For the situation in which a query string is too long to be manageable, a special body JSON can be supplied as the body of a POST request. The JSON must adhere to the following schema:

{ "type": "object",
  "required": ["__q"],
  "properties": {
    "__q": {
      "type": "object",
      "description": "Query string parameters"
    },
    "__method": {"enum": ["GET","POST","PUT","PATCH","DELETE"]},
    "__objs": {
      "description": "The object or list of objects that constitute the request body",
      "oneOf": [
        {"type": "object"},
        {"type":"array", "items": {"type": "object"}}
      ]
    }
  }
Responses

Except for autogenerated help, all formats return some manner of JSON. Some methods and aggregations return “bare” values e.g. a string, integer, or number. Bare values are not valid JSON, so these values will be wrapped in a dummy object according to their type:

{"_": <value>}

In addition, because empty responses are not handled well by most browsers, a null return from a service call will result in an empty JSON object, {}.

In general, all webservices are self-describing with auto-generated help and JSON-schemas. JSON-schemas are very slightly enhanced to improve the interpretation of linkages across collections. See Schemas for more information.

Errors

Errors in production (non DEBUG) code will be returned as JSON in the very near future. For now they are returned in whatever form the underlying web framework (e.g. Flask, Django, etc) returns exceptions. JSON encoded errors may have their own schemas defined at the Suite level, however at a minimum they conform to this schema:

{
  "type": "object",
  "properties": {
    "errorName": {"type": "string"},
    "requestedUrl": {
      "type": "string",
      "format": "url"
    },
    "format": {
      "enum": ["json", "geojson", "help", "schema"]
    }
    "requestType": {
      "enum": [
        "application", "collection", "document",
        "application_method", "collection_method", "document_method"
      ]
    }
    "requestMethod": {"enum": ["GET", "POST", "PATCH", "PUT", "DELETE"]},
    "message": {"type": "string"}
  }
}

Authentication

Please make sure to expose and call APIs over HTTPS. JWT security relies on encryption to be effective.

APIs that support authentication as supplied by Sondra must include the auth app as part of their API. The auth app has auto-generated documentation that covers it entirely, and it is not in the scope of this document to replicate that here. However, there are a few basic points covered again here for login, logout, and making authenticated webservice calls.

Logging In

This method issues a new JSON Web Token and caches it as valid for the time being (300 seconds by default).

URL Form:

http://localhost:5000/auth.login

Request object properties

username (str)
The username (often the same as email address).
password (str)
The user’s password.

Returns an encoded JSON Web Token as a bare object {"_": <jwt>}

Logging Out

This method revokes a JSON Web Token, invalidating it.

URL Form:

http://localhost:5000/auth.logout

Request object properties

token (string, JWT)
The currently valid JWT

Returns an empty response, the blank object {}.

Renewing a Token

This method renews the token for a currently logged in user.

URL Form:

http://localhost:5000/auth.renew

Returns a new JWT that has been freshly issued.

Making Authenticated Requests

Once you obtain a JWT by logging in, you may use this JWT to authenticate requests. JWTs in Sondra by default must be renewed (see above, Renewing a Token every five minutes (300 seconds). This can be changed on the server by deriving a new subclass of the Auth app.

Query Parameters

_auth (string, JWT)
This is valid on all requests for authenticated application, suites, collections, and documents. The JWT, as returned by auth.login (Logging In) is the value of _auth.
Authentication Service Exceptions
AuthenticationError [1]

Occurs when a request is made that requires authentication to complete, and the call was made anonymously.

ValidationError

Occurs when a request is made with a token that is expired or whose issuer is not recognized.

ParseError

Occurs when a request is made with a token that is corrupted or unreadable for any reason.

AuthorizationError [1]

Occurs when a request is made by an authenticated user who has insufficient permissions to perform the operation.

Schemas

Schemas follow JSON-Schema format and use the jsonschema package to validate documents that will be added to persistent collections. JSON-Schema is strictly followed, with only a few minor additions.

refersTo

Applies to schema type “string” and provides a prefix for linking foreign keys to other API endpoints, specifically other collections. The value of refersTo is itself a string in URL format and must refer to a collection. Single objects will be validated against this string in the same manner as JSON-schema’s pattern specifier. The difference between refersTo and pattern is that refersTo must be a valid URL and must also be a collection API endpoint. This allows code to make certain assumptions (such as formats and metadata endpoints) about the endpoint, whereas a pattern can be any valid regular expression.

See Collection Python documentation for more details.

Suite schema

URL Form:

http://localhost:5000/schema

The suite schema is a “dummy schema” that contains suite-wide definitions that are referenced by other schemas within a suite’s set of enclosed applications. Additionally, it contains another property:

  • applications - whose value is a list of URLs to the schema of all applications in the suite.
Application schema

URL Form:

http://localhost:5000/application;schema

The application schema is a “dummy schema” that contains application-wide definitions that are referenced by schemas within a application’s set of enclosed collections and documents. Additionally, it contains two properties:

  • collections - whose value is a list of URLs to the schema of all collections in the suite.
  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the application level.
Collection schema

URL Form:

http://localhost:5000/application/collection;schema

The collection schema defines the validation set for all documents within a single collection. Additionally, it contains two properties:

  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the collection level.
  • documentMethods - whose value is an object whose property names are method slugs that can be called at the individual document level, and whose property values are the request and response schema of all methods that attach at the document level.
Document schema

URL Form:

http://localhost:5000/application/collection/primary-key;schema

The document schema mirrors the collection schema and mainly exists for completeness. Generally you should use the collection schema. In addition, it contains a property:

  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the document level.
Method schemas

URL Form:

http://localhost:5000/application.method;schema
http://localhost:5000/application/collection.method;schema
http://localhost:5000/application/collection/primary-key.method;schema

Requesting a schema directly on the method call returns the named schema from the “methods” (or documentMethods) section of the schema for that class (application, collection, or document).

Auto-generated help

URL Form:

http://localhost:5000/help
http://localhost:5000/application;help
http://localhost:5000/application.method;help
http://localhost:5000/application/collection;help
http://localhost:5000/application/collection.method;help
http://localhost:5000/application/collection/primary-key;help
http://localhost:5000/application/collection/primary-key.method;help

Help for every level of an API heirarchy can be retrieved by using the format specifier ;help at the end of the URL. Help is completely auto-generated from docstrings (using Markdown, reStructuredText, or Google formats, see suite docs) in the definition of each Python class definition and each schema that makes up the API. Help is available in HTML format. Currently localized help is not supported for APIs, but this may happen in the future.

Operations on Collections

Collections support the standard REST CRUD operations: Create, List, Update, and Delete.

URL Form:

http://localhost:5000/application/collection;format
POST: Create

Create a new document. Raises an error if the document already exists in the database.

Request

Accepts JSON in the post body. If the JSON is an array, then each document in the array is added in turn. If it is an document, then that document will be added.

Response

The response will be a JSON array of the URLs of added documents.

Errors
  • KeyError if any of the POSTed documents already exist in the database by primary key.
  • ValidationError if any of the POSTed documents do not fit the collection schema.
GET: List and Filter

Retrieve a listing of documents in the given format, or retrieve an HTML form for entering a new document. Note that filtering parameters described here can also be accessed via POST using the Alternative Request Syntax.

Request
flt (JSON object or list, see Simple filters)
A (list) of simple filter(s), which will be performed in sequence to narrow the result.
geo (JSON object, see Spatial filters)
A spatial filter which will be used to limit the spatial extent of results.
agg (JSON object, see Aggregations)
An object that describes aggregations to perform on the query
start (int)
The index of the first document to return.
limit (int)
The maximum number of documents to return.
end (int)
The index of the last document to return.
Simple filters

Simple filters are JSON objects that narrow down the documents being returned. Currently these do not support the use of indexes, but they may in the future. They are performed by successive applications of the “filter” method in ReQL.

Each filter must conform to the following JSON specification:

{ "op": "<operator>",
  "args": { "lhs": <field-name>, "rhs": <value> }}

Operators

  • ==: Equality check. Equivalent to .filter(r.row(lhs).eq(rhs))
  • !=: Inequality check. Equivalent to .filter(r.row(lhs).eq(rhs))
  • >: Strictly greater than. Equivalent to .filter(r.row(lhs).gt(rhs))
  • >=: Greater or equal to. Equivalent to .filter(r.row(lhs).ge(rhs))
  • <: Strictly less than. Equivalent to .filter(r.row(lhs).lt(rhs))
  • <=: Less than or equal to. Equivalent to .filter(r.row(lhs).le(rhs))
  • match: Regex match. Equivalent to .filter(r.row(lhs).match(rhs))
  • contains: Containment check. Equivalent to .filter(r.row(lhs).contains(rhs))
  • has_fields: Field presence check. Equivalent to .filter(r.row(lhs).has_fields(rhs))
Spatial filters

Spatial filters narrow down the returned documents using RethinkDB’s geographic operators. There must be at least one spatial property defined on the Collection or the inclusion of a spatial filter will result in an error. Spatial filters must conform to the following JSON schema:

{
  "type": "object",
  "required": ["op", "test"],
  "properties": {
    "against": {"type": "string"},
    "op": {"$ref": "#/definitions/op"},
    "test": {"type": "object", "description": "GeoJSON geometry or distance object"}
  },
  "definitions": {
    "op": {
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": {"enum": ["distance", "get_intersecting", "get_nearest"])
        "args": {"type": "array"},
        "kwargs": {"type": "object"}
      }
  }
}

Spatial operators

Spatial operators all work exactly the same as their corresponding RethinkDB commands. The “test” is a geometry to test against. Geometries should be described in GeoJSON. Distance isn’t very useful yet, since it returns distances without primary keys. Against is the geometric index to test against. If left blank, it is the default geometry field.

Only one spatial filter may be supplied.

Aggregations

Aggregations transform returned documents and often supply summaries of data. Aggregtions must conform to the following JSON schema:

{
  "type": "object",
  "required": ["name"],
  "properties": {
    "name": {"enum": [
      "with_fields",
      "count",
      "max",
      "min",
      "avg",
      "sample",
      "sum",
      "distinct",
      "contains",
      "pluck",
      "without",
      "has_fields",
      "order_by",
      "between"]
    },
    "args": {"type": "array"},
    "kwargs": {"type": "object"}
  }
}

Again, these operators work the same way as their RethinkDB counterparts. Consult the ReQL API documentation for more information. Only one aggregation may be supplied.

Response

Unless an aggregation was specified, the response is a list of documents that conform to the collection schema. If an aggregation (or distance) was specified, then the result will be a bare JSON object, {"_": <value>}.

PUT. Replace

Replace an document currently in the database with a new document.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
Request

Accepts JSON in the PUT body. If the JSON is an array, then each document in the array is replaced in turn. If it is an document, then that document will be added.

Response

The response will be the same as the RethinkDB response to an insert.

Errors
  • KeyError if any of the PUT documents do not already exist in the database by primary key.
  • ValidationError if any of the PUT documents do not fit the collection schema.
PATCH. Update

Update document(s) in place. Raises an error if a supplied document does not already exist in the database. The semantic difference between a PATCH and a PUT request is that the PATCH request retrieves the document first and updates that document with the values provided in the PATCH. The primary key must be present in each replacement document for a lookup to occur.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
Request

Accepts JSON in the PATCH body. Like PUT and POST, PATCH can be, if the JSON is an array, then each document in the array is updated in turn. If it is an document, then that document will be updated.

Response

The response will be the same as the RethinkDB response to an insert.

Errors
  • KeyError if any of the PATCH documents do not already exist in the database by primary key, or if a primary key is not supplied.
  • ValidationError if any of the PUT documents do not fit the collection schema.
DELETE. Delete

Delete documents in place. Raises an error if the document doesn’t exist in the first place, or if the entire collection would be deleted, unless specifically requested to delete everything.

Request

Accepts a JSON list of primary keys or index keys in the body, OR accepts simple and spatial filters.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
delete_all (true | false)
Set this flag on the query string if you want to
flt (JSON object or list, see Simple filters)
A (list) of simple filter(s), which will be performed in sequence to narrow the deletion.
geo (JSON object, see Spatial filters)
A spatial filter which will be used to limit the spatial extent of the deletion.
index (str index name)
If index is supplied then the keys to be deleted are assumed to be index keys instead of primary keys.
Response

The same as the RethinkDB delete response.

Errors
  • KeyError if any of the DELETE documents do not already exist in the database by primary key.
  • PermissionError if no filters or primary keys have been supplied (all documents in the collection would be deleted) and delete_all is not true.

Operations on Documents

Documents support the standard REST CRUD operations: Create, Detail, Update, Replace, and Delete.

URL Form:

http://localhost:5000/application/collection/primary-key;format
POST and PUT. Replace

Replace the referenced document with the given document. Raises an error if the primary key exists and conflicts with the primary key of the document.

Request

Accepts a single JSON object in the body.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
Response

The response will be the same as RethinkDB’s save method.

Errors
  • KeyError if the POSTed document already exists in the database by primary key.
  • ValidationError if the POSTed document does not fit the collection schema.
PATCH. Update

Update document in place. Raises an error if a supplied document does not already exist in the database or if a primary key is present in the supplied document and conflicts with the document key. The semantic difference between a PATCH and a PUT or POST request is that the PATCH request retrieves the document first and updates that document with the values provided in the PATCH.

Query Params

durability (hard | soft)
Same as RethinkDB save.
return_changes (boolean=True)
Same as RethinkDB save.
Request

Accepts a single JSON in the PATCH body.

Response

The response will be the same as the RethinkDB response to an save.

Errors
  • KeyError if the PATCH document does not already exist in the database by primary key or conflicts with the existing key.
  • ValidationError if the PATCH document does not fit the collection schema.
DELETE. Delete

Delete an document. Raises an error if the document doesn’t exist to be deleted.

Request

The request body should be bare.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
Response

The same as the RethinkDB delete response.

Errors
  • KeyError if the DELETE document does not already exist in the database by primary key.

Complete URL scheme reference

/schema
GET. Suite-wide schema. Typically this includes definitions that are common for the entire suite, including valid filter names, dates, times, geography, and localization.
/help
GET. Suite-wide help with links to applications. Help is autogenerated from schema definitions and from docstrings in the class.
/{application};schema
GET. Application-wide schema.
/{application};help
GET. Application-wide help with links to collections. Help is autogenerated from schema definitions and from docstrings in the class.
/{application}.{method-name};schema
GET. Method schema for application.
/{application}.{method-name};help
GET. Autogenerated help for the method.
/{application}.{method-name};json
GET, POST. Applicationlication method call.
/{application}/{collection};schema
GET. Collection schema.
/{application}/{collection};help
GET. Autogenerated help for the collection.
/{application}/{collection};json
GET, POST, PUT, DELETE. Get, Add, Update, or Delete documents of the collection, respectively.
/{application}/{collection};geojson
GET, DELETE. Get or Delete documents of the collection, respectively, but as GeoJSON FeatureCollections.
/{application}/{collection}.{method-name};schema
GET. Method schemas for a collection-wide method.
/{application}/{collection}.{method-name};help
GET. Autogenerated help for the collection method.
/{application}/{collection}.{method-name};json
GET, POST. Call a collection method.
/{application}/{collection}/{document};schema
GET. Same as /{application}/{collection};schema, generally speaking.
/{application}/{collection}/{document};help
GET. Autogenerated help for document methods.
/{application}/{collection}/{document};json
GET, POST, PUT, DELETE. Get, Replace, Update, or Delete document, respectively.
/{application}/{collection}/{document};geojson
GET, DELETE. Get or Delete document, respectively, but as a GeoJSON Feature.
/{application}/{collection}/{document}.{method-name};schema
GET. Document method schema.
/{application}/{collection}/{document}.{method-name};help
GET. Document method help.
/{application}/{collection}/{document}.{method-name};json
GET, POST. Document method call.
[1](1, 2) The following exceptions are not yet supported, but will be very soon. Currently all authorization and authentication errors raise a standard Python PermissionError on exception.

Command Line Utilities

sondra.commands.document_collections

sondra.commands.schema2doc

Python API Documentation

.

sondra package

Subpackages
sondra.application package
Submodules
sondra.application.signals module
Module contents
class sondra.application.Application(suite, name=None)[source]

Bases: collections.abc.Mapping

An Application groups collections that serve a related purpose.

In addition to collections, methods and schemas can be exposed at the application level. Any exposed methods might be termed “library functions” in the sense that they apply to all collections, or configure the collections at a high level. Schemas exposed on the application level should be common to several collections or somehow logically “broader” than definitions at the document/collection level.

Application behave as Python dicts. The keys in the application’s dictionary are the slugs of the collections the application houses. In addition, applications are stored as items in a Suite’s dictionary. Thus to access the ‘Users’ collection in the ‘Auth’ application, one could start with the suite and work down thus:

> suite['auth']['users']
...
<Application object 0x...>

Also see the `webservices reference`_ for more on how to access applications and their schemas and methods over the web.

db

str – The name of a RethinkDB database

connection

str – The name of a RethinkDB connection in the application’s suite

slug

str – read-only. The name of this application class, slugified (all lowercase, and separate words with -)

anonymous_reads

bool=True – Override this attribute in your subclass if you want to disable anonymous queries for help and schema for this application and all its collections.

definitions

dict – This should be a JSON serializable dictionary of schemas. Each key will be the name of that schema definiton in this application schema’s “definitions” object.

url[source]

str – read-only. The full URL for this application.

schema_url[source]

str – read-only. A shortcut to the application’s schema URL.

schema[source]

dict – read-only. A JSON serializable schema definition for this application. In addition to the standard JSON-schema definitions, a dictionary of collection schema URLs and a list of methods are included as “collections” and “methods” respectively. The keys for the collection schemas are the slugged names of the collections themselves.

full_schema[source]

dict – Same as schema, except that collections are fully defined instead of merely referenced in the “collections” sub-object.

..webservices reference: /docs/web-services.html

anonymous_reads = True
collections = ()
connection = 'default'
create_database()[source]

Create the db for the application.

If the db exists, log a warning.

Returns:None
create_tables(*args, **kwargs)[source]

Create tables in the db for all collections in the application.

If the table exists, log a warning.

Signals sent:

pre_create_tables(instance=``self``, args=``args``, kwargs=``kwargs``)
Sent before tables are created
post_create_tables(instance=``self``)
Sent after the tables are created
Parameters:
  • *args – Sent to collection.create_table as vargs.
  • **kwargs – Sent to collection.create_table as keyword args.
Returns:

None

db = 'default'
definitions = {}
drop_database()[source]

Drop the db for the application.

If the db exists, log a warning.

Returns:None
drop_tables(*args, **kwargs)[source]

Create tables in the db for all collections in the application.

If the table exists, log a warning.

Signals sent:

pre_delete_tables(instance=``self``, args=``args``, kwargs=``kwargs``)
Sent before tables are created
post_delete_tables(instance=``self``)
Sent after the tables are created
Parameters:
  • *args – Sent to collection.delete_table as vargs.
  • **kwargs – Sent to collection.delete_table as keyword args.
Returns:

None

exposed_methods = {}
full_schema[source]
help(out=None, initial_heading_level=0)[source]

Return full reStructuredText help for this class.

Parameters:
  • out (io) – An output, usually io.StringIO
  • initial_heading_level (int) – 0-5, default 0. The heading level to start at, in case this is being included as part of a broader help scheme.
Returns:

reStructuredText help.

Return type:

(str)

schema[source]
schema_url[source]
slug = None
title = None
url[source]
exception sondra.application.ApplicationException[source]

Bases: builtins.Exception

Represents a misconfiguration in an Application class definition

class sondra.application.ApplicationMetaclass(name, bases, attrs)[source]

Bases: abc.ABCMeta

Inherit definitions from base classes, and let the subclass override any definitions from the base classes.

sondra.collection package
Submodules
sondra.collection.signals module
Module contents
class sondra.collection.Collection(application)[source]

Bases: collections.abc.MutableMapping

The collection is the workhorse of Sondra.

Collections are mutable mapping types, like dicts, whose keys are the keys in the database collection. The database table, or collection, has the same name as the collection’s slug with hyphens replaced by underscores for compatibility.

Collections expand on a document schema, specifying:

  • properties that should be treated specially.
  • the primary key
  • indexes that should be built
  • relationships to other collections

Like applications, collections have webservice endpoints:

http://localhost:5000/application/collection;(schema|help|json|geojson)
http://localhost:5000/application/collection.method;(schema|help|json)

These endpoints allow the user to create, update, filter, list, and delete objects in the collection, which are individual documents. Also, any methods that are exposed by the sondra.decorators.expose decorator are exposed as method endpoints.

To use Python to retrieve individual Document instances, starting with the suite:

> suite['app-name']['collection-name']['primary-key']
...
<sondra.document.Document object at 0x....>

The specials attribute is a dictionary of property names to sondra.document.ValueHandler instances, which tell the collection how to handle properties containing objects that aren’t standard JSON. This includes date-time objects and geometry, which is handled via the Shapely library. Shapely is not supported by readthedocs, so you must install it separately. See the individual ValueHandler subclasses in sondra.document for more information.

name

str – read-only. The name of the collection, based on the classname.

slug

str – read-only. The hyphen separated name of the collection, based on the classname

schema

str – read-only. The collection’s schema, based on the document_class

suite[source]

sondra.suite.Suite – read-only. The suite this collection’s application is a part of. None for abstract classes.

application

sondra.application.Application – classes.

document_class

sondra.document.Document – The document class this collection contains. The schema is derived from this.

primary_key

str – The field (if different from id) to use as the primary key. Individual documents are referenced by primary key, both in the Python interface and the webservice interface.

private

bool=False – can be very useful for collections whose data should never be available over the ‘net.

specials

dict – A dictionary of properties to be treated specially.

indexes

[str]

relations

dict

anonymous_reads

bool=True

abstract

bool

table[source]

ReQL

url[source]

str

schema_url[source]

str

__contains__(item)[source]

Checks to see if the primary key is in the database.

Parameters:item (dict, Document, str, or int) – If a dict or a document, then the primary key will be checked. Str or ints are assumed to be the primary key.
Returns:True or False.
__delitem__(key)[source]

Delete an object from the database.

Sends pre- and post- delete signals. See signals documentation for more details.

Parameters:key (str or int) – The primary key for the document.
__getitem__(key)[source]

Get an object from the database and populate an instance of self.document_class with its contents.

Parameters:key (str or int) – Primary key for the document.
Returns:An instance of self.document_class with data from the database.
Return type:Document
Raises:KeyError if the object is not found in the database.
__setitem__(key, value)[source]

Add or replace a document object to the database.

Sends pre- and post- save signals. See signals documentation for more details.

Parameters:
  • key (str or int) – The primary key for the document.
  • value – (dict or Document): If a Document, it should be this collection’s document_class.
abstract = True
anonymous_reads = True
application = None
create(value)[source]

Create a document from a dict. Saves document before returning, and thus also sends pre- and post- save signals.

Parameters:value (dict) – The value to use for the new document.
Returns:Document instance, guaranteed to have been saved.
create_table(*args, **kwargs)[source]

Create the database table for this collection. Args and keyword args are sent along to the rethinkdb table_create function. Sends pre_table_creation and post_table_creation signals.

definitions = {}
delete(docs=None, **kwargs)[source]

Delete a document or list of documents from the database.

Parameters:
  • docs (Document or [Document] or [primary_key]) – List of documents to delete.
  • **kwargs – Passed to rethinkdb.delete
Returns:

The result of RethinkDB delete.

doc(value)[source]

Return a document instance populated from a dict. Does not save document before returning.

Parameters:value (dict) – The value to use for the document. Should conform to document_class’s schema.
Returns:Document instance.
document_class

alias of Document

drop_table()[source]

Delete the database table for this collection. Sends pre_table_deletion and post_table_deletion signals.

exposed_methods = {}
file_storage = None
help(out=None, initial_heading_level=0)[source]

Return full reStructuredText help for this class

indexes = []
json(docs)[source]
name = 'collection'
primary_key = 'id'
private = False
q(query)[source]

Perform a query on this collection’s database connection.

Parameters:query (ReQL) – Should be a RethinkDB query that returns documents for this collection.
Yields:Document instances.
relations = []
save(docs, **kwargs)[source]

Save a document or list of documents to the database.

Parameters:
  • docs (Document or [Document] or [dict]) – List of documents to save.
  • **kwargs – Passed to rethinkdb.save
Returns:

The result of the RethinkDB save.

schema = None
schema_url[source]
slug = None
suite[source]
table[source]
title = None
url[source]
validator(value)[source]

Override this method to do extra validation above and beyond a simple schema check.

Parameters:value (Document) – The value to validate.
Returns:bool
Raises:ValidationError if the document fails to validate.
exception sondra.collection.CollectionException[source]

Bases: builtins.Exception

Represents a misconfiguration in a Collection class definition

class sondra.collection.CollectionMetaclass(name, bases, nmspc)[source]

Bases: abc.ABCMeta

The metaclass sets name and schema and registers this collection with an application.

The schema description is updated with the docstring of the concrete collection class. The title is set to the name of the class, if it is not already set.

This metaclass also post-processes inheritance, so that:

  • definitions from base classes are included in subclasses.
  • exposed methods in base classes are included in subclasses.
sondra.commands package
Submodules
sondra.commands.document_collections module
sondra.commands.schema2doc module
Module contents
sondra.document package
Submodules
sondra.document.schema_tools module
sondra.document.signals module
Module contents

Core data document types.

class sondra.document.Document(obj, collection=None, from_db=False)[source]

Bases: collections.abc.MutableMapping

The base type of an individual RethinkDB record.

Each record is an instance of exactly one document class. To combine schemas and object definitions, you can use Python inheritance normally. Inherit from multiple Document classes to create one Document class whose schema and definitions are combined by reference.

Most Document subclasses will define at the very least a docstring,

collection

sondra.collection.Collection – The collection this document belongs to. FIXME could also use URL.

defaults

dict – The list of default values for this document’s properties.

title

str – The title of the document schema. Defaults to the case-split name of the class.

template

string – A template string for formatting documents for rendering. Can be markdown.

schema

dict – A JSON-serializable object that is the JSON schema of the document.

definitions

dict – A JSON-serializable object that holds the schemas of all referenced object subtypes.

exposed_methods

list – A list of method slugs of all the exposed methods in the document.

__eq__(other)[source]

True if and only if the primary keys are the same

__getitem__(key)[source]

Return either the value of the property or the default value of the property if the real value is undefined

__len__()[source]

The number of keys in the object

__setitem__(key, value)[source]

Set the value of the property, saving it if it is an unsaved Document instance

application[source]

The application instance this document’s collection is attached to.

defaults = {}
definitions = {}
delete(**kwargs)[source]
exposed_methods = {}
fetch(key)[source]

Return the value of the property interpreting it as a reference to another document

help(out=None, initial_heading_level=0)[source]

Return full reStructuredText help for this class

id[source]

The value of the primary key field. None if the value has not yet been saved.

json(*args, **kwargs)[source]
name[source]
processors = []
save(conflict='replace', *args, **kwargs)[source]
schema = {'description': "\n The base type of an individual RethinkDB record.\n\n Each record is an instance of exactly one document class. To combine schemas and object definitions, you can use\n Python inheritance normally. Inherit from multiple Document classes to create one Document class whose schema and\n definitions are combined by reference.\n\n Most Document subclasses will define at the very least a docstring,\n\n Attributes:\n collection (sondra.collection.Collection): The collection this document belongs to. FIXME could also use URL.\n defaults (dict): The list of default values for this document's properties.\n title (str): The title of the document schema. Defaults to the case-split name of the class.\n template (string): A template string for formatting documents for rendering. Can be markdown.\n schema (dict): A JSON-serializable object that is the JSON schema of the document.\n definitions (dict): A JSON-serializable object that holds the schemas of all referenced object subtypes.\n exposed_methods (list): A list of method slugs of all the exposed methods in the document.\n ", 'template': '{id}', 'type': 'object', 'methods': [], 'properties': {}, 'title': 'Document', 'definitions': {}}
schema_url[source]
slug[source]

Included for symmetry with application and collection, the same as ‘id’.

specials = {}
suite[source]

The suite instance this document’s application is attached to.

template = '{id}'
title = 'Document'
url[source]
validate()[source]
class sondra.document.DocumentMetaclass(name, bases, nmspc)[source]

Bases: abc.ABCMeta

The metaclass for all documents merges definitions and schema into a single schema attribute and makes sure that exposed methods are catalogued.

sondra.suite package
Submodules
sondra.suite.signals module
Module contents
class sondra.suite.Suite[source]

Bases: collections.abc.Mapping

This is the “environment” for Sondra. Similar to a settings.py file in Django, it defines the environment in which all :class:`Application`s exist.

The Suite is also a mapping type, and it should be used to access or enumerate all the Application objects that are registered.

always_allowed_formats

set – A set of formats where a

applications

dict – A mapping from application name to Application objects. Suite itself implements a mapping protocol and this is its backend.

base_url

str – The base URL for the API. The Suite will be mounted off of here.

base_url_scheme

str – http or https, automatically set.

base_url_netloc

str – automatically set hostname of the suite.

connection_config

dict – For each key in connections setup keyword args to be passed to rethinkdb.connect()

connections

dict – RethinkDB connections for each key in connection_config

docstring_processor_name

str – Any member of DOCSTRING_PROCESSORS: preformatted, rst, markdown, google, or numpy.

docstring_processor

callable – A lambda (str) that returns HTML for a docstring.

logging

dict – A dict-config for logging.

log

logging.Logger – A logger object configured with the above dictconfig.

cross_origin

bool=False – Allow cross origin API requests from the browser.

schema[source]

dict – The schema of a suite is a dict where the keys are the names of Application objects registered to the suite. The values are the schemas of the named app. See Application for more details on application schemas.

__getitem__(item)[source]

Application objects are indexed by “slug.” Every Application object registered has its name slugified.

This means that if your app was called MyCoolApp, its registered name would be my-cool-app. This key is used whether you are accessing the application via URL or locally via Python. For example, the following both produce the same result:

URL (yields schema as application/json):

    http://localhost:5000/api/my-cool-app;schema

Python (yields schema as a dict):

    suite = Suite()
    suite['my-cool-app'].schema
allow_anonymous_formats = {'schema', 'help'}
api_request_processors = ()
applications = None
base_url_netloc = 'localhost:5000'
base_url_path = '/api'
base_url_scheme = 'http'
clear_databases()[source]
connection_config = {'default': {}}
cross_origin = False
debug = False
definitions = {'geojsonFeatureCollection': {'type': 'object', 'properties': {'type': {'enum': ['FeatureCollection']}, 'features': {'items': {'$ref': '#/definitions/geojsonFeature'}, 'type': 'array'}}}, 'filterOps': {'enum': ['with_fields', 'count', 'max', 'min', 'avg', 'sample', 'sum', 'distinct', 'contains', 'pluck', 'without', 'has_fields', 'order_by', 'between']}, 'lineStringCoordinates': {'minLength': 2, 'items': {'items': {'$ref': '#/definitions/pointCoordinates'}, 'type': 'array'}, 'type': 'array'}, 'lineString': {'type': 'object', 'properties': {'coordinates': {'$ref': '#/definitions/lineStringCoordinates'}, 'type': {'enum': ['Point']}}}, 'geojsonGeometry': {'oneOf': [{'$ref': '#/definitions/point'}, {'$ref': '#/definitions/lineString'}, {'$ref': '#/definitions/polygon'}], 'type': 'object'}, 'pointCoordinates': {'minLength': 2, 'maxLength': 3, 'items': {'type': 'number'}, 'type': 'array'}, 'timedelta': {'required': ['start', 'end'], 'type': 'object', 'properties': {'minutes': {'type': 'integer'}, 'seconds': {'type': 'number'}, 'days': {'type': 'integer'}, 'hours': {'type': 'integer'}}}, 'point': {'type': 'object', 'properties': {'coordinates': {'$ref': '#/definitions/pointCoordinates'}, 'type': {'enum': ['Point']}}}, 'geojsonFeature': {'type': 'object', 'properties': {'geometry': {'$ref': '#/definitions/geojsonGeometry'}, 'type': {'enum': ['Feature']}, 'properties': {'type': 'object'}}}, 'polygon': {'type': 'object', 'properties': {'coordinates': {'$ref': '#/definitions/polygonCoordinates'}, 'type': {'enum': ['Point']}}}, 'polygonCoordinates': {'minLength': 4, 'items': {'items': {'$ref': '#/definitions/lineStringCoordinates'}, 'type': 'array'}, 'type': 'array'}, 'spatialOps': {'enum': ['distance', 'get_intersecting', 'get_nearest']}}
docstring_processor_name = 'preformatted'
drop_database_objects()[source]
ensure_database_objects()[source]
file_storage = 'sondra.files.storage.FileSystemStorage'
file_upload_directory_permissions = 493
file_upload_handlers = 'sondra.files.uploadhandler.MemoryFileUploadHandlersondra.files.uploadhandler.TemporaryFileUploadHandler'
file_upload_max_memory_size = 104857600
file_upload_permissions = 420
file_upload_temp_dir = None
full_schema[source]
help(out=None, initial_heading_level=0)[source]

Return full reStructuredText help for this class

logging = None
lookup(url)[source]
lookup_document(url)[source]
media_root = 'media'
media_url = '/media'
name = None
register_application(app)[source]

This is called automatically whenever an Application object is constructed.

schema[source]
schema_url[source]
slug = 'api'
title = 'Sondra-Based API'
url = 'http://localhost:5000/api'
working_directory = '/home/docs/checkouts/readthedocs.org/user_builds/sondra/checkouts/latest/docs'
exception sondra.suite.SuiteException[source]

Bases: builtins.Exception

Represents a misconfiguration in a Suite class

class sondra.suite.SuiteMetaclass(name, bases, attrs)[source]

Bases: abc.ABCMeta

sondra.suite.google_processor(s)[source]
sondra.suite.numpy_processor(s)[source]
Submodules
sondra.api module
sondra.auth module
sondra.decorators module
exception sondra.decorators.ParseError[source]

Bases: builtins.Exception

class sondra.decorators.expose(method)[source]

Bases: builtins.object

exposed = True
help(out=None, initial_heading_level=0)[source]
parse_arg(instance, arg)[source]
request_schema()[source]
response_schema()[source]
schema()[source]
url[source]
sondra.django module
sondra.flask module
sondra.help module
class sondra.help.RSTBuilder(fmt='rst', out=None, initial_heading_level=0)[source]

Bases: builtins.object

A simple builder for reStructuredText help.

Does not support all of RST by a long shot. However, it provides sufficient functionality to be useful for generating help from JSON.

HEADING_CHARS = "#=*+-~^`_:.'"
begin(title)[source]
begin_code(preface='')[source]
begin_list()[source]
begin_subheading(title)[source]
build()[source]
dedent()[source]
define(term, definition)[source]
end()[source]
end_code()[source]
end_list()[source]
end_lists()[source]
end_subheading()[source]
full_stop()[source]
html[source]
indent(amt=None)[source]
line(s='')[source]
odt[source]
rst[source]
sep(separator=' ')[source]
class sondra.help.SchemaHelpBuilder(schema, url='', fmt='rst', out=None, initial_heading_level=0)[source]

Bases: sondra.help.RSTBuilder

Builds help from a JSON-Schema as either HTML or reStructuredText source.

RESERVED_WORDS = {'description', 'type', 'minProperties', 'required', 'additionalProperties', 'maxProperties', 'id', 'properties', 'name', 'dependencies', 'title', 'patternProperties'}
build()[source]
sondra.ref module
exception sondra.ref.EndpointError[source]

Bases: builtins.Exception

Raised when an API request is parsed correctly, but the endpoint isn’t found

exception sondra.ref.ParseError[source]

Bases: builtins.Exception

Called when an API request is not parsed as valid

class sondra.ref.Reference(env, url=None, **kw)[source]

Bases: builtins.object

Contains the application, collection, document, methods, and fragment the URL refers to

FORMATS = {'schema', 'help', 'geojson', 'json'}
construct()[source]

Construct a URL from its base parts

classmethod dereference(sondra, value)[source]
classmethod dereference_all(sondra, value)[source]
get_application()[source]

Return an Application instance for the given URL.

Parameters:url (str) – The URL to a collection or a document.
Returns:An Application object
get_application_method()[source]

Return everything you need to call an application method.

Returns:
  • application object
  • method name
  • method object
Return type:A three-tuple of
Raises:EndpointError if the method or application is not found or the method is not exposable.
get_collection()[source]

Return a DocumentCollection instance for the given URL.

Parameters:url (str) – The URL to a collection or a document.
Returns:A DocumentCollection object
get_collection_method()[source]

Return everything you need to call an collection method.

Returns:
  • collection object
  • method name
  • method object
Return type:A three-tuple of
Raises:EndpointError if the method or collection is not found or the method is not exposable.
get_document()[source]

Return the Document for a given URL.

get_document_method()[source]

Return everything you need to call an document method.

Returns:
  • document object
  • method name
  • method object
Return type:A three-tuple of
Raises:EndpointError if the method or document is not found or the method is not exposable.
get_subdocument()[source]

Return the fragment within the Document referred to by this URL.

is_application()[source]
is_application_method_call()[source]
is_collection()[source]
is_collection_method_call()[source]
is_document()[source]
is_document_method_call()[source]
is_subdocument()[source]
kind[source]
classmethod maybe_reference(sondra, value)[source]
schema[source]
value[source]
sondra.utils module
sondra.utils.camelcase_slugify(name)[source]
sondra.utils.convert_camelcase(name)[source]
sondra.utils.get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')[source]

Adapted from Django. REMOVED SECRET_KEY FOR NOW

Returns a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns a 71-bit value. log_2((26+26+10)^12) =~ 71 bits

sondra.utils.import_string(dotted_path)[source]

Adapted from Django.

Import a dotted module path and return the attribute/class designated by the last name in the path. Raise ImportError if the import failed.

sondra.utils.is_exposed(fun)[source]
sondra.utils.mapjson(fun, doc)[source]
sondra.utils.qiter(o)[source]
sondra.utils.resolve_class(obj, required_superclass=<class 'object'>, required_metaclass=<class 'type'>)[source]
sondra.utils.schema_sans_definitions(original, *properties)[source]
sondra.utils.schema_sans_properties(original, *properties)[source]
sondra.utils.schema_with_definitions(original, **updates)[source]
sondra.utils.schema_with_properties(original, **updates)[source]
sondra.utils.split_camelcase(name)[source]
Module contents

Indices and tables