Skip to main content

TypeScript-inspired fluent API for Pydantic validation

Project description

Tyck (Type Check) - TypeScript-style Pydantic Wrapper

A Simpliled API for Pydantic validation that makes it easier to define schemas with method chaining while maintaining full compatibility with Pydantic's features.

Quick Start

from Tyck import interface, string, number, integer

# Define a schema with fluent API
User = interface({
    'id': integer.positive(),
    'name': string.min(1).max(100),
    'email': string.email(),
    'age': number.gt(0).lt(150).default(None)
})

# Create and validate
user = User(
        id=1, name="John",
        email="john@example.com"
        )
        
print(user.model_dump())
#  {'id': 1,
#  'name': 'John',
#  'email': 'john@example.com', 
#  'age': None
#   }

Installation

pip install Tyck (Type Check)

Table of Contents


Data Types

Primitive Types

String

from Tyck (Type Check) import string

# Basic usage
name = string

# With constraints
username = string.min(3).max(20)
email_field = string.email()
url_field = string.url()
uuid_field = string.uuid()
datetime_field = string.datetime()
date_field = string.date()
time_field = string.time()
json_field = string.json()
code = string.pattern(r'^[A-Z]{3}$')
zip_code = string.length(5)
ip_address = string.ip()  # or string.ip(4) for IPv4, string.ip(6) for IPv6

Available Methods:

  • .min(length) - Minimum string length
  • .max(length) - Maximum string length
  • .length(exact) - Exact string length
  • .pattern(regex) - Regex pattern validation
  • .email() - Email format validation
  • .url() - URL format validation
  • .uuid() - UUID format validation
  • .datetime() - ISO datetime format
  • .date() - ISO date format
  • .time() - ISO time format
  • .ip(version=None) - IP address validation
  • .json() - JSON string validation
  • .strip() - Strip whitespace
  • .lower() - Convert to lowercase
  • .upper() - Convert to uppercase
  • .default(value) - Set default value
  • .alias(name) - Set field alias
  • .description(text) - Set field description
  • .title(text) - Set field title
  • .examples(*values) - Set example values
  • .deprecated(bool) - Mark as deprecated

Number (Float)

from Tyck (Type Check) import number

# Basic usage
price = number

# With constraints
positive_num = number.positive()
negative_num = number.negative()
percentage = number.gte(0).lte(100)
range_num = number.gt(10).lt(100)
finite_num = number.finite()
multiple = number.multiple_of(0.5)

Available Methods:

  • .gt(value) - Greater than
  • .gte(value) - Greater than or equal
  • .lt(value) - Less than
  • .lte(value) - Less than or equal
  • .positive() - Greater than 0
  • .negative() - Less than 0
  • .non_positive() - Less than or equal to 0
  • .non_negative() - Greater than or equal to 0
  • .finite() - Not infinite/NaN
  • .multiple_of(value) - Divisible by value
  • .range(min, max) - Inclusive range (gte + lte)
  • .default(value) - Set default value

Integer

from Tyck (Type Check) import integer

# Basic usage
id_field = integer

# With constraints
positive_int = integer.positive()
age = integer.range(0, 150)

Available Methods: (same as Number)

  • .gt(value), .gte(value), .lt(value), .lte(value)
  • .positive(), .negative(), .non_positive(), .non_negative()
  • .multiple_of(value), .range(min, max)
  • .default(value)

Boolean

from Tyck (Type Check) import boolean

# Basic usage
is_active = boolean

# With strict validation
strict_bool = boolean.strict()

Available Methods:

  • .strict() - Strict boolean validation
  • .default(value) - Set default value

DateTime

from Tyck (Type Check) import datetime, date, time

# DateTime
created_at = datetime

# Date
birth_date = date

# Time
opening_time = time

UUID

from Tyck (Type Check) import uuid

# Any UUID version
user_id = uuid

Bytes

from Tyck (Type Check) import bytes_type

# Basic usage
data = bytes_type

# With constraints
limited_data = bytes_type.min(1).max(1024)

Available Methods:

  • .min(length) - Minimum bytes length
  • .max(length) - Maximum bytes length
  • .default(value) - Set default value

Decimal

from Tyck (Type Check) import decimal

# Basic usage
price = decimal

# With constraints
precise = decimal.max_digits(10).decimal_places(2)
positive_decimal = decimal.gt(0)

Available Methods:

  • .max_digits(n) - Maximum total digits
  • .decimal_places(n) - Maximum decimal places
  • .gt(value), .gte(value), .lt(value), .lte(value)
  • .default(value) - Set default value

Any

from Tyck (Type Check) import any_type

# Accept any value
metadata = any_type

None

from Tyck (Type Check) import none_type

# None value
null_field = none_type

Complex Types

Array / List

from Tyck (Type Check) import array, string, number

# Basic array
tags = array(string)

# With constraints
members = array(string).min(1).max(10)
scores = array(number).unique()  # Unique items only
exact_count = array(string).length(5)

Available Methods:

  • .min(length) - Minimum items
  • .max(length) - Maximum items
  • .length(exact) - Exact item count
  • .unique() - Require unique items
  • .default(value) - Set default value

Optional

from Tyck (Type Check) import optional, string

# Optional field
bio = optional(string.max(500))
age = optional(number)

Literal

from Tyck (Type Check) import literal

# Enum-like validation
role = literal('admin', 'user', 'guest')
status = literal('active', 'inactive', 'pending')

Dictionary

from Tyck (Type Check) import dict_type, string, any_type

# Dict[str, Any]
metadata = dict_type(string, any_type)

# Dict[str, int]
counts = dict_type(string, integer)

Available Methods:

  • .min(length) - Minimum key-value pairs
  • .max(length) - Maximum key-value pairs
  • .default(value) - Set default value

Set

from Tyck (Type Check) import set_type, string

# Set of unique strings
tags = set_type(string)

Available Methods:

  • .min(length) - Minimum items
  • .max(length) - Maximum items
  • .default(value) - Set default value

Tuple

from Tyck (Type Check) import tuple_type, number, string

# Fixed-size tuple
point = tuple_type(number, number)  # (x, y)
person = tuple_type(string, number)  # (name, age)

Union

from Tyck (Type Check) import union, string, number

# Multiple allowed types
id_or_name = union(string, number)

Enum

from enum import Enum
from Tyck (Type Check) import enum_type

class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

color_field = enum_type(Color)

APIs

Functional API

Create models using the interface() function with a dictionary of fields.

from Tyck (Type Check) import interface, string, number, integer, array, optional, config

# Basic interface
User = interface({
    'id': integer.positive(),
    'name': string.min(1).max(100),
    'email': string.email(),
    'age': optional(number.gt(0).lt(150)),
    'tags': array(string).max(10),
})

# With configuration
StrictUser = interface({
    'name': string,
    'email': string.email(),
}, config=config(
    strict=True,           # Strict type checking
    frozen=True,           # Immutable models
    extra='forbid',        # Forbid extra fields
))

# Create instance
user = User(id=1, name="John", email="john@example.com")

# Access fields
print(user.id)        # 1
print(user.name)      # "John"

# Serialization
user_dict = user.model_dump()       # Dict
user_json = user.model_dump_json()  # JSON string

interface() Parameters:

  • fields (dict) - Field definitions mapping names to type builders
  • config (ConfigDict, optional) - Pydantic configuration
  • validators (dict, optional) - Custom validator functions
  • name (str, optional) - Custom class name
  • base (BaseModel, optional) - Base class to inherit from
  • doc (str, optional) - Docstring for the model

Class-Based API

Define models using Python classes with the @model decorator.

from Tyck (Type Check) import model, string, number, integer, array, optional

@model()
class User:
    id: integer.positive()
    name: string.min(1).max(100)
    email: string.email()
    age: optional(number.gt(0).lt(150))
    tags: array(string).max(10)
    
    def greet(self) -> str:
        return f"Hello, {self.name}!"

# Create instance
user = User(id=1, name="John", email="john@example.com")

# Call methods
print(user.greet())  # "Hello, John!"

@model() Parameters:

  • config (ConfigDict, optional) - Pydantic configuration
  • validators (dict, optional) - Custom validator functions
  • base (BaseModel, optional) - Base class to inherit from
  • frozen (bool) - Make model immutable
  • strict (bool) - Enable strict type checking
  • extra (str) - How to handle extra fields ('ignore', 'allow', 'forbid')

Inheritance

from Tyck (Type Check) import model, string, integer

@model()
class BaseUser:
    id: integer.positive()
    name: string

@model()
class Admin(BaseUser):
    role: string.default("admin")
    permissions: array(string)

# Admin has id, name, role, and permissions
admin = Admin(id=1, name="Super Admin", permissions=["read", "write"])

Field Helper

from Tyck (Type Check) import model, field, string

@model()
class User:
    name: field(
        string.min(1).max(100),
        alias="fullName",
        description="The user's full name",
        examples=["John Doe", "Jane Smith"]
    )

Utility Functions

pick()

Create a new model with only the specified fields.

from Tyck (Type Check) import pick

User = interface({
    'id': integer,
    'name': string,
    'email': string,
    'password': string,
})

# Pick only public fields
PublicUser = pick(User, 'id', 'name', 'email')
# PublicUser has: id, name, email

omit()

Create a new model without the specified fields.

from Tyck (Type Check) import omit

# Omit sensitive fields
SafeUser = omit(User, 'password')
# SafeUser has: id, name, email

partial()

Make all fields optional (useful for PATCH requests).

from Tyck (Type Check) import partial

# All fields are now optional
UserUpdate = partial(User)

# Update only specific fields
update = UserUpdate(name="New Name")

required()

Make all fields required (remove defaults).

from Tyck (Type Check) import required

# All fields must be provided
StrictUser = required(User)

extend()

Extend a model with new fields.

from Tyck (Type Check) import extend

User = interface({
    'id': integer,
    'name': string,
})

Admin = extend(User, {
    'role': string.default('admin'),
    'permissions': array(string),
})
# Admin has: id, name, role, permissions

merge()

Merge multiple models into one.

from Tyck (Type Check) import merge

User = interface({'id': integer, 'name': string})
Profile = interface({'bio': string, 'avatar': string})

UserWithProfile = merge(User, Profile)
# Has: id, name, bio, avatar

Configuration

config() Helper

from Tyck (Type Check) import config

# Create configuration
cfg = config(
    strict=True,              # Strict type checking
    frozen=True,              # Immutable models
    extra='forbid',           # Forbid extra fields
    populate_by_name=True,    # Allow population by field name
    validate_assignment=True, # Validate on assignment
    str_to_lower=False,       # Convert strings to lowercase
    str_to_upper=False,       # Convert strings to uppercase
    str_strip_whitespace=True,# Strip whitespace from strings
    use_enum_values=True,     # Use enum values instead of instances
    validate_default=True,    # Validate default values
)

Configuration Options:


Option Type Description
strict bool Enable strict type checking
frozen bool Make models immutable
extra str Handle extra fields: 'ignore', 'allow', 'forbid'
populate_by_name bool Allow population by field name
validate_assignment bool Validate on field assignment
str_to_lower bool Convert strings to lowercase
str_to_upper bool Convert strings to uppercase
str_strip_whitespace bool Strip whitespace from strings
use_enum_values bool Use enum values instead of instances
validate_default bool Validate default values


Validation & Serialization

Validation

from pydantic import ValidationError

User = interface({
    'email': string.email(),
    'age': number.gt(0).lt(150),
})

# Valid data
user = User(email="test@example.com", age=25)

# Invalid data raises ValidationError
try:
    invalid_user = User(email="invalid", age=-5)
except ValidationError as e:
    print(e.error_count())  # Number of errors
    for error in e.errors():
        print(f"{error['loc']}: {error['msg']}")

Serialization

user = User(email="test@example.com", age=25)

# To dictionary
data = user.model_dump()
# {'email': 'test@example.com', 'age': 25}

# To JSON
json_str = user.model_dump_json()
# '{"email":"test@example.com","age":25}'

# Exclude fields
data = user.model_dump(exclude={'age'})
# {'email': 'test@example.com'}

# Include only specific fields
json_str = user.model_dump_json(include={'email'})
# '{"email":"test@example.com"}'

JSON Schema

User = interface({
    'id': integer.positive(),
    'name': string.min(1).max(100),
    'email': string.email(),
})

# Generate JSON Schema
schema = User.model_json_schema()

print(schema)
# {
#   'title': 'Interface_...',
#   'type': 'object',
#   'properties': {
#     'id': {'type': 'integer', 'exclusiveMinimum': 0},
#     'name': {'type': 'string', 'minLength': 1, 'maxLength': 100},
#     'email': {'type': 'string', 'format': 'email'}
#   },
#   'required': ['id', 'name', 'email']
# }

Examples

API Request/Response Models

from Tyck (Type Check) import interface, string, number, integer, optional, array, pick, partial

# Request models
CreateUserRequest = interface({
    'name': string.min(1).max(100),
    'email': string.email(),
    'age': optional(number.gt(0).lt(150))
})

UpdateUserRequest = partial(CreateUserRequest)

# Response models
UserResponse = interface({
    'id': integer.positive(),
    'name': string,
    'email': string.email(),
    'created_at': string.datetime()
})

PublicUserResponse = omit(UserResponse, 'email')

Configuration Models

from Tyck (Type Check) import interface, string, number, boolean, literal

DatabaseConfig = interface({
    'host': string.default('localhost'),
    'port': number.integer().range(1, 65535).default(5432),
    'username': string,
    'password': string,
    'ssl': boolean.default(False)
})

AppConfig = interface({
    'name': string,
    'debug': boolean.default(False),
    'database': DatabaseConfig,
    'log_level': literal('debug', 'info', 'warning', 'error').default('info')
})

Nested Models

from Tyck (Type Check) import interface, string, number, array

Address = interface({
    'street': string,
    'city': string,
    'zip': string.pattern(r'^\d{5}$'),
    'country': string.default('USA')
})

Customer = interface({
    'id': number.integer().positive(),
    'name': string.min(1),
    'email': string.email(),
    'addresses': array(Address),
    'phone': optional(string.pattern(r'^\+?1?\d{9,15}$'))
})

# Create nested instance
customer = Customer(
    id=1,
    name="John Doe",
    email="john@example.com",
    addresses=[{
        'street': "123 Main St",
        'city': "New York",
        'zip': "10001"
    }]
)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.

Acknowledgments

Built on top of Pydantic - the most widely used data validation library for Python.

Inspired by TypeScript's type system and Zod's API design.

Project details


Download files

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

Source Distribution

tyck-0.1.0.tar.gz (26.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tyck-0.1.0-py3-none-any.whl (30.0 kB view details)

Uploaded Python 3

File details

Details for the file tyck-0.1.0.tar.gz.

File metadata

  • Download URL: tyck-0.1.0.tar.gz
  • Upload date:
  • Size: 26.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tyck-0.1.0.tar.gz
Algorithm Hash digest
SHA256 66bfa8dc57a385dd380622e8697462f42678ab7a5125a29b7e4ff5c903d4aa64
MD5 59c502eb8d2380f005d4bb2c8d548ff4
BLAKE2b-256 5e0b6d10701b8f54ba6c849fd728542263acd9405ee4abd7baeea12b64d7d0c0

See more details on using hashes here.

Provenance

The following attestation bundles were made for tyck-0.1.0.tar.gz:

Publisher: publish-to-pypi.yml on anshdadhich/Tyck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tyck-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tyck-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tyck-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dd33c94e4442cb75d98efc9242b9e605eded42d23ecfcd7dcb2a7288672054a7
MD5 cf35440cac5c57d40b92667db9bf350a
BLAKE2b-256 df08478de32e30a563186b0754c7d8989f6cdca32f493cbab71e8d67ba4cdc46

See more details on using hashes here.

Provenance

The following attestation bundles were made for tyck-0.1.0-py3-none-any.whl:

Publisher: publish-to-pypi.yml on anshdadhich/Tyck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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