Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_schema.py 75KB

1 year ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. import json
  27. import os
  28. import io
  29. import pprint
  30. import hashlib
  31. import textwrap
  32. from pathlib import Path
  33. from pprint import pformat
  34. from typing import Union, Dict, List, Optional, IO, Any, Tuple
  35. from collections.abc import Sequence
  36. # FIXME
  37. # https://github.com/google/yapf#example-as-a-module
  38. from yapf.yapflib.yapf_api import FormatCode
  39. import txaio
  40. from autobahn.wamp.exception import InvalidPayload
  41. from autobahn.util import hlval
  42. from zlmdb.flatbuffers.reflection.Schema import Schema as _Schema
  43. from zlmdb.flatbuffers.reflection.BaseType import BaseType as _BaseType
  44. from zlmdb.flatbuffers.reflection.Field import Field
  45. class FbsType(object):
  46. """
  47. Flatbuffers type.
  48. See: https://github.com/google/flatbuffers/blob/11a19887053534c43f73e74786b46a615ecbf28e/reflection/reflection.fbs#L33
  49. """
  50. __slots__ = ('_repository', '_schema', '_basetype', '_element', '_index', '_objtype', '_elementtype')
  51. UType = _BaseType.UType
  52. # scalar types
  53. Bool = _BaseType.Bool
  54. Byte = _BaseType.Byte
  55. UByte = _BaseType.UByte
  56. Short = _BaseType.Short
  57. UShort = _BaseType.UShort
  58. Int = _BaseType.Int
  59. UInt = _BaseType.UInt
  60. Long = _BaseType.Long
  61. ULong = _BaseType.ULong
  62. Float = _BaseType.Float
  63. Double = _BaseType.Double
  64. String = _BaseType.String
  65. SCALAR_TYPES = [_BaseType.Bool,
  66. _BaseType.Byte,
  67. _BaseType.UByte,
  68. _BaseType.Short,
  69. _BaseType.UShort,
  70. _BaseType.Int,
  71. _BaseType.UInt,
  72. _BaseType.Long,
  73. _BaseType.ULong,
  74. _BaseType.Float,
  75. _BaseType.Double,
  76. _BaseType.String]
  77. # structured types
  78. Vector = _BaseType.Vector
  79. Obj = _BaseType.Obj
  80. Union = _BaseType.Union
  81. STRUCTURED_TYPES = [_BaseType.Vector,
  82. _BaseType.Obj,
  83. _BaseType.Union]
  84. FBS2PY = {
  85. _BaseType.UType: 'int',
  86. _BaseType.Bool: 'bool',
  87. _BaseType.Byte: 'bytes',
  88. _BaseType.UByte: 'int',
  89. _BaseType.Short: 'int',
  90. _BaseType.UShort: 'int',
  91. _BaseType.Int: 'int',
  92. _BaseType.UInt: 'int',
  93. _BaseType.Long: 'int',
  94. _BaseType.ULong: 'int',
  95. _BaseType.Float: 'float',
  96. _BaseType.Double: 'float',
  97. _BaseType.String: 'str',
  98. _BaseType.Vector: 'List',
  99. _BaseType.Obj: 'object',
  100. _BaseType.Union: 'Union',
  101. }
  102. FBS2PY_TYPE = {
  103. _BaseType.UType: int,
  104. _BaseType.Bool: bool,
  105. _BaseType.Byte: int,
  106. _BaseType.UByte: int,
  107. _BaseType.Short: int,
  108. _BaseType.UShort: int,
  109. _BaseType.Int: int,
  110. _BaseType.UInt: int,
  111. _BaseType.Long: int,
  112. _BaseType.ULong: int,
  113. _BaseType.Float: float,
  114. _BaseType.Double: float,
  115. _BaseType.String: str,
  116. _BaseType.Vector: list,
  117. _BaseType.Obj: dict,
  118. # _BaseType.Union: 'Union',
  119. }
  120. FBS2FLAGS = {
  121. _BaseType.Bool: 'BoolFlags',
  122. _BaseType.Byte: 'Int8Flags',
  123. _BaseType.UByte: 'Uint8Flags',
  124. _BaseType.Short: 'Int16Flags',
  125. _BaseType.UShort: 'Uint16Flags',
  126. _BaseType.Int: 'Int32Flags',
  127. _BaseType.UInt: 'Uint32Flags',
  128. _BaseType.Long: 'Int64Flags',
  129. _BaseType.ULong: 'Uint64Flags',
  130. _BaseType.Float: 'Float32Flags',
  131. _BaseType.Double: 'Float64Flags',
  132. }
  133. FBS2PREPEND = {
  134. _BaseType.Bool: 'PrependBoolSlot',
  135. _BaseType.Byte: 'PrependInt8Slot',
  136. _BaseType.UByte: 'PrependUint8Slot',
  137. _BaseType.Short: 'PrependInt16Slot',
  138. _BaseType.UShort: 'PrependUint16Slot',
  139. _BaseType.Int: 'PrependInt32Slot',
  140. _BaseType.UInt: 'PrependUint32Slot',
  141. _BaseType.Long: 'PrependInt64Slot',
  142. _BaseType.ULong: 'PrependUint64Slot',
  143. _BaseType.Float: 'PrependFloat32Slot',
  144. _BaseType.Double: 'PrependFloat64Slot',
  145. }
  146. FBS2STR = {
  147. _BaseType.UType: 'UType',
  148. _BaseType.Bool: 'Bool',
  149. _BaseType.Byte: 'Byte',
  150. _BaseType.UByte: 'UByte',
  151. _BaseType.Short: 'Short',
  152. _BaseType.UShort: 'UShort',
  153. _BaseType.Int: 'Int',
  154. _BaseType.UInt: 'UInt',
  155. _BaseType.Long: 'Long',
  156. _BaseType.ULong: 'ULong',
  157. _BaseType.Float: 'Float',
  158. _BaseType.Double: 'Double',
  159. _BaseType.String: 'String',
  160. _BaseType.Vector: 'Vector',
  161. _BaseType.Obj: 'Obj',
  162. _BaseType.Union: 'Union',
  163. }
  164. STR2FBS = {
  165. 'UType': _BaseType.UType,
  166. 'Bool': _BaseType.Bool,
  167. 'Byte': _BaseType.Byte,
  168. 'UByte': _BaseType.UByte,
  169. 'Short': _BaseType.Short,
  170. 'UShort': _BaseType.UShort,
  171. 'Int': _BaseType.Int,
  172. 'UInt': _BaseType.UInt,
  173. 'Long': _BaseType.Long,
  174. 'ULong': _BaseType.ULong,
  175. 'Float': _BaseType.Float,
  176. 'Double': _BaseType.Double,
  177. 'String': _BaseType.String,
  178. 'Vector': _BaseType.Vector,
  179. 'Obj': _BaseType.Obj,
  180. 'Union': _BaseType.Union,
  181. }
  182. def __init__(self,
  183. repository: 'FbsRepository',
  184. schema: 'FbsSchema',
  185. basetype: int,
  186. element: int,
  187. index: int,
  188. objtype: Optional[str] = None,
  189. elementtype: Optional[str] = None):
  190. self._repository = repository
  191. self._schema = schema
  192. self._basetype = basetype
  193. self._element = element
  194. self._elementtype = elementtype
  195. self._index = index
  196. self._objtype = objtype
  197. @property
  198. def repository(self) -> 'FbsRepository':
  199. return self._repository
  200. @property
  201. def schema(self) -> 'FbsSchema':
  202. return self._schema
  203. @property
  204. def basetype(self) -> int:
  205. """
  206. Flatbuffers base type.
  207. :return:
  208. """
  209. return self._basetype
  210. @property
  211. def element(self) -> int:
  212. """
  213. Only if basetype == Vector
  214. :return:
  215. """
  216. return self._element
  217. @property
  218. def index(self) -> int:
  219. """
  220. If basetype == Object, index into "objects".
  221. If base_type == Union, UnionType, or integral derived from an enum, index into "enums".
  222. If base_type == Vector && element == Union or UnionType.
  223. :return:
  224. """
  225. return self._index
  226. @property
  227. def elementtype(self) -> Optional[str]:
  228. """
  229. If basetype == Vector, fully qualified element type name.
  230. :return:
  231. """
  232. # lazy-resolve of element type index to element type name. this is important (!)
  233. # to decouple from loading order of type objects
  234. if self._basetype == FbsType.Vector and self._elementtype is None:
  235. if self._element == FbsType.Obj:
  236. self._elementtype = self._schema.objs_by_id[self._index].name
  237. # print('filled in missing elementtype "{}" for element type index {} in vector'.format(self._elementtype, self._index))
  238. else:
  239. assert False, 'FIXME'
  240. return self._elementtype
  241. @property
  242. def objtype(self) -> Optional[str]:
  243. """
  244. If basetype == Object, fully qualified object type name.
  245. :return:
  246. """
  247. # lazy-resolve of object type index to object type name. this is important (!)
  248. # to decouple from loading order of type objects
  249. if self._basetype == FbsType.Obj and self._objtype is None:
  250. self._objtype = self._schema.objs_by_id[self._index].name
  251. # print('filled in missing objtype "{}" for object type index {} in object'.format(self._objtype, self._index))
  252. return self._objtype
  253. def map(self, language: str, attrs: Optional[Dict] = None, required: Optional[bool] = True,
  254. objtype_as_string: bool = False) -> str:
  255. """
  256. :param language:
  257. :param attrs:
  258. :param required:
  259. :param objtype_as_string:
  260. :return:
  261. """
  262. if language == 'python':
  263. _mapped_type = None
  264. if self.basetype == FbsType.Vector:
  265. # vectors of uint8 are mapped to byte strings
  266. if self.element == FbsType.UByte:
  267. if attrs and 'uuid' in attrs:
  268. _mapped_type = 'uuid.UUID'
  269. else:
  270. _mapped_type = 'bytes'
  271. # whereas all other vectors are mapped to list of the same element type
  272. else:
  273. if self.objtype:
  274. # FIXME
  275. _mapped_type = 'List[{}]'.format(self.objtype.split('.')[-1])
  276. # _mapped_type = 'List[{}.{}]'.format(self._repository.render_to_basemodule, self.objtype)
  277. else:
  278. _mapped_type = 'List[{}]'.format(FbsType.FBS2PY[self.element])
  279. elif self.basetype == FbsType.Obj:
  280. if self.objtype:
  281. # FIXME
  282. _mapped_type = self.objtype.split('.')[-1]
  283. # _mapped_type = '{}.{}'.format(self._repository.render_to_basemodule, self.objtype)
  284. else:
  285. _mapped_type = 'List[{}]'.format(FbsType.FBS2PY[self.element])
  286. elif self.basetype in FbsType.SCALAR_TYPES + [FbsType.UType, FbsType.Union]:
  287. # FIXME: follow up processing of Unions (UType/Union)
  288. if self.basetype == FbsType.ULong and attrs and 'timestamp' in attrs:
  289. _mapped_type = 'np.datetime64'
  290. else:
  291. _mapped_type = FbsType.FBS2PY[self.basetype]
  292. else:
  293. raise NotImplementedError(
  294. 'FIXME: implement mapping of FlatBuffers type "{}" to Python in {}'.format(self.basetype, self.map))
  295. if objtype_as_string and self.basetype == FbsType.Obj:
  296. # for object types, use 'TYPE' rather than TYPE so that the type reference
  297. # does not depend on type declaration order within a single file
  298. # https://peps.python.org/pep-0484/#forward-references
  299. if required:
  300. return "'{}'".format(_mapped_type)
  301. else:
  302. return "Optional['{}']".format(_mapped_type)
  303. else:
  304. if required:
  305. return '{}'.format(_mapped_type)
  306. else:
  307. return 'Optional[{}]'.format(_mapped_type)
  308. else:
  309. raise RuntimeError('cannot map FlatBuffers type to target language "{}" in {}'.format(language, self.map))
  310. def __str__(self) -> str:
  311. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  312. def marshal(self) -> Dict[str, Any]:
  313. # important: use properties, not private object attribute access (!)
  314. obj = {
  315. 'basetype': self.FBS2STR.get(self.basetype, None),
  316. 'element': self.FBS2STR.get(self.element, None),
  317. 'index': self.index,
  318. 'objtype': self.objtype,
  319. }
  320. return obj
  321. class FbsAttribute(object):
  322. def __init__(self):
  323. pass
  324. def __str__(self):
  325. return ''.format()
  326. class FbsField(object):
  327. __slots__ = ('_repository', '_schema', '_name', '_type', '_id', '_offset', '_default_int',
  328. '_default_real', '_deprecated', '_required', '_attrs', '_docs')
  329. def __init__(self,
  330. repository: 'FbsRepository',
  331. schema: 'FbsSchema',
  332. name: str,
  333. type: FbsType,
  334. id: int,
  335. offset: int,
  336. default_int: int,
  337. default_real: float,
  338. deprecated: bool,
  339. required: bool,
  340. attrs: Dict[str, FbsAttribute],
  341. docs: str):
  342. self._repository = repository
  343. self._schema = schema
  344. self._name = name
  345. self._type = type
  346. self._id = id
  347. self._offset = offset
  348. self._default_int = default_int
  349. self._default_real = default_real
  350. self._deprecated = deprecated
  351. self._required = required
  352. self._attrs = attrs
  353. self._docs = docs
  354. @property
  355. def repository(self) -> 'FbsRepository':
  356. return self._repository
  357. @property
  358. def schema(self) -> 'FbsSchema':
  359. return self._schema
  360. @property
  361. def name(self) -> str:
  362. return self._name
  363. @property
  364. def type(self) -> FbsType:
  365. return self._type
  366. @property
  367. def id(self) -> int:
  368. return self._id
  369. @property
  370. def offset(self) -> int:
  371. return self._offset
  372. @property
  373. def default_int(self) -> int:
  374. return self._default_int
  375. @property
  376. def default_real(self) -> float:
  377. return self._default_real
  378. @property
  379. def deprecated(self) -> bool:
  380. return self._deprecated
  381. @property
  382. def required(self) -> bool:
  383. return self._required
  384. @property
  385. def attrs(self) -> Dict[str, FbsAttribute]:
  386. return self._attrs
  387. @property
  388. def docs(self) -> str:
  389. return self._docs
  390. def __str__(self) -> str:
  391. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  392. def marshal(self) -> Dict[str, Any]:
  393. obj = {
  394. 'name': self._name,
  395. 'type': self._type.marshal() if self._type else None,
  396. 'id': self._id,
  397. 'offset': self._offset,
  398. 'default_int': self._default_int,
  399. 'default_real': self._default_real,
  400. 'deprecated': self._deprecated,
  401. 'required': self._required,
  402. 'attrs': {},
  403. 'docs': self._docs,
  404. }
  405. if self._attrs:
  406. for k, v in self._attrs.items():
  407. obj['attrs'][k] = v
  408. return obj
  409. def parse_attr(obj):
  410. attrs = {}
  411. for j in range(obj.AttributesLength()):
  412. fbs_attr = obj.Attributes(j)
  413. attr_key = fbs_attr.Key()
  414. if attr_key:
  415. attr_key = attr_key.decode('utf8')
  416. attr_value = fbs_attr.Value()
  417. if attr_value:
  418. attr_value = attr_value.decode('utf8')
  419. assert attr_key not in attrs
  420. attrs[attr_key] = attr_value
  421. return attrs
  422. def parse_docs(obj):
  423. docs = []
  424. for j in range(obj.DocumentationLength()):
  425. doc_line = obj.Documentation(j)
  426. if doc_line:
  427. doc_line = doc_line.decode('utf8').strip()
  428. docs.append(doc_line)
  429. # docs = '\n'.join(docs).strip()
  430. docs = ' '.join(docs).strip()
  431. return docs
  432. def parse_fields(repository, schema, obj, objs_lst=None):
  433. # table Object { // Used for both tables and structs.
  434. # ...
  435. # fields:[Field] (required); // Sorted.
  436. # ...
  437. # }
  438. # https://github.com/google/flatbuffers/blob/11a19887053534c43f73e74786b46a615ecbf28e/reflection/reflection.fbs#L91
  439. fields_by_name = {}
  440. # the type index of a field is stored in ``fbs_field.Id()``, whereas the index of the field
  441. # within the list of fields is different (!) because that list is alphabetically sorted (!).
  442. # thus, we need to fill this map to recover the type index ordered list of fields
  443. field_id_to_name = {}
  444. for j in range(obj.FieldsLength()):
  445. fbs_field: Field = obj.Fields(j)
  446. field_name = fbs_field.Name()
  447. if field_name:
  448. field_name = field_name.decode('utf8')
  449. field_id = int(fbs_field.Id())
  450. # IMPORTANT: this is NOT true, since j is according to sort-by-name
  451. # assert field_id == j
  452. # instead, maintain this map to recover sort-by-position order later
  453. field_id_to_name[field_id] = field_name
  454. fbs_field_type = fbs_field.Type()
  455. # we use lazy-resolve for this property
  456. _objtype = None
  457. # # FIXME
  458. # _objtype = None
  459. # if fbs_field_type.Index() >= 0:
  460. # if len(objs_lst) > fbs_field_type.Index():
  461. # _obj = objs_lst[fbs_field_type.Index()]
  462. # _objtype = _obj.name
  463. field_type = FbsType(repository=repository,
  464. schema=schema,
  465. basetype=fbs_field_type.BaseType(),
  466. element=fbs_field_type.Element(),
  467. index=fbs_field_type.Index(),
  468. objtype=_objtype)
  469. field = FbsField(repository=repository,
  470. schema=schema,
  471. name=field_name,
  472. type=field_type,
  473. id=field_id,
  474. offset=fbs_field.Offset(),
  475. default_int=fbs_field.DefaultInteger(),
  476. default_real=fbs_field.DefaultReal(),
  477. deprecated=fbs_field.Deprecated(),
  478. required=fbs_field.Required(),
  479. attrs=parse_attr(fbs_field),
  480. docs=parse_docs(fbs_field))
  481. assert field_name not in fields_by_name, 'field "{}" with id "{}" already in fields {}'.format(field_name,
  482. field_id,
  483. sorted(fields_by_name.keys()))
  484. fields_by_name[field_name] = field
  485. # recover the type index ordered list of fields
  486. fields_by_id = []
  487. for i in range(len(fields_by_name)):
  488. fields_by_id.append(fields_by_name[field_id_to_name[i]])
  489. return fields_by_name, fields_by_id
  490. def parse_calls(repository, schema, svc_obj, objs_lst=None):
  491. calls = {}
  492. calls_by_id = {}
  493. for j in range(svc_obj.CallsLength()):
  494. fbs_call = svc_obj.Calls(j)
  495. call_name = fbs_call.Name()
  496. if call_name:
  497. call_name = call_name.decode('utf8')
  498. # FIXME: schema reflection.RPCCall lacks "Id" (!)
  499. # call_id = int(fbs_call.Id())
  500. call_id = j
  501. fbs_call_req = fbs_call.Request()
  502. call_req_name = fbs_call_req.Name()
  503. if call_req_name:
  504. call_req_name = call_req_name.decode('utf8')
  505. call_req_declaration_file = fbs_call_req.DeclarationFile()
  506. if call_req_declaration_file:
  507. call_req_declaration_file = call_req_declaration_file.decode('utf8')
  508. call_req_is_struct = fbs_call_req.IsStruct()
  509. call_req_min_align = fbs_call_req.Minalign()
  510. call_req_bytesize = fbs_call_req.Bytesize()
  511. call_req_docs = parse_docs(fbs_call_req)
  512. call_req_attrs = parse_attr(fbs_call_req)
  513. call_req_fields, call_fields_by_id = parse_fields(repository, schema, fbs_call_req, objs_lst=objs_lst)
  514. call_req = FbsObject(repository=repository,
  515. schema=schema,
  516. declaration_file=call_req_declaration_file,
  517. name=call_req_name,
  518. fields=call_req_fields,
  519. fields_by_id=call_fields_by_id,
  520. is_struct=call_req_is_struct,
  521. min_align=call_req_min_align,
  522. bytesize=call_req_bytesize,
  523. attrs=call_req_attrs,
  524. docs=call_req_docs)
  525. fbs_call_resp = fbs_call.Response()
  526. call_resp_name = fbs_call_resp.Name()
  527. if call_resp_name:
  528. call_resp_name = call_resp_name.decode('utf8')
  529. call_resp_declaration_file = fbs_call_resp.DeclarationFile()
  530. if call_resp_declaration_file:
  531. call_resp_declaration_file = call_resp_declaration_file.decode('utf8')
  532. call_resp_is_struct = fbs_call_resp.IsStruct()
  533. call_resp_min_align = fbs_call_resp.Minalign()
  534. call_resp_bytesize = fbs_call_resp.Bytesize()
  535. call_resp_docs = parse_docs(fbs_call_resp)
  536. call_resp_attrs = parse_attr(fbs_call_resp)
  537. call_resp_fields, call_resp_fields_by_id = parse_fields(repository, schema, fbs_call_resp, objs_lst=objs_lst)
  538. call_resp = FbsObject(repository=repository,
  539. schema=schema,
  540. declaration_file=call_resp_declaration_file,
  541. name=call_resp_name,
  542. fields=call_resp_fields,
  543. fields_by_id=call_resp_fields_by_id,
  544. is_struct=call_resp_is_struct,
  545. min_align=call_resp_min_align,
  546. bytesize=call_resp_bytesize,
  547. attrs=call_resp_attrs,
  548. docs=call_resp_docs)
  549. call_docs = parse_docs(fbs_call)
  550. call_attrs = parse_attr(fbs_call)
  551. call = FbsRPCCall(repository=repository,
  552. schema=schema,
  553. name=call_name,
  554. id=call_id,
  555. request=call_req,
  556. response=call_resp,
  557. docs=call_docs,
  558. attrs=call_attrs)
  559. assert call_name not in calls, 'call "{}" with id "{}" already in calls {}'.format(call_name, call_id,
  560. sorted(calls.keys()))
  561. calls[call_name] = call
  562. assert call_id not in calls_by_id, 'call "{}" with id " {}" already in calls {}'.format(call_name, call_id,
  563. sorted(calls.keys()))
  564. calls_by_id[call_id] = call_name
  565. res = []
  566. for _, value in sorted(calls_by_id.items()):
  567. res.append(value)
  568. calls_by_id = res
  569. return calls, calls_by_id
  570. class FbsObject(object):
  571. __slots__ = ('_repository', '_schema', '_declaration_file', '_name', '_fields', '_fields_by_id',
  572. '_is_struct', '_min_align', '_bytesize', '_attrs', '_docs',
  573. 'modulename', 'classname', 'module_relimport')
  574. def __init__(self,
  575. repository: 'FbsRepository',
  576. schema: 'FbsSchema',
  577. declaration_file: str,
  578. name: str,
  579. fields: Dict[str, FbsField],
  580. fields_by_id: List[FbsField],
  581. is_struct: bool,
  582. min_align: int,
  583. bytesize: int,
  584. attrs: Dict[str, FbsAttribute],
  585. docs: str):
  586. self._repository = repository
  587. self._schema = schema
  588. self._declaration_file = declaration_file
  589. self._name = name
  590. self._fields = fields
  591. self._fields_by_id = fields_by_id
  592. self._is_struct = is_struct
  593. self._min_align = min_align
  594. self._bytesize = bytesize
  595. self._attrs = attrs
  596. self._docs = docs
  597. def map(self, language: str, required: Optional[bool] = True, objtype_as_string: bool = False) -> str:
  598. if language == 'python':
  599. klass = self._name.split('.')[-1]
  600. if objtype_as_string:
  601. # for object types, use 'TYPE' rather than TYPE so that the type reference
  602. # does not depend on type declaration order within a single file
  603. # https://peps.python.org/pep-0484/#forward-references
  604. if required:
  605. return "'{}'".format(klass)
  606. else:
  607. return "Optional['{}']".format(klass)
  608. else:
  609. if required:
  610. return '{}'.format(klass)
  611. else:
  612. return 'Optional[{}]'.format(klass)
  613. else:
  614. raise NotImplementedError()
  615. def map_import(self, language: str) -> str:
  616. if language == 'python':
  617. base = self._name.split('.')[-2]
  618. klass = self._name.split('.')[-1]
  619. return 'from {} import {}'.format(base, klass)
  620. else:
  621. raise NotImplementedError()
  622. @property
  623. def repository(self) -> 'FbsRepository':
  624. return self._repository
  625. @property
  626. def schema(self) -> 'FbsSchema':
  627. return self._schema
  628. @property
  629. def declaration_file(self) -> str:
  630. return self._declaration_file
  631. @property
  632. def name(self) -> str:
  633. return self._name
  634. @property
  635. def fields(self) -> Dict[str, FbsField]:
  636. return self._fields
  637. @property
  638. def fields_by_id(self) -> List[FbsField]:
  639. return self._fields_by_id
  640. @property
  641. def is_struct(self) -> bool:
  642. return self._is_struct
  643. @property
  644. def min_align(self) -> int:
  645. return self._min_align
  646. @property
  647. def bytesize(self) -> int:
  648. return self._bytesize
  649. @property
  650. def attrs(self) -> Dict[str, FbsAttribute]:
  651. return self._attrs
  652. @property
  653. def docs(self) -> str:
  654. return self._docs
  655. def __str__(self) -> str:
  656. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  657. def marshal(self) -> Dict[str, Any]:
  658. obj = {
  659. 'name': self._name,
  660. 'declaration_file': self._declaration_file,
  661. 'fields': {},
  662. 'is_struct': self._is_struct,
  663. 'min_align': self._min_align,
  664. 'bytesize': self._bytesize,
  665. 'attrs': {},
  666. 'docs': self._docs,
  667. }
  668. if self._fields:
  669. for k, v in self._fields.items():
  670. obj['fields'][k] = v.marshal() if v else None
  671. if self._attrs:
  672. for k, v in self._attrs.items():
  673. obj['attrs'][k] = v
  674. return obj
  675. @staticmethod
  676. def parse(repository, schema, fbs_obj, objs_lst=None):
  677. obj_name = fbs_obj.Name()
  678. if obj_name:
  679. obj_name = obj_name.decode('utf8')
  680. obj_declaration_file = fbs_obj.DeclarationFile()
  681. if obj_declaration_file:
  682. obj_declaration_file = obj_declaration_file.decode('utf8')
  683. obj_docs = parse_docs(fbs_obj)
  684. obj_attrs = parse_attr(fbs_obj)
  685. fields_by_name, fields_by_id = parse_fields(repository, schema, fbs_obj, objs_lst=objs_lst)
  686. # print('ok, parsed fields in object "{}": {}'.format(obj_name, fields_by_name))
  687. obj = FbsObject(repository=repository,
  688. schema=schema,
  689. declaration_file=obj_declaration_file,
  690. name=obj_name,
  691. fields=fields_by_name,
  692. fields_by_id=fields_by_id,
  693. is_struct=fbs_obj.IsStruct(),
  694. min_align=fbs_obj.Minalign(),
  695. bytesize=fbs_obj.Bytesize(),
  696. attrs=obj_attrs,
  697. docs=obj_docs)
  698. return obj
  699. class FbsRPCCall(object):
  700. def __init__(self,
  701. repository: 'FbsRepository',
  702. schema: 'FbsSchema',
  703. name: str,
  704. id: int,
  705. request: FbsObject,
  706. response: FbsObject,
  707. docs: str,
  708. attrs: Dict[str, FbsAttribute]):
  709. self._repository = repository
  710. self._schema = schema
  711. self._name = name
  712. self._id = id
  713. self._request = request
  714. self._response = response
  715. self._docs = docs
  716. self._attrs = attrs
  717. @property
  718. def repository(self):
  719. return self._repository
  720. @property
  721. def schema(self):
  722. return self._schema
  723. @property
  724. def name(self):
  725. return self._name
  726. @property
  727. def id(self):
  728. return self._id
  729. @property
  730. def request(self):
  731. return self._request
  732. @property
  733. def response(self):
  734. return self._response
  735. @property
  736. def docs(self):
  737. return self._docs
  738. @property
  739. def attrs(self):
  740. return self._attrs
  741. def __str__(self):
  742. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  743. def marshal(self):
  744. obj = {
  745. 'name': self._name,
  746. 'request': self._request.marshal() if self._request else None,
  747. 'response': self._response.marshal() if self._response else None,
  748. 'attrs': {},
  749. 'docs': self._docs,
  750. }
  751. if self._attrs:
  752. for k, v in self._attrs.items():
  753. obj['attrs'][k] = v
  754. return obj
  755. class FbsService(object):
  756. def __init__(self,
  757. repository: 'FbsRepository',
  758. schema: 'FbsSchema',
  759. declaration_file: str,
  760. name: str,
  761. calls: Dict[str, FbsRPCCall],
  762. calls_by_id: List[FbsRPCCall],
  763. attrs: Dict[str, FbsAttribute],
  764. docs: str):
  765. self._repository = repository
  766. self._schema = schema
  767. self._declaration_file = declaration_file
  768. self._name = name
  769. self._calls = calls
  770. self._calls_by_id = calls_by_id
  771. self._attrs = attrs
  772. self._docs = docs
  773. @property
  774. def repository(self):
  775. return self._repository
  776. @property
  777. def schema(self):
  778. return self._schema
  779. @property
  780. def declaration_file(self):
  781. return self._declaration_file
  782. @property
  783. def name(self):
  784. return self._name
  785. @property
  786. def calls(self):
  787. return self._calls
  788. @property
  789. def calls_by_id(self):
  790. return self._calls_by_id
  791. @property
  792. def attrs(self):
  793. return self._attrs
  794. @property
  795. def docs(self):
  796. return self._docs
  797. def __str__(self):
  798. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  799. def marshal(self):
  800. obj = {
  801. 'name': self._name,
  802. 'declaration_file': self._declaration_file,
  803. 'calls': {},
  804. 'attrs': {},
  805. 'docs': self._docs,
  806. }
  807. if self._calls:
  808. for k, v in self._calls.items():
  809. obj['calls'][k] = v.marshal()
  810. if self._attrs:
  811. for k, v in self._attrs.items():
  812. obj['attrs'][k] = v
  813. return obj
  814. class FbsEnumValue(object):
  815. def __init__(self,
  816. repository: 'FbsRepository',
  817. schema: 'FbsSchema',
  818. name: str,
  819. id: int,
  820. value,
  821. docs):
  822. """
  823. :param repository:
  824. :param name:
  825. :param value:
  826. :param docs:
  827. """
  828. self._repository = repository
  829. self._schema = schema
  830. self._name = name
  831. self._id = id
  832. self._value = value
  833. self._attrs = {}
  834. self._docs = docs
  835. @property
  836. def repository(self):
  837. return self._repository
  838. @property
  839. def schema(self):
  840. return self._schema
  841. @property
  842. def name(self):
  843. return self._name
  844. @property
  845. def id(self):
  846. return self._id
  847. @property
  848. def value(self):
  849. return self._value
  850. @property
  851. def attrs(self):
  852. return self._attrs
  853. @property
  854. def docs(self):
  855. return self._docs
  856. def __str__(self):
  857. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  858. def marshal(self):
  859. obj = {
  860. 'id': self._id,
  861. 'name': self._name,
  862. 'attrs': self._attrs,
  863. 'docs': self._docs,
  864. 'value': self._value,
  865. }
  866. if self._attrs:
  867. for k, v in self._attrs.items():
  868. obj['attrs'][k] = v
  869. return obj
  870. class FbsEnum(object):
  871. """
  872. FlatBuffers enum type.
  873. See https://github.com/google/flatbuffers/blob/11a19887053534c43f73e74786b46a615ecbf28e/reflection/reflection.fbs#L61
  874. """
  875. def __init__(self,
  876. repository: 'FbsRepository',
  877. schema: 'FbsSchema',
  878. declaration_file: str,
  879. name: str,
  880. id: int,
  881. values: Dict[str, FbsEnumValue],
  882. values_by_id: List[FbsEnumValue],
  883. is_union: bool,
  884. underlying_type: int,
  885. attrs: Dict[str, FbsAttribute],
  886. docs: str):
  887. self._repository = repository
  888. self._schema = schema
  889. self._declaration_file = declaration_file
  890. self._name = name
  891. self._id = id
  892. self._values = values
  893. self._values_by_id = values_by_id
  894. self._is_union = is_union
  895. # zlmdb.flatbuffers.reflection.Type.Type
  896. self._underlying_type = underlying_type
  897. self._attrs = attrs
  898. self._docs = docs
  899. @property
  900. def repository(self):
  901. return self._repository
  902. @property
  903. def schema(self):
  904. return self._schema
  905. @property
  906. def declaration_file(self):
  907. return self._declaration_file
  908. @property
  909. def name(self):
  910. return self._name
  911. @property
  912. def id(self):
  913. return self._id
  914. @property
  915. def values(self):
  916. return self._values
  917. @property
  918. def values_by_id(self):
  919. return self._values_by_id
  920. @property
  921. def is_union(self):
  922. return self._is_union
  923. @property
  924. def underlying_type(self):
  925. return self._underlying_type
  926. @property
  927. def attrs(self):
  928. return self._attrs
  929. @property
  930. def docs(self):
  931. return self._docs
  932. def __str__(self):
  933. return '\n{}\n'.format(pprint.pformat(self.marshal()))
  934. def marshal(self):
  935. obj = {
  936. 'name': self._name,
  937. 'id': self._id,
  938. 'values': {},
  939. 'is_union': self._is_union,
  940. 'underlying_type': FbsType.FBS2STR.get(self._underlying_type, None),
  941. 'attrs': {},
  942. 'docs': self._docs,
  943. }
  944. if self._values:
  945. for k, v in self._values.items():
  946. obj['values'][k] = v.marshal()
  947. if self._attrs:
  948. for k, v in self._attrs.items():
  949. obj['attrs'][k] = v
  950. return obj
  951. class FbsSchema(object):
  952. """
  953. """
  954. def __init__(self,
  955. repository: 'FbsRepository',
  956. file_name: str,
  957. file_sha256: str,
  958. file_size: int,
  959. file_ident: str,
  960. file_ext: str,
  961. fbs_files: List[Dict[str, str]],
  962. root_table: FbsObject,
  963. root: _Schema,
  964. objs: Optional[Dict[str, FbsObject]] = None,
  965. objs_by_id: Optional[List[FbsObject]] = None,
  966. enums: Optional[Dict[str, FbsEnum]] = None,
  967. enums_by_id: Optional[List[FbsEnum]] = None,
  968. services: Optional[Dict[str, FbsService]] = None,
  969. services_by_id: Optional[List[FbsService]] = None):
  970. """
  971. :param repository:
  972. :param file_name:
  973. :param file_sha256:
  974. :param file_size:
  975. :param file_ident:
  976. :param file_ext:
  977. :param fbs_files:
  978. :param root_table:
  979. :param root:
  980. :param objs:
  981. :param objs_by_id:
  982. :param enums:
  983. :param enums_by_id:
  984. :param services:
  985. :param services_by_id:
  986. """
  987. self._repository = repository
  988. self._file_name = file_name
  989. self._file_sha256 = file_sha256
  990. self._file_size = file_size
  991. self._file_ident = file_ident
  992. self._file_ext = file_ext
  993. self._fbs_files = fbs_files
  994. self._root_table = root_table
  995. self._root = root
  996. self._objs = objs
  997. self._objs_by_id = objs_by_id
  998. self._enums = enums
  999. self._enums_by_id = enums_by_id
  1000. self._services = services
  1001. self._services_by_id = services_by_id
  1002. @property
  1003. def repository(self):
  1004. return self._repository
  1005. @property
  1006. def file_name(self):
  1007. return self._file_name
  1008. @property
  1009. def file_sha256(self):
  1010. return self._file_sha256
  1011. @property
  1012. def file_size(self):
  1013. return self._file_size
  1014. @property
  1015. def file_ident(self):
  1016. return self._file_ident
  1017. @property
  1018. def file_ext(self):
  1019. return self._file_ext
  1020. @property
  1021. def fbs_files(self):
  1022. return self._fbs_files
  1023. @property
  1024. def root_table(self):
  1025. return self._root_table
  1026. @property
  1027. def root(self):
  1028. return self._root
  1029. @property
  1030. def objs(self):
  1031. return self._objs
  1032. @property
  1033. def objs_by_id(self):
  1034. return self._objs_by_id
  1035. @property
  1036. def enums(self):
  1037. return self._enums
  1038. @property
  1039. def enums_by_id(self):
  1040. return self._enums_by_id
  1041. @property
  1042. def services(self):
  1043. return self._services
  1044. @property
  1045. def services_by_id(self):
  1046. return self._services_by_id
  1047. def __str__(self):
  1048. return '\n{}\n'.format(pprint.pformat(self.marshal(), width=255))
  1049. def marshal(self) -> Dict[str, object]:
  1050. """
  1051. :return:
  1052. """
  1053. obj = {
  1054. 'schema': {
  1055. 'ident': self._file_ident,
  1056. 'ext': self._file_ext,
  1057. 'name': os.path.basename(self._file_name) if self._file_name else None,
  1058. 'files': self._fbs_files,
  1059. 'sha256': self._file_sha256,
  1060. 'size': self._file_size,
  1061. 'objects': len(self._objs),
  1062. 'enums': len(self._enums),
  1063. 'services': len(self._services),
  1064. },
  1065. 'root_table': self._root_table.marshal() if self._root_table else None,
  1066. 'enums': {},
  1067. 'objects': {},
  1068. 'services': {},
  1069. }
  1070. if self._enums:
  1071. for k, v in self._enums.items():
  1072. obj['enums'][k] = v.marshal()
  1073. if self._objs:
  1074. for k, v in self._objs.items():
  1075. obj['objects'][k] = v.marshal()
  1076. if self._services:
  1077. for k, v in self._services.items():
  1078. obj['services'][k] = v.marshal()
  1079. return obj
  1080. @staticmethod
  1081. def load(repository: 'FbsRepository',
  1082. sfile: Union[str, io.RawIOBase, IO[bytes]],
  1083. filename: Optional[str] = None) -> 'FbsSchema':
  1084. """
  1085. :param repository:
  1086. :param sfile:
  1087. :param filename:
  1088. :return:
  1089. """
  1090. data: bytes
  1091. if type(sfile) == str and os.path.isfile(sfile):
  1092. with open(sfile, 'rb') as fd:
  1093. data = fd.read()
  1094. else:
  1095. data = sfile.read()
  1096. m = hashlib.sha256()
  1097. m.update(data)
  1098. # print('loading schema file "{}" ({} bytes, SHA256 0x{})'.format(filename, len(data), m.hexdigest()))
  1099. # get root object in Flatbuffers reflection schema
  1100. # see: https://github.com/google/flatbuffers/blob/master/reflection/reflection.fbs
  1101. root = _Schema.GetRootAsSchema(data, 0)
  1102. file_ident = root.FileIdent()
  1103. if file_ident is not None:
  1104. file_ident = file_ident.decode('utf8')
  1105. file_ext = root.FileExt()
  1106. if file_ext is not None:
  1107. file_ext = file_ext.decode('utf8')
  1108. fbs_files = []
  1109. for i in range(root.FbsFilesLength()):
  1110. # zlmdb.flatbuffers.reflection.SchemaFile.SchemaFile
  1111. schema_file = root.FbsFiles(i)
  1112. schema_file_filename = schema_file.Filename()
  1113. if schema_file_filename:
  1114. schema_file_filename = schema_file_filename.decode('utf8')
  1115. schema_file_included_filenames = []
  1116. for j in range(schema_file.IncludedFilenamesLength()):
  1117. included_filename = schema_file.IncludedFilenames(j)
  1118. if included_filename:
  1119. included_filename = included_filename.decode('utf8')
  1120. schema_file_included_filenames.append(included_filename)
  1121. fbs_files.append(
  1122. {
  1123. 'filename': schema_file_filename,
  1124. 'included_filenames': schema_file_included_filenames,
  1125. }
  1126. )
  1127. root_table = root.RootTable()
  1128. if root_table is not None:
  1129. root_table = FbsObject.parse(repository, root_table)
  1130. schema = FbsSchema(repository=repository,
  1131. file_name=filename,
  1132. file_size=len(data),
  1133. file_sha256=m.hexdigest(),
  1134. file_ident=file_ident,
  1135. file_ext=file_ext,
  1136. fbs_files=fbs_files,
  1137. root_table=root_table,
  1138. root=root)
  1139. # enum types from the schema by name and by index
  1140. enums = {}
  1141. enums_by_id = []
  1142. for i in range(root.EnumsLength()):
  1143. fbs_enum = root.Enums(i)
  1144. enum_name = fbs_enum.Name()
  1145. if enum_name:
  1146. enum_name = enum_name.decode('utf8')
  1147. enum_declaration_file = fbs_enum.DeclarationFile()
  1148. if enum_declaration_file:
  1149. enum_declaration_file = enum_declaration_file.decode('utf8')
  1150. enum_underlying_type = fbs_enum.UnderlyingType()
  1151. enum_values = {}
  1152. enum_values_by_id = []
  1153. for j in range(fbs_enum.ValuesLength()):
  1154. fbs_enum_value = fbs_enum.Values(j)
  1155. enum_value_name = fbs_enum_value.Name()
  1156. if enum_value_name:
  1157. enum_value_name = enum_value_name.decode('utf8')
  1158. enum_value_value = fbs_enum_value.Value()
  1159. enum_value_docs = parse_docs(fbs_enum_value)
  1160. enum_value = FbsEnumValue(repository=repository,
  1161. schema=schema,
  1162. name=enum_value_name,
  1163. id=j,
  1164. value=enum_value_value,
  1165. docs=enum_value_docs)
  1166. assert enum_value_name not in enum_values
  1167. enum_values[enum_value_name] = enum_value
  1168. enum_values_by_id.append(enum_value)
  1169. enum = FbsEnum(repository=repository,
  1170. schema=schema,
  1171. declaration_file=enum_declaration_file,
  1172. name=enum_name,
  1173. id=i,
  1174. values=enum_values,
  1175. values_by_id=enum_values_by_id,
  1176. is_union=fbs_enum.IsUnion(),
  1177. underlying_type=enum_underlying_type,
  1178. attrs=parse_attr(fbs_enum),
  1179. docs=parse_docs(fbs_enum))
  1180. assert enum_name not in enums
  1181. enums[enum_name] = enum
  1182. enums_by_id.append(enum)
  1183. schema._enums = enums
  1184. schema._enums_by_id = enums_by_id
  1185. # type objects (structs and tables) from the schema by name and by index
  1186. objs = {}
  1187. objs_by_id = []
  1188. for i in range(root.ObjectsLength()):
  1189. fbs_obj = root.Objects(i)
  1190. obj = FbsObject.parse(repository, schema, fbs_obj, objs_lst=objs_by_id)
  1191. assert obj.name not in objs
  1192. objs[obj.name] = obj
  1193. objs_by_id.append(obj)
  1194. # print('ok, processed schema object "{}"'.format(obj.name))
  1195. schema._objs = objs
  1196. schema._objs_by_id = objs_by_id
  1197. # service type objects (interfaces) from the schema by name and by index
  1198. services = {}
  1199. services_by_id = []
  1200. for i in range(root.ServicesLength()):
  1201. svc_obj = root.Services(i)
  1202. svc_name = svc_obj.Name()
  1203. if svc_name:
  1204. svc_name = svc_name.decode('utf8')
  1205. svc_declaration_file = svc_obj.DeclarationFile()
  1206. if svc_declaration_file:
  1207. svc_declaration_file = svc_declaration_file.decode('utf8')
  1208. docs = parse_docs(svc_obj)
  1209. attrs = parse_attr(svc_obj)
  1210. calls, calls_by_id = parse_calls(repository, schema, svc_obj, objs_lst=objs_by_id)
  1211. service = FbsService(repository=repository,
  1212. schema=schema,
  1213. declaration_file=svc_declaration_file,
  1214. name=svc_name,
  1215. calls=calls,
  1216. calls_by_id=calls_by_id,
  1217. attrs=attrs,
  1218. docs=docs)
  1219. assert svc_name not in services
  1220. services[svc_name] = service
  1221. services_by_id.append(service)
  1222. schema._services = services
  1223. schema._services_by_id = services_by_id
  1224. return schema
  1225. def validate_scalar(field, value: Optional[Any]):
  1226. # print('validate scalar "{}" for type {} (attrs={})'.format(field.name,
  1227. # FbsType.FBS2STR[field.type.basetype],
  1228. # field.attrs))
  1229. if field.type.basetype in FbsType.FBS2PY_TYPE:
  1230. expected_type = FbsType.FBS2PY_TYPE[field.type.basetype]
  1231. if type(value) != expected_type:
  1232. raise InvalidPayload('invalid type {} for value, expected {}'.format(type(value), expected_type))
  1233. else:
  1234. assert False, 'FIXME'
  1235. class FbsRepository(object):
  1236. """
  1237. crossbar.interfaces.IInventory
  1238. - add: FbsRepository[]
  1239. - load: FbsSchema[]
  1240. https://github.com/google/flatbuffers/blob/master/reflection/reflection.fbs
  1241. """
  1242. def __init__(self, basemodule: str):
  1243. self.log = txaio.make_logger()
  1244. self._basemodule = basemodule
  1245. self._schemata: Dict[str, FbsSchema] = {}
  1246. self._objs: Dict[str, FbsObject] = {}
  1247. self._enums: Dict[str, FbsEnum] = {}
  1248. self._services: Dict[str, FbsService] = {}
  1249. @staticmethod
  1250. def from_archive(filename: str) -> 'FbsRepository':
  1251. catalog = FbsRepository()
  1252. return catalog
  1253. @staticmethod
  1254. def from_address(address: str) -> 'FbsRepository':
  1255. catalog = FbsRepository()
  1256. return catalog
  1257. @property
  1258. def basemodule(self) -> str:
  1259. return self._basemodule
  1260. @property
  1261. def schemas(self) -> Dict[str, FbsSchema]:
  1262. return self._schemata
  1263. @property
  1264. def objs(self) -> Dict[str, FbsObject]:
  1265. return self._objs
  1266. @property
  1267. def enums(self) -> Dict[str, FbsEnum]:
  1268. return self._enums
  1269. @property
  1270. def services(self) -> Dict[str, FbsService]:
  1271. return self._services
  1272. @property
  1273. def total_count(self):
  1274. return len(self._objs) + len(self._enums) + len(self._services)
  1275. def load(self, filename: str) -> Tuple[int, int]:
  1276. """
  1277. Load and add all schemata from Flatbuffers binary schema files (`*.bfbs`)
  1278. found in the given directory. Alternatively, a path to a single schema file
  1279. can be provided.
  1280. :param filename: Filesystem path of a directory or single file from which to
  1281. load and add Flatbuffers schemata.
  1282. """
  1283. file_dups = 0
  1284. load_from_filenames = []
  1285. if os.path.isdir(filename):
  1286. for path in Path(filename).rglob('*.bfbs'):
  1287. fn = os.path.join(filename, path.name)
  1288. if fn not in self._schemata:
  1289. load_from_filenames.append(fn)
  1290. else:
  1291. # print('duplicate schema file skipped ("{}" already loaded)'.format(fn))
  1292. file_dups += 1
  1293. elif os.path.isfile(filename):
  1294. if filename not in self._schemata:
  1295. load_from_filenames.append(filename)
  1296. else:
  1297. # print('duplicate schema file skipped ("{}" already loaded)'.format(filename))
  1298. file_dups += 1
  1299. elif ',' in filename:
  1300. for filename_single in filename.split(','):
  1301. filename_single = os.path.expanduser(filename_single)
  1302. # filename_single = os.path.expandvars(filename_single)
  1303. if os.path.isfile(filename_single):
  1304. if filename_single not in self._schemata:
  1305. load_from_filenames.append(filename_single)
  1306. else:
  1307. print('duplicate schema file skipped ("{}" already loaded)'.format(filename_single))
  1308. else:
  1309. raise RuntimeError('"{}" in list is not a file'.format(filename_single))
  1310. else:
  1311. raise RuntimeError('cannot open schema file or directory: "{}"'.format(filename))
  1312. enum_dups = 0
  1313. obj_dups = 0
  1314. svc_dups = 0
  1315. # iterate over all schema files found
  1316. for fn in load_from_filenames:
  1317. # load this schema file
  1318. schema: FbsSchema = FbsSchema.load(self, fn)
  1319. # add enum types to repository by name
  1320. for enum in schema.enums.values():
  1321. if enum.name in self._enums:
  1322. # print('skipping duplicate enum type for name "{}"'.format(enum.name))
  1323. enum_dups += 1
  1324. else:
  1325. self._enums[enum.name] = enum
  1326. # add object types
  1327. for obj in schema.objs.values():
  1328. if obj.name in self._objs:
  1329. # print('skipping duplicate object (table/struct) type for name "{}"'.format(obj.name))
  1330. obj_dups += 1
  1331. else:
  1332. self._objs[obj.name] = obj
  1333. # add service definitions ("APIs")
  1334. for svc in schema.services.values():
  1335. if svc.name in self._services:
  1336. # print('skipping duplicate service type for name "{}"'.format(svc.name))
  1337. svc_dups += 1
  1338. else:
  1339. self._services[svc.name] = svc
  1340. self._schemata[fn] = schema
  1341. type_dups = enum_dups + obj_dups + svc_dups
  1342. return file_dups, type_dups
  1343. def summary(self, keys=False):
  1344. if keys:
  1345. return {
  1346. 'schemata': sorted(self._schemata.keys()),
  1347. 'objs': sorted(self._objs.keys()),
  1348. 'enums': sorted(self._enums.keys()),
  1349. 'services': sorted(self._services.keys()),
  1350. }
  1351. else:
  1352. return {
  1353. 'schemata': len(self._schemata),
  1354. 'objs': len(self._objs),
  1355. 'enums': len(self._enums),
  1356. 'services': len(self._services),
  1357. }
  1358. def print_summary(self):
  1359. # brown = (160, 110, 50)
  1360. # brown = (133, 51, 51)
  1361. brown = (51, 133, 255)
  1362. # steel_blue = (70, 130, 180)
  1363. orange = (255, 127, 36)
  1364. # deep_pink = (255, 20, 147)
  1365. # light_pink = (255, 102, 204)
  1366. # pink = (204, 82, 163)
  1367. pink = (127, 127, 127)
  1368. for obj_key, obj in self.objs.items():
  1369. prefix_uri = obj.attrs.get('wampuri', self._basemodule)
  1370. obj_name = obj_key.split('.')[-1]
  1371. obj_color = 'blue' if obj.is_struct else brown
  1372. obj_label = '{} {}'.format('Struct' if obj.is_struct else 'Table', obj_name)
  1373. print('{}\n'.format(hlval(' {} {} {}'.format('====', obj_label, '=' * (118 - len(obj_label))),
  1374. color=obj_color)))
  1375. # print(' {} {} {}\n'.format(obj_kind, hlval(obj_name, color=obj_color), '=' * (120 - len(obj_name))))
  1376. if prefix_uri:
  1377. print(' Type URI: {}.{}'.format(hlval(prefix_uri), hlval(obj_name)))
  1378. else:
  1379. print(' Type URI: {}'.format(hlval(obj_name)))
  1380. print()
  1381. print(textwrap.fill(obj.docs,
  1382. width=100,
  1383. initial_indent=' ',
  1384. subsequent_indent=' ',
  1385. expand_tabs=True,
  1386. replace_whitespace=True,
  1387. fix_sentence_endings=False,
  1388. break_long_words=True,
  1389. drop_whitespace=True,
  1390. break_on_hyphens=True,
  1391. tabsize=4))
  1392. print()
  1393. for field in obj.fields_by_id:
  1394. docs = textwrap.wrap(field.docs,
  1395. width=70,
  1396. initial_indent='',
  1397. subsequent_indent='',
  1398. expand_tabs=True,
  1399. replace_whitespace=True,
  1400. fix_sentence_endings=False,
  1401. break_long_words=True,
  1402. drop_whitespace=True,
  1403. break_on_hyphens=True,
  1404. tabsize=4)
  1405. if field.type.basetype == FbsType.Obj:
  1406. type_desc_str = field.type.objtype.split('.')[-1]
  1407. if self.objs[field.type.objtype].is_struct:
  1408. type_desc = hlval(type_desc_str, color='blue')
  1409. else:
  1410. type_desc = hlval(type_desc_str, color=brown)
  1411. elif field.type.basetype == FbsType.Vector:
  1412. type_desc_str = 'Vector[{}]'.format(FbsType.FBS2STR[field.type.element])
  1413. type_desc = hlval(type_desc_str, color='white')
  1414. else:
  1415. type_desc_str = FbsType.FBS2STR[field.type.basetype]
  1416. type_desc = hlval(type_desc_str, color='white')
  1417. if field.attrs:
  1418. attrs_text_str = '(' + ', '.join(field.attrs.keys()) + ')'
  1419. attrs_text = hlval(attrs_text_str, color=pink)
  1420. type_text_str = ' '.join([type_desc_str, attrs_text_str])
  1421. type_text = ' '.join([type_desc, attrs_text])
  1422. else:
  1423. type_text_str = type_desc_str
  1424. type_text = type_desc
  1425. # print('>>', len(type_text_str), len(type_text))
  1426. print(' {:<36} {} {}'.format(hlval(field.name),
  1427. type_text + ' ' * (28 - len(type_text_str)),
  1428. docs[0] if docs else ''))
  1429. for line in docs[1:]:
  1430. print(' ' * 57 + line)
  1431. print()
  1432. for svc_key, svc in self.services.items():
  1433. prefix_uri = svc.attrs.get('wampuri', self._basemodule)
  1434. ifx_uuid = svc.attrs.get('uuid', None)
  1435. ifc_name = svc_key.split('.')[-1]
  1436. ifc_label = 'Interface {}'.format(ifc_name)
  1437. print('{}\n'.format(hlval(' {} {} {}'.format('====', ifc_label, '=' * (118 - len(ifc_label))),
  1438. color='yellow')))
  1439. print(' Interface UUID: {}'.format(hlval(ifx_uuid)))
  1440. print(' Interface URIs: {}.({}|{})'.format(hlval(prefix_uri), hlval('procedure', color=orange),
  1441. hlval('topic', color='green')))
  1442. print()
  1443. print(textwrap.fill(svc.docs,
  1444. width=100,
  1445. initial_indent=' ',
  1446. subsequent_indent=' ',
  1447. expand_tabs=True,
  1448. replace_whitespace=True,
  1449. fix_sentence_endings=False,
  1450. break_long_words=True,
  1451. drop_whitespace=True,
  1452. break_on_hyphens=True,
  1453. tabsize=4))
  1454. for uri in svc.calls.keys():
  1455. print()
  1456. ep: FbsRPCCall = svc.calls[uri]
  1457. ep_type = ep.attrs['type']
  1458. ep_color = {'topic': 'green', 'procedure': orange}.get(ep_type, 'white')
  1459. # uri_long = '{}.{}'.format(hlval(prefix_uri, color=(127, 127, 127)),
  1460. # hlval(ep.attrs.get('wampuri', ep.name), color='white'))
  1461. uri_short = '{}'.format(hlval(ep.attrs.get('wampuri', ep.name), color=(255, 255, 255)))
  1462. print(' {} {} ({}) -> {}'.format(hlval(ep_type, color=ep_color),
  1463. uri_short,
  1464. hlval(ep.request.name.split('.')[-1], color='blue', bold=False),
  1465. hlval(ep.response.name.split('.')[-1], color='blue', bold=False)))
  1466. print()
  1467. print(textwrap.fill(ep.docs,
  1468. width=90,
  1469. initial_indent=' ',
  1470. subsequent_indent=' ',
  1471. expand_tabs=True,
  1472. replace_whitespace=True,
  1473. fix_sentence_endings=False,
  1474. break_long_words=True,
  1475. drop_whitespace=True,
  1476. break_on_hyphens=True,
  1477. tabsize=4))
  1478. print()
  1479. def render(self, jinja2_env, output_dir, output_lang):
  1480. """
  1481. :param jinja2_env:
  1482. :param output_dir:
  1483. :param output_lang:
  1484. :return:
  1485. """
  1486. # type categories in schemata in the repository
  1487. #
  1488. work = {
  1489. 'obj': self.objs.values(),
  1490. 'enum': self.enums.values(),
  1491. 'service': self.services.values(),
  1492. }
  1493. # collect code sections by module
  1494. #
  1495. code_modules = {}
  1496. test_code_modules = {}
  1497. is_first_by_category_modules = {}
  1498. for category, values in work.items():
  1499. # generate and collect code for all FlatBuffers items in the given category
  1500. # and defined in schemata previously loaded int
  1501. for item in values:
  1502. assert isinstance(item, FbsObject) or isinstance(item, FbsEnum) or isinstance(item, FbsService), 'unexpected type {}'.format(type(item))
  1503. # metadata = item.marshal()
  1504. # pprint(item.marshal())
  1505. metadata = item
  1506. # com.example.device.HomeDeviceVendor => com.example.device
  1507. modulename = '.'.join(metadata.name.split('.')[0:-1])
  1508. metadata.modulename = modulename
  1509. # com.example.device.HomeDeviceVendor => HomeDeviceVendor
  1510. metadata.classname = metadata.name.split('.')[-1].strip()
  1511. # com.example.device => device
  1512. metadata.module_relimport = modulename.split('.')[-1]
  1513. is_first = modulename not in code_modules
  1514. is_first_by_category = (modulename, category) not in is_first_by_category_modules
  1515. if is_first_by_category:
  1516. is_first_by_category_modules[(modulename, category)] = True
  1517. # render template into python code section
  1518. if output_lang == 'python':
  1519. # render obj|enum|service.py.jinja2 template
  1520. tmpl = jinja2_env.get_template('py-autobahn/{}.py.jinja2'.format(category))
  1521. code = tmpl.render(repo=self, metadata=metadata, FbsType=FbsType,
  1522. render_imports=is_first,
  1523. is_first_by_category=is_first_by_category,
  1524. render_to_basemodule=self.basemodule)
  1525. # FIXME
  1526. # code = FormatCode(code)[0]
  1527. # render test_obj|enum|service.py.jinja2 template
  1528. test_tmpl = jinja2_env.get_template('py-autobahn/test_{}.py.jinja2'.format(category))
  1529. test_code = test_tmpl.render(repo=self, metadata=metadata, FbsType=FbsType,
  1530. render_imports=is_first,
  1531. is_first_by_category=is_first_by_category,
  1532. render_to_basemodule=self.basemodule)
  1533. elif output_lang == 'eip712':
  1534. # render obj|enum|service-eip712.sol.jinja2 template
  1535. tmpl = jinja2_env.get_template('so-eip712/{}-eip712.sol.jinja2'.format(category))
  1536. code = tmpl.render(repo=self, metadata=metadata, FbsType=FbsType,
  1537. render_imports=is_first,
  1538. is_first_by_category=is_first_by_category,
  1539. render_to_basemodule=self.basemodule)
  1540. # FIXME
  1541. # code = FormatCode(code)[0]
  1542. test_tmpl = None
  1543. test_code = None
  1544. elif output_lang == 'json':
  1545. code = json.dumps(metadata.marshal(),
  1546. separators=(', ', ': '),
  1547. ensure_ascii=False,
  1548. indent=4,
  1549. sort_keys=True)
  1550. test_code = None
  1551. else:
  1552. raise RuntimeError('invalid language "{}" for code generation'.format(output_lang))
  1553. # collect code sections per-module
  1554. if modulename not in code_modules:
  1555. code_modules[modulename] = []
  1556. test_code_modules[modulename] = []
  1557. code_modules[modulename].append(code)
  1558. if test_code:
  1559. test_code_modules[modulename].append(test_code)
  1560. else:
  1561. test_code_modules[modulename].append(None)
  1562. # ['', 'com.example.bla.blub', 'com.example.doo']
  1563. namespaces = {}
  1564. for code_file in code_modules.keys():
  1565. name_parts = code_file.split('.')
  1566. for i in range(len(name_parts)):
  1567. pn = name_parts[i]
  1568. ns = '.'.join(name_parts[:i])
  1569. if ns not in namespaces:
  1570. namespaces[ns] = []
  1571. if pn and pn not in namespaces[ns]:
  1572. namespaces[ns].append(pn)
  1573. print('Namespaces:\n{}\n'.format(pformat(namespaces)))
  1574. # write out code modules
  1575. #
  1576. i = 0
  1577. initialized = set()
  1578. for code_file, code_sections in code_modules.items():
  1579. code = '\n\n\n'.join(code_sections)
  1580. if code_file:
  1581. code_file_dir = [''] + code_file.split('.')[0:-1]
  1582. else:
  1583. code_file_dir = ['']
  1584. # FIXME: cleanup this mess
  1585. for i in range(len(code_file_dir)):
  1586. d = os.path.join(output_dir, *(code_file_dir[:i + 1]))
  1587. if not os.path.isdir(d):
  1588. os.mkdir(d)
  1589. if output_lang == 'python':
  1590. fn = os.path.join(d, '__init__.py')
  1591. _modulename = '.'.join(code_file_dir[:i + 1])[1:]
  1592. _imports = namespaces[_modulename]
  1593. tmpl = jinja2_env.get_template('py-autobahn/module.py.jinja2')
  1594. init_code = tmpl.render(repo=self, modulename=_modulename, imports=_imports,
  1595. render_to_basemodule=self.basemodule)
  1596. data = init_code.encode('utf8')
  1597. if not os.path.exists(fn):
  1598. with open(fn, 'wb') as f:
  1599. f.write(data)
  1600. print('Ok, rendered "module.py.jinja2" in {} bytes to "{}"'.format(len(data), fn))
  1601. initialized.add(fn)
  1602. else:
  1603. with open(fn, 'ab') as f:
  1604. f.write(data)
  1605. if output_lang == 'python':
  1606. if code_file:
  1607. code_file_name = '{}.py'.format(code_file.split('.')[-1])
  1608. test_code_file_name = 'test_{}.py'.format(code_file.split('.')[-1])
  1609. else:
  1610. code_file_name = '__init__.py'
  1611. test_code_file_name = None
  1612. elif output_lang == 'json':
  1613. if code_file:
  1614. code_file_name = '{}.json'.format(code_file.split('.')[-1])
  1615. else:
  1616. code_file_name = 'init.json'
  1617. test_code_file_name = None
  1618. else:
  1619. code_file_name = None
  1620. test_code_file_name = None
  1621. # write out code modules
  1622. #
  1623. if code_file_name:
  1624. try:
  1625. code = FormatCode(code)[0]
  1626. except Exception as e:
  1627. print('error during formatting code: {}'.format(e))
  1628. data = code.encode('utf8')
  1629. fn = os.path.join(*(code_file_dir + [code_file_name]))
  1630. fn = os.path.join(output_dir, fn)
  1631. # FIXME
  1632. # if fn not in initialized and os.path.exists(fn):
  1633. # os.remove(fn)
  1634. # with open(fn, 'wb') as fd:
  1635. # fd.write('# Generated by Autobahn v{}\n'.format(__version__).encode('utf8'))
  1636. # initialized.add(fn)
  1637. with open(fn, 'ab') as fd:
  1638. fd.write(data)
  1639. print('Ok, written {} bytes to {}'.format(len(data), fn))
  1640. # write out unit test code modules
  1641. #
  1642. if test_code_file_name:
  1643. test_code_sections = test_code_modules[code_file]
  1644. test_code = '\n\n\n'.join(test_code_sections)
  1645. try:
  1646. test_code = FormatCode(test_code)[0]
  1647. except Exception as e:
  1648. print('error during formatting code: {}'.format(e))
  1649. data = test_code.encode('utf8')
  1650. fn = os.path.join(*(code_file_dir + [test_code_file_name]))
  1651. fn = os.path.join(output_dir, fn)
  1652. if fn not in initialized and os.path.exists(fn):
  1653. os.remove(fn)
  1654. with open(fn, 'wb') as fd:
  1655. fd.write('# Copyright (c) ...\n'.encode('utf8'))
  1656. initialized.add(fn)
  1657. with open(fn, 'ab') as fd:
  1658. fd.write(data)
  1659. print('Ok, written {} bytes to {}'.format(len(data), fn))
  1660. def validate_obj(self, validation_type: Optional[str], value: Optional[Any]):
  1661. """
  1662. Validate value against the validation type given.
  1663. If the application payload does not validate against the provided type,
  1664. an :class:`autobahn.wamp.exception.InvalidPayload` is raised.
  1665. :param validation_type: Flatbuffers type (fully qualified) against to validate application payload.
  1666. :param value: Value to validate.
  1667. :return:
  1668. """
  1669. # print('validate_obj', validation_type, type(value))
  1670. if validation_type is None:
  1671. # any value validates against the None validation type
  1672. return
  1673. if validation_type not in self.objs:
  1674. raise RuntimeError('validation type "{}" not found in inventory'.format(self.objs))
  1675. # the Flatbuffers table type from the realm's type inventory against which we
  1676. # will validate the WAMP args/kwargs application payload
  1677. vt: FbsObject = self.objs[validation_type]
  1678. if type(value) == dict:
  1679. vt_kwargs = set(vt.fields.keys())
  1680. for k, v in value.items():
  1681. if k not in vt.fields:
  1682. raise InvalidPayload('unexpected argument "{}" in value of validation type "{}"'.format(k, vt.name))
  1683. vt_kwargs.discard(k)
  1684. field = vt.fields[k]
  1685. # validate object-typed field, eg "uint160_t"
  1686. if field.type.basetype == FbsType.Obj:
  1687. self.validate_obj(field.type.objtype, v)
  1688. elif field.type.basetype == FbsType.Union:
  1689. pass
  1690. print('FIXME-003-Union')
  1691. elif field.type.basetype == FbsType.Vector:
  1692. if isinstance(v, str) or isinstance(v, bytes):
  1693. print('FIXME-003-1-Vector')
  1694. elif isinstance(v, Sequence):
  1695. for ve in v:
  1696. self.validate_obj(field.type.elementtype, ve)
  1697. else:
  1698. raise InvalidPayload('invalid type {} for value (expected Vector/List/Tuple) '
  1699. 'of validation type "{}"'.format(type(v), vt.name))
  1700. else:
  1701. validate_scalar(field, v)
  1702. if vt.is_struct and vt_kwargs:
  1703. raise InvalidPayload('missing argument(s) {} in validation type "{}"'.format(list(vt_kwargs), vt.name))
  1704. elif type(value) in [tuple, list]:
  1705. # FIXME: KeyValues
  1706. if not vt.is_struct:
  1707. raise InvalidPayload('**: invalid type {} for (non-struct) validation type "{}"'.format(type(value), vt.name))
  1708. idx = 0
  1709. for field in vt.fields_by_id:
  1710. # consume the next positional argument from input
  1711. if idx >= len(value):
  1712. raise InvalidPayload('missing argument "{}" in type "{}"'.format(field.name, vt.name))
  1713. v = value[idx]
  1714. idx += 1
  1715. # validate object-typed field, eg "uint160_t"
  1716. if field.type.basetype == FbsType.Obj:
  1717. self.validate_obj(field.type.objtype, v)
  1718. elif field.type.basetype == FbsType.Union:
  1719. pass
  1720. print('FIXME-005-Union')
  1721. elif field.type.basetype == FbsType.Vector:
  1722. if isinstance(v, str) or isinstance(v, bytes):
  1723. print('FIXME-005-1-Vector')
  1724. elif isinstance(v, Sequence):
  1725. for ve in v:
  1726. print(field.type.elementtype, ve)
  1727. self.validate_obj(field.type.elementtype, ve)
  1728. else:
  1729. print('FIXME-005-3-Vector')
  1730. else:
  1731. validate_scalar(field, v)
  1732. if len(value) > idx:
  1733. raise InvalidPayload('unexpected argument(s) in validation type "{}"'.format(vt.name))
  1734. else:
  1735. raise InvalidPayload('invalid type {} for value of validation type "{}"'.format(type(value), vt.name))
  1736. def validate(self, validation_type: str, args: List[Any], kwargs: Dict[str, Any]) -> Optional[FbsObject]:
  1737. """
  1738. Validate the WAMP application payload provided in positional argument in ``args``
  1739. and in keyword-based arguments in ``kwargs`` against the FlatBuffers table
  1740. type ``validation_type`` from this repository.
  1741. If the application payload does not validate against the provided type,
  1742. an :class:`autobahn.wamp.exception.InvalidPayload` is raised.
  1743. :param validation_type: Flatbuffers type (fully qualified) against to validate application payload.
  1744. :param args: The application payload WAMP positional arguments.
  1745. :param kwargs: The application payload WAMP keyword-based arguments.
  1746. :return: The validation type object from this repository (reference in ``validation_type``)
  1747. which has been used for validation.
  1748. """
  1749. # any value validates against the None validation type
  1750. if validation_type is None:
  1751. return None
  1752. if validation_type not in self.objs:
  1753. raise RuntimeError('validation type "{}" not found in inventory (among {} types)'.format(validation_type, len(self.objs)))
  1754. # the Flatbuffers table type from the realm's type inventory against which we
  1755. # will validate the WAMP args/kwargs application payload
  1756. vt: FbsObject = self.objs[validation_type]
  1757. # we use this to index and consume positional args from the input
  1758. args_idx = 0
  1759. # we use this to track any kwargs not consumed while processing the validation type.
  1760. # and names left in this set after processing the validation type in full is an error ("unexpected kwargs")
  1761. kwargs_keys = set(kwargs.keys() if kwargs else [])
  1762. # iterate over all fields of validation type in field index order (!)
  1763. for field in vt.fields_by_id:
  1764. # field is a WAMP positional argument, that is one that needs to map to the next arg from args
  1765. if field.required or 'arg' in field.attrs or 'kwarg' not in field.attrs:
  1766. # consume the next positional argument from input
  1767. if args is None or args_idx >= len(args):
  1768. raise InvalidPayload('missing positional argument "{}" in type "{}"'.format(field.name, vt.name))
  1769. value = args[args_idx]
  1770. args_idx += 1
  1771. # validate object-typed field, eg "uint160_t"
  1772. if field.type.basetype == FbsType.Obj:
  1773. self.validate_obj(field.type.objtype, value)
  1774. elif field.type.basetype == FbsType.Union:
  1775. pass
  1776. print('FIXME-003-Union')
  1777. elif field.type.basetype == FbsType.Vector:
  1778. if isinstance(value, str) or isinstance(value, bytes):
  1779. print('FIXME-005-1-Vector')
  1780. elif isinstance(value, Sequence):
  1781. for ve in value:
  1782. print(field.type.elementtype, ve)
  1783. self.validate_obj(field.type.elementtype, ve)
  1784. else:
  1785. print('FIXME-005-3-Vector')
  1786. else:
  1787. validate_scalar(field, value)
  1788. # field is a WAMP keyword argument, that is one that needs to map into kwargs
  1789. elif 'kwarg' in field.attrs:
  1790. if field.name in kwargs_keys:
  1791. value = kwargs[field.name]
  1792. # FIXME: validate value vs field type
  1793. print('FIXME-003')
  1794. kwargs_keys.discard(field.name)
  1795. else:
  1796. assert False, 'should not arrive here'
  1797. if len(args) > args_idx:
  1798. raise InvalidPayload('{} unexpected positional arguments in type "{}"'.format(len(args) - args_idx, vt.name))
  1799. if kwargs_keys:
  1800. raise InvalidPayload('{} unexpected keyword arguments {} in type "{}"'.format(len(kwargs_keys), list(kwargs_keys), vt.name))
  1801. return vt