Skip to content

Commit

Permalink
Version 0.3.0 (#98)
Browse files Browse the repository at this point in the history
Co-authored-by: Amin Alaee <[email protected]>
  • Loading branch information
tomchristie and aminalaee committed Sep 15, 2021
1 parent 1027238 commit 851b60f
Show file tree
Hide file tree
Showing 31 changed files with 944 additions and 951 deletions.
64 changes: 33 additions & 31 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ uvicorn
**app.py**

```python
import typesystem
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
import typesystem
import uvicorn

users = []


class User(typesystem.Schema):
username = typesystem.String(max_length=100)
is_admin = typesystem.Boolean(default=False)
user_schema = typesystem.Schema(
fields={
"username": typesystem.String(max_length=100),
"is_admin": typesystem.Boolean(default=False),
}
)


async def list_users(request):
Expand All @@ -39,7 +42,7 @@ async def add_user(request):
if errors:
return JSONResponse(dict(errors), status_code=400)
users.append(user)
return JSONResponse(dict(user))
return JSONResponse(user)


app = Starlette(debug=True, routes=[
Expand Down Expand Up @@ -73,56 +76,55 @@ uvicorn
**app.py**

```python
import typesystem
import uvicorn
from starlette.applications import Starlette
from starlette.responses import RedirectResponse
from starlette.routing import Route, Mount
from starlette.routing import Mount, Route
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
import typesystem
import uvicorn

forms = typesystem.Jinja2Forms(package="bootstrap4")
templates = Jinja2Templates(directory="templates")
statics = StaticFiles(directory="statics", packages=["bootstrap4"])
bookings = []


class BookingSchema(typesystem.Schema):
start_date = typesystem.Date(title="Start date")
end_date = typesystem.Date(title="End date")
room = typesystem.Choice(
title="Room type",
choices=[
("double", "Double room"),
("twin", "Twin room"),
("single", "Single room"),
],
)
include_breakfast = typesystem.Boolean(title="Include breakfast", default=False)

def __str__(self):
breakfast = (
"(with breakfast)" if self.include_breakfast else "(without breakfast)"
)
return f"Booking for {self.room} from {self.start_date} to {self.end_date}"
booking_schema = typesystem.Schema(
fields={
"start_date": typesystem.Date(title="Start date"),
"end_date": typesystem.Date(title="End date"),
"room": typesystem.Choice(
title="Room type",
choices=[
("double", "Double room"),
("twin", "Twin room"),
("single", "Single room"),
],
),
"include_breakfast": typesystem.Boolean(
title="Include breakfast", default=False
),
}
)


async def homepage(request):
form = forms.Form(BookingSchema)
form = forms.create_form(booking_schema)
context = {"request": request, "form": form, "bookings": bookings}
return templates.TemplateResponse("index.html", context)


async def make_booking(request):
data = await request.form()
booking, errors = BookingSchema.validate_or_error(data)
booking, errors = booking_schema.validate_or_error(data)
if errors:
form = forms.Form(BookingSchema, values=data, errors=errors)
form = forms.create_form(booking_schema)
context = {"request": request, "form": form, "bookings": bookings}
return templates.TemplateResponse("index.html", context)

bookings.append(booking)
return RedirectResponse(request.url_for("homepage"))
return RedirectResponse(request.url_for("homepage"), status_code=303)


app = Starlette(
Expand Down
29 changes: 21 additions & 8 deletions docs/fields.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
Fields are usually declared as attributes on schema classes:
Fields are passed as a dictionary to the Schema classes:

```python
class Organisation(typesystem.Schema):
name = typesystem.String(title="Name", max_length=100)
date_created = typesystem.Date(title="Date created", default=datetime.date.today)
owner = typesystem.Reference(to=User, allow_null=True)
import typesystem

user_schema = typesystem.Schema(fields={"name": typesystem.String()})

definitions = typesystem.Definitions()
definitions["User"] = user_schema

organization_schema = typesystem.Schema(
fields={
"name": typesystem.String(title="Name", max_length=100),
"date_created": typesystem.Date(title="Date created", default=datetime.date.today),
"owner": typesystem.Reference(to="User", allow_null=True, definitions=definitions),
}
)
```

Fields are always *required* in inputs, unless a *default* value is set.
Expand All @@ -20,6 +30,7 @@ All fields support the following arguments.
* `description` - A string describing the input. **Default: `None`**
* `default` - A value to be used if no input is provided for this field. May be a callable, such as `datetime.datetime.now`. **Default: `NO_DEFAULT`**
* `allow_null` - A boolean determining if `None` values are valid. **Default: `False`**
* `read_only` - A boolean determining if field should be considered as read-only, this is usually used in form rendering. **Default: `False`**

## Using fields directly

Expand Down Expand Up @@ -60,6 +71,7 @@ For example: `username = typesystem.String(max_length=100)`
* `min_length` - A minimum number of characters that valid input stings may contain. **Default: `None`**
* `pattern` - A regular expression that must match. This can be either a string or a compiled regular expression. E.g. `pattern="^[A-Za-z]+$"` **Default: `None`**
* `format` - A string used to indicate a semantic type, such as `"email"`, `"url"`, or `"color"`. **Default: `None`**
* `coerce_types` - A boolean determining if type casting should be done, E.g. changing `None` to `""` if `allow_blank`. **Default: `True`**

### Text

Expand Down Expand Up @@ -181,7 +193,7 @@ extra_metadata = typesystem.Object(properties=typesystem.String(max_length=100))
Schema classes implement their validation behaviour by generating an `Object`
field, and automatically determining the `properties` and `required` attributes.

You'll typically want to use `typesystem.Reference(to=SomeSchema)` rather than
You'll typically want to use `typesystem.Reference(to="SomeSchema")` rather than
using the `Object` field directly, but it can be useful if you have a more
complex data structure that you need to validate.

Expand All @@ -201,12 +213,13 @@ Used to reference a nested schema.
For example:

```python
owner = typesystem.Reference(to=User, allow_null=True)
owner = typesystem.Reference(to="User", allow_null=True, definitions=definitions)
```

**Arguments**:

* `to` - A schema class or field instance. **Required**
* `to` - Name of schema defined in definitions. **Required**
* `definitions` - `Definitions` instance. **Required**

## Other data types

Expand Down
52 changes: 24 additions & 28 deletions docs/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import typesystem

forms = typesystem.Jinja2Forms(package="typesystem") # Use the default templates.

class BookingSchema(typesystem.Schema):
start_date = typesystem.Date(title="Start date")
end_date = typesystem.Date(title="End date")
room = typesystem.Choice(title="Room type", choices=[
('double', 'Double room'),
('twin', 'Twin room'),
('single', 'Single room')
])
include_breakfast = typesystem.Boolean(title="Include breakfast", default=False)

form = forms.Form(BookingSchema)
booking_schema = typesystem.Schema(
fields={
"start_date": typesystem.Date(title="Start date"),
"end_date": typesystem.Date(title="End date"),
"room": typesystem.Choice(title="Room type", choices=[
('double', 'Double room'),
('twin', 'Twin room'),
('single', 'Single room')
]),
"include_breakfast": typesystem.Boolean(title="Include breakfast", default=False),

}
)

form = forms.create_form(booking_schema)
print(form)
```

Expand All @@ -34,26 +38,26 @@ Notice that only the fields in the form are rendered. The surrounding `<form>`,
```html
<tr>
<td>
<label for="form-bookingschema-start-date">Start date</label>
<label for="start-date">Start date</label>
</td>
<td>
<input type="date" id="form-bookingschema-start-date" name="start_date" required>
<input type="date" id="start-date" name="start_date" required >
</td>
</tr>
<tr>
<td>
<label for="form-bookingschema-end-date">End date</label>
<label for="end-date">End date</label>
</td>
<td>
<input type="date" id="form-bookingschema-end-date" name="end_date" required>
<input type="date" id="end-date" name="end_date" required >
</td>
</tr>
<tr>
<td>
<label for="form-bookingschema-room">Room type</label>
<label for="room">Room type</label>
</td>
<td>
<select id="form-bookingschema-room" name="room">
<select id="room" name="room">
<option></option>
<option value="double">Double room</option>
<option value="twin">Twin room</option>
Expand All @@ -63,10 +67,10 @@ Notice that only the fields in the form are rendered. The surrounding `<form>`,
</tr>
<tr>
<td>
<label for="form-bookingschema-include-breakfast">Include breakfast</label>
<label for="include-breakfast">Include breakfast</label>
</td>
<td>
<input type="checkbox" id="form-bookingschema-include-breakfast" name="include_breakfast" value="true">
<input type="checkbox" id="include-breakfast" name="include_breakfast" value="true" >
</td>
</tr>
```
Expand All @@ -92,15 +96,7 @@ We can include values in a form like so:

```python
initial_values = {'room': 'double', 'include_breakfast': True}
form = forms.Form(BookingSchema, values=initial_values)
```

We can also include validation errors:

```python
booking, errors = BookingSchema.validate_or_error(data)
if errors:
form = forms.Form(BookingSchema, values=data, errors=errors)
form = forms.create_form(booking_schema, values=initial_values)
```

## Customizing field rendering
Expand Down
38 changes: 19 additions & 19 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,30 +50,30 @@ $ pip3 install typesystem[pyyaml]
```python
import typesystem

class Artist(typesystem.Schema):
name = typesystem.String(max_length=100)

class Album(typesystem.Schema):
title = typesystem.String(max_length=100)
release_date = typesystem.Date()
artist = typesystem.Reference(Artist)

album = Album.validate({
artist_schema = typesystem.Schema(
fields={
"name": typesystem.String(max_length=100)
}
)

definitions = typesystem.Definitions()
definitions["Artist"] = artist_schema

album_schema = typesystem.Schema(
fields={
"title": typesystem.String(max_length=100),
"release_date": typesystem.Date(),
"artist": typesystem.Reference("Artist", definitions=definitions)
}
)

album = album_schema.validate({
"title": "Double Negative",
"release_date": "2018-09-14",
"artist": {"name": "Low"}
})

print(album)
# Album(title='Double Negative', release_date=datetime.date(2018, 9, 14), artist=Artist(name='Low'))

print(album.release_date)
# datetime.date(2018, 9, 14)

print(album['release_date'])
# '2018-09-14'

print(dict(album))
# {'title': 'Double Negative', 'release_date': '2018-09-14', 'artist': {'name': 'Low'}}
```

Expand All @@ -82,7 +82,7 @@ print(dict(album))
There are plenty of other great validation libraries for Python out there,
including [Marshmallow](https://github.com/marshmallow-code/marshmallow),
[Schematics](https://github.com/schematics/schematics),
[Voluptuous](https://github.com/alecthomas/voluptuous), and many others.
[Voluptuous](https://github.com/alecthomas/voluptuous), [Pydantic](https://github.com/samuelcolvin/pydantic/) and many others.

TypeSystem exists because I want a data validation library that offers
first-class support for:
Expand Down
29 changes: 15 additions & 14 deletions docs/json_schema.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
TypeSystem can convert Schema classes or Field instances to/from JSON Schema.
TypeSystem can convert Schema or Field instances to/from JSON Schema.

!!! note
TypeSystem only supports `$ref` pointers that use the standard "definitions"
Expand All @@ -15,20 +15,21 @@ Let's define a schema, and dump it out into a JSON schema document:
import json
import typesystem

class BookingSchema(typesystem.Schema):
start_date = typesystem.Date(title="Start date")
end_date = typesystem.Date(title="End date")
room = typesystem.Choice(
title="Room type",
choices=[
("double", "Double room"),
("twin", "Twin room"),
("single", "Single room"),
],
)
include_breakfast = typesystem.Boolean(title="Include breakfast", default=False)
booking_schema = typesystem.Schema(
fields={
"start_date": typesystem.Date(title="Start date"),
"end_date": typesystem.Date(title="End date"),
"room": typesystem.Choice(title="Room type", choices=[
('double', 'Double room'),
('twin', 'Twin room'),
('single', 'Single room')
]),
"include_breakfast": typesystem.Boolean(title="Include breakfast", default=False),

schema = typesystem.to_json_schema(BookingSchema)
}
)

schema = typesystem.to_json_schema(booking_schema)
print(json.dumps(schema, indent=4))
```

Expand Down
Loading

0 comments on commit 851b60f

Please sign in to comment.