buildantic


Namebuildantic JSON
Version 0.0.1 PyPI version JSON
download
home_pagehttps://github.com/synacktraa/buildantic
SummaryJSON schema generation and data validation, with native support for LLM function-calling formats
upload_time2024-10-21 20:46:19
maintainerNone
docs_urlNone
authorHarsh Verma
requires_python<3.13,>=3.8
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Buildantic: A library for JSON schema generation and data validation, with native support for LLM function-calling formats.

---

<p align="center">
    <a href="https://img.shields.io/github/v/release/synacktraa/buildantic">
        <img src="https://img.shields.io/github/v/release/synacktraa/buildantic" alt="buildantic version">
    </a>
    <a href="https://github.com/synacktraa/buildantic/actions/workflows/master.yml">
        <img src="https://github.com/synacktraa/buildantic/actions/workflows/master.yml/badge.svg" alt="buildantic CI status">
    </a>
    <a href="https://codecov.io/gh/synacktraa/buildantic">
        <img src="https://codecov.io/gh/synacktraa/buildantic/branch/master/graph/badge.svg" alt="buildantic codecov">
    </a>
    <a href="https://img.shields.io/github/license/synacktraa/buildantic">
        <img src="https://img.shields.io/github/license/synacktraa/buildantic" alt="buildantic license">
    </a>
</p>

Buildantic streamlines the process of generating schemas from types and OpenAPI specification operations, as well as validating data against these schemas.

Beyond standard JSON Schema generation, It facilitates the creation of schema formats tailored for Large Language Model (LLM) function calling. The supported formats include `OpenAI` (compatible with most function-calling LLMs), `Anthropic`, and `Gemini`.

- [Type support](#working-with-types)
- [OpenAPI specification support](#working-with-openapi-specification)

`Buildantic` is highly inspired from the talk "Pydantic is all you need" by [Jason Liu](https://github.com/jxnl), author of Instructor library.

## Getting Started

```
pip install -U buildantic
```

### Working with types

`TypeDescriptor` utilizes pydantic's `TypeAdapter` internally. The schema generated by the adapter is updated with docstring recursively.
Any type supported by pydantic will work with this descriptor.

#### Descripting a simple type

```python
import typing as t

from buildantic import TypeDescriptor

descriptor = TypeDescriptor(t.List[str])
```

- Get standard JsON schema

  ```python
  print(descriptor.schema)
  """{'items': {'type': 'string'}, 'type': 'array'}"""
  ```

- Get function calling schema

  > As function-calling only accepts object input, the simple type is transformed into object type with `input` being the only property key.

  ```python
  print(descriptor.openai_schema)
  """
  {
      'name': 'List',
      'parameters': {
          'type': 'object',
          'properties': {
              'input': {
                  'items': {'type': 'string'}, 'type': 'array'
              }
          }
      }
  }
  """
  ```

- Validating a python object

  ```python
  print(descriptor.validate_python(["name", "age"]))
  # OR output generated from function-calling schema
  print(descriptor.validate_python({"input": ["name", "age"]}))
  """['name', 'age']"""
  ```

- Validating a JsON object

  ```python
  print(descriptor.validate_json('["name", "age"]'))
  # OR output generated from function-calling schema
  print(descriptor.validate_json('{"input": ["name", "age"]}'))
  """['name', 'age']"""
  ```

#### Descripting a simple type with custom name and description

> Annonate the simple type (non-object type) with pydantic's `FieldInfo` to add name and description

```python
import typing as t

from buildantic import TypeDescriptor
from pydantic.fields import Field

descriptor = TypeDescriptor[t.List[str]](
    t.Annotated[t.List[str], Field(alias="strings", description="List of string")]
)
print(descriptor.schema)
"""{'items': {'type': 'string'}, 'type': 'array'}"""

print(descriptor.openai_schema)
"""
{
    "name": "strings",
    "description": "List of string",
    "parameters": {
        "type": "object",
        "properties": {
            "input": {"type": "array", "items": {"type": "string"}}
        },
        "required": ["input"]
    }
}
"""

print(descriptor.validate_python(["name", "age"]))
"""['name', 'age']"""

print(descriptor.validate_json('{"input": ["name", "age"]}'))
"""['name', 'age']"""
```

#### Descripting an object type

> An object type refers to type with properties. `TypedDict`, pydantic model, dataclasses and functions are some examples of it.

> `TypeDescriptor` aliased as `descript` can be used as a decorator.

```python
from buildantic import descript
from typing import Any, Dict, Literal, Tuple

@descript # same as TypeDescriptor(create_user)
async def create_user(
    name: str, age: int, role: Literal["developer", "tester"] = "tester"
) -> Tuple[bool, Dict[str, Any]]:
    """
    Create a new user

    :param name: Name of the user
    :param age: Age of the user
    :param role: Role to assign.
    """
    return (True, {"metadata": [name, age, role]})

print(create_user.gemini_schema)
"""
{
    "name": "create_user",
    "description": "Create a new user",
    "parameters": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string", "description": "Name of the user"
            },
            "age": {
                "type": "integer", "description": "Age of the user"
            },
            "role": {
                "type": "string",
                "description": "Role to assign.",
                "enum": ["developer", "tester"],
                "format": "enum"
            }
        },
        "required": ["name", "age"]
    }
}
"""

import asyncio

print(asyncio.run(create_user.validate_python({
    "name": "synacktra", "age": 21, "role": "developer"
})))
"""(True, {'metadata': ['synacktra', 21, 'developer']})"""
```

#### Creating a registry of type descriptors

```python
from typing import Tuple, Literal

from pydantic import BaseModel
from buildantic import Registry

registry = Registry()

@registry.register
class UserInfo(BaseModel):
    """
    User Information

    :param name: Name of the user
    :param age: Age of the user
    :param role: Role to assign.
    """
    name: str
    age: int
    role: Literal["developer", "tester"] = "tester"


@registry.register
def get_coordinates(location: str) -> Tuple[float, float]:
    """Get coordinates of a location."""
    return (48.858370, 2.2944813)
```

- Getting schema list in different formats

  ```python
  print(registry.schema)
  print(registry.openai_schema)
  print(registry.anthropic_schema)
  print(registry.gemini_schema)
  ```

- Validating a python object

  ```python
  print(registry.validate_python(id="UserInfo", obj={"name": "synacktra", "age": 21}))
  """name='synacktra' age=21 role='tester'"""
  print(registry.validate_python(id="get_coordinates", obj={"location": "eiffeltower"}))
  """(48.85837, 2.2944813)"""
  ```

- Validating a JsON object

  ```python
  print(registry.validate_json(id="UserInfo", data='{"name": "synacktra", "age": 21}'))
  """name='synacktra' age=21 role='tester'"""
  print(registry.validate_json(id="get_coordinates", data='{"location": "eiffeltower"}'))
  """(48.85837, 2.2944813)"""
  ```

- Accessing descriptor from registry instance

  ```python
  get_coords_descriptor = registry["get_coordinates"]
  ```

### Working with OpenAPI Specification

OpenAPI operations are loaded as operation descriptors in the `OpenAPIRegistry`.

Validation methods returns a `RequestModel`, after which you can use your favorite
http client library to finally make request to the API.

- Loading the specification as a registyr

  ```python
  from buildantic.registry import OpenAPIRegistry
  openapi_registry = OpenAPIRegistry.from_file("/path/to/petstore-v3.json_or_yml")
  # or
  openapi_registry = OpenAPIRegistry.from_url(
      "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/refs/heads/main/examples/v3.0/petstore.json"
  )
  ```

- Get list of operations

  ```python
  print(openapi_registry.ids)
  """['listPets', 'createPets', 'showPetById']"""
  ```

- Accessing specific operation descriptor from registry

  ```python
  print(openapi_registry["listPets"].schema)
  """
  {
      'type': 'object',
      'description': 'List all pets',
      'properties': {
          'limit': {
              'type': 'integer',
              'maximum': 100,
              'format': 'int32',
              'description': 'How many items to return at one time (max 100)'
          }
      }
  }
  """

  print(openapi_registry["createPets"].schema)
  {
      'type': 'object',
      'description': 'Create a pet',
      'properties': {
          'requestBody': {
              'type': 'object',
              'properties': {
                  'id': {'type': 'integer', 'format': 'int64'},
                  'name': {'type': 'string'},
                  'tag': {'type': 'string'}
              }
          }
      },
      'required': ['requestBody']
  }
  ```

- Getting schema list in different formats

  ```python
  print(registry.schema)
  print(registry.openai_schema)
  print(registry.anthropic_schema)
  print(registry.gemini_schema)
  ```

- Validating a python object

  ```python
  print(openapi_registry.validate_python(id="listPets", obj={"limit": 99}))
  """
  path='/pets' method='get' queries={'limit': 99} encoded_query='limit=99' headers=None cookies=None body=None
  """
  print(openapi_registry.validate_python(id="listPets", obj={"limit": 101}))
  # This will raise `jsonschema.exceptions.ValidationError` exception
  ```

- Validating a JsON object

  ```python
  print(openapi_registry.validate_json(
      id="createPets",
      data='{"requestBody": {"id": 12, "name": "rocky", "tag": "dog"}}'
  ))
  """
  path='/pets' method='post' queries=None encoded_query=None headers=None cookies=None body={'id': 12, 'name': 'rocky', 'tag': 'dog'}
  """
  ```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/synacktraa/buildantic",
    "name": "buildantic",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<3.13,>=3.8",
    "maintainer_email": null,
    "keywords": null,
    "author": "Harsh Verma",
    "author_email": "synacktra.work@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/bc/9e/0f3a270a46938cc8cec5bff1e4154f6e4dde8a120a65291c3a4ee5321a19/buildantic-0.0.1.tar.gz",
    "platform": null,
    "description": "# Buildantic: A library for JSON schema generation and data validation, with native support for LLM function-calling formats.\n\n---\n\n<p align=\"center\">\n    <a href=\"https://img.shields.io/github/v/release/synacktraa/buildantic\">\n        <img src=\"https://img.shields.io/github/v/release/synacktraa/buildantic\" alt=\"buildantic version\">\n    </a>\n    <a href=\"https://github.com/synacktraa/buildantic/actions/workflows/master.yml\">\n        <img src=\"https://github.com/synacktraa/buildantic/actions/workflows/master.yml/badge.svg\" alt=\"buildantic CI status\">\n    </a>\n    <a href=\"https://codecov.io/gh/synacktraa/buildantic\">\n        <img src=\"https://codecov.io/gh/synacktraa/buildantic/branch/master/graph/badge.svg\" alt=\"buildantic codecov\">\n    </a>\n    <a href=\"https://img.shields.io/github/license/synacktraa/buildantic\">\n        <img src=\"https://img.shields.io/github/license/synacktraa/buildantic\" alt=\"buildantic license\">\n    </a>\n</p>\n\nBuildantic streamlines the process of generating schemas from types and OpenAPI specification operations, as well as validating data against these schemas.\n\nBeyond standard JSON Schema generation, It facilitates the creation of schema formats tailored for Large Language Model (LLM) function calling. The supported formats include `OpenAI` (compatible with most function-calling LLMs), `Anthropic`, and `Gemini`.\n\n- [Type support](#working-with-types)\n- [OpenAPI specification support](#working-with-openapi-specification)\n\n`Buildantic` is highly inspired from the talk \"Pydantic is all you need\" by [Jason Liu](https://github.com/jxnl), author of Instructor library.\n\n## Getting Started\n\n```\npip install -U buildantic\n```\n\n### Working with types\n\n`TypeDescriptor` utilizes pydantic's `TypeAdapter` internally. The schema generated by the adapter is updated with docstring recursively.\nAny type supported by pydantic will work with this descriptor.\n\n#### Descripting a simple type\n\n```python\nimport typing as t\n\nfrom buildantic import TypeDescriptor\n\ndescriptor = TypeDescriptor(t.List[str])\n```\n\n- Get standard JsON schema\n\n  ```python\n  print(descriptor.schema)\n  \"\"\"{'items': {'type': 'string'}, 'type': 'array'}\"\"\"\n  ```\n\n- Get function calling schema\n\n  > As function-calling only accepts object input, the simple type is transformed into object type with `input` being the only property key.\n\n  ```python\n  print(descriptor.openai_schema)\n  \"\"\"\n  {\n      'name': 'List',\n      'parameters': {\n          'type': 'object',\n          'properties': {\n              'input': {\n                  'items': {'type': 'string'}, 'type': 'array'\n              }\n          }\n      }\n  }\n  \"\"\"\n  ```\n\n- Validating a python object\n\n  ```python\n  print(descriptor.validate_python([\"name\", \"age\"]))\n  # OR output generated from function-calling schema\n  print(descriptor.validate_python({\"input\": [\"name\", \"age\"]}))\n  \"\"\"['name', 'age']\"\"\"\n  ```\n\n- Validating a JsON object\n\n  ```python\n  print(descriptor.validate_json('[\"name\", \"age\"]'))\n  # OR output generated from function-calling schema\n  print(descriptor.validate_json('{\"input\": [\"name\", \"age\"]}'))\n  \"\"\"['name', 'age']\"\"\"\n  ```\n\n#### Descripting a simple type with custom name and description\n\n> Annonate the simple type (non-object type) with pydantic's `FieldInfo` to add name and description\n\n```python\nimport typing as t\n\nfrom buildantic import TypeDescriptor\nfrom pydantic.fields import Field\n\ndescriptor = TypeDescriptor[t.List[str]](\n    t.Annotated[t.List[str], Field(alias=\"strings\", description=\"List of string\")]\n)\nprint(descriptor.schema)\n\"\"\"{'items': {'type': 'string'}, 'type': 'array'}\"\"\"\n\nprint(descriptor.openai_schema)\n\"\"\"\n{\n    \"name\": \"strings\",\n    \"description\": \"List of string\",\n    \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"input\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}\n        },\n        \"required\": [\"input\"]\n    }\n}\n\"\"\"\n\nprint(descriptor.validate_python([\"name\", \"age\"]))\n\"\"\"['name', 'age']\"\"\"\n\nprint(descriptor.validate_json('{\"input\": [\"name\", \"age\"]}'))\n\"\"\"['name', 'age']\"\"\"\n```\n\n#### Descripting an object type\n\n> An object type refers to type with properties. `TypedDict`, pydantic model, dataclasses and functions are some examples of it.\n\n> `TypeDescriptor` aliased as `descript` can be used as a decorator.\n\n```python\nfrom buildantic import descript\nfrom typing import Any, Dict, Literal, Tuple\n\n@descript # same as TypeDescriptor(create_user)\nasync def create_user(\n    name: str, age: int, role: Literal[\"developer\", \"tester\"] = \"tester\"\n) -> Tuple[bool, Dict[str, Any]]:\n    \"\"\"\n    Create a new user\n\n    :param name: Name of the user\n    :param age: Age of the user\n    :param role: Role to assign.\n    \"\"\"\n    return (True, {\"metadata\": [name, age, role]})\n\nprint(create_user.gemini_schema)\n\"\"\"\n{\n    \"name\": \"create_user\",\n    \"description\": \"Create a new user\",\n    \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"name\": {\n                \"type\": \"string\", \"description\": \"Name of the user\"\n            },\n            \"age\": {\n                \"type\": \"integer\", \"description\": \"Age of the user\"\n            },\n            \"role\": {\n                \"type\": \"string\",\n                \"description\": \"Role to assign.\",\n                \"enum\": [\"developer\", \"tester\"],\n                \"format\": \"enum\"\n            }\n        },\n        \"required\": [\"name\", \"age\"]\n    }\n}\n\"\"\"\n\nimport asyncio\n\nprint(asyncio.run(create_user.validate_python({\n    \"name\": \"synacktra\", \"age\": 21, \"role\": \"developer\"\n})))\n\"\"\"(True, {'metadata': ['synacktra', 21, 'developer']})\"\"\"\n```\n\n#### Creating a registry of type descriptors\n\n```python\nfrom typing import Tuple, Literal\n\nfrom pydantic import BaseModel\nfrom buildantic import Registry\n\nregistry = Registry()\n\n@registry.register\nclass UserInfo(BaseModel):\n    \"\"\"\n    User Information\n\n    :param name: Name of the user\n    :param age: Age of the user\n    :param role: Role to assign.\n    \"\"\"\n    name: str\n    age: int\n    role: Literal[\"developer\", \"tester\"] = \"tester\"\n\n\n@registry.register\ndef get_coordinates(location: str) -> Tuple[float, float]:\n    \"\"\"Get coordinates of a location.\"\"\"\n    return (48.858370, 2.2944813)\n```\n\n- Getting schema list in different formats\n\n  ```python\n  print(registry.schema)\n  print(registry.openai_schema)\n  print(registry.anthropic_schema)\n  print(registry.gemini_schema)\n  ```\n\n- Validating a python object\n\n  ```python\n  print(registry.validate_python(id=\"UserInfo\", obj={\"name\": \"synacktra\", \"age\": 21}))\n  \"\"\"name='synacktra' age=21 role='tester'\"\"\"\n  print(registry.validate_python(id=\"get_coordinates\", obj={\"location\": \"eiffeltower\"}))\n  \"\"\"(48.85837, 2.2944813)\"\"\"\n  ```\n\n- Validating a JsON object\n\n  ```python\n  print(registry.validate_json(id=\"UserInfo\", data='{\"name\": \"synacktra\", \"age\": 21}'))\n  \"\"\"name='synacktra' age=21 role='tester'\"\"\"\n  print(registry.validate_json(id=\"get_coordinates\", data='{\"location\": \"eiffeltower\"}'))\n  \"\"\"(48.85837, 2.2944813)\"\"\"\n  ```\n\n- Accessing descriptor from registry instance\n\n  ```python\n  get_coords_descriptor = registry[\"get_coordinates\"]\n  ```\n\n### Working with OpenAPI Specification\n\nOpenAPI operations are loaded as operation descriptors in the `OpenAPIRegistry`.\n\nValidation methods returns a `RequestModel`, after which you can use your favorite\nhttp client library to finally make request to the API.\n\n- Loading the specification as a registyr\n\n  ```python\n  from buildantic.registry import OpenAPIRegistry\n  openapi_registry = OpenAPIRegistry.from_file(\"/path/to/petstore-v3.json_or_yml\")\n  # or\n  openapi_registry = OpenAPIRegistry.from_url(\n      \"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/refs/heads/main/examples/v3.0/petstore.json\"\n  )\n  ```\n\n- Get list of operations\n\n  ```python\n  print(openapi_registry.ids)\n  \"\"\"['listPets', 'createPets', 'showPetById']\"\"\"\n  ```\n\n- Accessing specific operation descriptor from registry\n\n  ```python\n  print(openapi_registry[\"listPets\"].schema)\n  \"\"\"\n  {\n      'type': 'object',\n      'description': 'List all pets',\n      'properties': {\n          'limit': {\n              'type': 'integer',\n              'maximum': 100,\n              'format': 'int32',\n              'description': 'How many items to return at one time (max 100)'\n          }\n      }\n  }\n  \"\"\"\n\n  print(openapi_registry[\"createPets\"].schema)\n  {\n      'type': 'object',\n      'description': 'Create a pet',\n      'properties': {\n          'requestBody': {\n              'type': 'object',\n              'properties': {\n                  'id': {'type': 'integer', 'format': 'int64'},\n                  'name': {'type': 'string'},\n                  'tag': {'type': 'string'}\n              }\n          }\n      },\n      'required': ['requestBody']\n  }\n  ```\n\n- Getting schema list in different formats\n\n  ```python\n  print(registry.schema)\n  print(registry.openai_schema)\n  print(registry.anthropic_schema)\n  print(registry.gemini_schema)\n  ```\n\n- Validating a python object\n\n  ```python\n  print(openapi_registry.validate_python(id=\"listPets\", obj={\"limit\": 99}))\n  \"\"\"\n  path='/pets' method='get' queries={'limit': 99} encoded_query='limit=99' headers=None cookies=None body=None\n  \"\"\"\n  print(openapi_registry.validate_python(id=\"listPets\", obj={\"limit\": 101}))\n  # This will raise `jsonschema.exceptions.ValidationError` exception\n  ```\n\n- Validating a JsON object\n\n  ```python\n  print(openapi_registry.validate_json(\n      id=\"createPets\",\n      data='{\"requestBody\": {\"id\": 12, \"name\": \"rocky\", \"tag\": \"dog\"}}'\n  ))\n  \"\"\"\n  path='/pets' method='post' queries=None encoded_query=None headers=None cookies=None body={'id': 12, 'name': 'rocky', 'tag': 'dog'}\n  \"\"\"\n  ```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "JSON schema generation and data validation, with native support for LLM function-calling formats",
    "version": "0.0.1",
    "project_urls": {
        "Documentation": "https://synacktraa.github.io/buildantic/",
        "Homepage": "https://github.com/synacktraa/buildantic",
        "Repository": "https://github.com/synacktraa/buildantic"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ca592a309043fd4775c8f1f2583b6d86c3fabe60508dc9f7233c5c8d35244aca",
                "md5": "ecbf04d3075b3d213df23f145a33e44a",
                "sha256": "43a0d3107559671c9785a58c5388ac6c5c3865c7c26c06ea3388851e1cda6d2b"
            },
            "downloads": -1,
            "filename": "buildantic-0.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ecbf04d3075b3d213df23f145a33e44a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<3.13,>=3.8",
            "size": 18832,
            "upload_time": "2024-10-21T20:46:18",
            "upload_time_iso_8601": "2024-10-21T20:46:18.208032Z",
            "url": "https://files.pythonhosted.org/packages/ca/59/2a309043fd4775c8f1f2583b6d86c3fabe60508dc9f7233c5c8d35244aca/buildantic-0.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bc9e0f3a270a46938cc8cec5bff1e4154f6e4dde8a120a65291c3a4ee5321a19",
                "md5": "4bf1f425bf2811ed41cd368576450238",
                "sha256": "f6ab67384a20f9356f1665830075b46af29f076ccf0ed833ba140d62bf52c825"
            },
            "downloads": -1,
            "filename": "buildantic-0.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "4bf1f425bf2811ed41cd368576450238",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<3.13,>=3.8",
            "size": 16966,
            "upload_time": "2024-10-21T20:46:19",
            "upload_time_iso_8601": "2024-10-21T20:46:19.878947Z",
            "url": "https://files.pythonhosted.org/packages/bc/9e/0f3a270a46938cc8cec5bff1e4154f6e4dde8a120a65291c3a4ee5321a19/buildantic-0.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-21 20:46:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "synacktraa",
    "github_project": "buildantic",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "buildantic"
}
        
Elapsed time: 2.37434s