valbridge 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- valbridge-0.1.1/.gitignore +25 -0
- valbridge-0.1.1/CHANGELOG.md +7 -0
- valbridge-0.1.1/PKG-INFO +54 -0
- valbridge-0.1.1/README.md +24 -0
- valbridge-0.1.1/pyproject.toml +55 -0
- valbridge-0.1.1/src/valbridge/__init__.py +135 -0
- valbridge-0.1.1/src/valbridge/py.typed +0 -0
- valbridge-0.1.1/tests/test_client.py +101 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Temporary compliance test harness files
|
|
2
|
+
xschema-harness-*.py
|
|
3
|
+
|
|
4
|
+
# Python cache
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.pyc
|
|
7
|
+
*.pyo
|
|
8
|
+
*.pyd
|
|
9
|
+
.Python
|
|
10
|
+
|
|
11
|
+
# Pytest cache
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
|
|
14
|
+
# mypy
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.dmypy.json
|
|
17
|
+
dmypy.json
|
|
18
|
+
|
|
19
|
+
# uv
|
|
20
|
+
.venv/
|
|
21
|
+
|
|
22
|
+
# Build artifacts
|
|
23
|
+
dist/
|
|
24
|
+
build/
|
|
25
|
+
*.egg-info/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.1](https://github.com/vectorfy-co/valbridge/compare/py-client-v0.1.0...py-client-v0.1.1) (2026-04-07)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* import valbridge monorepo ([2a7ed24](https://github.com/vectorfy-co/valbridge/commit/2a7ed246c8cb87206d13860fe01155de24de1ae8))
|
valbridge-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: valbridge
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Runtime client for valbridge-generated validators with type-safe schema lookup
|
|
5
|
+
Project-URL: Homepage, https://github.com/vectorfy-co/valbridge
|
|
6
|
+
Project-URL: Documentation, https://github.com/vectorfy-co/valbridge
|
|
7
|
+
Project-URL: Repository, https://github.com/vectorfy-co/valbridge
|
|
8
|
+
Project-URL: Issues, https://github.com/vectorfy-co/valbridge/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/vectorfy-co/valbridge/blob/main/CHANGELOG.md
|
|
10
|
+
Author: vectorfyco
|
|
11
|
+
Maintainer: vectorfyco
|
|
12
|
+
License: MIT
|
|
13
|
+
Keywords: code-generation,json-schema,pydantic,runtime-client,schema,type-safety,validation
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# valbridge
|
|
32
|
+
|
|
33
|
+
Runtime client for valbridge-generated validators with type-safe schema lookup.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install valbridge
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from valbridge import create_valbridge
|
|
45
|
+
from _valbridge import schemas
|
|
46
|
+
|
|
47
|
+
valbridge = create_valbridge(schemas)
|
|
48
|
+
|
|
49
|
+
# Full key lookup
|
|
50
|
+
user_validator = valbridge("user:Profile")
|
|
51
|
+
|
|
52
|
+
# Validate data
|
|
53
|
+
user_validator.validate_python({"name": "Alice", "email": "alice@example.com"})
|
|
54
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# valbridge
|
|
2
|
+
|
|
3
|
+
Runtime client for valbridge-generated validators with type-safe schema lookup.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install valbridge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from valbridge import create_valbridge
|
|
15
|
+
from _valbridge import schemas
|
|
16
|
+
|
|
17
|
+
valbridge = create_valbridge(schemas)
|
|
18
|
+
|
|
19
|
+
# Full key lookup
|
|
20
|
+
user_validator = valbridge("user:Profile")
|
|
21
|
+
|
|
22
|
+
# Validate data
|
|
23
|
+
user_validator.validate_python({"name": "Alice", "email": "alice@example.com"})
|
|
24
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "valbridge"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Runtime client for valbridge-generated validators with type-safe schema lookup"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "vectorfyco" }]
|
|
9
|
+
maintainers = [{ name = "vectorfyco" }]
|
|
10
|
+
keywords = [
|
|
11
|
+
"json-schema",
|
|
12
|
+
"validation",
|
|
13
|
+
"pydantic",
|
|
14
|
+
"code-generation",
|
|
15
|
+
"runtime-client",
|
|
16
|
+
"schema",
|
|
17
|
+
"type-safety",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
dependencies = []
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/vectorfy-co/valbridge"
|
|
37
|
+
Documentation = "https://github.com/vectorfy-co/valbridge"
|
|
38
|
+
Repository = "https://github.com/vectorfy-co/valbridge"
|
|
39
|
+
Issues = "https://github.com/vectorfy-co/valbridge/issues"
|
|
40
|
+
Changelog = "https://github.com/vectorfy-co/valbridge/blob/main/CHANGELOG.md"
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["hatchling"]
|
|
49
|
+
build-backend = "hatchling.build"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/valbridge"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runtime client for valbridge-generated validators.
|
|
3
|
+
|
|
4
|
+
This module provides runtime schema lookup for validators generated by valbridge.
|
|
5
|
+
The client wraps a dictionary of validators and provides a callable interface.
|
|
6
|
+
|
|
7
|
+
Design decisions:
|
|
8
|
+
|
|
9
|
+
1. NAMESPACE:ID FORMAT - Keys use "namespace:id" format (e.g., "user:Profile")
|
|
10
|
+
matching the Go CLI output. This provides:
|
|
11
|
+
- Namespacing to avoid collisions when multiple config files define schemas
|
|
12
|
+
- Clear provenance: namespace often maps to the config filename
|
|
13
|
+
- Consistency across TypeScript and Python (same format in both)
|
|
14
|
+
|
|
15
|
+
2. NO DEFAULT NAMESPACE - Unlike the TypeScript client, Python doesn't support
|
|
16
|
+
declaration merging for type safety on shorthand lookups. Without type
|
|
17
|
+
benefits, shorthand adds API complexity without value. Full keys are clearer.
|
|
18
|
+
|
|
19
|
+
3. DICTIONARY-BASED STORAGE - Schemas stored in a dict keyed by "namespace:id".
|
|
20
|
+
Generated code creates this dict statically. Runtime lookup is O(1).
|
|
21
|
+
|
|
22
|
+
4. CALLABLE INTERFACE - Client is callable: valbridge("namespace:id")
|
|
23
|
+
This mimics function call semantics while allowing stateful configuration.
|
|
24
|
+
|
|
25
|
+
5. USER-FRIENDLY ERRORS - ValbridgeError includes "Run `valbridge generate`" hint
|
|
26
|
+
because the most common error is forgetting to regenerate after schema changes.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
from _valbridge import valbridge # Generated by valbridge CLI
|
|
30
|
+
|
|
31
|
+
User = valbridge("user:User") # Returns Pydantic model class
|
|
32
|
+
user = User.model_validate({"name": "Alice"})
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from typing import Any, Dict, Generic, Protocol, TypeVar
|
|
36
|
+
|
|
37
|
+
__all__ = ["ValbridgeClient", "ValbridgeError", "create_valbridge"]
|
|
38
|
+
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _SchemaLookup(Protocol[T_co]):
|
|
44
|
+
def __call__(self, key: str) -> T_co: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ValbridgeError(Exception):
|
|
48
|
+
"""
|
|
49
|
+
Raised when a schema cannot be found.
|
|
50
|
+
|
|
51
|
+
Includes a hint to run `valbridge generate` since the most common cause
|
|
52
|
+
is forgetting to regenerate after adding/renaming schemas.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ValbridgeClient(Generic[T]):
|
|
59
|
+
"""
|
|
60
|
+
Type-safe client for looking up schemas by namespace:id.
|
|
61
|
+
|
|
62
|
+
Generic over T to allow typing the return value. In generated code,
|
|
63
|
+
T is typically a Union of all generated model types, providing IDE
|
|
64
|
+
autocompletion for the returned validator.
|
|
65
|
+
|
|
66
|
+
The class wraps a dictionary but provides a callable interface for
|
|
67
|
+
ergonomic usage: valbridge("namespace:id") instead of valbridge.get("namespace:id")
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, schemas: Dict[str, T]) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Initialize the valbridge client.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
schemas: Dictionary of schemas keyed by "namespace:id".
|
|
76
|
+
Keys use colon separator matching CLI output format.
|
|
77
|
+
"""
|
|
78
|
+
self._schemas = schemas
|
|
79
|
+
|
|
80
|
+
def __call__(self, key: str) -> T:
|
|
81
|
+
"""
|
|
82
|
+
Look up a schema by key.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
key: Schema key in "namespace:id" format (e.g., "user:Profile")
|
|
86
|
+
The namespace is typically the config filename without extension.
|
|
87
|
+
The id is the schema's declared identifier.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The schema validator (e.g., Pydantic model class)
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValbridgeError: If schema not found. Error message includes hint
|
|
94
|
+
to run `valbridge generate` since stale code is common.
|
|
95
|
+
"""
|
|
96
|
+
if key not in self._schemas:
|
|
97
|
+
msg = f"Unknown schema: {key}. Run `valbridge generate`."
|
|
98
|
+
raise ValbridgeError(msg)
|
|
99
|
+
|
|
100
|
+
return self._schemas[key]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def create_valbridge(schemas: Dict[str, T]) -> _SchemaLookup[T]:
|
|
104
|
+
"""
|
|
105
|
+
Create an valbridge client for looking up schemas by namespace:id.
|
|
106
|
+
|
|
107
|
+
This is the public factory function used by generated code. It returns
|
|
108
|
+
a callable that provides runtime schema lookup.
|
|
109
|
+
|
|
110
|
+
Why a factory function instead of direct ValbridgeClient instantiation?
|
|
111
|
+
- Cleaner generated code: `valbridge = create_valbridge(schemas)`
|
|
112
|
+
- Hides implementation detail (class vs function doesn't matter to user)
|
|
113
|
+
- Allows future changes to internal representation without breaking API
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
schemas: Dictionary of schemas keyed by "namespace:id".
|
|
117
|
+
Generated code provides this dict with all validators.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A ValbridgeClient instance. It's callable and takes a "namespace:id"
|
|
121
|
+
key, returning the validator. Raises ValbridgeError if the key is not found.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> from valbridge import create_valbridge
|
|
125
|
+
>>> schemas = {"user:Profile": ProfileModel, "config:TSConfig": TSConfigModel}
|
|
126
|
+
>>> valbridge = create_valbridge(schemas)
|
|
127
|
+
>>>
|
|
128
|
+
>>> # Look up by full namespace:id key
|
|
129
|
+
>>> Profile = valbridge("user:Profile")
|
|
130
|
+
>>>
|
|
131
|
+
>>> # Use the validator
|
|
132
|
+
>>> user = Profile.model_validate({"name": "Alice"})
|
|
133
|
+
"""
|
|
134
|
+
client = ValbridgeClient(schemas)
|
|
135
|
+
return client
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Tests for valbridge."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from valbridge import ValbridgeError, create_valbridge
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MockValidator:
|
|
8
|
+
"""Mock validator for testing."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str):
|
|
11
|
+
self.name = name
|
|
12
|
+
|
|
13
|
+
def model_validate(self, data):
|
|
14
|
+
return data
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_full_key_lookup():
|
|
18
|
+
"""Test looking up schemas with full namespace:id keys."""
|
|
19
|
+
schemas = {
|
|
20
|
+
"user:Profile": MockValidator("Profile"),
|
|
21
|
+
"another:TSConfig": MockValidator("TSConfig"),
|
|
22
|
+
}
|
|
23
|
+
valbridge = create_valbridge(schemas)
|
|
24
|
+
|
|
25
|
+
# Should find by full key
|
|
26
|
+
assert valbridge("user:Profile").name == "Profile"
|
|
27
|
+
assert valbridge("another:TSConfig").name == "TSConfig"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_schema_not_found():
|
|
31
|
+
"""Test error when schema doesn't exist."""
|
|
32
|
+
schemas = {
|
|
33
|
+
"user:Profile": MockValidator("Profile"),
|
|
34
|
+
}
|
|
35
|
+
valbridge = create_valbridge(schemas)
|
|
36
|
+
|
|
37
|
+
# Non-existent key
|
|
38
|
+
with pytest.raises(ValbridgeError) as exc_info:
|
|
39
|
+
valbridge("user:NonExistent")
|
|
40
|
+
assert "Unknown schema: user:NonExistent" in str(exc_info.value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_empty_schemas():
|
|
44
|
+
"""Test client with empty schemas dict."""
|
|
45
|
+
valbridge = create_valbridge({})
|
|
46
|
+
|
|
47
|
+
with pytest.raises(ValbridgeError):
|
|
48
|
+
valbridge("anything")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_type_inference():
|
|
52
|
+
"""Test that client preserves type information."""
|
|
53
|
+
schemas = {
|
|
54
|
+
"user:Profile": MockValidator("Profile"),
|
|
55
|
+
}
|
|
56
|
+
valbridge = create_valbridge(schemas)
|
|
57
|
+
|
|
58
|
+
# Should return the exact validator instance
|
|
59
|
+
validator = valbridge("user:Profile")
|
|
60
|
+
assert isinstance(validator, MockValidator)
|
|
61
|
+
assert validator.name == "Profile"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_nonexistent_namespace():
|
|
65
|
+
"""Test error when namespace doesn't exist."""
|
|
66
|
+
schemas = {
|
|
67
|
+
"user:Profile": MockValidator("Profile"),
|
|
68
|
+
"another:Config": MockValidator("Config"),
|
|
69
|
+
}
|
|
70
|
+
valbridge = create_valbridge(schemas)
|
|
71
|
+
|
|
72
|
+
# Non-existent namespace should fail
|
|
73
|
+
with pytest.raises(ValbridgeError) as exc_info:
|
|
74
|
+
valbridge("nonexistent:Schema")
|
|
75
|
+
assert "Unknown schema: nonexistent:Schema" in str(exc_info.value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_client_is_callable():
|
|
79
|
+
"""Test that returned client is callable."""
|
|
80
|
+
schemas = {"user:Profile": MockValidator("Profile")}
|
|
81
|
+
valbridge = create_valbridge(schemas)
|
|
82
|
+
|
|
83
|
+
# Should be callable
|
|
84
|
+
assert callable(valbridge)
|
|
85
|
+
|
|
86
|
+
# Should work like a function
|
|
87
|
+
result = valbridge("user:Profile")
|
|
88
|
+
assert result.name == "Profile"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_multiple_clients():
|
|
92
|
+
"""Test creating multiple independent clients."""
|
|
93
|
+
schemas1 = {"user:Profile": MockValidator("Profile1")}
|
|
94
|
+
schemas2 = {"user:Profile": MockValidator("Profile2")}
|
|
95
|
+
|
|
96
|
+
valbridge1 = create_valbridge(schemas1)
|
|
97
|
+
valbridge2 = create_valbridge(schemas2)
|
|
98
|
+
|
|
99
|
+
# Should be independent
|
|
100
|
+
assert valbridge1("user:Profile").name == "Profile1"
|
|
101
|
+
assert valbridge2("user:Profile").name == "Profile2"
|