Skip to content

typedconfig/tyco-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Tyco Python

PyPI version Python Version License: MIT

A Python library for parsing and working with Tyco configuration files - a modern, type-safe configuration language designed for clarity and flexibility.

πŸš€ Quick Start

Installation

pip install tyco

Basic Usage

import tyco

# Load the bundled example.tyco file (included in the package)
with tyco.open_example_file() as f:
  context = tyco.load(f.name)

# Or import from JSON directly
# with open("config.json") as handle:
#   context = tyco.load_from_json(handle)

# Materialize configuration as Python objects
config = context.as_object()

# Access global configuration values
timezone = config.timezone

# Access struct instances (lists of Structs)
applications = config.Application
hosts = config.Host

# Access individual instance fields
primary_app = applications[0]
host = primary_app.host
port = primary_app.port

# Export to JSON
json_data = context.as_json()

Example Tyco File

The package includes a ready-to-use example Tyco file at:

tyco/example.tyco

You can inspect this file after installation, or load it directly as shown above.

str timezone: UTC  # this is a global config setting

Application:       # schema defined first, followed by instance creation
  str service:
  str profile:
  str command: start_app {service}.{profile} -p {port.number}
  Host host:
  Port port: Port(http_web)  # reference to Port instance defined below
  - service: webserver, profile: primary, host: Host(prod-01-us)
  - service: webserver, profile: backup,  host: Host(prod-02-us)
  - service: database,  profile: mysql,   host: Host(prod-02-us), port: Port(http_mysql)

Host:
 *str hostname:  # star character (*) used as reference primary key
  int cores:
  bool hyperthreaded: true
  str os: Debian
  - prod-01-us, cores: 64, hyperthreaded: false
  - prod-02-us, cores: 32, os: Fedora

Port:
 *str name:
  int number:
  - http_web,   80  # can skip field keys when obvious
  - http_mysql, 3306

✨ Features

🎯 Type Safety

  • Strong Type Annotations: str, int, float, bool, date, time, datetime
  • Array Types: int[], str[], etc. for typed arrays
  • Nullable Types: ?str, ?int for fields that can be null
  • Runtime Validation: Type safety enforced during parsing

πŸ—οΈ Structured Configuration

  • Struct Definitions: Define reusable configuration structures
  • Primary Key Fields: * marks fields used for instance references
  • Nullable Fields: ? allows fields to have null values
  • Multiple Instances: Create multiple instances of the same struct
  • Cross-References: Reference instances by their primary key values

πŸ”§ Template System

  • Variable Substitution: Use {variable} syntax for dynamic values
  • Nested References: {struct.field} for complex relationships
  • Global Access: {global.variable} for explicit global scope
  • Template Expansion: Automatic resolution during parsing

🌐 Cross-Platform

  • Pure Python: No external dependencies
  • Python 3.8+: Modern Python support
  • All Operating Systems: Linux, macOS, Windows

πŸ“– Language Features

Type Annotations

# Basic types
str app_name: MyApplication
int port: 8080
float timeout: 30.5
bool enabled: true

# Date and time types
date launch_date: 2025-01-01
time start_time: 09:00:00
datetime created_at: 2025-01-01 09:00:00Z

# Array types
str[] environments: [dev, staging, prod]
int[] ports: [80, 443, 8080]

# Nullable types (can be null)
?str description: null
?int backup_port: 8081
?str[] optional_tags: [tag1, tag2]

Struct Definitions

# Define a struct with primary key (*) and nullable (?) fields
User:
 *str username:        # Primary key field - used for references
  str email:           # Required field
 ?str full_name:       # Nullable field - can be null
 ?int age:             # Nullable with explicit value
  bool active: true    # Required field with default value
  # Create instances
  - admin, admin@example.com, "Administrator", 35, true
  - user1, user1@example.com, "John Doe", 28, true
  - guest, guest@example.com, null, null, false  # nulls for nullable fields

# Reference other struct instances using primary keys
Project:
 *str name:
  User owner:          # Reference to User struct by username
  str[] tags:
  - webapp, User(admin), [frontend, react]    # References user "admin"
  - api, User(user1), [backend, python, fastapi]

Primary Keys and References

# Structs with primary keys can be referenced
Host:
 *str name:           # Single primary key
  int cores:
  bool enabled:
  - web1, 4, true
  - db1, 8, false

Service:
 *str name:
 *str environment:    # Multiple primary keys
  Host host:
  int port:
  - auth, production, Host(web1), 8001
  - auth, staging, Host(db1), 8002

# Reference by primary key values
Deployment:
 *str name:
  Service service:
  - prod_auth, Service(auth, production)  # References by both primary keys

Template Variables

# Global variables for reuse
str environment: production
str region: us-east-1
str domain: example.com

# Template expansion in values
str api_url: https://api-{environment}-{region}.{domain}
str log_path: /var/log/{environment}

# Templates in struct instances with field access
Service:
 *str name:
  str url:
  str log_file:
  - auth, https://{name}-{environment}.{domain}, /logs/{environment}/{name}.log
  - users, https://{name}-{environment}.{domain}, /logs/{environment}/{name}.log

# Global scope access in templates
Config:
 *str key:
  str value:
  str message:
  - region_key, {region}, "Region is {global.region}"
  - env_key, {environment}, "Environment: {global.environment}"

Nullable Values and Arrays

# Nullable global values
?str optional_config: null
?str present_config: "I have a value"

# Nullable arrays
?int[] optional_numbers: null
?str[] tags: [tag1, tag2, tag3]

# Struct with nullable fields
Resource:
 *str id:
  str name:
 ?str description:     # Can be null
 ?str[] labels:        # Nullable array
 ?int priority:        # Nullable number
  # Instances with null values
  - res1, "Resource One", "A description", [prod, web], 10
  - res2, "Resource Two", null, null, null  # All nullable fields are null
  - res3, "Resource Three", null, [test], 5

πŸ”§ API Reference

Core Functions

tyco.load(path: str | Path) -> TycoContext

Parses one file (or every *.tyco file underneath a directory) and returns a rendered TycoContext.

import tyco

context = tyco.load("config.tyco")

tyco.loads(content: str) -> TycoContext

Parses Tyco configuration from an in-memory stringβ€”handy for tests.

context = tyco.loads("""
str app_name: MyApp
int port: 8080
""")

Both helpers raise tyco.TycoParseError on syntax issues (subclass of tyco.TycoException).

TycoContext Helpers

Once parsing succeeds you interact with the returned TycoContext.

context = tyco.load("tyco/example.tyco")

config = context.as_object()
print(config.environment)     # -> "production"
print(config.timeout)         # -> 30

databases = config["Database"]
primary = databases[0]
print(primary.name, primary.host, primary.port)

json_payload = context.as_json()  # Plain dict ready for json.dumps(...)
json_text = context.dumps_json(indent=2)
tyco_text = context.dumps(compact=True)
with open("roundtrip.tyco", "w", encoding="utf-8") as tyco_fh:
    context.dump(tyco_fh)  # Writes the verbose form by default
context.dump_json("roundtrip.json", indent=2)
  • as_object() returns a tyco.Struct where both globals and struct definitions become attributes. Use attribute access (config.debug) or dictionary-style access (config['debug']).
  • Struct names (e.g. Database) materialize as lists of typed Struct instances.
  • as_json() materialises the canonical JSON-compatible dictionary (matching the shared test suite expectations). Use dumps_json(**json_kwargs) / dump_json(path_or_file, **json_kwargs) for convenience wrappers around json.dumps.
  • dumps(compact=False) rebuilds a textual Tyco document. Pass compact=True to suppress schema attribute comments; use dump(file_like_or_path) to stream directly to disk similar to pickle.dump.
  • load_from_json() / loads_from_json() import JSON data into the Tyco runtime so you can reuse the same typed object model regardless of the original source format.

Working with References

References are resolved automaticallyβ€”fields declared as another struct type give you the actual instance:

User:
 *str username:
  str email:
  - alice, alice@example.com
  - bob, bob@example.com

Project:
 *str name:
  User owner:
  - webapp, User(alice)
  - api,   User(bob)
context = tyco.load("projects.tyco")
projects = context.as_object().Project

webapp = projects[0]
owner = webapp.owner           # Already resolved to the underlying User instance
print(f"{webapp.name} -> {owner.username} ({owner.email})")

Custom Struct Classes

You can subclass tyco.Struct to add validation or helper methods. Registering a subclass lets the parser materialise instances of your class automatically:

import tyco

class Database(tyco.Struct):
    def validate(self):
        if self.port <= 0:
            raise ValueError("port must be positive")

context = tyco.load("tyco/example.tyco")
dbs = context.as_object().Database
print(isinstance(dbs[0], Database))  # True

Bundled Example

Use tyco.open_example_file() to access the packaged tyco/example.tyco no matter where the package is installed:

with tyco.open_example_file() as handle:
    context = tyco.load(handle.name)

πŸ§ͺ Testing

The library includes comprehensive tests covering all language features:

# Run tests
python -m pytest

# Run with coverage
python -m pytest --cov=tyco --cov-report=html

Test Coverage

  • βœ… Type System: All basic types, arrays, nullable types
  • βœ… Structs: Primary keys, nullable fields, instances, defaults
  • βœ… References: Primary key lookup and cross-references
  • βœ… Templates: Variable substitution and nested references
  • βœ… Edge Cases: Complex nesting, special characters, error handling

πŸ“ Project Structure

tyco-python/
β”œβ”€β”€ tyco/
β”‚   β”œβ”€β”€ __init__.py          # Main API exports
β”‚   β”œβ”€β”€ parser.py            # Core parsing logic and classes
β”‚   └── tests/
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ test_parser.py   # Parser functionality tests
β”‚       β”œβ”€β”€ test_load_features.py  # Load feature tests
β”‚       β”œβ”€β”€ inputs/          # Test Tyco files
β”‚       └── expected/        # Expected JSON outputs
β”œβ”€β”€ pyproject.toml           # Project configuration
β”œβ”€β”€ README.md               # This file
└── LICENSE                 # MIT License

🌟 Examples

Web Application Configuration

# app.tyco
str environment: production
bool debug: false
str secret_key: your-secret-key-here

# Database configuration with primary key
Database:
 *str name:               # Primary key for referencing
  str host:
  int port:
  str user:
 ?str password:           # Nullable - can be null for security
  str connection_string:
  - main, db.example.com, 5432, webapp_user, null, postgresql://{user}@{host}:{port}/{name}
  - cache, cache.example.com, 6379, cache_user, "secret123", redis://{host}:{port}

# Application servers  
Server:
 *str name:               # Primary key
  str host:
  int port:
  int workers:
 ?str description:        # Nullable description
  Database database:      # Reference to Database by name
  - web, 0.0.0.0, 8080, 4, "Main web server", Database(main)
  - api, 0.0.0.0, 8081, 2, null, Database(main)  # null description
  - worker, 127.0.0.1, 8082, 1, "Background worker", Database(cache)

# Feature flags (non-nullable array)
str[] enabled_features: [authentication, caching, analytics]

# Optional configuration (nullable)
?str[] optional_modules: [reporting, monitoring]
?int max_connections: null
# app.py
import tyco

config = tyco.load('app.tyco')

# Use configuration
print(f"Environment: {config.environment}")
print(f"Debug mode: {config.debug}")

# Server configuration with references
for server in config.Server:
    db = server.database  # This is the actual Database instance
    desc = server.description or "No description"
    print(f"Server {server.name}: {server.host}:{server.port}")
    print(f"  Description: {desc}")
    print(f"  Database: {db.name} at {db.host}:{db.port}")
    
    # Handle nullable database password
    if db.password is not None:
        print(f"  Database has password configured")
    else:
        print(f"  Database password is null (using other auth)")

# Handle nullable configuration
if config.optional_modules is not None:
    print(f"Optional modules: {', '.join(config.optional_modules)}")
else:
    print("No optional modules configured")

Microservices with Multi-Key References

# services.tyco
str environment: staging
str base_domain: internal.company.com
int default_timeout: 30

# Services with compound primary key
Service:
 *str name:
 *str region:           # Multiple primary keys
  str host:
  int port:
  int timeout:
 ?str health_endpoint:  # Nullable health check
  - auth, us-east, auth-east.{base_domain}, 8001, {default_timeout}, /health
  - auth, us-west, auth-west.{base_domain}, 8001, {default_timeout}, /health
  - users, us-east, users-east.{base_domain}, 8002, {default_timeout}, null
  - payments, us-east, payments-east.{base_domain}, 8003, 60, /status

# Load balancer referencing services by compound keys
LoadBalancer:
 *str name:
  Service[] upstream_services:  # Array of service references
  str algorithm:
 ?int max_connections:          # Nullable configuration
  - east_lb, [
      Service(auth, us-east),
      Service(users, us-east),
      Service(payments, us-east)
    ], round_robin, 1000
  - west_lb, [Service(auth, us-west)], round_robin, null

# Monitoring with nullable fields
Monitor:
 *str service_name:
  Service service:
 ?str custom_endpoint:          # Override default health endpoint
  int check_interval:
  - auth_east_monitor, Service(auth, us-east), null, 30
  - users_east_monitor, Service(users, us-east), /api/status, 30

🀝 Contributing

We welcome contributions! The parser implementation follows these principles:

  1. Type Safety: Strong type checking and validation
  2. Clarity: Clean, readable configuration syntax
  3. Flexibility: Support for complex referencing and nullable fields
  4. Performance: Efficient parsing for large configuration files
  5. Reliability: Comprehensive test coverage for all features

Development Setup

# Clone the repository
git clone https://github.com/typedconfig/tyco-python.git
cd tyco-python

# Install development dependencies
pip install -e .
pip install pytest pytest-cov

# Run tests
python -m pytest

οΏ½οΏ½ Requirements

  • Python: 3.8 or higher
  • Dependencies: None (pure Python implementation)
  • Operating Systems: Linux, macOS, Windows

οΏ½οΏ½ Related Projects

πŸ“„ License

MIT License - see the LICENSE file for details.

🌍 Learn More

About

Python bindings for the Tyco configuration language

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages