Skip to main content

Type declaration and validation library for JSON

Project description

jsonene

A library to define and use JSON schema as python classes.

Inspired by

jsonschema

json-schema

The basic idea is to provide light weight class based schema defination and data classes

Demos:

import datetime
import json
from jsonene.fields import (
    Boolean,
    List,
    GenericList,
    Null,
    Const,
    Enum,
    Number,
    Integer,
    Schema,
    GenericSchema,
    String,
    Format,
)
from jsonene.operators import AllOf, AnyOf, OneOf, Not
from jsonene.constraints import RequiredDependency
from jsonene.exceptions import ValidationError


# Define a Schema
class Person(Schema):
    name = String(min_len=3)
    gender = Enum(["MALE", "FEMALE", "OTHER"])
    emails = List(Format(Format.EMAIL), unique_items=True)
    contact = String(required=False)
    age = Integer(required=False)
    date_of_birth = Format(Format.DATE, name="date-of-birth")  # non python names

    class Meta:
        # Must provide contact if emails is provided
        field_dependencies = [RequiredDependency("emails", ["contact"])]


# Schema Inheritance
class Owner(Person):
    pass


class Broker(Person):
    brokerage = Integer()  # additional properity
    is_broker = Const(True)

    class Meta(Person.Meta):
        field_dependencies = [
            RequiredDependency("emails", ["contact"]),
            RequiredDependency("contact", ["emails"]),
        ]


# Nested schemas
class House(Schema):
    seller = AnyOf(Owner, Broker)  # accepts any of owner or broken
    address = List(Number, String, String)  # accept list in specific type order.
    is_ready = Boolean()
    area = Number()
    country = Const("India")
    garden_area = Number(required=False, use_default=0)
    sqtft_rate = Number(required=False, use_default=0)
    secrete_key = Number(required=False, name="__secrete_key")  # Private
    possesion_date = Format(Format.DATE)
    # Extend instance class and add properties
    class Instance(Schema.Instance):
        @property
        def cost(self):
            # Safety: fields with required=False should be checked before access.
            # Optionaly you can provide default value.
            return self.sqtft_rate * self.area

    # Provide custom meta
    class Meta:
        # Must provide area and sqtft_rate if sqtft_rate provided
        # OR v.v.
        field_dependencies = [
            RequiredDependency("area", ["sqtft_rate"]),
            RequiredDependency("sqtft_rate", ["area"]),
        ]


Const(2).instance(2).validate()  # won't raise error

try:
    Const(2).instance(3).validate()  # raises error
except ValidationError:
    assert True

assert Enum([1, 2, 3]).instance(3).errors == []  # no error

# Raises error
try:
    Enum([1, 2, "Three"])(5).validate()
except ValidationError:
    assert True

# Lists

# Generic list
l = List.instance([1, 23, 56, "anything"])
l.append("wow")
l[1:6]  # slice
l.append(23)
l.extend([45])
l[2] = 100
l.validate()  # No errors!

# List accepting only string type
l = List(String).instance(["only", "strings", "are", "allowed"])
assert len(l.errors) == 0  # No errors!

l = List(String).instance(["only", "strings", 60, 30])
assert [e.message for e in l.exceptions] == [
    "60 is not of type 'string'",
    "30 is not of type 'string'",
]

# Instances
generic = GenericSchema.instance(anything="you want", almost_anything=[1, 2, "3"])
assert len(generic.errors) == 0
assert generic.anything == "you want"
assert generic.almost_anything == [1, 2, "3"]


# Create a instances using Schema
owner = Owner.instance(
    name="Test owner",
    gender="MALE",
    emails=["test@test.com"],
    date_of_birth="1989-01-01",
)

assert owner.errors == ["'contact' is a dependency of 'emails'"]
assert owner["date-of-birth"] == "1989-01-01"

test = Broker.instance(
    name="Test Rupanwar",
    gender="MALE",
    emails=["testtest.com", "testtest.com"],
    contact="123456",
    brokerage=12345,
    is_broker=True,
    date_of_birth="1989-01-01",
)
assert test.errors == [
    "'testtest.com' is not a 'email'",
    "'testtest.com' is not a 'email'",
    "['testtest.com', 'testtest.com'] has non-unique elements",
]

owner = Owner.instance(
    name="Nikhil Rupanawar",
    gender="MALE",
    emails=["conikhil@gmail.com"],
    contact="4545454545",
    date_of_birth="1989-09-11",
)
house = House.instance()
house.seller = owner
house.address = [123, "A building", "Singad road"]
house.is_ready = True
house.country = "India"
house.area = 7000
house.possesion_date = datetime.datetime.now()
assert house.cost == 0 # sqtft_rate is 0 as default
assert len(house.errors) == 0


# Another House
another_house = House.instance(
    seller=Broker.instance(
        name="Test Rupanwar",
        gender="MALE",
        emails=["test@test.com"],
        contact="123456",
        brokerage=12345,
        is_broker=True,
        date_of_birth="2002-09-08",
    ),
    address=[123, "A building", "Baner road"],
    sqtft_rate=5000,
    area=1100,
    is_ready=True,
    country="India",
    secrete_key=12345,
    possesion_date=datetime.datetime.now()
)
another_house.validate()
assert another_house.cost == 5500000

# Validate any json by document
House().validate(
    {
        "seller": {
            "age": 22,
            "emails": ["test@test.com", "test2@test.com"],
            "name": "nikhil",
            "gender": "MALE",
            "contact": "1234567",
            "date-of-birth": "1978-09-04",
        },
        "address": [120, "Flat A", "Sarang"],
        "area": 1234,
        "sqtft_rate": 2000,
        "garden_area": 123,
        "is_ready": True,
        "country": "India",
        "possesion_date": str(datetime.datetime.now()),
    }
)

# generate json
houses = List(House).instance([house, another_house])
houses.validate()
houses.to_json()


HOUSE_DATA_VALID = json.dumps(
    {
        "seller": {
            "age": 22,
            "emails": ["test@test.com", "test2@test.com"],
            "name": "nikhil",
            "gender": "MALE",
            "contact": "1234567",
            "date-of-birth": "1978-09-04",
        },
        "address": [120, "Flat A", "Sarang"],
        "area": 1234,
        "sqtft_rate": 2000,
        "garden_area": 123,
        "is_ready": True,
        "country": "India",
        "possesion_date": "2020-02-05",  # str(datetime.datetime.now()),
    }
)

h = House.from_json(HOUSE_DATA_VALID)
h.validate(check_formats=True)

Project details


Download files

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

Source Distribution

jsonene-0.0.10.tar.gz (10.8 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page