Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import json
- from string import ascii_lowercase
- from typing import Any, Dict
- from hypothesis import strategies as hs
- def gen_prop_name():
- return hs.text(alphabet=ascii_lowercase + '_', min_size=1)
- def gen_boolean(prop=None):
- prop = prop or {}
- bools = hs.booleans()
- return bools | hs.none() if prop.get('nullable') is True else bools
- def gen_integer(prop=None):
- # TODO: multipleOf, exclusiveMinimum, exclusiveMaximum
- prop = prop or {}
- min_value = prop.get('minimum')
- max_value = prop.get('maximum')
- ints = hs.integers(min_value=min_value, max_value=max_value)
- return ints | hs.none() if prop.get('nullable') is True else ints
- def gen_number(prop=None):
- # TODO: multipleOf, exclusiveMinimum, exclusiveMaximum
- prop = prop or {}
- min_value = prop.get('minimum')
- max_value = prop.get('maximum')
- numbers = hs.floats(
- min_value=min_value,
- max_value=max_value,
- # NaN and Inf not allowed in JSON.
- allow_nan=False,
- allow_infinity=False)
- return numbers | hs.none() if prop.get('nullable') is True else numbers
- def gen_string(prop=None):
- prop = prop or {}
- min_length = prop.get('minLength', 0)
- max_length = prop.get('maxLength', None)
- if isinstance(prop.get('pattern'), str):
- # Note: re.search('^...$', s) will actually match strings that end in
- # a newline \n. It will _not_ match s if s ends in more than one \n. In
- # ECMAScript, /^...$/.test(s) is false if s ends in a newline.
- strings = hs.from_regex(prop['pattern']).map(
- lambda x:
- x[:-1]
- if prop['pattern'].endswith('$') and x.endswith('\n')
- else x
- ).filter(
- lambda x:
- (min_length == 0 or len(x) >= min_length) and
- (max_length is None or len(x) <= min_length))
- elif isinstance(prop.get('format'), str):
- if prop['format'] == 'date-time':
- from hypothesis.extra.pytz import timezones
- strings = hs.datetimes(timezones=hs.none() | timezones()).map(
- lambda x: x.isoformat())
- elif prop['format'] == 'date':
- strings = hs.dates().map(lambda x: x.isoformat())
- elif prop['format'] == 'time':
- from hypothesis.extra.pytz import timezones
- strings = hs.times(timezones=hs.none() | timezones()).map(
- lambda x: x.isoformat())
- elif prop['format'] == 'email':
- strings = hs.emails()
- else:
- return NotImplementedError(f'Format {prop["format"]} unknown')
- else:
- strings = hs.text(min_size=min_length, max_size=max_length)
- return strings | hs.none() if prop.get('nullable') is True else strings
- def gen_array(prop=None):
- prop = prop or {}
- min_items = prop.get('minItems', 0)
- max_items = prop.get('maxItems', None)
- unique_items = prop.get('uniqueItems', False)
- items = prop.get('items', {})
- if isinstance(items, dict):
- # List:
- # - "items": {"type": "integer"}
- # - "items": {"type": ["integer", "string"]}
- item_types = items.get('type')
- if not isinstance(item_types, list):
- item_types = [item_types]
- item_generators = [
- get_generator({**items, **{'type': item_type}})
- for item_type in item_types]
- arrays = hs.lists(
- elements=hs.one_of(*item_generators),
- min_size=min_items,
- max_size=max_items,
- unique_by=(lambda x: json.dumps(x)) if unique_items else None)
- elif isinstance(items, list):
- # Tuple:
- # - "items": [{"type": "integer"}, {"type": "string"}]
- item_generators = [get_generator(item) for item in items]
- arrays = hs.tuples(*item_generators)
- if unique_items is True:
- arrays = arrays.filter(
- lambda x: len(x) == len(set(json.dumps(e) for e in x)))
- return arrays | hs.none() if prop.get('nullable') is True else arrays
- def gen_object(prop=None):
- def gen_base_objects(prop_is_selected):
- return hs.fixed_dictionaries({
- prop_name: prop_generator
- for prop_name, prop_generator in proto.items()
- if prop_is_selected[prop_name]
- })
- @hs.composite
- def add_additional_properties(draw, elements):
- base_object = draw(elements)
- additional_props = {}
- if prop.get('additionalProperties') is True:
- additional_props = draw(hs.dictionaries(
- keys=gen_prop_name(),
- values=get_generator()))
- elif isinstance(prop.get('additionalProperties'), dict):
- additional_props = draw(hs.dictionaries(
- keys=gen_prop_name(),
- values=get_generator(prop.get('additionalProperties'))))
- return {**base_object, **additional_props}
- prop = prop or {}
- # Step 1: Create the base objects, consisting of required and optional
- # properties.
- proto = {
- prop_name: get_generator(prop_schema)
- for prop_name, prop_schema in prop.get('properties', {}).items()
- }
- prop_is_selected = hs.fixed_dictionaries({
- prop_name:
- hs.just(True) if prop_name in prop.get('required', []) else
- hs.booleans()
- for prop_name in proto
- })
- base_objects = prop_is_selected.flatmap(gen_base_objects)
- # Step 2: Add additional properties.
- objects = add_additional_properties(base_objects)
- # Step 3: Add pattern properties.
- # TODO
- return objects | hs.none() if prop.get('nullable') is True else objects
- def gen_document():
- def wrap_scalars_in_containers(scalars):
- arrays = hs.lists(elements=scalars)
- objects = hs.dictionaries(keys=gen_prop_name(), values=scalars)
- return arrays | objects
- scalars = \
- hs.text() | hs.booleans() | hs.integers() | hs.floats() | hs.none()
- return hs.recursive(
- base=scalars,
- extend=wrap_scalars_in_containers,
- max_leaves=10)
- def get_generator(prop=None):
- # TODO: anyOf, allOf
- prop = prop or {}
- if prop.get('enum', None) is not None:
- enums = hs.sampled_from(prop['enum'])
- return enums | hs.none() if prop.get('nullable') is True else enums
- elif prop.get('oneOf', None) is not None:
- return hs.one_of(*[get_generator(prop) for prop in prop['oneOf']])
- else:
- return {
- 'string': gen_string,
- 'integer': gen_integer,
- 'number': gen_number,
- 'boolean': gen_boolean,
- 'object': gen_object,
- 'array': gen_array,
- None: gen_document,
- }[prop.get('type', None)](prop)
- def examples_from_json_schema(json_schema):
- json_documents = get_generator(json_schema)
- while True:
- yield json_documents.example()
- def json_schema_from_json_documents(
- *json_documents: Dict[str, Any]) -> Dict[str, Any]:
- from genson import SchemaBuilder
- builder = SchemaBuilder()
- for json_document in json_documents:
- builder.add_object(json_document)
- json_schema = builder.to_schema()
- return json_schema
- def examples_from_json_documents(*json_documents):
- json_schema = json_schema_from_json_documents(*json_documents)
- return examples_from_json_schema(json_schema)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement