ML3Seq Format
Project description
ML3Seq Format Core
The core implementation of the ML3Seq (JSON + Multi-part hybrid) serialization format.
Status: On the way out... to make way for the new.
See status notes in project README.md#status.
Overview
ML3Seq Format Core provides the fundamental building blocks for the ML3Seq serialization format, which combines JSON for structured data with unescaped multiline blocks for text content. This format is particularly well-suited for applications involving language models (LLMs) where preserving original formatting reduces cognitive load and improves reliability.
Key Components
1. ML3Seq - The Sequence Container
The main container for ML3Seq format data:
from ml3on.core import ML3Seq, ML3SeqItem
# Create a sequence with items
sequence = ML3Seq(
ML3SeqItem(kind="FILE", name="file1.txt", content="Content here"),
ML3SeqItem(kind="FILE", name="file2.txt", content="More content")
)
# Serialize to ML3Seq format
ml3seq_string = sequence.as_ml3seq
# Deserialize from ML3Seq format
parsed_sequence = ML3Seq.from_ml3seq(ml3seq_string)
2. ML3SeqItem - Individual Items
Represents a single item in an ML3Seq:
from ml3on.core import ML3SeqItem
# Create an item
item = ML3SeqItem(
kind="DOCUMENT",
title="Sample",
content="This is multiline\ncontent"
)
# Serialize item
ml3seq_item_string = item.to_ml3seq(config)
# Deserialize item
parsed_item = ML3SeqItem.from_ml3seq(ml3seq_item_string, config)
3. ML3SeqMultilineString - Type-Based Control
A string subclass that provides explicit control over serialization format:
from ml3on.core import ML3SeqMultilineString
# Create a multiline string
ml_string = ML3SeqMultilineString("This will always\nbe in a multiline block")
# Behaves like a regular string
print(len(ml_string)) # String methods work
print(ml_string.upper()) # All string operations supported
# But serializes differently in ML3Seq format
4. ML3SeqFormatConfig - Configuration Management
Manages ML3Seq format configuration:
from ml3on.core import ML3SeqFormatConfig
# Create configuration
config = ML3SeqFormatConfig(separator_prefix="CUSTOM|")
# Get current separator
separator = config.separator_prefix()
# Convert to dictionary
config_dict = config.to_dict()
Format Specification
Basic Structure
{separatorPrefix}BEGIN:{kind}
{jsonWithNonMultilineValues}
{separatorPrefix}{multilineField1}
{multilineField1Content}
...
{separatorPrefix}{multilineFieldN}
{multilineFieldNContent}
{separatorPrefix}END:{kind}
Complete Sequence Format
[configJson] # Optional configuration
[item1]
[item2]
...
[itemN]
Format Rules
- Control Markers: BEGIN/END markers are only recognized at the start of lines (no leading whitespace)
- Field Validation: Field names must be non-empty strings and cannot start with "BEGIN:" or "END:"
- JSON Values: Non-multiline values are stored as JSON
- Multi-line Fields: Multi-line content appears after field markers without escaping
- Immutability: All data structures are immutable for safety
Example Formats
Simple Item (No Multiline Content)
-~<§BEGIN:SIMPLE
{"field1": "value1", "field2": "value2"}
-~<§END:SIMPLE
Item with Multiline Content
-~<§BEGIN:DOCUMENT
{"title": "Sample", "author": "Test"}
-~<§content
This is the first line of content.
This is the second line of content.
This is the third line of content.
-~<§END:DOCUMENT
Multiple Items in Sequence
-~<§BEGIN:ITEM1
{"id": 1, "name": "First"}
-~<§description
First item description
with multiple lines
-~<§END:ITEM1
-~<§BEGIN:ITEM2
{"id": 2, "name": "Second"}
-~<§description
Second item description
also with multiple lines
-~<§END:ITEM2
With Configuration
{"separator_prefix": "BOOP|"}
BOOP|BEGIN:CONFIGURED
{"field": "value"}
BOOP|multiline
Multiline content here
BOOP|END:CONFIGURED
Configuration
Separator Prefix
The separator prefix can be configured in several ways:
-
Constructor Argument (highest priority):
config = ML3SeqFormatConfig(separator_prefix="CUSTOM|")
-
Environment Variable:
export ML3Seq_FORMAT_SEPARATOR_PREFIX="CUSTOM|"
-
Config JSON (in serialized format):
{"separator_prefix": "CUSTOM|"} -
Default:
-~<§(if none of the above are provided)
Configuration Precedence
- Explicit constructor argument
- Environment variable
- Config JSON in serialized format
- Default constant
Advanced Usage
Working with Complex Data
from ml3on.core import ML3Seq, ML3SeqItem
# Nested structures
item = ML3SeqItem(
kind="COMPLEX",
metadata={"key1": "value1", "key2": [1, 2, 3]},
tags=["tag1", "tag2", "tag3"],
content="Multiline content\nwith multiple lines"
)
# Lists of items
items = [
ML3SeqItem(kind="ITEM", name="item1"),
ML3SeqItem(kind="ITEM", name="item2"),
ML3SeqItem(kind="ITEM", name="item3")
]
sequence = ML3Seq(*items)
Error Handling
from ml3on.core import ML3Seq, ML3SeqItem
try:
# Invalid format
sequence = ML3Seq.from_ml3seq("invalid ml3seq format")
except ValueError as e:
print(f"Format error: {e}")
try:
# Missing required fields
item = ML3SeqItem(kind="") # Empty kind
ml3seq_str = item.to_ml3seq(config)
except ValueError as e:
print(f"Validation error: {e}")
try:
# Invalid field names
item = ML3SeqItem(kind="TEST", **{"": "invalid"})
except ValueError as e:
print(f"Field error: {e}")
Custom Validation
from ml3on.core import ML3SeqItem
def validate_item(item: ML3SeqItem):
"""Custom validation logic"""
if not item.kind:
raise ValueError("Item kind cannot be empty")
# Check required fields
required_fields = ["id", "name"]
for field in required_fields:
if field not in [k for k, v in item.kv_pairs]:
raise ValueError(f"Missing required field: {field}")
return True
Performance Considerations
Large Data Handling
# Large multiline strings (10k+ characters)
large_content = "A" * 10000 + "\n" + "B" * 10000
item = ML3SeqItem(kind="LARGE", content=large_content)
# Large sequences (1000+ items)
large_sequence = ML3Seq(*[ML3SeqItem(kind=f"ITEM_{i}") for i in range(1000)])
Memory Efficiency
The implementation uses:
frozendictfor immutable dictionariestuplefor immutable sequences- Generators where appropriate
- Efficient string handling
Benchmarking
import time
from ml3on.core import ML3Seq, ML3SeqItem
# Create test data
test_items = [ML3SeqItem(kind=f"ITEM_{i}", value=str(i)) for i in range(1000)]
# Benchmark serialization
start = time.time()
sequence = ML3Seq(*test_items)
ml3seq_str = sequence.as_ml3seq
end = time.time()
print(f"Serialization: {end - start:.4f} seconds")
# Benchmark deserialization
start = time.time()
parsed = ML3Seq.from_ml3seq(ml3seq_str)
end = time.time()
print(f"Deserialization: {end - start:.4f} seconds")
Integration Patterns
File System Integration
import os
from ml3on.core import ML3Seq
def save_ml3seq_file(sequence: ML3Seq, filepath: str):
"""Save ML3Seq to file"""
with open(filepath, 'w', encoding='utf-8') as f:
f.write(sequence.as_ml3seq)
def load_ml3seq_file(filepath: str) -> ML3Seq:
"""Load ML3Seq from file"""
with open(filepath, 'r', encoding='utf-8') as f:
return ML3Seq.from_ml3seq(f.read())
Network Integration
import requests
from ml3on.core import ML3Seq
def send_ml3seq_api_request(url: str, sequence: ML3Seq):
"""Send ML3Seq data via API"""
headers = {'Content-Type': 'text/ml3seq'}
response = requests.post(url, data=sequence.as_ml3seq, headers=headers)
return ML3Seq.from_ml3seq(response.text)
Database Integration
from ml3on.core import ML3Seq
def store_ml3seq_in_database(db_connection, sequence: ML3Seq):
"""Store ML3Seq in database"""
cursor = db_connection.cursor()
cursor.execute(
"INSERT INTO ml3seq_data (content) VALUES (%s)",
(sequence.as_ml3seq,)
)
db_connection.commit()
Testing
Running Tests
# Run all core tests
just test packages/ml3seq-format-core/tests/
# Run specific test file
just test packages/ml3seq-format-core/tests/ml3seq/core/test_sequence.py
# Run with verbose output
just test packages/ml3seq-format-core/tests/ -v
Test Structure
packages/ml3seq-format-core/tests/
├── ml3seq/
│ ├── core/
│ │ ├── test_sequence.py # ML3Seq tests
│ │ ├── test_item.py # ML3SeqItem tests
│ │ ├── test_multiline.py # ML3SeqMultilineString tests
│ │ ├── test_multiline__serde.py # Serialization tests
│ │ ├── test_multiline__typing.py # Type tests
│ │ ├── test_multiline__edge_cases.py # Edge case tests
│ │ ├── test_sequence.py # Sequence tests
│ │ ├── test_integration.py # Integration tests
│ │ └── test_edge_cases.py # Edge cases
│ └── config/
│ ├── test_config.py # Config tests
│ ├── test_constants.py # Constants tests
│ └── test_protocol.py # Protocol tests
Writing Tests
import pytest
from ml3on.core import ML3Seq, ML3SeqItem
def test_basic_serialization():
"""Test basic serialization"""
item = ML3SeqItem(kind="TEST", field="value")
sequence = ML3Seq(item)
ml3seq_str = sequence.as_ml3seq
assert "-~<§BEGIN:TEST" in ml3seq_str
assert "-~<§END:TEST" in ml3seq_str
def test_round_trip():
"""Test serialization/deserialization round trip"""
original = ML3Seq(
ML3SeqItem(kind="TEST", field="value")
)
ml3seq_str = original.as_ml3seq
parsed = ML3Seq.from_ml3seq(ml3seq_str)
assert len(parsed.items) == len(original.items)
assert parsed.items[0].kind == original.items[0].kind
API Reference
ML3Seq
Class: ML3Seq(*args, config=None, **kwargs)
Properties:
config: Optional[ML3SeqFormatConfigProtocol]- Configurationitems: tuple[ML3SeqItem, ...]- Sequence itemsseparator_prefix: str- Current separator prefixas_ml3seq: str- Serialized ML3Seq string
Methods:
from_ml3seq(cls, value: str) -> ML3Seq- Parse ML3Seq format string
ML3SeqItem
Class: ML3SeqItem(kind: str, **kwargs)
Properties:
kind: str- Item type identifierkv_pairs: tuple[tuple[str, Any], ...]- Key-value pairs
Methods:
to_ml3seq(config: ML3SeqFormatConfigProtocol) -> str- Serialize to ML3Seq formatfrom_ml3seq(cls, value: str, config: ML3SeqFormatConfigProtocol) -> ML3SeqItem- Parse ML3Seq item
ML3SeqMultilineString
Class: ML3SeqMultilineString(value: str | bytes)
Inherits from: str
Methods:
- All standard string methods
__new__(cls, value)- Create instance__reduce__()- Pickle support
ML3SeqFormatConfig
Class: ML3SeqFormatConfig(separator_prefix=None, **kwargs)
Methods:
separator_prefix() -> str- Get separator prefixto_dict() -> Mapping[str, Any]- Get config as dictionary
Best Practices
1. Choose Unique Separators
# Good: Unique, unlikely to appear in content
config = ML3SeqFormatConfig(separator_prefix="-~<§")
# Avoid: Common characters that might appear in content
config = ML3SeqFormatConfig(separator_prefix="---")
2. Validate Input Data
# Validate before serialization
if not isinstance(data, dict):
raise ValueError("Data must be a dictionary")
# Validate field names
for field_name in data.keys():
if not isinstance(field_name, str) or not field_name.strip():
raise ValueError(f"Invalid field name: {field_name}")
3. Handle Large Data Efficiently
# Process large sequences in chunks
chunk_size = 100
all_items = []
for i in range(0, len(large_data), chunk_size):
chunk = large_data[i:i + chunk_size]
items = [ML3SeqItem(kind="DATA", **item) for item in chunk]
all_items.extend(items)
sequence = ML3Seq(*all_items)
4. Error Recovery
# Graceful error handling
try:
sequence = ML3Seq.from_ml3seq(user_provided_string)
except ValueError as e:
# Fallback to default or alternative format
logger.error(f"ML3Seq parse error: {e}")
sequence = create_default_sequence()
5. Configuration Management
# Centralized configuration
DEFAULT_CONFIG = ML3SeqFormatConfig(separator_prefix="-~<§")
def get_ml3seq_config():
"""Get application-wide ML3Seq config"""
return DEFAULT_CONFIG
Comparison with Other Formats
ML3Seq vs JSON
Advantages of ML3Seq:
- Unescaped multiline content
- Better readability for mixed data
- Explicit structure boundaries
- Reduced cognitive load for LLMs
When to use JSON:
- Pure structured data
- Browser compatibility
- Simple configurations
- API responses
ML3Seq vs YAML
Advantages of ML3Seq:
- Explicit multiline blocks
- JSON compatibility for metadata
- Better for mixed structured/unstructured data
- More predictable parsing
When to use YAML:
- Human-edited configuration files
- Simple data structures
- When escaping is acceptable
ML3Seq vs Custom Formats
Advantages of ML3Seq:
- Standardized format
- Type safety
- Comprehensive error handling
- Integration with Pydantic
- Well-tested implementation
Migration Guide
From JSON to ML3Seq
import json
from ml3on.core import ML3Seq, ML3SeqItem
# Convert JSON to ML3Seq
def json_to_ml3seq(json_str: str) -> ML3Seq:
data = json.loads(json_str)
if isinstance(data, list):
items = []
for item_data in data:
# Extract kind or use default
kind = item_data.get("kind", "ITEM")
# Create ML3Seq item
item = ML3SeqItem(kind=kind, **item_data)
items.append(item)
return ML3Seq(*items)
else:
# Single item
kind = data.get("kind", "ITEM")
return ML3Seq(ML3SeqItem(kind=kind, **data))
From ML3Seq to JSON
import json
from ml3on.core import ML3Seq
def ml3seq_to_json(ml3seq_str: str) -> str:
sequence = ML3Seq.from_ml3seq(ml3seq_str)
json_data = []
for item in sequence.items:
item_dict = {
"kind": item.kind,
**{k: str(v) if hasattr(v, 'strip') else v for k, v in item.kv_pairs}
}
json_data.append(item_dict)
return json.dumps(json_data)
Troubleshooting
Common Issues
Issue: ValueError: Invalid ML3Seq format
- Cause: Malformed ML3Seq string
- Solution: Validate input and check for missing BEGIN/END markers
Issue: Fields appearing in wrong format
- Cause: Type annotations not properly specified
- Solution: Ensure
ML3SeqMultilineStringis used for multiline fields
Issue: Separator conflicts
- Cause: Separator prefix appears in content
- Solution: Choose a more unique separator prefix
Debugging Tips
# Debug serialization
def debug_serialize(item):
print(f"Item kind: {item.kind}")
print(f"Item fields: {[(k, type(v).__name__) for k, v in item.kv_pairs]}")
ml3seq_str = item.to_ml3seq(config)
print(f"ML3Seq output:\n{ml3seq_str}")
return ml3seq_str
# Debug deserialization
def debug_deserialize(ml3seq_str):
print(f"Input ML3Seq:\n{ml3seq_str}")
lines = ml3seq_str.split('\n')
print(f"Lines: {len(lines)}")
for i, line in enumerate(lines):
print(f"{i}: {repr(line)}")
return ML3SeqItem.from_ml3seq(ml3seq_str, config)
Performance Optimization
Caching
from functools import lru_cache
@lru_cache(maxsize=100)
def get_cached_ml3seq(item_data):
"""Cache frequently used ML3Seq items"""
item = ML3SeqItem(**item_data)
return item.to_ml3seq(config)
Batch Processing
def process_batch(items_data):
"""Process items in batches"""
items = []
for data in items_data:
item = ML3SeqItem(**data)
items.append(item)
# Single serialization call
sequence = ML3Seq(*items)
return sequence.as_ml3seq
Memory Management
def process_large_file(filepath):
"""Process large files efficiently"""
with open(filepath, 'r') as f:
while True:
chunk = f.read(4096) # 4KB chunks
if not chunk:
break
# Process chunk
yield process_chunk(chunk)
Security Considerations
Input Validation
# Validate ML3Seq input
def safe_parse_ml3seq(ml3seq_str: str, max_size=1000000):
"""Safely parse ML3Seq with size limits"""
if len(ml3seq_str) > max_size:
raise ValueError(f"ML3Seq input too large: {len(ml3seq_str)} bytes")
# Check for suspicious patterns
if "eval(" in ml3seq_str or "import " in ml3seq_str:
raise ValueError("Potentially unsafe ML3Seq content")
return ML3Seq.from_ml3seq(ml3seq_str)
Field Name Sanitization
def sanitize_field_names(data: dict) -> dict:
"""Sanitize field names before creating ML3Seq items"""
sanitized = {}
for key, value in data.items():
# Remove potentially dangerous characters
safe_key = ''.join(c for c in key if c.isalnum() or c in '_-')
if safe_key:
sanitized[safe_key] = value
return sanitized
Future Enhancements
Planned Features
- Streaming Support: For very large datasets
- Schema Validation: Integration with JSON Schema
- Performance Optimizations: For specific use cases
- Additional Formats: Alternative serialization options
- Enhanced Error Recovery: Better handling of malformed input
Potential Improvements
- Binary Data Support: Safe handling of binary content
- Compression: Built-in compression options
- Encryption: Secure serialization options
- Versioning: Format version management
- Extensions: Plugin system for custom features
Documentation
- Main README: Project overview
- Pydantic Integration: Pydantic usage
- Build System: Building and versioning
Support
For issues, questions, or contributions:
- GitHub Issues: Report bugs and request features
- Discussions: Ask questions and share ideas
- Pull Requests: Contribute improvements
License
MIT License - Open source and free to use.
Changelog
See VERSION file for version history.
Contributing
Contributions are welcome! Please:
- Follow existing code patterns
- Add comprehensive tests
- Update documentation
- Maintain backward compatibility
- Follow the project's coding standards
Examples
See the pydantic package for comprehensive usage examples with Pydantic models.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ml3on_format_core-0.0.1-py3-none-any.whl.
File metadata
- Download URL: ml3on_format_core-0.0.1-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b288ae5abab1c052fb41f3751eb2be24ab4ee7155c49c448770fb9fba285d7fe
|
|
| MD5 |
d86afd6ae2369c7c2622979cbf958b5a
|
|
| BLAKE2b-256 |
41745d5ffafc6bf6b44756cf418473436a028755fdc43bac988e22aea0a22967
|