Skip to main content

Validate JSON data using a schema written in Python

Project description

# Validate JSON data using a schema written in Python

# Examples

```
from okschema import validate, ValidationError, SchemaError

try:
validated_data = validate(schema, data)
except ValidationError as e:
print(e.js) # {'code': ValidationCode.<xxx>}
```

## Validation of a simple form
```
from okschema import validate, fmt_uuid

def val_email(v):
return v

schema = {
'my_password': {'@t': 'string', '@lteq': 100},
'user_id': {'@t': 'string', '@regexp': fmt_uuid},
'new_email': {'@t': 'string', '@val': val_email},
'new_password': {'@t': 'string', '@lteq': 100},
}
data = {
'my_password': 'abc',
'user_id': 'dbc8911c-92e8-4cdb-85b8-47a7a6a82db1',
'new_email': 'abc@example.com',
'new_password': 'abc'
}
validated_data = validate(schema, data)
```

## Potential use in request handling

```
@api_request(schema=lambda m: {
'email': {'@t': 'string', '@lteq': m.user.c.email.ui_len},
'password': {'@t': 'string', '@lteq': m.user.c.password.ui_len},
'key': {'@t': 'string', '@lteq': 10240},
'permission_level': {'@t': 'int', '@in': [PermissionE.ADMIN, PermissionE.USER]},
})
async def add_user_account(request, m):
...
```

## Nested structure - error handling
```
schemaA = {
'outer': {
'a': 'int',
'b': {'@t': 'str', '@lt': 2, '@optional': True, '@null': True, '@blank': True},
'c': [{'@t': 'str', '@in': ['x', 'y', 'z']}]
},
'is_ok': {'@type': 'bool', '@default': True}
}
data1 = {
}
data2 = {
'outer': {
'a': 'xxx',
'b': '3333',
'c': ['a', 'x', 12]
},
'is_ok': True
}
```

```
>>> validate(schemaA, data1)
ValidationError: ({
'outer': {'code': ValidationCode.MISSING},
'is_ok': {'code': ValidationCode.MISSING}
}, schemaA)
```

```
>>> validate(schemaA, data2)
ValidationError: ({
'outer': {
'a': {'code': ValidationCode.BAD_TYPE},
'b': {'code': ValidationCode.NOT_LT, 'details': 2},
'c': [
{'code': ValidationCode.NOT_IN},
None, # no error here
{'code': ValidationCode.BAD_TYPE}
]
},
'is_ok': {'code': ValidationCode.BAD_TYPE}
), schemaA)
```

## Custom validators - error handling

```
def bad_val1_cont(x):
raise NotValidButContinueError(ValidationCode.BAD_VALUE, 1)

def bad_val2_cont(x):
raise NotValidButContinueError(ValidationCode.BAD_VALUE, 5)

def bad_val3(x):
raise NotValidError(ValidationCode.BAD_VALUE)

schemaA = {
'b': {'@t': 'int', '@val': [bad_val1_cont, bad_val2_cont]}
}
schemaB = {
'b': {'@t': 'int', '@val': [bad_val1_cont, bad_val2_cont, bad_val3]}
}
data1 = {'b': 12}
```

```
>>> validate(schemaA, data1)
ValidationError: ({
'b': {
'code': ValidationCode.MANY_ERRORS, 'details': [
{'code': ValidationCode.BAD_VALUE, 'details': 1},
{'code': ValidationCode.BAD_VALUE, 'details': 5}
]
}
}, schemaA)

>>> validate(schemaB, data1)
ValidationError: ({
'b': {
'code': ValidationCode.MANY_ERRORS, 'details': [
{'code': ValidationCode.BAD_VALUE, 'details': 1},
{'code': ValidationCode.BAD_VALUE, 'details': 5},
{'code': ValidationCode.BAD_VALUE}
]
}
}, schemaA)
```

# ValidationCode enum

BAD_TYPE = 1
NOT_IN = 2
NULL = 3
MISSING = 4
OUT_OF_BOUNDS = 5 # for use by validators
REGEXP = 6
MANY_ERRORS = 8 # list of errors for a single field
NOT_GT = 9
NOT_GTEQ = 10
NOT_LT = 11
NOT_LTEQ = 12
NOT_EQ = 13

# Field description
```
"field: "type"
"field": { extended field description }
```

# Extended field description
By default all fields are required and strings must be non-empty. Extra fields in input data are discarded.
```
{
"@t": "type", # default type is dict

# -- validators --

# Checks that regexp matches - called before other validators.
"@regexp": "regexp",

# Validator function.
"@val": val_fun,
"@val: [val_fun, val_fun2, ...],

# -- options --

# If True and value is missing, it will not be present in result unless
# default is defined. Makes sense for subfields in dicts.
"@optional": bool,

# Allows empty strings.
"@blank": bool,

# Used when value is not present.
# Default value is never passed to validators.
"@default": value,

# Allow nulls (None). By default null is not allowed.
"@null": bool,

# -- limits --
# Checked before validators are called.
# They work for string lengths too.
# TODO: They work for list lengths too.

"@in": [value1, value2, ...],
"@gt": value,
"@gteq": value,
"@lt": value,
"@lteq": value,
"@neq": value,

# Extra fields if type is dict.
"field1": field description or extended field description,
...
}
```

# Lists.

```
"field": [field description]
"field": [{extended field description}]
```

## TODO: Optional lists.

## TODO: List length limits.
```
"field": [
{
# field description
}, {
# list parameters
"@optional": True,
"@gt": val,
}
]
```

# Available types
## scalar
- string (str)
- int
- decimal
- float
- bool

## composite
- dict

## TODO: Type "any" handles any type of subjson withot further validation

# Validator functions

```
def fun(val):
return val + 1
```

Should either return validated value or raise NotValidError(code, details).
May also raise NotValidButContinueError to store the error but call the next validator, constructing a list
of errors.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for okschema, version 0.2
Filename, size File type Python version Upload date Hashes
Filename, size okschema-0.2-py2-none-any.whl (6.3 kB) File type Wheel Python version 2.7 Upload date Hashes View hashes
Filename, size okschema-0.2.tar.gz (6.2 kB) File type Source Python version None Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN SignalFx SignalFx Supporter DigiCert DigiCert EV certificate StatusPage StatusPage Status page