Advertisement
Guest User

Untitled

a guest
Oct 14th, 2019
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.18 KB | None | 0 0
  1. import json
  2. from string import ascii_lowercase
  3. from typing import Any, Dict
  4.  
  5. from hypothesis import strategies as hs
  6.  
  7.  
  8. def gen_prop_name():
  9. return hs.text(alphabet=ascii_lowercase + '_', min_size=1)
  10.  
  11.  
  12. def gen_boolean(prop=None):
  13. prop = prop or {}
  14. bools = hs.booleans()
  15. return bools | hs.none() if prop.get('nullable') is True else bools
  16.  
  17.  
  18. def gen_integer(prop=None):
  19. # TODO: multipleOf, exclusiveMinimum, exclusiveMaximum
  20. prop = prop or {}
  21. min_value = prop.get('minimum')
  22. max_value = prop.get('maximum')
  23. ints = hs.integers(min_value=min_value, max_value=max_value)
  24. return ints | hs.none() if prop.get('nullable') is True else ints
  25.  
  26.  
  27. def gen_number(prop=None):
  28. # TODO: multipleOf, exclusiveMinimum, exclusiveMaximum
  29. prop = prop or {}
  30. min_value = prop.get('minimum')
  31. max_value = prop.get('maximum')
  32. numbers = hs.floats(
  33. min_value=min_value,
  34. max_value=max_value,
  35. # NaN and Inf not allowed in JSON.
  36. allow_nan=False,
  37. allow_infinity=False)
  38. return numbers | hs.none() if prop.get('nullable') is True else numbers
  39.  
  40.  
  41. def gen_string(prop=None):
  42. prop = prop or {}
  43. min_length = prop.get('minLength', 0)
  44. max_length = prop.get('maxLength', None)
  45. if isinstance(prop.get('pattern'), str):
  46. # Note: re.search('^...$', s) will actually match strings that end in
  47. # a newline \n. It will _not_ match s if s ends in more than one \n. In
  48. # ECMAScript, /^...$/.test(s) is false if s ends in a newline.
  49. strings = hs.from_regex(prop['pattern']).map(
  50. lambda x:
  51. x[:-1]
  52. if prop['pattern'].endswith('$') and x.endswith('\n')
  53. else x
  54. ).filter(
  55. lambda x:
  56. (min_length == 0 or len(x) >= min_length) and
  57. (max_length is None or len(x) <= min_length))
  58. elif isinstance(prop.get('format'), str):
  59. if prop['format'] == 'date-time':
  60. from hypothesis.extra.pytz import timezones
  61. strings = hs.datetimes(timezones=hs.none() | timezones()).map(
  62. lambda x: x.isoformat())
  63. elif prop['format'] == 'date':
  64. strings = hs.dates().map(lambda x: x.isoformat())
  65. elif prop['format'] == 'time':
  66. from hypothesis.extra.pytz import timezones
  67. strings = hs.times(timezones=hs.none() | timezones()).map(
  68. lambda x: x.isoformat())
  69. elif prop['format'] == 'email':
  70. strings = hs.emails()
  71. else:
  72. return NotImplementedError(f'Format {prop["format"]} unknown')
  73. else:
  74. strings = hs.text(min_size=min_length, max_size=max_length)
  75. return strings | hs.none() if prop.get('nullable') is True else strings
  76.  
  77.  
  78. def gen_array(prop=None):
  79. prop = prop or {}
  80. min_items = prop.get('minItems', 0)
  81. max_items = prop.get('maxItems', None)
  82. unique_items = prop.get('uniqueItems', False)
  83. items = prop.get('items', {})
  84. if isinstance(items, dict):
  85. # List:
  86. # - "items": {"type": "integer"}
  87. # - "items": {"type": ["integer", "string"]}
  88. item_types = items.get('type')
  89. if not isinstance(item_types, list):
  90. item_types = [item_types]
  91. item_generators = [
  92. get_generator({**items, **{'type': item_type}})
  93. for item_type in item_types]
  94. arrays = hs.lists(
  95. elements=hs.one_of(*item_generators),
  96. min_size=min_items,
  97. max_size=max_items,
  98. unique_by=(lambda x: json.dumps(x)) if unique_items else None)
  99. elif isinstance(items, list):
  100. # Tuple:
  101. # - "items": [{"type": "integer"}, {"type": "string"}]
  102. item_generators = [get_generator(item) for item in items]
  103. arrays = hs.tuples(*item_generators)
  104. if unique_items is True:
  105. arrays = arrays.filter(
  106. lambda x: len(x) == len(set(json.dumps(e) for e in x)))
  107. return arrays | hs.none() if prop.get('nullable') is True else arrays
  108.  
  109.  
  110. def gen_object(prop=None):
  111. def gen_base_objects(prop_is_selected):
  112. return hs.fixed_dictionaries({
  113. prop_name: prop_generator
  114. for prop_name, prop_generator in proto.items()
  115. if prop_is_selected[prop_name]
  116. })
  117.  
  118. @hs.composite
  119. def add_additional_properties(draw, elements):
  120. base_object = draw(elements)
  121. additional_props = {}
  122. if prop.get('additionalProperties') is True:
  123. additional_props = draw(hs.dictionaries(
  124. keys=gen_prop_name(),
  125. values=get_generator()))
  126. elif isinstance(prop.get('additionalProperties'), dict):
  127. additional_props = draw(hs.dictionaries(
  128. keys=gen_prop_name(),
  129. values=get_generator(prop.get('additionalProperties'))))
  130. return {**base_object, **additional_props}
  131.  
  132. prop = prop or {}
  133. # Step 1: Create the base objects, consisting of required and optional
  134. # properties.
  135. proto = {
  136. prop_name: get_generator(prop_schema)
  137. for prop_name, prop_schema in prop.get('properties', {}).items()
  138. }
  139. prop_is_selected = hs.fixed_dictionaries({
  140. prop_name:
  141. hs.just(True) if prop_name in prop.get('required', []) else
  142. hs.booleans()
  143. for prop_name in proto
  144. })
  145. base_objects = prop_is_selected.flatmap(gen_base_objects)
  146. # Step 2: Add additional properties.
  147. objects = add_additional_properties(base_objects)
  148. # Step 3: Add pattern properties.
  149. # TODO
  150. return objects | hs.none() if prop.get('nullable') is True else objects
  151.  
  152.  
  153. def gen_document():
  154. def wrap_scalars_in_containers(scalars):
  155. arrays = hs.lists(elements=scalars)
  156. objects = hs.dictionaries(keys=gen_prop_name(), values=scalars)
  157. return arrays | objects
  158.  
  159. scalars = \
  160. hs.text() | hs.booleans() | hs.integers() | hs.floats() | hs.none()
  161. return hs.recursive(
  162. base=scalars,
  163. extend=wrap_scalars_in_containers,
  164. max_leaves=10)
  165.  
  166.  
  167. def get_generator(prop=None):
  168. # TODO: anyOf, allOf
  169. prop = prop or {}
  170. if prop.get('enum', None) is not None:
  171. enums = hs.sampled_from(prop['enum'])
  172. return enums | hs.none() if prop.get('nullable') is True else enums
  173. elif prop.get('oneOf', None) is not None:
  174. return hs.one_of(*[get_generator(prop) for prop in prop['oneOf']])
  175. else:
  176. return {
  177. 'string': gen_string,
  178. 'integer': gen_integer,
  179. 'number': gen_number,
  180. 'boolean': gen_boolean,
  181. 'object': gen_object,
  182. 'array': gen_array,
  183. None: gen_document,
  184. }[prop.get('type', None)](prop)
  185.  
  186.  
  187. def examples_from_json_schema(json_schema):
  188. json_documents = get_generator(json_schema)
  189. while True:
  190. yield json_documents.example()
  191.  
  192.  
  193. def json_schema_from_json_documents(
  194. *json_documents: Dict[str, Any]) -> Dict[str, Any]:
  195. from genson import SchemaBuilder
  196. builder = SchemaBuilder()
  197. for json_document in json_documents:
  198. builder.add_object(json_document)
  199. json_schema = builder.to_schema()
  200. return json_schema
  201.  
  202.  
  203. def examples_from_json_documents(*json_documents):
  204. json_schema = json_schema_from_json_documents(*json_documents)
  205. return examples_from_json_schema(json_schema)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement