Package yapydata :: Package datatree :: Module synjson

Source Code for Module yapydata.datatree.synjson

  1  # -*- coding: utf-8 -*- 
  2  """The *YapyData.json* module provides *JSON* access in compliance to RFC-7159 [RFC7159]_. 
  3  """ 
  4   
  5  import os 
  6   
  7  import json as myjson 
  8   
  9  from pythonids import ISSTR 
 10   
 11  from yapydata.datatree import YapyDataTreeError 
 12  from yapydata.datatree.datatree import DataTree, YapyDataDataTreeOidError 
 13   
 14  __author__ = 'Arno-Can Uestuensoez' 
 15  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
 16  __copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \ 
 17                  " @Ingenieurbuero Arno-Can Uestuensoez" 
 18  __version__ = '0.1.1' 
 19  __uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741" 
 20   
 21  __docformat__ = "restructuredtext en" 
22 23 24 -class YapyDataJSONError(YapyDataTreeError):
25 """Basic JSON syntax error. 26 """ 27 pass
28
29 30 -def readout_data(xval, **kargs):
31 """For API call-compliance with other syntaxes. Returns here the 32 input tree only. 33 34 Args: 35 xval: 36 The input tree from the *DataTreeJSON* - which is 37 the result from *json.load()*. 38 39 Returns: 40 The returns here the input *xval*. 41 42 Raises: 43 pass-through 44 45 """ 46 return xval
47
48 49 -def grow_branch(*subpath, **kargs):
50 """Creates a new branch including the assigned value to 51 the last node. The node types are defined by the types 52 of the *subpath* entries. 53 54 Supports a single linear branch only, no sub-branching. 55 56 The created path is validated for permitted types. 57 The derived types such as JSON have to support their 58 own branch method. Thus provided as a static method. 59 60 Args: 61 subpath: 62 Variable list/tuple of path keys and indexes. 63 64 kargs: 65 value: 66 Value to be assigned to the final node. 67 68 Returns: 69 A created branch. 70 71 Raises: 72 pass-through 73 74 """ 75 _val = kargs.get('value') 76 _subpath=list(subpath) 77 try: 78 ik = _subpath.pop(0) 79 except IndexError: 80 return _val 81 82 if isinstance(ik, int): 83 if ik != 0: 84 # no padding 85 raise YapyDataDataTreeOidError( 86 "new list requires idx==0: %s\n see: %s\n" %( 87 str(subpath), 88 str(ik) 89 ) 90 ) 91 return [grow_branch(*_subpath, value=_val)] 92 93 elif isinstance(ik, ISSTR): 94 # python only: (True, False, None,) 95 return {ik: grow_branch(*_subpath, value=_val)} 96 97 raise YapyDataDataTreeOidError( 98 "invalid subpath key/index: %s\n see: %s\n" %( 99 str(subpath), 100 str(ik) 101 ) 102 )
103
104 105 -class DataTreeJSON(DataTree):
106 """Provides JSON RFC-7159 compliant in-memory data trees. 107 """ 108 109 @staticmethod
110 - def isvalid_top(value, **kargs):
111 """Validate conformance of top-node to RFC-7159. 112 """ 113 114 if ( 115 not isinstance( 116 value, 117 (dict, list, int, float,) 118 ) 119 and value not in (None, True, False,) 120 and not isinstance(value, ISSTR) 121 ): 122 raise YapyDataJSONError( 123 "top 'node' must be a valid JSON-RFC-7159 type, got: " 124 + str(type(value)) 125 )
126
127 - def __init__(self, data=None, **kargs):
128 """ 129 Args: 130 data: 131 A JSON compliant in-memory data tree in accordance to RFC-7159:: 132 133 json-value := ( 134 object | array 135 | number 136 | string 137 | false | true 138 | null 139 ) 140 141 The equivalent *Python* types are:: 142 143 data := <RFC-7159-type-for-json> 144 145 RFC-7159-type-for-json := ( 146 dict | list # see: object, array 147 | int | float # see: number 148 | str # see: unicode / for Python: ISSTR = (str(3) | unicode(2)) 149 | None | True | False # see: null, true, false 150 ) 151 152 The initial data defines the permitted type of the first item 153 within the *subpath* of the spanned data tree. 154 155 Thus atomic data types define a single node data tree only - new in RFC-7159. 156 157 Returns: 158 None / initialized object 159 160 Raises: 161 YapyDataDataTreeError 162 163 pass-through 164 165 """ 166 DataTreeJSON.isvalid_top(data) 167 super(DataTreeJSON, self).__init__(data)
168
169 - def __setattr__(self, name, value):
170 """Validates types of own data attributes. 171 172 Args: 173 name: 174 Name of the attribute. Following are reserved and 175 treated special: 176 177 * type: str - 'data' 178 The value is treated as the replacement of the internal 179 data attribute. Replaces or creates the complete data 180 of teh current instance. 181 182 value: 183 The value of the attribute. This by default superposes 184 present values by replacement. Non-present are created. 185 186 Returns: 187 188 Raises: 189 YapyDataDataTreeError 190 191 """ 192 if name == 'data': 193 # 194 # replacement of current managed data 195 # 196 DataTreeJSON.isvalid_top(value) 197 self.__dict__[name] = value 198 199 else: 200 # 201 # any standard attribute with standard behavior 202 # 203 return object.__setattr__(self, name, value)
204
205 - def create(self, *subpath, **kargs):
206 """Adds constraints in accordance to RFC-7159. 207 """ 208 if subpath: 209 DataTreeJSON.isvalid_top(subpath[0]) 210 else: 211 value = kargs.get('value') 212 DataTreeJSON.isvalid_top(value) 213 return DataTree.create(self, *subpath, **kargs)
214
215 - def import_data(self, input, key=None, node=None, **kargs):
216 """Reads a JSON file. This is a simple basic method for the application 217 on the lower layers of the software stack. It is designed for minimal 218 dependencies. The used library is the standard *json* package. 219 The data is not validated. 220 221 Args: 222 input: 223 The source of the *JSON* string data:: 224 225 input := ( 226 <fpname> # file path name 227 | <file-id> # file pointer id 228 | <io-stream> # io stream id 229 ) 230 231 fpname := <json-file-path-name> 232 json-file-path-name := ( 233 <file-path-name> # with extension 234 | <file-path-name> '.json' # without extension, for multiple syntaxes 235 ) 236 file-id := "open file: file_id = open(<fpname>)" 237 io-stream := "open io stream - io_stream = io.StreamIO(<json-string>)" 238 json-string := "a valid string in accordance to RFC-7159" 239 240 key: 241 The key for the insertion point:: 242 243 node[key] = <file-data> 244 245 default := None - replace self.data, 246 247 The caller is responsible for the containment of the provided 248 node within the data structure represented by this object. No 249 checks are performed. 250 251 node: 252 The node for the insertion of the read data.:: 253 254 default := <top> 255 256 Returns: 257 Reference to read data structure. 258 259 Raises: 260 YapyDataConfigError 261 262 pass-through 263 264 """ 265 if isinstance(input, ISSTR): 266 if os.path.isfile(input): 267 # complete path 268 datafile = os.path.abspath(input) 269 elif os.path.isfile(input + '.json'): 270 # auto extension 271 datafile = os.path.abspath(input) + '.json' 272 else: 273 raise YapyDataTreeError("Missing file: " + str(input)) 274 275 with open(datafile) as data_file: 276 jval = myjson.load(data_file) 277 278 elif hasattr(input, 'read'): 279 jval = myjson.load(input) 280 281 else: 282 raise YapyDataTreeError("Cannot read data: " + str(input)) 283 284 if key and node == None: 285 raise YapyDataTreeError("Given key(%s) requires a valid node." % (str(key))) 286 287 if key: 288 node[key] = jval 289 else: 290 self.data = jval 291 292 return jval
293