Source code for yapydata.datatree.synjson

# -*- coding: utf-8 -*-
"""The *YapyData.json* module provides *JSON* access in compliance to RFC-7159 [RFC7159]_.
"""

import os

import json as myjson

from pythonids import ISSTR

from yapydata.datatree import YapyDataTreeError
from yapydata.datatree.datatree import DataTree, YapyDataDataTreeOidError

__author__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \
                " @Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.1.1'
__uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741"

__docformat__ = "restructuredtext en"


[docs]class YapyDataJSONError(YapyDataTreeError): """Basic JSON syntax error. """ pass
[docs]def readout_data(xval, **kargs): """For API call-compliance with other syntaxes. Returns here the input tree only. Args: xval: The input tree from the *DataTreeJSON* - which is the result from *json.load()*. Returns: The returns here the input *xval*. Raises: pass-through """ return xval
[docs]def grow_branch(*subpath, **kargs): """Creates a new branch including the assigned value to the last node. The node types are defined by the types of the *subpath* entries. Supports a single linear branch only, no sub-branching. The created path is validated for permitted types. The derived types such as JSON have to support their own branch method. Thus provided as a static method. Args: subpath: Variable list/tuple of path keys and indexes. kargs: value: Value to be assigned to the final node. Returns: A created branch. Raises: pass-through """ _val = kargs.get('value') _subpath=list(subpath) try: ik = _subpath.pop(0) except IndexError: return _val if isinstance(ik, int): if ik != 0: # no padding raise YapyDataDataTreeOidError( "new list requires idx==0: %s\n see: %s\n" %( str(subpath), str(ik) ) ) return [grow_branch(*_subpath, value=_val)] elif isinstance(ik, ISSTR): # python only: (True, False, None,) return {ik: grow_branch(*_subpath, value=_val)} raise YapyDataDataTreeOidError( "invalid subpath key/index: %s\n see: %s\n" %( str(subpath), str(ik) ) )
[docs]class DataTreeJSON(DataTree): """Provides JSON RFC-7159 compliant in-memory data trees. """
[docs] @staticmethod def isvalid_top(value, **kargs): """Validate conformance of top-node to RFC-7159. """ if ( not isinstance( value, (dict, list, int, float,) ) and value not in (None, True, False,) and not isinstance(value, ISSTR) ): raise YapyDataJSONError( "top 'node' must be a valid JSON-RFC-7159 type, got: " + str(type(value)) )
[docs] def __init__(self, data=None, **kargs): """ Args: data: A JSON compliant in-memory data tree in accordance to RFC-7159:: json-value := ( object | array | number | string | false | true | null ) The equivalent *Python* types are:: data := <RFC-7159-type-for-json> RFC-7159-type-for-json := ( dict | list # see: object, array | int | float # see: number | str # see: unicode / for Python: ISSTR = (str(3) | unicode(2)) | None | True | False # see: null, true, false ) The initial data defines the permitted type of the first item within the *subpath* of the spanned data tree. Thus atomic data types define a single node data tree only - new in RFC-7159. Returns: None / initialized object Raises: YapyDataDataTreeError pass-through """ DataTreeJSON.isvalid_top(data) super(DataTreeJSON, self).__init__(data)
[docs] def __setattr__(self, name, value): """Validates types of own data attributes. Args: name: Name of the attribute. Following are reserved and treated special: * type: str - 'data' The value is treated as the replacement of the internal data attribute. Replaces or creates the complete data of teh current instance. value: The value of the attribute. This by default superposes present values by replacement. Non-present are created. Returns: Raises: YapyDataDataTreeError """ if name == 'data': # # replacement of current managed data # DataTreeJSON.isvalid_top(value) self.__dict__[name] = value else: # # any standard attribute with standard behavior # return object.__setattr__(self, name, value)
[docs] def create(self, *subpath, **kargs): """Adds constraints in accordance to RFC-7159. """ if subpath: DataTreeJSON.isvalid_top(subpath[0]) else: value = kargs.get('value') DataTreeJSON.isvalid_top(value) return DataTree.create(self, *subpath, **kargs)
[docs] def import_data(self, input, key=None, node=None, **kargs): """Reads a JSON file. This is a simple basic method for the application on the lower layers of the software stack. It is designed for minimal dependencies. The used library is the standard *json* package. The data is not validated. Args: input: The source of the *JSON* string data:: input := ( <fpname> # file path name | <file-id> # file pointer id | <io-stream> # io stream id ) fpname := <json-file-path-name> json-file-path-name := ( <file-path-name> # with extension | <file-path-name> '.json' # without extension, for multiple syntaxes ) file-id := "open file: file_id = open(<fpname>)" io-stream := "open io stream - io_stream = io.StreamIO(<json-string>)" json-string := "a valid string in accordance to RFC-7159" key: The key for the insertion point:: node[key] = <file-data> default := None - replace self.data, The caller is responsible for the containment of the provided node within the data structure represented by this object. No checks are performed. node: The node for the insertion of the read data.:: default := <top> Returns: Reference to read data structure. Raises: YapyDataConfigError pass-through """ if isinstance(input, ISSTR): if os.path.isfile(input): # complete path datafile = os.path.abspath(input) elif os.path.isfile(input + '.json'): # auto extension datafile = os.path.abspath(input) + '.json' else: raise YapyDataTreeError("Missing file: " + str(input)) with open(datafile) as data_file: jval = myjson.load(data_file) elif hasattr(input, 'read'): jval = myjson.load(input) else: raise YapyDataTreeError("Cannot read data: " + str(input)) if key and node == None: raise YapyDataTreeError("Given key(%s) requires a valid node." % (str(key))) if key: node[key] = jval else: self.data = jval return jval