JSON Document¶
This package provides intuitive and powerful binding system for JSON documents. It bridged the gap between raw python objects, json schema and json files. A powerful default system also allows developers to access an empty document and see the default values from the schema without any code changes.
Installation¶
You can install json-document
from source using pip. Please note that currently
the code is in alpha stages and is not recommended outside of the early
adopter group.
Table Of Contents¶
Basic features¶
If you have a few seconds, just read the next two sub-sections.
Setting and accessing value¶
You can use a Document class much like a normal python object. If you are using JSON via raw dictionaries/lists you’ll find that a lot of things are the same:
>>> from json_document.document import Document
>>> doc = Document({})
>>> doc['hello'] = 'world'
The first major difference is evident when you want to refer to data. Instead
of returning the value directly you get an instance of
DocumentFragment
. To access the value you’ll
have to use the value
property:
>>> doc['hello'].value
'world'
>>> doc.value
{'hello': 'world'}
Accessing the fragment directly is also possible and indeed is where a lot of the value json_document brings is hidden. To learn about that though you’ll have to learn about using schema with your documents.
Using document schema¶
Schema defines how a valid document looks like. It is constructed of a series of descriptions (written in JSON itself) and is quite powerful in what can be expressed. If you have no experience with JSON-Schema you can think of it as DTD for XML on steroids. Don’t worry it’s easy to learn by example.
Let’s design a simple schema for a personnel registry. Each document will describe one person and will remember their name and age:
>>> person_schema = {
... "type": "object",
... "title": "Person record",
... "description": "Simplified description of a person",
... "properties": {
... "name": {
... "type": "string",
... "title": "Full name"
... },
... "age": {
... "type": "number",
... "title": "Age in years"
... }
... }
... }
This schema can be read as follows:
- The root element is an object titled “Person record”.
- It has a property “name” that is a string titled “Full name”.
- It also has a property “age” that is a number titled “Age in years”
This schema is very simple but it already defines the correct shape of a document. It defines the type of the root object (a json “object”, for python that translates to a dictionary instance). It also describes the attributes of that object (name and age) and their type. The schema also mixes documentation elements via the “title” and “description” properties.
Using a schema you can validate documents for correctness. Let’s see how that works:
>>> joe = Document({"name": "joe", "age": 32}, person_schema)
>>> joe.validate()
Calling validate()
would have
raised an exception if joe was not a valid “Person record”. Let’s set the age
to an invalid type to see how that works:
>>> joe["age"] = "thirty two"
>>> joe.validate()
Traceback (most recent call last):
...
ValidationError: ValidationError: Object has incorrect type (expected number) object_expr='object.age', schema_expr='schema.properties.age.type')
Boom! Not only did the validation fail. We’ve got a detailed error message that
outlines the problem. It also gives us the JavaScript expression that describes
the part that did not match the schema (object.age
) and the part of the schema
that was violated (schema.properties.age.type
).
Because the actual value is hidden behind the .value property we can stash a
set of useful properties and methods in each DocumentFragment
. One of them
is .schema
which unsurprisingly returns the associated schema element (if
we have one). Instead of returning the raw JSON schema it returns a smart wrapper
around it that has properties corresponding to each legal schema part (such as
.type
and .properties
). You can use it to access meta-data such as
title and description:
>>> joe["age"].schema.title
'Age in years'
>>> joe.schema.description
'Simplified description of a person'
You can also access things like type but be aware that it has some quirks. Refer to json-schema-validator documentation for details on the Schema class. For example, the type is automatically converted to a list of valid types:
>>> joe["name"].schema.type
['string']
One useful property is .schema.optional which tells if if an element is required or not. By default everything is required, unless marked optional:
>>> joe["name"].schema.optional
False
Core features¶
So now you know roughly about documents and schema. You know that accessing
items on a document instance returns
DocumentFragment
objects (that have a
value
and
schema
properties) but setting
items sets the value directly. You know that a document may have an associated
schema and that calling
validate()
checks for errors.
Supported types¶
Now let’s expand that. So far we’ve only used objects (that map to Python dictionaries). We can use the following types in our documents:
- Dictionaries (JSON objects, schema type “object”)
- Lists (JSON arrays, schema type “array”)
- Strings (and Unicode strings, schema type “string”)
- Integers, floating point numbers and Decimals (JSON numbers, schema types “integer”, “number”)
- True and False (JSON true and false values, schema type “boolean”)
- None (JSON null value, schema type “null”)
You can use any of those items as the root object:
>>> from json_document.document import Document
>>> shopping_list = Document([])
>>> shopping_list.value.append("milk")
>>> shopping_list.value.append("cookies")
>>> shopping_list.value
['milk', 'cookies']
>>> yummy = Document("json")
>>> yummy.value
'json'
>>> life = Document(42)
>>> life.value
42
>>> long_example = Document(True)
>>> long_example.value
True
>>> surprise = Document(None)
>>> surprise.value
None
Default schema¶
All documents have a schema, even if you don’t specify one. By default the schema describes an arbitrary JSON value (one of any type):
>>> doc = Document({})
>>> doc.schema
Schema({'type': 'any'})
This does not apply to fragments you create yourself. Those always inherit the schema from their
parent document (depending on the item used to create or access that fragment). Since the default
schema does not describe the foo
property it is assigned an empty schema instead:
>>> doc['foo'] = 'bar'
>>> doc['foo'].schema
Schema({})
It’s important to point out that default type is any
. It allows the value
to be of any previously mentioned type:
>>> doc['foo'].schema.type
['any']
Schema on fragments¶
It’s pretty obvious but important to point out that when a schema describes a document and you access a fragment of that document the fragment’s schema is the corresponding fragment of the whole:
>>> doc = Document({"foo": "bar"}, {"properties": {"foo": {"type": "string"}}})
>>> doc["foo"].schema
Schema({'type': 'string'})
This is very useful when you consider that a schema can specify default values for missing elements.
Using default values¶
Having a schema for a document is not only useful because you can validate it. It is also useful because you can embed default values in the schema and transparently use them as if they were specified in the document.
Let’s see how this works. Imagine a simple application that has a save on exit feature. The application starts up, loads settings from a configuration file and does something useful. When the user quits the application it can save the current document without asking for confirmation. Traditionally you’d embed the default value in the code of your application. If you were smart you’d build an API for your configuration to transparently provide the default for you (or you’d generate the default configuration file if it was missing).
Both of those approaches are not very nice in practice. The former requires you to build additional layers of API around your basic notion of configuration. The latter prevents you from differentiating default values and settings identical to default values.
We can do better than that. Let’s start with describing our configuration schema:
>>> schema = {
... "type": "object",
... "properties": {
... "save_on_exit": {
... "type": "boolean",
... "default": True,
... "optional": True
... }
... }
... }
There are a couple of new elements here:
- The default value is specified, exactly once, in the schema
- The property is marked as optional, when missing the document will still be valid.
Let’s create a configuration object to see how this works:
>>> config = Document({}, schema)
>>> config["save_on_exit"].value
True
Success! Still a little verbose but already doing much, much better. The
default value was looked up in the schema and provided in place of our missing
configuration option. We can see this option is default by accessing a few
methods and properties. With
is_default
you can check if .value is a
real thing or a substitute from the schema. With
default_value
you can see what
the default is. Lastly, with
default_value_exists
you can
check if there even is a default specified. After all, if the schema has no
defaults then your code will simply trigger an exception instead:
>>> config["save_on_exit"].is_default
True
>>> config["save_on_exit"].default_value
True
>>> config["save_on_exit"].default_value_exists
True
We can still change the value as we had before, all of that works as expected. The non-obvious part is what the value of our document is. Before we change anything it is still left as-is, as we provided it initially, that is, empty.:
>>> config.value
{}
If we change it, however, it reflects that change:
>>> config["save_on_exit"] = False
>>> config.value
{'save_on_exit': False}
Reverting to defaults¶
Let’s suppose our application wants to provide a “revert to defaults” button that resets all configuration options to what was provided out of the box. JSON document has a sweet feature to support this kind of behavior.
Let’s start with some settings we loaded for this user (we are reusing the schema from the previous example):
>>> config = Document({"save_on_exit": True}, schema)
The first thing to point out is that a default value is a ‘special’ thing.
Being equal to the default value is not the same as being default. Here, the
save_on_exit
option is True, the same as the default from the schema. It is
not default though:
>>> config["save_on_exit"].is_default
False
To really make it default you need to call the
revert_to_default()
method:
>>> config["save_on_exit"].revert_to_default()
>>> config["save_on_exit"].value
True
>>> config["save_on_exit"].is_default
True
When you do that the document is transformed and the part we’ve customized is removed. Obviously without a default value in the schema this method would raise an exception with an appropriate message:
>>> config.value
{}
Defaults are a very powerful system. Used correctly they allow applications to recover from manually edited configuration files (config errors), allow users to customize parts of their configuration while allowing defaults to evolve with future versions and significantly simplify application configuration handling for programmers where less checking is needed, especially when coupled with JSON schema validation that can not only shape but constrain values of specific properties.
Fragments and references¶
So far in this document we’ve been referring to document fragments by accessing
dictionary items and array elements on the root document object. Accessing
those items transparently creates
DocumentFragment
instances. Wrapper objects
pointing to a sub-tree of the document object. It is possible to save those
references and use them freely for convenience. Let’s see how this works:
>>> doc = Document({})
>>> doc["list"] = [1, 2, 3]
>>> doc["dict"] = {"hello": "world"}
>>> doc["value"] = "I'm a plain string"
For clarity, this is how the document looks like now:
>>> doc.value
{'dict': {'hello': 'world'}, 'list': [1, 2, 3], 'value': "I'm a plain string"}
Let’s obtain a reference to the list:
>>> lst = doc["list"]
A document fragment is much like a document itself
(Document
is also a DocumentFragment subclass)
it has a .value and .schema properties. It has a revert_to_default() method and
everything you’ve learned so far.
It can also be modified, and here it gets interesting. You can modify the value by assigning to the .value property:
>>> lst.value
[1, 2, 3]
>>> lst.value = [4, 5]
>>> lst.value
[4, 5]
The interesting part is that this automatically integrates into the document this fragment is a part of:
>>> doc.value
{'dict': {'hello': 'world'}, 'list': [4, 5], 'value': "I'm a plain string"}
In general it you can freely modify the tree and it will work as expected:
>>> dct = doc["dict"]
>>> dct.value = {'hello': 'there'}
>>> val = doc["value"]
>>> val.value = 42
>>> doc.value
{'dict': {'hello': 'there'}, 'list': [4, 5], 'value': 42}
You can also use mutating methods (those that alter the state of the value), in this case you are not assigning a new value to the .value property but rather calling some method on it:
>>> lst.value.append(6)
>>> dct.value['hello'] = 'joe'
>>> doc.value
{'dict': {'hello': 'joe'}, 'list': [4, 5, 6], 'value': 42}
Fragments also have a few interesting properties. The .document property allows you to reach the document object this fragment is a part of. The .parent property points to the parent fragment (say, if you have a fragment to member of a list then the .parent will be pointing to the list itself). The .item property is perhaps named confusingly but it is the index of this fragment in the parent fragment (the list index or dictionary key)
Fragments also have few special methods that make using them more natural in
python. You can check the length (of strings, dicts and lists), you can check
for membership using the foo in bar
syntax. You can also iterate over
containers (lists and dicts only)
Orphaned fragments¶
Since you can keep references to fragments around for as long as you like it is possible to create an interesting situation. It is only interesting in a problematic way though. A fragment can become orphaned (and useless) when its parent (or its parent, all the way up to the root document object) are overwritten. Let’s see how this works:
>>> doc = Document({})
>>> doc['foo'] = 'bar'
>>> foo = doc['foo']
>>> doc.value = {}
>>> foo.is_orphaned
True
So now the foo
fragment is an orphaned. A few things happen when this
occurs:
- The .document property is set to None
- The .parent property is set to None
- The .value is set to a deep copy of the original value
So for all intents and purposes an orphaned node is independent leftover that
is totally disconnected from the original. This means that changing its value
is not going to alter the document anymore (since this would make no sense). In
fact, attempting to change the value will raise an
OrphanedFragmentError
:
>>> foo.value = "barf"
Traceback (most recent call last):
...
OrphanedFragmentError: Attempt to modify orphaned document fragment
Usually when you see this it indicates a programming error. If you want to keep using something don’t overwrite its parent. For convenience it is not an error to read from an orphaned fragment as it is useful in some cases and provides some level of ‘transaction isolation’ where you can bet that you’ve got a working fragment (just that the writes will fail)
Advanced features¶
If you got this far you know pretty much everything there is about the basic feature set. The rest of this document will make you more productive by letting you write less code and by letting you write code that is more natural to read and use later.
Custom fragments¶
Since you get fragment objects every time you access some of its parts would not it be nice to be able to put your custom methods there? This way you could somewhat forget about working with JSON and see this as a part of your class hierarchy.
I thought it was useful so here it is. You need to have a schema for your document the reason for that is we’ll be embedding special schema elements that will override the instantiated fragment class. Usually this is simple but it is boiler-plate-ish at times:
For the sake of documentation we’ll be writing a word counter program that will store the count of each encountered word. We’ll need to subclass DocumentFragment so let’s pull that in to our namespace:
>>> from json_document.document import Document, DocumentFragment
The Word class is what will represent each word we’ve encountered. We’ll start by keeping it simple, just a inc() method:
>>> class Word(DocumentFragment):
...
... def inc(self):
... self.value += 1
The WordCounter class will be a custom Document that just has the schema. Here
we also see that the schema can be defined once in the document class by
creating a document_schema
property. This is convenient when you have one
schema and want to make the life of your users easier. The schema defines an
object (with a default value of {}). This object can have additional properties
(that is, properties not explicitly mentioned in the schema) but each one has
to be an integer. If missing the default value of each property is zero.
Finally the special __fragment_cls
schema entry instructs which
DocumentFragment sub-class to instantiate:
>>> class WordCounter(Document):
...
... document_schema = {
... 'type': 'object',
... 'default': {},
... 'additionalProperties': {
... 'type': 'integer',
... 'default': 0,
... '__fragment_cls': Word
... }
... }
Having done that we can now start using this:
>>> doc = WordCounter({})
Default values work:
>>> doc['json'].value
0
As did our custom class declaration:
>>> for word in "json is a nice thing to keep your data, json".split():
... doc[word].inc()
Finally the data is saved and we can inspect it or save it later:
>>> doc['json'].value
2
Value bridge¶
So we have all the nice features so far, we even have custom fragment classes to keep our code more maintainable and readable. The last thing that was annoying me was the need to use dictionary notation to access my fragments. I wanted to use object traversal notation instead. I ended up writing a lot of properties that were just exposing the fragment in a more natural syntax.
Let’s see what it was like:
>>> class Config(Document):
...
... document_schema = {
... 'type': 'object',
... 'default': {},
... 'properties': {
... 'save_on_exit': {
... 'type': 'bool',
... 'default': True
... }
... }
... }
...
... @property
... def save_on_exit(self):
... return self['save_on_exit']
>>> conf = Config()
>>> conf.save_on_exit.value
True
It worked but was somewhat tedious (I had to repeat the name of the property. It was also annoying if it the property was a simple value (not something more complicated that itself would be having extra methods/state) and I had to type .value all the time.
So I wrote three good decorators that made this easy. They are all in the bridge module:
>>> from json_document import bridge
We can now improve our Config class with one of them the ‘readwrite’ bridge:
>>> class BetterConfig(Config):
...
... @bridge.readwrite
... def save_on_exit(self):
... ''' documentation on this property '''
The intent and code is very clear, it simply allows you to read and write the .value directly, without having the extra lookup on your side. It also gives your JSON document pythonic look and documentation:
>>> conf = BetterConfig()
>>> conf.save_on_exit
True
>>> conf.save_on_exit = False
>>> conf.save_on_exit
False
If something is not really going to change (say you are only reading a part of
a document that is modified by third party program) you can make that explicit
in your code by using bridge.readonly
instead.
Fragment bridge¶
Fragment bridge is very similar to the value bridge (readonly and readwrite) but instead of returning the value it returns the fragment itself. It allows for more readable code that can still access all the methods and properties that DocumentFragment provides.
I found it useful to document my JSON structure on the python side by mapping larger pieces of the schema to custom classes and putting fragment bridges in the document class.
Let’s say you have a person record with first and last name strings:
>>> class PersonName(DocumentFragment):
... """ Person's name """
...
... @bridge.readwrite
... def first(self):
... """ First name """
...
... @bridge.readwrite
... def last(self):
... """ Last name """
...
... @property
... def full(self):
... return "%s %s" % (self.first, self.last)
>>> class Person(Document):
... """ Person record """
...
... document_schema = {
... 'type': object,
... 'properties': {
... 'name': {
... 'type': 'object',
... 'default': {},
... '__fragment_cls': PersonName,
... 'properties': {
... 'first': {
... 'type': 'string'
... },
... 'last': {
... 'type': 'string'
... }
... }
... }
... }
... }
...
... @bridge.fragment
... def name(self):
... """ Name data """
Uh, that was verbose, the good part is that after
the bulky class is
written we can write lean code using that class. Let’s see how this works:
>>> john = Person({})
>>> john.name.first = "John"
>>> john.name.last = "Doe"
>>> john.name.full
'John Doe'
>>> john.value
{'name': {'last': 'Doe', 'first': 'John'}}
Did you notice this was a JSON object? Nice eh :-)
That’s it
Code Reference¶
json_document.document¶
Document and fragment classes
-
class
json_document.document.
Document
(value, schema=None)¶ Class representing a smart JSON document
A document is also a fragment that wraps the entire value. Is inherits all of its properties. There are two key differences: a document has no parent fragment and it holds the revision counter that is incremented on each modification of the document.
-
revision
¶ Return the revision number of this document.
Each change increments this value by one. You should not really care about the count as sometimes the increments may be not what you expected. It is best to use this to spot difference (if your count is different than mine we’re different).
-
-
class
json_document.document.
DocumentFragment
(document, parent, value, item=None, schema=None)¶ Wrapper around a fragment of a document.
Fragment may wrap a single item (such as None, bool, int, float, string) or to a container (such as list or dict). You can access the value pointed to with the
value
property.Each fragment is linked to a
parent
fragment and the :attr:’document itself. When the parent fragment wraps a list or a dictionary then the index (or key) that this fragment was references as is stored initem
. Sometimes this linkage becomes broken and a fragment is considered orphaned. Orphaned fragments still allow you to read the value they wrap but since they are no longer associated with any document you cannot set the value anymore.Fragment is also optionally associated with a schema (typically the relevant part of the document schema). When schema is available you can
validate()
a fragment for correctness. If the schema designates adefault_value
you canrevert_to_default()
to discard the current value in favour of the one from the schema.-
default_value
¶ Get the default value.
Note: This method will raise SchemaError if the default is not defined in the schema
-
default_value_exists
¶ Returns True if a default value exists for this fragment.
The default value can be accessed with
default_value
. You can also revert the current value to default by callingrevert_to_default()
.When there is no default value any attempt to use or access it will raise a SchemaError.
-
document
¶ The document object (the topmost parent document fragment)
-
is_default
¶ Check if this fragment points to a default value.
Note
A fragment that points to a value equal to the value of the default is not considered default. Only fragments that were not assigned a value previously are considered default.
-
is_orphaned
¶ Check if a fragment is orphaned.
Orphaned fragments can occur in this scenario:
>>> doc = Document() >>> doc["foo"] = "value" >>> foo = doc["foo"] >>> doc.value = {} >>> foo.is_orphaned True
That is, when the parent fragment value is overwritten.
-
item
¶ The index of this fragment in the parent collection.
Item is named somewhat misleadingly. It is the name of the index that was used to access this fragment from the parent fragment. Typically this is the dictionary key name or list index.
-
parent
¶ The parent fragment (if any)
The document root (typically a
Document
instance) has no parent. If the parent exist thenfragment.parent[fragment.item]
points back to the same value asfragment
but wrapped in a different instance of DocumentFragment.
-
revert_to_default
()¶ Discard current value and use defaults from the schema.
@raises TypeError: when default value does not exist Revert the value that this fragment points to to the default value.
-
schema
¶ Schema associated with this fragment
Schema may be None
This is a read-only property. Schema is automatically provided when a sub-fragment is accessed on a parent fragment (all the way up to the document). To provide schema for your fragments make sure to include them in the
properties
oritems
. Alternatively you can provideadditionalProperties
that will act as a catch-all clause allowing you to define a schema for anything that was not explicitly matched byproperties
.
-
validate
()¶ Validate the fragment value against the schema
-
value
¶ Value being wrapped by this document fragment.
Getting reads the value (if not
is_default
) from the document or transparently returns the default values from the schema (ifdefault_value_exists
).Setting a value instantiates default values in this or any parent fragment. That is, if the value of this fragment or any of the parent fragments is default (
is_default
returns True), then the default value is copied and used as the effective value instead.When
is_default
is True setting any value (including the value ofdefault_value
) will overwrite the value so thatis_default
will return False. If you want to set the default value userevert_to_default()
explicitly.Setting a value that is different from the current value bumps the revision of the whole document.
-
-
class
json_document.document.
DocumentPersistence
(document, storage, serializer=None)¶ Simple glue layer between document and storage:
document <-> serializer <-> storage
You can have any number of persistence instances associated with a single document.
-
load
()¶ Load the document from the storage layer
-
save
()¶ Save the document to the storage layer.
The document is only saved if the document was modified since it was last saved. The document revision is non-persistent property (so you cannot use it as a version control system) but as long as the document instance is alive you can optimize saving easily.
-
Exceptions¶
-
exception
json_document.errors.
DocumentError
(document, msg)¶ Base class for all Document exceptions.
-
exception
json_document.errors.
DocumentSyntaxError
(document, error)¶ Syntax error in document
-
exception
json_document.errors.
DocumentSchemaError
(document, error)¶ Schema error in document
-
exception
json_document.errors.
OrphanedFragmentError
(fragment)¶ Exception raised when an orphaned document fragment is being modified.
A fragment becomes orphaned if a saved reference no longer belongs to any document tree. This can happen when one reverts a document fragment to default value while still holding references do elements of that fragment.
-
exception
json_document.errors.
DocumentError
(document, msg) Base class for all Document exceptions.
-
exception
json_document.errors.
DocumentSchemaError
(document, error) Schema error in document
-
exception
json_document.errors.
DocumentSyntaxError
(document, error) Syntax error in document
-
exception
json_document.errors.
OrphanedFragmentError
(fragment) Exception raised when an orphaned document fragment is being modified.
A fragment becomes orphaned if a saved reference no longer belongs to any document tree. This can happen when one reverts a document fragment to default value while still holding references do elements of that fragment.
json_document.serializers¶
Document serializer classes
-
class
json_document.serializers.
JSON
¶ JSON class encapsulates loading and saving JSON files using simplejson module. It handles ‘raw’ json without any of the additions specified in the
Document
class.-
classmethod
dump
(stream, doc, human_readable=True, sort_keys=False)¶ Dump JSON to a stream-like object
Discussion: If human_readable is True the serialized stream is meant to be read by humans, it will have newlines, proper indentation and spaces after commas and colons. This option is enabled by default.
If sort_keys is True then resulting JSON object will have sorted keys in all objects. This is useful for predictable format but is not recommended if you want to load-modify-save an existing document without altering it’s general structure. This option is not enabled by default.
Return value: None
-
classmethod
dumps
(doc, human_readable=True, sort_keys=False)¶ Dump JSON to a string
Discussion: If human_readable is True the serialized value is meant to be read by humans, it will have newlines, proper indentation and spaces after commas and colons. This option is enabled by default.
If sort_keys is True then resulting JSON object will have sorted keys in all objects. This is useful for predictable format but is not recommended if you want to load-modify-save an existing document without altering it’s general structure. This option is not enabled by default.
Return value: JSON document as string
-
classmethod
load
(stream, retain_order=True)¶ Load a JSON document from the specified stream
Discussion: The document is read from the stream and parsed as JSON text.
Return value: The document loaded from the stream. If retain_order is True then the resulting objects are composed of ordered dictionaries. This mode is slightly slower and consumes more memory but allows one to save the document exactly as it was before (apart from whitespace differences).
Exceptions: - JSONDecodeError
When the text does not represent a correct JSON document.
-
classmethod
loads
(text, retain_order=True)¶ Same as load() but reads data from a string
-
classmethod
-
exception
json_document.serializers.
JSONDecodeError
(msg, doc, pos, end=None)[source]¶ Subclass of ValueError with the following additional properties:
msg: The unformatted error message doc: The JSON document being parsed pos: The start index of doc where parsing failed end: The end index of doc where parsing failed (may be None) lineno: The line corresponding to pos colno: The column corresponding to pos endlineno: The line corresponding to end (may be None) endcolno: The column corresponding to end (may be None)
json_document.storage¶
Storage classes (for storing serializer output)
-
class
json_document.storage.
FileStorage
(pathname, ignore_missing=False)¶ File-based storage class.
This class is used in conjunction with
DocumentPersistance
to bind a Document to a file (via a serializer).-
read
()¶ Read all data from the file.
Data is transparently interpreted as UTF-8 encoded Unicode string. If ignore_missing is True and the file does not exist an empty string is returned instead.
-
write
(data)¶ Write the specified data to the file
The data overwrites anything present in the file earlier. If data is an Unicode object it is automatically converted to UTF-8.
-
json_document.bridge¶
Collection of decorator methods for accessing document fragments
You want to use those decorators if you are not interested in raw JSON or high-level DocumentFragments (which would require you to access each value via the .value property) but want to offer a pythonic API instead.
-
json_document.bridge.
fragment
(func)¶ Bridge to a document fragment.
The name of the fragment is identical to to the name of the decorated function. The function is never called, it is only used to obtain the docstring.
This is equivalent to:
@property def foo(self): return self['foo']
-
json_document.bridge.
readonly
(func)¶ Read-only bridge to the value of a document fragment.
The name of the fragment is identical to to the name of the decorated function. The function is never called, it is only used to obtain the docstring.
This is equivalent to:
@property def foo(self): return self['foo'].value
-
json_document.bridge.
readwrite
(func)¶ Read-write bridge to the value of a document fragment.
The name of the fragment is identical to to the name of the decorated function. The function is never called, it is only used to obtain the docstring.
This is equivalent to:
@property def foo(self): return self['foo'].value
Followed by:
@foo.setter def foo(self, new_value): return self['foo'] = new_value
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line