# Pydantic-SURQL
Pydantic Surql is a utility set to automatically convert Pydantic models to SURQL SDL definitions.
✅ it supports the following features
- [collection definitions (a.k.a tables)](#collections-definitions)
- [schemafull / schemaless](#schemafull--schemaless-definitions)
- [drop](#drop-definitions)
- [changefeed](#changefeed-definitions)
- [view](#view-definitions)
- [indexes definitions](#indexes-analyzers-and-tokenizers-definitions)
- regular indexes
- unique indexes
- search indexes
- [fields and tables permissions](#table-an-field-permissions)
- [analyzers definition](#indexes-analyzers-and-tokenizers-definitions)
- [tokenizers definition](#indexes-analyzers-and-tokenizers-definitions)
- [events definition](#events-definitions)
and the [following types](#types-definitions) out of the box :
- [basic types (string | int | float | boolean | datetime | any, enum)](#basic-types)
- [union](#union-types)
- [optional and null](#optional-and-null-types)
- [array](#array-types)
- [set](#set-types)
- [object](#object-types)
- [record](#record-types)
❌ what it doesn't support yet :
- Future types
- Geometry types
- SURQL queries definitions (select, update, delete, where, ...)
- SURQL functions definitions
- SURQL scopes definitions
- SURQL SDL field validation (validation goes through pydantic, too complex at this stage)
- namespaces definitions
- databases definitions
- users definitions
- tokens definitions
- params definitions
PRs are welcome 😉
## installation
To install pydantic-surql run :
```bash
pip install pydantic-surql
```
or with poetry :
```bash
poetry add pydantic-surql
```
## basic usage
to convert a pydantic model to a surql SDL definition you can use a simple decorator :
```python
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
from pydantic_surql import surql_collection
@surql_collection("writers")
class Writer(BaseModel):
id: str
firstname: str
lastname: str
birthdate: datetime
@surql_collection("books")
class Book(BaseModel):
id: str
title: str
pages: Optional[int]
description: str
weight: float
writer: Writer
```
All the models decorated with `@surql_collection` will be collected by the `Metadata` object, which will be used to generate the SDL.
To generate the SDL :
```python
from pydantic_surql import Metadata
models_sdl: str = Metadata.collect()
```
this will generate the following SDL :
```surql
DEFINE TABLE writers SCHEMAFULL;
DEFINE FIELD firstname ON TABLE writers TYPE string;
DEFINE FIELD lastname ON TABLE writers TYPE string;
DEFINE FIELD birthdate ON TABLE writers TYPE datetime;
DEFINE TABLE books SCHEMAFULL;
DEFINE FIELD title ON TABLE books TYPE string;
DEFINE FIELD pages ON TABLE books TYPE option<number>;
DEFINE FIELD description ON TABLE books TYPE string;
DEFINE FIELD weight ON TABLE books TYPE number;
DEFINE FIELD writer ON TABLE books TYPE record<writers>;
```
## Collections definitions
### schemafull / schemaless definitions
By default collections are schemafull because pydantic doesn't allow models to have extra values.\
to make a collection schemaless you can use [pydantic built in feature](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra).
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
@surql_collection("schemaless_collection")
class SchemaLessCollection(BaseModel):
model_config = ConfigDict(extra='allow')
#...
print(Metadata.collect())
```
or
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLTableConfig
from pydantic import BaseModel, ConfigDict
@surql_collection("schemaless_collection", SurQLTableConfig(strict=False))
class SchemaLessCollection(BaseModel):
pass
print(Metadata.collect())
```
> [!NOTE]
> if `model_config.extra == "allow"`, the `table.config.strict` will be set to `true`
>
> if `strict == False`, the `model_config.extra` will be set to `allow`
this will generate the following SDL :
```surql
DEFINE TABLE schemaless_collection SCHEMALESS;
```
### drop definitions
you can define the collection as dropped through the config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLTableConfig
from pydantic import BaseModel
@surql_collection("drop_collection", SurQLTableConfig(drop=True))
class DropCollection(BaseModel):
pass
print(Metadata.collect())
```
this will generate the following SDL :
```surql
DEFINE TABLE drop_collection DROP SCHEMAFULL;
```
### changefeed definitions
you can define the changefeed on collection through the config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLTableConfig
from pydantic import BaseModel
@surql_collection("changefeed_collection", SurQLTableConfig(changeFeed="1d"))
class ChangefeedCollection(BaseModel):
pass
print(Metadata.collect())
```
this will generate the following SDL :
```surql
DEFINE TABLE changefeed_collection SCHEMAFULL CHANGEFEED 1d;
```
### view definitions
you can define a collection as a view through the config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLTableConfig, SurQLView
from pydantic import BaseModel
config = SurQLTableConfig(asView=SurQLView(select=["name", "age"], from_t=["users"], where=["age > 18"], group_by=["age"]))
@surql_collection("view_collection", config)
class ViewCollection(BaseModel):
name: list[str]
age: str
print(Metadata.collect())
```
this will generate the following SDL :
```surql
DEFINE TABLE view_collection AS SELECT name,age FROM users WHERE age > 18 GROUP BY age;
```
### indexes, analyzers and tokenizers definitions
You can define indexes on collections through the config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
from pydantic_surql.types import (
SurQLTableConfig,
SurQLIndex,
SurQLUniqueIndex,
SurQLSearchIndex,
SurQLAnalyzer,
SurQLTokenizers
)
index = SurQLIndex(name="index_name", fields=["field1", "field2"])
unique_index = SurQLUniqueIndex(name="unique_index_name", fields=["field1", "field2"])
analyzer = SurQLAnalyzer(name="analyzer_name", tokenizers=[SurQLTokenizers.BLANK])
search_index = SurQLSearchIndex(
name="search_index_name",
fields=["field3"],
analyzer=analyzer,
highlights=True
)
@surql_collection("indexed_collection", SurQLTableConfig(indexes=[index, unique_index, search_index]))
class IndexedCollection(BaseModel):
field1: str
field2: str
field3: str
print(Metadata.collect())
```
this will generate the following SDL :
```surql
DEFINE ANALYZER analyzer_name TOKENIZERS blank;
DEFINE TABLE indexed_collection SCHEMAFULL;
DEFINE FIELD field1 ON TABLE indexed_collection TYPE string;
DEFINE FIELD field2 ON TABLE indexed_collection TYPE string;
DEFINE FIELD field3 ON TABLE indexed_collection TYPE string;
DEFINE INDEX index_name ON TABLE indexed_collection FIELDS field1,field2;
DEFINE INDEX unique_index_name ON TABLE indexed_collection FIELDS field1,field2 UNIQUE;
DEFINE INDEX search_index_name ON TABLE indexed_collection FIELDS field3 SEARCH ANALYZER analyzer_name HIGHLIGHTS;
```
> [!NOTE]
> only the used tokenizers (used in a configuration) will be collected
### table an field permissions
You can define permissions on collections through the config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
from pydantic_surql.types import (
SurQLTableConfig,
SurQLPermissions,
)
Metadata.clear()
permission_config = SurQLTableConfig(
permissions=SurQLPermissions(
select=["WHERE published = true", "OR user = $auth.id"],
create=["WHERE user = $auth.id"],
update=["WHERE user = $auth.id"],
delete=["WHERE user = $auth.id", "OR $auth.admin = true"]
)
)
@surql_collection("permission_collection", permission_config)
class PermissionCollection(BaseModel):
field1: str
field2: str
field3: str
published: bool
print(Metadata.collect())
```
this will generate the following SDL :
```surql
DEFINE TABLE permission_collection SCHEMAFULL PERMISSIONS
FOR SELECT
WHERE published = true
OR user = $auth.id
FOR CREATE
WHERE user = $auth.id
FOR UPDATE
WHERE user = $auth.id
FOR DELETE
WHERE user = $auth.id
OR $auth.admin = true;
DEFINE FIELD field1 ON TABLE permission_collection TYPE string;
DEFINE FIELD field2 ON TABLE permission_collection TYPE string;
DEFINE FIELD field3 ON TABLE permission_collection TYPE string;
DEFINE FIELD published ON TABLE permission_collection TYPE bool;
```
You can define field permissions throught the `SurQLFieldConfig` function.\
it's a wrapper around the pydantic `Field` function (so you can use all the properties from the `Field` definition) :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
from pydantic_surql.types import (
SurQLTableConfig,
SurQLPermissions,
SurQLFieldConfig
)
fields_permission = SurQLPermissions(
select=["WHERE user = $auth.id"],
create=["WHERE user = $auth.id"],
update=["WHERE user = $auth.id"],
delete=["WHERE user = $auth.id", "OR $auth.admin = true"]
)
@surql_collection("permission_collection", permission_config)
class FieldPermissionsCollection(BaseModel):
field1: str = SurQLFieldConfig(permissions=fields_permission, min_length=2)
field2: str
field3: str
published: bool
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE FIELD field1 ON TABLE field_permission_collection TYPE string PERMISSIONS
FOR SELECT
WHERE user = $auth.id
FOR CREATE
WHERE user = $auth.id
FOR UPDATE
WHERE user = $auth.id
FOR DELETE
WHERE user = $auth.id
OR $auth.admin = true;
DEFINE FIELD field2 ON TABLE field_permission_collection TYPE string;
DEFINE FIELD field3 ON TABLE field_permission_collection TYPE string;
DEFINE FIELD published ON TABLE field_permission_collection TYPE bool;
```
## events definitions
You can define events through the collection config :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
from pydantic_surql.types import SurQLTableConfig, SurQLEvent
event_config = SurQLTableConfig(events=[
SurQLEvent(
name="event_name",
whenSDL=["$event = \"INSERT\"", "$event = \"UPDATE\""],
querySDL="INSERT INTO notification_collection (name, collection) VALUES ('something changed', 'event_collection')"
)])
@surql_collection("event_collection", event_config)
class EventCollection(BaseModel):
field1: str
field2: str
field3: str
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE ANALYZER analyzer_name TOKENIZERS blank;
DEFINE TABLE event_collection SCHEMAFULL;
DEFINE FIELD field1 ON TABLE event_collection TYPE string;
DEFINE FIELD field2 ON TABLE event_collection TYPE string;
DEFINE FIELD field3 ON TABLE event_collection TYPE string;
DEFINE EVENT event_name ON TABLE event_collection WHEN $event = "INSERT" OR $event = "UPDATE" THEN (INSERT INTO notification_collection (name, collection) VALUES ('something changed', 'event_collection'));
```
## Types definitions
### basic types
to define a basic type you can use the following python types :
```python
from enum import Enum
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
from datetime import datetime
from typing import Any
class BasicEnum(Enum):
ONE = "ONE"
TWO = "TWO"
THREE = "THREE"
FOUR = 4
FIVE = 5
SIX = 6
@surql_collection("basic_types")
class BasicTypes(BaseModel):
id: str
string: str
number: int
number_two: float
date: datetime
flag: bool
any_v: Any
enum_v: BasicEnum
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE basic_types SCHEMAFULL;
DEFINE FIELD string ON TABLE basic_types TYPE string;
DEFINE FIELD number ON TABLE basic_types TYPE number;
DEFINE FIELD number_two ON TABLE basic_types TYPE number;
DEFINE FIELD date ON TABLE basic_types TYPE datetime;
DEFINE FIELD flag ON TABLE basic_types TYPE bool;
DEFINE FIELD any_v ON TABLE basic_types TYPE any;
DEFINE FIELD enum_v ON TABLE basic_types TYPE string|number ASSERT ($value in ["ONE","TWO","THREE",4,5,6]);
```
### union types
to define union types you can use the `Union[T, Y]` or the `|` notation :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
from typing import Union
from datetime import datetime
@surql_collection("union_types")
class UnionTypes(BaseModel):
id: str
str_number: Union[str, int]
date_timestamp: int | datetime
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE union_types SCHEMAFULL;
DEFINE FIELD str_number ON TABLE union_types TYPE string|number;
DEFINE FIELD date_timestamp ON TABLE union_types TYPE number|datetime;
```
### optional and null types
to define an optional type you can use the `Optional` notation :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
from typing import Optional
@surql_collection("optional_types")
class OptionalTypes(BaseModel):
id: str
opt_str: Optional[str]
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE optional_types SCHEMAFULL;
DEFINE FIELD opt_str ON TABLE optional_types TYPE option<string>;
```
to define a null value you can use the `SurQLNullable` type :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLNullable
from pydantic import BaseModel
@surql_collection("nullable_types")
class BasicTypes(BaseModel):
id: str
nullable_str: str | SurQLNullable
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE nullable_types SCHEMAFULL;
DEFINE FIELD nullable_str ON TABLE nullable_types TYPE string|null;
```
> [!CAUTION]
> using `None` will result in an `optional` field (`Optional[T] <=> T | None`)
### array types
to define array types you can use the `list[T]` notation. \
You can nest arrays as much as you want :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
@surql_collection("array_types")
class ArrayTypes(BaseModel):
id: str
str_list: list[str | int]
list_str_list: list[list[str]]
list_list_str_list: list[list[list[str]]]
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE array_types SCHEMAFULL;
DEFINE FIELD str_list ON TABLE array_types TYPE array;
DEFINE FIELD str_list.* ON TABLE array_types TYPE string|number;
DEFINE FIELD list_str_list ON TABLE array_types TYPE array;
DEFINE FIELD list_str_list.* ON TABLE array_types TYPE array;
DEFINE FIELD list_str_list.*.* ON TABLE array_types TYPE string;
DEFINE FIELD list_list_str_list ON TABLE array_types TYPE array;
DEFINE FIELD list_list_str_list.* ON TABLE array_types TYPE array;
DEFINE FIELD list_list_str_list.*.* ON TABLE array_types TYPE array;
DEFINE FIELD list_list_str_list.*.*.* ON TABLE array_types TYPE string;
```
### set types
to define set types you can use the `set[T]` notation. \
You can nest sets as much as you want :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
@surql_collection("set_types")
class SetTypes(BaseModel):
id: str
str_set: set[str | int]
set_str_set: set[set[str]]
set_set_str_set: set[set[set[str]]]
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE set_types SCHEMAFULL;
DEFINE FIELD str_set ON TABLE set_types TYPE set;
DEFINE FIELD str_set.* ON TABLE set_types TYPE string|number;
DEFINE FIELD set_str_set ON TABLE set_types TYPE set;
DEFINE FIELD set_str_set.* ON TABLE set_types TYPE set;
DEFINE FIELD set_str_set.*.* ON TABLE set_types TYPE string;
DEFINE FIELD set_set_str_set ON TABLE set_types TYPE set;
DEFINE FIELD set_set_str_set.* ON TABLE set_types TYPE set;
DEFINE FIELD set_set_str_set.*.* ON TABLE set_types TYPE set;
DEFINE FIELD set_set_str_set.*.*.* ON TABLE set_types TYPE string;
```
### object types
to define an object you can use a Pydantic model. \
to mark the object as `flexible`, you can use [pydantic built in feature](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra) \
You can also nest objects as much as you want :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel, ConfigDict
class SubSubObject(BaseModel):
model_config = ConfigDict(extra='allow')
some_mandatory_field: str
#...
class SubObject(BaseModel):
sub_sub_object: SubSubObject
#...
@surql_collection("object_types")
class ObjectTypes(BaseModel):
id: str
sub_object: SubObject
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE object_types SCHEMAFULL;
DEFINE FIELD sub_object ON TABLE object_types TYPE object;
DEFINE FIELD sub_object.sub_sub_object ON TABLE object_types FLEXIBLE TYPE object;
DEFINE FIELD sub_object.sub_sub_object.some_mandatory_field ON TABLE object_types TYPE string;
```
> [!WARNING]
> surql doesn't support recursive objects, if you want to use recursive structures use a [`record` definition](#record-types)
### record types
Internally the `@surql_collection` decorator will mark the model as a surql collection. \
Defining a record is simple as :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic import BaseModel
@surql_collection("record_target")
class RecordTarget(BaseModel):
id: str
some_field: str
#...
@surql_collection("record_types")
class RecordTypes(BaseModel):
id: str
record_target: RecordTarget
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE record_target SCHEMAFULL;
DEFINE FIELD some_field ON TABLE record_target TYPE string;
DEFINE TABLE record_types SCHEMAFULL;
DEFINE FIELD record_target ON TABLE record_types TYPE record<record_target>;
```
It's also possible to use a generic record :
```python
from pydantic_surql import surql_collection, Metadata
from pydantic_surql.types import SurQLAnyRecord
from pydantic import BaseModel
@surql_collection("generic_record_types")
class GenericRecordTypes(BaseModel):
id: str
record_target: SurQLAnyRecord
#...
print(Metadata.collect())
```
this will generate the following SDL:
```surql
DEFINE TABLE generic_record_types SCHEMAFULL;
DEFINE FIELD id ON TABLE generic_record_types TYPE string;
DEFINE FIELD record_target ON TABLE generic_record_types TYPE record();
```
> [!NOTE]
> `SurQLAnyRecord <=> Type[dict]` so your pydantic model wont be able to map to pydantic classes automatically.
Raw data
{
"_id": null,
"home_page": "https://github.com/nathanschwarz/pydantic-SURQL",
"name": "pydantic-surql",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.11,<4.0",
"maintainer_email": "",
"keywords": "pydantic,surql,sdl,schema,schema definition language",
"author": "Nathan Schwarz",
"author_email": "nathan.schwarz.pro@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/c0/74/b65d3a52569f18748a1e581fcc5cb75f0d2ff3f382060dd07dde70ac3146/pydantic_surql-1.0.2.tar.gz",
"platform": null,
"description": "# Pydantic-SURQL\n\nPydantic Surql is a utility set to automatically convert Pydantic models to SURQL SDL definitions.\n\n\u2705 it supports the following features\n\n- [collection definitions (a.k.a tables)](#collections-definitions)\n - [schemafull / schemaless](#schemafull--schemaless-definitions)\n - [drop](#drop-definitions)\n - [changefeed](#changefeed-definitions)\n - [view](#view-definitions)\n - [indexes definitions](#indexes-analyzers-and-tokenizers-definitions)\n - regular indexes\n - unique indexes\n - search indexes\n - [fields and tables permissions](#table-an-field-permissions)\n- [analyzers definition](#indexes-analyzers-and-tokenizers-definitions)\n- [tokenizers definition](#indexes-analyzers-and-tokenizers-definitions)\n- [events definition](#events-definitions)\n\nand the [following types](#types-definitions) out of the box :\n\n- [basic types (string | int | float | boolean | datetime | any, enum)](#basic-types)\n- [union](#union-types)\n- [optional and null](#optional-and-null-types)\n- [array](#array-types)\n- [set](#set-types)\n- [object](#object-types)\n- [record](#record-types)\n\n\u274c what it doesn't support yet :\n\n- Future types\n- Geometry types\n- SURQL queries definitions (select, update, delete, where, ...)\n- SURQL functions definitions\n- SURQL scopes definitions\n- SURQL SDL field validation (validation goes through pydantic, too complex at this stage)\n- namespaces definitions\n- databases definitions\n- users definitions\n- tokens definitions\n- params definitions\n\nPRs are welcome \ud83d\ude09\n\n## installation\n\nTo install pydantic-surql run :\n\n```bash\npip install pydantic-surql\n```\n\nor with poetry :\n\n```bash\npoetry add pydantic-surql\n```\n\n## basic usage\n\nto convert a pydantic model to a surql SDL definition you can use a simple decorator :\n\n```python\nfrom datetime import datetime\nfrom typing import Optional\nfrom pydantic import BaseModel\nfrom pydantic_surql import surql_collection\n\n@surql_collection(\"writers\")\nclass Writer(BaseModel):\n id: str\n firstname: str\n lastname: str\n birthdate: datetime\n\n\n@surql_collection(\"books\")\nclass Book(BaseModel):\n id: str\n title: str\n pages: Optional[int]\n description: str\n weight: float\n writer: Writer\n```\n\nAll the models decorated with `@surql_collection` will be collected by the `Metadata` object, which will be used to generate the SDL.\n\nTo generate the SDL :\n\n```python\nfrom pydantic_surql import Metadata\nmodels_sdl: str = Metadata.collect()\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE writers SCHEMAFULL;\nDEFINE FIELD firstname ON TABLE writers TYPE string;\nDEFINE FIELD lastname ON TABLE writers TYPE string;\nDEFINE FIELD birthdate ON TABLE writers TYPE datetime;\n\nDEFINE TABLE books SCHEMAFULL;\nDEFINE FIELD title ON TABLE books TYPE string;\nDEFINE FIELD pages ON TABLE books TYPE option<number>;\nDEFINE FIELD description ON TABLE books TYPE string;\nDEFINE FIELD weight ON TABLE books TYPE number;\nDEFINE FIELD writer ON TABLE books TYPE record<writers>;\n```\n\n## Collections definitions\n\n### schemafull / schemaless definitions\n\nBy default collections are schemafull because pydantic doesn't allow models to have extra values.\\\nto make a collection schemaless you can use [pydantic built in feature](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra).\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\n\n@surql_collection(\"schemaless_collection\")\nclass SchemaLessCollection(BaseModel):\n model_config = ConfigDict(extra='allow')\n #...\n\nprint(Metadata.collect())\n```\n\nor\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLTableConfig\nfrom pydantic import BaseModel, ConfigDict\n\n@surql_collection(\"schemaless_collection\", SurQLTableConfig(strict=False))\nclass SchemaLessCollection(BaseModel):\n pass\n\nprint(Metadata.collect())\n```\n\n> [!NOTE]\n> if `model_config.extra == \"allow\"`, the `table.config.strict` will be set to `true`\n>\n> if `strict == False`, the `model_config.extra` will be set to `allow`\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE schemaless_collection SCHEMALESS;\n```\n\n### drop definitions\n\nyou can define the collection as dropped through the config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLTableConfig\nfrom pydantic import BaseModel\n\n@surql_collection(\"drop_collection\", SurQLTableConfig(drop=True))\nclass DropCollection(BaseModel):\n pass\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE drop_collection DROP SCHEMAFULL;\n```\n\n### changefeed definitions\n\nyou can define the changefeed on collection through the config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLTableConfig\nfrom pydantic import BaseModel\n\n@surql_collection(\"changefeed_collection\", SurQLTableConfig(changeFeed=\"1d\"))\nclass ChangefeedCollection(BaseModel):\n pass\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE changefeed_collection SCHEMAFULL CHANGEFEED 1d;\n```\n\n### view definitions\n\nyou can define a collection as a view through the config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLTableConfig, SurQLView\nfrom pydantic import BaseModel\n\nconfig = SurQLTableConfig(asView=SurQLView(select=[\"name\", \"age\"], from_t=[\"users\"], where=[\"age > 18\"], group_by=[\"age\"]))\n@surql_collection(\"view_collection\", config)\nclass ViewCollection(BaseModel):\n name: list[str]\n age: str\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE view_collection AS SELECT name,age FROM users WHERE age > 18 GROUP BY age;\n```\n\n### indexes, analyzers and tokenizers definitions\n\nYou can define indexes on collections through the config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\nfrom pydantic_surql.types import (\n SurQLTableConfig,\n SurQLIndex,\n SurQLUniqueIndex,\n SurQLSearchIndex,\n SurQLAnalyzer,\n SurQLTokenizers\n)\n\nindex = SurQLIndex(name=\"index_name\", fields=[\"field1\", \"field2\"])\nunique_index = SurQLUniqueIndex(name=\"unique_index_name\", fields=[\"field1\", \"field2\"])\nanalyzer = SurQLAnalyzer(name=\"analyzer_name\", tokenizers=[SurQLTokenizers.BLANK])\nsearch_index = SurQLSearchIndex(\n name=\"search_index_name\",\n fields=[\"field3\"],\n analyzer=analyzer,\n highlights=True\n)\n\n@surql_collection(\"indexed_collection\", SurQLTableConfig(indexes=[index, unique_index, search_index]))\nclass IndexedCollection(BaseModel):\n field1: str\n field2: str\n field3: str\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE ANALYZER analyzer_name TOKENIZERS blank;\n\nDEFINE TABLE indexed_collection SCHEMAFULL;\nDEFINE FIELD field1 ON TABLE indexed_collection TYPE string;\nDEFINE FIELD field2 ON TABLE indexed_collection TYPE string;\nDEFINE FIELD field3 ON TABLE indexed_collection TYPE string;\nDEFINE INDEX index_name ON TABLE indexed_collection FIELDS field1,field2;\nDEFINE INDEX unique_index_name ON TABLE indexed_collection FIELDS field1,field2 UNIQUE;\nDEFINE INDEX search_index_name ON TABLE indexed_collection FIELDS field3 SEARCH ANALYZER analyzer_name HIGHLIGHTS;\n```\n\n> [!NOTE]\n> only the used tokenizers (used in a configuration) will be collected\n\n### table an field permissions\n\nYou can define permissions on collections through the config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\nfrom pydantic_surql.types import (\n SurQLTableConfig,\n SurQLPermissions,\n)\n\nMetadata.clear()\npermission_config = SurQLTableConfig(\n permissions=SurQLPermissions(\n select=[\"WHERE published = true\", \"OR user = $auth.id\"],\n create=[\"WHERE user = $auth.id\"],\n update=[\"WHERE user = $auth.id\"],\n delete=[\"WHERE user = $auth.id\", \"OR $auth.admin = true\"]\n )\n)\n@surql_collection(\"permission_collection\", permission_config)\nclass PermissionCollection(BaseModel):\n field1: str\n field2: str\n field3: str\n published: bool\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL :\n\n```surql\nDEFINE TABLE permission_collection SCHEMAFULL PERMISSIONS\n FOR SELECT\n WHERE published = true\n OR user = $auth.id\n FOR CREATE\n WHERE user = $auth.id\n FOR UPDATE\n WHERE user = $auth.id\n FOR DELETE\n WHERE user = $auth.id\n OR $auth.admin = true;\nDEFINE FIELD field1 ON TABLE permission_collection TYPE string;\nDEFINE FIELD field2 ON TABLE permission_collection TYPE string;\nDEFINE FIELD field3 ON TABLE permission_collection TYPE string;\nDEFINE FIELD published ON TABLE permission_collection TYPE bool;\n```\n\nYou can define field permissions throught the `SurQLFieldConfig` function.\\\nit's a wrapper around the pydantic `Field` function (so you can use all the properties from the `Field` definition) :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\nfrom pydantic_surql.types import (\n SurQLTableConfig,\n SurQLPermissions,\n SurQLFieldConfig\n)\n\nfields_permission = SurQLPermissions(\n select=[\"WHERE user = $auth.id\"],\n create=[\"WHERE user = $auth.id\"],\n update=[\"WHERE user = $auth.id\"],\n delete=[\"WHERE user = $auth.id\", \"OR $auth.admin = true\"]\n)\n@surql_collection(\"permission_collection\", permission_config)\nclass FieldPermissionsCollection(BaseModel):\n field1: str = SurQLFieldConfig(permissions=fields_permission, min_length=2)\n field2: str\n field3: str\n published: bool\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE FIELD field1 ON TABLE field_permission_collection TYPE string PERMISSIONS\n FOR SELECT\n WHERE user = $auth.id\n FOR CREATE\n WHERE user = $auth.id\n FOR UPDATE\n WHERE user = $auth.id\n FOR DELETE\n WHERE user = $auth.id\n OR $auth.admin = true;\nDEFINE FIELD field2 ON TABLE field_permission_collection TYPE string;\nDEFINE FIELD field3 ON TABLE field_permission_collection TYPE string;\nDEFINE FIELD published ON TABLE field_permission_collection TYPE bool;\n```\n\n## events definitions\n\nYou can define events through the collection config :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\nfrom pydantic_surql.types import SurQLTableConfig, SurQLEvent\n\nevent_config = SurQLTableConfig(events=[\n SurQLEvent(\n name=\"event_name\",\n whenSDL=[\"$event = \\\"INSERT\\\"\", \"$event = \\\"UPDATE\\\"\"],\n querySDL=\"INSERT INTO notification_collection (name, collection) VALUES ('something changed', 'event_collection')\"\n )])\n@surql_collection(\"event_collection\", event_config)\nclass EventCollection(BaseModel):\n field1: str\n field2: str\n field3: str\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE ANALYZER analyzer_name TOKENIZERS blank;\n\nDEFINE TABLE event_collection SCHEMAFULL;\nDEFINE FIELD field1 ON TABLE event_collection TYPE string;\nDEFINE FIELD field2 ON TABLE event_collection TYPE string;\nDEFINE FIELD field3 ON TABLE event_collection TYPE string;\nDEFINE EVENT event_name ON TABLE event_collection WHEN $event = \"INSERT\" OR $event = \"UPDATE\" THEN (INSERT INTO notification_collection (name, collection) VALUES ('something changed', 'event_collection'));\n```\n\n## Types definitions\n\n### basic types\n\nto define a basic type you can use the following python types :\n\n```python\nfrom enum import Enum\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\nfrom datetime import datetime\nfrom typing import Any\n\nclass BasicEnum(Enum):\n ONE = \"ONE\"\n TWO = \"TWO\"\n THREE = \"THREE\"\n FOUR = 4\n FIVE = 5\n SIX = 6\n\n@surql_collection(\"basic_types\")\nclass BasicTypes(BaseModel):\n id: str\n string: str\n number: int\n number_two: float\n date: datetime\n flag: bool\n any_v: Any\n enum_v: BasicEnum\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE basic_types SCHEMAFULL;\nDEFINE FIELD string ON TABLE basic_types TYPE string;\nDEFINE FIELD number ON TABLE basic_types TYPE number;\nDEFINE FIELD number_two ON TABLE basic_types TYPE number;\nDEFINE FIELD date ON TABLE basic_types TYPE datetime;\nDEFINE FIELD flag ON TABLE basic_types TYPE bool;\nDEFINE FIELD any_v ON TABLE basic_types TYPE any;\nDEFINE FIELD enum_v ON TABLE basic_types TYPE string|number ASSERT ($value in [\"ONE\",\"TWO\",\"THREE\",4,5,6]);\n```\n\n### union types\n\nto define union types you can use the `Union[T, Y]` or the `|` notation :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\nfrom typing import Union\nfrom datetime import datetime\n\n@surql_collection(\"union_types\")\nclass UnionTypes(BaseModel):\n id: str\n str_number: Union[str, int]\n date_timestamp: int | datetime\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE union_types SCHEMAFULL;\nDEFINE FIELD str_number ON TABLE union_types TYPE string|number;\nDEFINE FIELD date_timestamp ON TABLE union_types TYPE number|datetime;\n```\n\n### optional and null types\n\nto define an optional type you can use the `Optional` notation :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\nfrom typing import Optional\n\n@surql_collection(\"optional_types\")\nclass OptionalTypes(BaseModel):\n id: str\n opt_str: Optional[str]\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE optional_types SCHEMAFULL;\nDEFINE FIELD opt_str ON TABLE optional_types TYPE option<string>;\n```\n\nto define a null value you can use the `SurQLNullable` type :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLNullable\nfrom pydantic import BaseModel\n\n@surql_collection(\"nullable_types\")\nclass BasicTypes(BaseModel):\n id: str\n nullable_str: str | SurQLNullable\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE nullable_types SCHEMAFULL;\nDEFINE FIELD nullable_str ON TABLE nullable_types TYPE string|null;\n```\n\n> [!CAUTION]\n> using `None` will result in an `optional` field (`Optional[T] <=> T | None`)\n\n### array types\n\nto define array types you can use the `list[T]` notation. \\\nYou can nest arrays as much as you want :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\n\n@surql_collection(\"array_types\")\nclass ArrayTypes(BaseModel):\n id: str\n str_list: list[str | int]\n list_str_list: list[list[str]]\n list_list_str_list: list[list[list[str]]]\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE array_types SCHEMAFULL;\nDEFINE FIELD str_list ON TABLE array_types TYPE array;\nDEFINE FIELD str_list.* ON TABLE array_types TYPE string|number;\nDEFINE FIELD list_str_list ON TABLE array_types TYPE array;\nDEFINE FIELD list_str_list.* ON TABLE array_types TYPE array;\nDEFINE FIELD list_str_list.*.* ON TABLE array_types TYPE string;\nDEFINE FIELD list_list_str_list ON TABLE array_types TYPE array;\nDEFINE FIELD list_list_str_list.* ON TABLE array_types TYPE array;\nDEFINE FIELD list_list_str_list.*.* ON TABLE array_types TYPE array;\nDEFINE FIELD list_list_str_list.*.*.* ON TABLE array_types TYPE string;\n```\n\n### set types\n\nto define set types you can use the `set[T]` notation. \\\nYou can nest sets as much as you want :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\n\n@surql_collection(\"set_types\")\nclass SetTypes(BaseModel):\n id: str\n str_set: set[str | int]\n set_str_set: set[set[str]]\n set_set_str_set: set[set[set[str]]]\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE set_types SCHEMAFULL;\nDEFINE FIELD str_set ON TABLE set_types TYPE set;\nDEFINE FIELD str_set.* ON TABLE set_types TYPE string|number;\nDEFINE FIELD set_str_set ON TABLE set_types TYPE set;\nDEFINE FIELD set_str_set.* ON TABLE set_types TYPE set;\nDEFINE FIELD set_str_set.*.* ON TABLE set_types TYPE string;\nDEFINE FIELD set_set_str_set ON TABLE set_types TYPE set;\nDEFINE FIELD set_set_str_set.* ON TABLE set_types TYPE set;\nDEFINE FIELD set_set_str_set.*.* ON TABLE set_types TYPE set;\nDEFINE FIELD set_set_str_set.*.*.* ON TABLE set_types TYPE string;\n```\n\n### object types\n\nto define an object you can use a Pydantic model. \\\nto mark the object as `flexible`, you can use [pydantic built in feature](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra) \\\nYou can also nest objects as much as you want :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel, ConfigDict\n\nclass SubSubObject(BaseModel):\n model_config = ConfigDict(extra='allow')\n some_mandatory_field: str\n #...\n\nclass SubObject(BaseModel):\n sub_sub_object: SubSubObject\n #...\n\n@surql_collection(\"object_types\")\nclass ObjectTypes(BaseModel):\n id: str\n sub_object: SubObject\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE object_types SCHEMAFULL;\nDEFINE FIELD sub_object ON TABLE object_types TYPE object;\nDEFINE FIELD sub_object.sub_sub_object ON TABLE object_types FLEXIBLE TYPE object;\nDEFINE FIELD sub_object.sub_sub_object.some_mandatory_field ON TABLE object_types TYPE string;\n```\n\n> [!WARNING]\n> surql doesn't support recursive objects, if you want to use recursive structures use a [`record` definition](#record-types)\n\n### record types\n\nInternally the `@surql_collection` decorator will mark the model as a surql collection. \\\nDefining a record is simple as :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic import BaseModel\n\n@surql_collection(\"record_target\")\nclass RecordTarget(BaseModel):\n id: str\n some_field: str\n #...\n\n@surql_collection(\"record_types\")\nclass RecordTypes(BaseModel):\n id: str\n record_target: RecordTarget\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE record_target SCHEMAFULL;\nDEFINE FIELD some_field ON TABLE record_target TYPE string;\n\nDEFINE TABLE record_types SCHEMAFULL;\nDEFINE FIELD record_target ON TABLE record_types TYPE record<record_target>;\n```\n\nIt's also possible to use a generic record :\n\n```python\nfrom pydantic_surql import surql_collection, Metadata\nfrom pydantic_surql.types import SurQLAnyRecord\nfrom pydantic import BaseModel\n\n@surql_collection(\"generic_record_types\")\nclass GenericRecordTypes(BaseModel):\n id: str\n record_target: SurQLAnyRecord\n #...\n\nprint(Metadata.collect())\n```\n\nthis will generate the following SDL:\n\n```surql\nDEFINE TABLE generic_record_types SCHEMAFULL;\nDEFINE FIELD id ON TABLE generic_record_types TYPE string;\nDEFINE FIELD record_target ON TABLE generic_record_types TYPE record();\n```\n\n> [!NOTE]\n> `SurQLAnyRecord <=> Type[dict]` so your pydantic model wont be able to map to pydantic classes automatically.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Pydantic Surql is a utility set to automatically convert Pydantic models to SURQL SDL definitions.",
"version": "1.0.2",
"project_urls": {
"Homepage": "https://github.com/nathanschwarz/pydantic-SURQL",
"Repository": "https://github.com/nathanschwarz/pydantic-SURQL"
},
"split_keywords": [
"pydantic",
"surql",
"sdl",
"schema",
"schema definition language"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c074b65d3a52569f18748a1e581fcc5cb75f0d2ff3f382060dd07dde70ac3146",
"md5": "1048408fc5a9363fcedec7e7e2b91e4a",
"sha256": "b7a6e439fe6e49c1b069ec3121328df1eb024e51201ea4a56fcffc6e2d176abd"
},
"downloads": -1,
"filename": "pydantic_surql-1.0.2.tar.gz",
"has_sig": false,
"md5_digest": "1048408fc5a9363fcedec7e7e2b91e4a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11,<4.0",
"size": 14858,
"upload_time": "2024-01-16T19:03:50",
"upload_time_iso_8601": "2024-01-16T19:03:50.830893Z",
"url": "https://files.pythonhosted.org/packages/c0/74/b65d3a52569f18748a1e581fcc5cb75f0d2ff3f382060dd07dde70ac3146/pydantic_surql-1.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-01-16 19:03:50",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "nathanschwarz",
"github_project": "pydantic-SURQL",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pydantic-surql"
}