pydantic-surql


Namepydantic-surql JSON
Version 1.0.2 PyPI version JSON
download
home_pagehttps://github.com/nathanschwarz/pydantic-SURQL
SummaryPydantic Surql is a utility set to automatically convert Pydantic models to SURQL SDL definitions.
upload_time2024-01-16 19:03:50
maintainer
docs_urlNone
authorNathan Schwarz
requires_python>=3.11,<4.0
licenseMIT
keywords pydantic surql sdl schema schema definition language
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 2.77422s