text: import typing
  import re
  from base64 import b64decode
  from datetime import datetime
  import ujson as json
  from typing import get_type_hints
  from functools import partial
  #bare minimum validations and schema generators
  _OBJ_END_ = object()
  
  
  
  class _Optional:
  	def __init__(self, *types):
  		self._types = types
  
  	def __getitem__(self, *keys):
  		return _Optional(*keys)
  
  
  Optional = _Optional()
  
  class Int:
  	def __init__(self, one_of=None, _min=_OBJ_END_, _max=_OBJ_END_, default=_OBJ_END_):
  		self._min = _min
  		self._max = _max
  		self.one_of = set(one_of) if one_of else None
  		self._default = default
  		#make schema
  		self._schema_ = _schema = {"type": "integer"}
  		if(default != _OBJ_END_):
  			_schema["default"] = default
  		if(self._min != _OBJ_END_):
  			_schema["minimum"] = self._min
  		if(self._max != _OBJ_END_):
  			_schema["maximum"] = self._max
  		if(self.one_of):
  			_schema["enum"] = list(self.one_of)
  
  	def validate(self, e):
  		if(e == None):
  			if(self._default != _OBJ_END_):
  				return self._default
  			raise Exception("should be int")
  		if(self._min != _OBJ_END_ and e < self._min):
  			raise Exception("should be minlen %d"%(self._min))
  		if(self._max != _OBJ_END_ and e > self._max):
  			raise Exception("more than maxlen %d"%(self._max))
  		if(self.one_of and e not in self.one_of):
  			raise Exception("should be one of %s"%(str(self.one_of)))
  		return e
  
  class Str:
  
  	format_validators = {
  		"date-time": lambda e: datetime.strptime(e.strip(), "%Y-%m-%dT%H:%M:%S.%fZ"),
  		"date": lambda e: datetime.strptime(e.strip(), "%Y-%m-%d"),
  		"binary": lambda e: e,
  		"byte": lambda e: b64decode(e)
  	}
  
  	def __init__(self, one_of=None, minlen=0, maxlen=4294967295, regex=None, default=_OBJ_END_, **kwargs):
  		self.minlen = minlen
  		self.maxlen = maxlen
  		self.one_of = set(one_of) if one_of else None
  		self._default = default
  		self.regex = regex and re.compile(regex)
  		_fmt = kwargs.pop("format", None)
  		self.fmt = _fmt and Str.format_validators[_fmt]
  
  		#make schema
  		self._schema_ = _schema = {"type": "string"}
  		if(default != _OBJ_END_):
  			_schema["default"] = default
  		_schema = {"type": "string"}
  		if(self.minlen):
  			_schema["minimum"] = self.minlen
  		if(self.maxlen and self.maxlen < 4294967295):
  			_schema["maximum"] = self.maxlen
  		if(self.one_of):
  			_schema["enum"] = list(self.one_of)
  		if(regex):
  			_schema["pattern"] = regex
  		if(_fmt):
  			_schema["format"] = _fmt
  
  	def validate(self, e):
  		if(e == None):
  			if(self._default != _OBJ_END_):
  				return self._default
  			raise Exception("should be string")
  		if(len(e) < self.minlen):
  			raise Exception("should be minlen %s"%(self.minlen))
  		if(len(e) > self.maxlen):
  			e = e[:self.maxlen]
  		if(self.one_of and e not in self.one_of):
  			raise Exception("should be one of %s"%(str(self.one_of)))
  		if(self.regex and not self.regex.fullmatch(e)):
  			raise Exception("did not match the pattern %s"%(str(self.one_of)))
  		if(self.fmt):
  			return self.fmt(e)
  		return e
  
  class Array:
  	def __init__(self, _types, default=_OBJ_END_):
  		self._types = _types if isinstance(_types, (list, tuple)) else [_types]
  		self._default = default
  
  		#ceontents validation
  		_s, _v = schema(self._types)
  
  		self._schema_ = _schema = {"type": "array", "items": _s}
  		if(default != _OBJ_END_):
  			_schema["default"] = default
  		#this is spefic validataion
  		self.validate = partial(
  			array_validation,
  			mix=isinstance(self._types, tuple),
  			default=self._default,
  			**_v.keywords
  		)
  
  
  class Object:
  	def __init__(self, default=_OBJ_END_, _required_=None, **keys):
  		self._keys = keys
  		self._default = default
  		self._required = set(_required_) if _required_ else set()
  
  		#instance specific schema
  		self._properties = {}
  		self._validations = {}
  		for k, _type in keys.items():
  			_s, _v = schema(_type)
  			self._properties[k] = _s
  			self._validations[k] = _v
  
  		self._schema_ = _schema = {"type": "object", "properties": self._properties}
  		if(default != _OBJ_END_):
  			_schema["default"] = default
  
  	#validates Object(a=str, b=int, h=Test)
  	def validate(self, e=_OBJ_END_):
  		is_validating_dict = True
  		if(e == _OBJ_END_):
  			e = self.__dict__
  			is_validating_dict = False
  		elif(isinstance(e, str)):
  			e = json.loads(e)
  
  		k = None
  		attr_value = None
  		cls = self.__class__
  		try:
  			for k, attr_validation in self._validations.items():
  				attr_value = e.get(k, _OBJ_END_)
  				if(attr_value != _OBJ_END_):
  					_validated_attr = attr_validation(attr_value)
  				else:
  					_validated_attr = getattr(cls, k, None) # try getting class attribute dfault
  
  				if(_validated_attr == None and k in self._required):
  					raise Exception("Field is required")
  
  				if(_validated_attr != attr_value):
  					e[k] = _validated_attr
  			return e if is_validating_dict else self
  		except Exception as ex:
  			raise Exception({
  				"exception": (ex.args and ex.args[0]) or "Validation failed",
  				"key": k,
  				"value": attr_value,
  				"schema": self._properties[k]
  			})
  
  def to_int(e):
  	return e and int(e)
  def to_str(e):
  	return e and str(e)
  def to_float(e):
  	return e and float(e)
  
  def item_validation(e, simple_types=(), complex_validations=(), nullable=True):
  	if(e == None and not nullable):
  		raise Exception("Cannot be none")
  
  	valid = False
  	if(simple_types and isinstance(e, simple_types)):
  		valid = True
  	if(complex_validations):
  		for _complex_validation in complex_validations:
  			if(_complex_validation(e)):
  				valid = True
  				break
  
  	if(valid):
  		return e
  
  	if(nullable):
  		return None
  
  	raise Exception("Cannot be none")
  
  
  def array_validation(arr, simple_types=(), complex_validations=(), default=_OBJ_END_, mix=False, nullable=True):
  	#sequece
  	if(arr == None and not nullable):
  		if(default != _OBJ_END_):
  			return default
  		raise Exception("Cannot be none")
  	if(not isinstance(arr, list)):
  		arr = json.loads(arr)
  
  	filter_nones = False
  	_prev_type = _OBJ_END_
  	for i in range(len(arr)):
  		valid = False
  		e = arr[i]
  		if(simple_types and isinstance(e, simple_types)):
  			valid = True
  		if(complex_validations):
  			for _complex_validation in complex_validations:
  				if(_complex_validation(e)):
  					valid = True
  		#check types should not mixed
  		if(not mix): # single type, so check type matches with previous
  			_cur_type = type(e)
  			if(_prev_type == _OBJ_END_):
  				_prev_type = _cur_type
  			elif(_prev_type != _cur_type):
  				valid = False
  					
  		if(not valid):
  			arr[i] = None
  			filter_nones = True
  
  	if(filter_nones and not nullable): # null not allowed
  		n = len(arr)
  		i = n - 1
  		while(i >= 0):
  			if(arr[i] == None):
  				arr.pop(i)
  			i -= 1
  
  	return arr
  
  
  '''
  	given any instance/class, returns schema, _validation function
  '''
  def schema(x):
  	if(isinstance(x, type) and issubclass(x, Object) and x != Object):
  		ret = schema.defs.get(x.__name__)
  		if(ret):
  			return {"schema": {"$ref": "#/definitions/" + x.__name__}}, x.validate
  
  		x._validations = _validations = {}
  		x._properties = _properties = {}
  		x._required = _required = set()
  		for k, _type in get_type_hints(x).items():
  			is_required = True
  			if(_type.__module__ == 'typing'
  				and getattr(_type, "__origin__", None) == typing.Union
  				and getattr(_type, "__args__", None)
  			):
  				_type = _type.__args__[0]
  				is_required = False
  			if(isinstance(_type, _Optional)):
  				_type = _type._types[0]
  				is_required = False
  			_schema_and_validation = schema(_type)
  			if(_schema_and_validation):
  				_properties[k], _validations[k] = _schema_and_validation
  				is_required and _required.add(k)
  				_default = x.__dict__.get(k, _OBJ_END_)
  				if(_default != _OBJ_END_ and not isinstance(_type, type)):
  					_type._default = _default
  
  
  
  		#create schema
  		x._schema_ = ret = schema.defs[x.__name__] = {
  				"type": "object",
  			}
  		_title = getattr(x, "_title_", None)
  		_description = getattr(x, "_description_", None)
  		if(_title):
  			ret["title"] = _title
  		if(_description):
  			ret["description"] = getattr(x, "_description_", None)
  				
  		if(_properties):
  			ret["properties"] = _properties
  
  		if(_required):
  			ret["required"] = list(_required)
  		
  		return x._schema_, x.validate
  
  	#special for tuples and list
  	elif(isinstance(x, (list, tuple))): # x = [int, str]->oneof, (int, str)->anyof
  		is_nullable = False
  		if(None in x):
  			x.pop(x.index(None))
  			is_nullable = True
  
  		simple_types = [] # str, int, etc
  		complex_validations = [] # array, object, SomeObject
  		_schemas = []
  		for _type in x: #
  			_s, _v = schema(_type)
  			_schemas.append(_s)
  
  			if(_type in (int, str, float)):
  				simple_types.append(_type)
  			else:
  				complex_validations.append(_type)
  
  		ret = None
  		if(len(x) == 1):
  			ret = _schemas[0]
  		elif(isinstance(x, list)):
  			ret = {"oneOf": _schemas}
  		else:
  			ret = {"anyOf": _schemas}
  
  		if(is_nullable):
  			ret["nullable"] = True
  
  		return ret, partial(
  						item_validation,
  						simple_types=tuple(simple_types),
  						complex_validations=complex_validations,
  						nullable=is_nullable
  					)
  
  	elif(x == int or x == Int):
  		return {"type": "integer"}, to_int
  
  	elif(x == float):
  		return {"type": "number", "format": "float"}, to_float
  
  	elif(x == str or x == Str):
  		return {"type": "string"}, to_str
  
  	elif(isinstance(x, Str)):
  		return x._schema_, x.validate
  
  	elif(isinstance(x, Int)):
  		return x._schema_, x.validate
  
  	elif(x == Array or x == list): # genric
  		return {"type": "array"}, (lambda e: e if isinstance(e, list) else None)
  
  	elif(x == Object or x == dict):
  		return {"type": "object"}, (lambda e: e if isinstance(e, dict) else None)
  
  	elif(isinstance(x, Array)): # Array(_types)
  		return x._schema_, x.validate
  
  	elif(isinstance(x, Object)): # Object(id=Array)
  		return x._schema_, x.validate
  
  	else:
  		return None, None
  
  
  schema.defs = {}
  
  def all_subclasses(cls):
  	return set(cls.__subclasses__()).union(
  		[s for c in cls.__subclasses__() for s in all_subclasses(c)]
  	)
  
  
  schema.init = lambda: [schema(x) for x in all_subclasses(Object)]
  