def _visited_union(self, results: Sequence[JsonSchema]) -> JsonSchema: if len(results) == 1: return results[0] elif any(alt == {} for alt in results): return JsonSchema() elif all(alt.keys() == {"type"} for alt in results): types: Any = chain.from_iterable( [res["type"]] if isinstance(res["type"], ( str, JsonType)) else res["type"] for res in results) return json_schema(type=list(types)) elif (len(results) == 2 and all("type" in res for res in results) and { "type": "null" } in results): for result in results: if result != {"type": "null"}: types = result["type"] if isinstance(types, (str, JsonType)): types = [types] if "null" not in types: result = JsonSchema({ **result, "type": [*types, "null"] }) return result else: raise NotImplementedError else: return json_schema(anyOf=results)
def _union_result(self, results: Iterable[JsonSchema]) -> JsonSchema: results = list(results) if len(results) == 1: return results[0] elif all(alt.keys() == {"type"} for alt in results): types = chain.from_iterable( [res["type"]] if isinstance(res["type"], JsonType ) else res["type"] for res in results) return json_schema(type=list(types)) elif (len(results) == 2 and all("type" in res for res in results) and { "type": "null" } in results): for result in results: if result != {"type": "null"}: types = result["type"] if isinstance(types, str): types = [types] # type: ignore if "null" not in types: result = JsonSchema({ **result, "type": [*types, "null"] }) return result else: raise NotImplementedError() else: return json_schema(anyOf=results)
def literal(self, values: Sequence[Any]) -> JsonSchema: if not values: raise TypeError("Empty Literal") types = {JsonType.from_type(type(v)) for v in literal_values(values)} # Mypy issue type_: Any = types.pop() if len(types) == 1 else types if len(values) == 1: return json_schema(type=type_, const=values[0]) else: return json_schema(type=type_, enum=values)
def literal(self, values: Sequence[Any]) -> JsonSchema: if not values: raise TypeError("Empty Literal") types = sorted( set( JsonType.from_type(type(v.value if isinstance(v, Enum) else v)) for v in values)) # Mypy issue type_: Any = types[0] if len(types) == 1 else types if len(values) == 1: return json_schema(type=type_, const=values[0]) else: return json_schema(type=type_, enum=values)
def object(self, tp: AnyType, fields: Sequence[ObjectField]) -> JsonSchema: cls = get_origin_or_type(tp) flattened_schemas: List[JsonSchema] = [] pattern_properties = {} additional_properties: Union[bool, JsonSchema] = self.additional_properties properties = {} required = [] for field in fields: if field.flattened: self._check_flattened_schema(cls, field) flattened_schemas.append(self.visit_field(field)) elif field.pattern_properties is not None: if field.pattern_properties is ...: pattern = infer_pattern(field.type, self.default_conversion) else: assert isinstance(field.pattern_properties, Pattern) pattern = field.pattern_properties pattern_properties[pattern] = self._properties_schema(field) elif field.additional_properties: additional_properties = self._properties_schema(field) else: alias = self.aliaser(field.alias) if is_typed_dict(cls): is_required = field.required else: is_required = self._field_required(field) properties[alias] = self.visit_field(field, is_required) if is_required: required.append(alias) alias_by_names = {f.name: f.alias for f in fields}.__getitem__ dependent_required = get_dependent_required(cls) result = json_schema( type=JsonType.OBJECT, properties=properties, required=required, additionalProperties=additional_properties, patternProperties=pattern_properties, dependentRequired=OrderedDict( (alias_by_names(f), sorted(map(alias_by_names, dependent_required[f]))) for f in sorted(dependent_required, key=alias_by_names)), ) if flattened_schemas: result = json_schema( type=JsonType.OBJECT, allOf=[result, *flattened_schemas], unevaluatedProperties=False, ) return result
def mapping(self, cls: Type[Mapping], key_type: AnyType, value_type: AnyType) -> JsonSchema: with context_setter(self): self._ignore_first_ref = True key = self.visit(key_type) if key["type"] != JsonType.STRING: raise ValueError("Mapping types must string-convertible key") value = self.visit(value_type) if "pattern" in key: return json_schema(type=JsonType.OBJECT, patternProperties={key["pattern"]: value}) else: return json_schema(type=JsonType.OBJECT, additionalProperties=value)
def tuple(self, types: Sequence[AnyType]) -> JsonSchema: return json_schema( type=JsonType.ARRAY, items=[self.visit(cls) for cls in types], minItems=len(types), maxItems=len(types), )
def collection(self, cls: Type[Collection], value_type: AnyType) -> JsonSchema: return json_schema( type=JsonType.ARRAY, items=self.visit(value_type), uniqueItems=issubclass(cls, AbstractSet), )
def collection(self, cls: Type[Iterable], value_type: AnyType) -> JsonSchema: self._check_constraints(ArrayConstraints) return json_schema( type=JsonType.ARRAY, items=self.visit(value_type), uniqueItems=issubclass(cls, AbstractSet), )
def typed_dict(self, cls: Type, keys: Mapping[str, AnyType], total: bool) -> JsonSchema: self._check_constraints(ObjectConstraints) return json_schema( type=JsonType.OBJECT, properties={key: self.visit(tp) for key, tp in keys.items()}, required=list(keys) if total else [], )
def mapping( self, cls: Type[Mapping], key_type: AnyType, value_type: AnyType, ) -> JsonSchema: self._check_constraints(ObjectConstraints) with self._without_ref(): key = self.visit(key_type) if key["type"] != JsonType.STRING: raise ValueError("Mapping types must string-convertible key") value = self.visit(value_type) if "pattern" in key: return json_schema(type=JsonType.OBJECT, patternProperties={key["pattern"]: value}) else: return json_schema(type=JsonType.OBJECT, additionalProperties=value)
def tuple(self, types: Sequence[AnyType]) -> JsonSchema: self._check_constraints(ArrayConstraints) if self._schema is not None and self._schema.constraints is not None: assert isinstance(self._schema.constraints, ArrayConstraints) if (self._schema.constraints.max_items is not None or self._schema.constraints.min_items is not None): raise TypeError( "Tuple cannot have min_items/max_items constraints") return json_schema( type=JsonType.ARRAY, items=[self.visit(cls) for cls in types], minItems=len(types), maxItems=len(types), )
def named_tuple( self, cls: Type[Tuple], types: Mapping[str, AnyType], defaults: Mapping[str, Any], ) -> JsonSchema: self._check_constraints(ObjectConstraints) return json_schema( type=JsonType.OBJECT, properties={ self.aliaser(key): self.visit(key) for key, cls in types.items() }, required=sorted(types.keys() - defaults.keys()), additionalProperties=settings.additional_properties, )
def dataclass( self, cls: Type, types: Mapping[str, AnyType], fields: Sequence[Field], init_vars: Sequence[Field], ) -> JsonSchema: assert is_dataclass(cls) self._check_constraints(ObjectConstraints) properties = {} required: List[str] = [] merged_schemas = [] pattern_properties = {} additional_properties: Union[ bool, JsonSchema] = settings.additional_properties for field in get_fields(fields, init_vars, self.operation): metadata = check_metadata(field) field_type = types[field.name] if MERGED_METADATA in metadata: self._check_merged_schema(cls, field, field_type) merged_schemas.append(self.visit_field(field, field_type)) elif PROPERTIES_METADATA in metadata: pattern = metadata[PROPERTIES_METADATA] properties_schema = self._properties_schema(field, field_type) if pattern is None: additional_properties = properties_schema elif pattern is ...: pattern_properties[infer_pattern( field_type)] = properties_schema else: pattern_properties[pattern] = properties_schema else: alias = self.aliaser(get_alias(field)) properties[alias] = json_schema( readOnly=not field.init, writeOnly=field in init_vars, **self.visit_field(field, field_type), ) if is_required(field): required.append(alias) if self.operation == Operation.SERIALIZATION: for name, resolver in get_serialized_resolvers(cls).items(): with self._replace_conversions(resolver.conversions): properties[self.aliaser(name)] = json_schema( readOnly=True, **self.visit_with_schema(resolver.return_type, resolver.schema), ) dependent_required = { self.aliaser(get_alias(field)): sorted(self.aliaser(get_alias(req)) for req in required_by) for field, required_by in get_requirements( cls, DependentRequired.requiring, self.operation).items() } result = json_schema( type=JsonType.OBJECT, properties=properties, required=required, additionalProperties=additional_properties, patternProperties=pattern_properties, dependentRequired=OrderedDict((f, dependent_required[f]) for f in sorted(dependent_required)), ) if merged_schemas: result = json_schema( type=JsonType.OBJECT, allOf=[result, *merged_schemas], unevaluatedProperties=False, ) return result