vclient 0.2.0__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.
vclient-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: vclient
3
+ Version: 0.2.0
4
+ Summary: Auto-discover API schemas and generate type-safe clients in 6 languages
5
+ Home-page: https://github.com/yc-trails/dynamic-api-adapter
6
+ Author: Dynamic API Adapter
7
+ Author-email: Dynamic API Adapter <dev@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/yc-trails/dynamic-api-adapter
10
+ Project-URL: Repository, https://github.com/yc-trails/dynamic-api-adapter
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: aiohttp>=3.9.5
22
+ Requires-Dist: pydantic>=2.7.0
23
+ Requires-Dist: click>=8.1.7
24
+ Requires-Dist: jinja2>=3.1.2
25
+ Requires-Dist: jsonschema>=4.21.1
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.4.4; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
30
+ Requires-Dist: pyinstaller>=6.0.0; extra == "dev"
31
+ Requires-Dist: wheel>=0.42.0; extra == "dev"
32
+ Dynamic: author
33
+ Dynamic: home-page
34
+ Dynamic: requires-python
35
+
36
+ # vclient — Auto-Generate API Clients
37
+
38
+ Auto-discover OpenAPI endpoints and generate **type-safe client libraries in 6 languages** instantly.
39
+
40
+ ## Features
41
+
42
+ ✨ **Single Command:**
43
+ ```bash
44
+ vclient infer https://api.example.com
45
+ ```
46
+
47
+ Generates:
48
+ - 🐍 **Python** (httpx + Pydantic models)
49
+ - 📘 **TypeScript** (Fetch API + Zod validation)
50
+ - 🐹 **Go** (net/http)
51
+ - 🦀 **Rust** (reqwest + serde)
52
+ - ☕ **Java** (HttpClient)
53
+ - 💎 **Ruby** (Faraday)
54
+
55
+ ## How It Works
56
+
57
+ 1. **Discover** — Probe API endpoints and extract OpenAPI specs
58
+ 2. **Infer** — Analyze responses → infer complete JSON schemas
59
+ 3. **Cache** — Store schemas, detect drift on next run
60
+ 4. **Generate** — Produce idiomatic clients for all 6 languages
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ # via PyPI
66
+ pip install vclient
67
+
68
+ # via Homebrew (after setup)
69
+ brew install yc-trails/vclient/vclient
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```bash
75
+ # Infer schema from live API
76
+ vclient infer https://be.vyonica.com --max-endpoints 25 \
77
+ --gen-python --gen-typescript --gen-go --gen-rust --gen-java --gen-ruby
78
+
79
+ # Generates: schema.json + client.py + client.ts + client.go + client.rs + Client.java + client.rb
80
+ ```
81
+
82
+ ## Generate from Existing Schema
83
+
84
+ ```bash
85
+ vclient codegen schema.json \
86
+ --python client.py \
87
+ --typescript client.ts \
88
+ --go client.go \
89
+ --rust client.rs \
90
+ --java Client.java \
91
+ --ruby client.rb
92
+ ```
93
+
94
+ ## Architecture
95
+
96
+ ```
97
+ vclient/
98
+ ├── src/
99
+ │ ├── cli.py # Command-line interface
100
+ │ ├── sampler.py # Endpoint discovery + HTTP sampling
101
+ │ ├── inferrer.py # Schema inference from responses
102
+ │ ├── codegen.py # 6-language code generation
103
+ │ └── cache.py # SQLite schema caching + drift detection
104
+ ├── tests/ # 55+ unit tests
105
+ ├── test-clients/ # Generated examples (153 Vyonica endpoints)
106
+ └── Formula/vclient.rb # Homebrew formula
107
+ ```
108
+
109
+ ## Testing
110
+
111
+ All generators tested on real-world APIs:
112
+
113
+ ```bash
114
+ # Run test suite
115
+ pytest tests/ -v
116
+
117
+ # Test with Vyonica API (153 endpoints)
118
+ vclient infer https://be.vyonica.com --no-cache
119
+ ```
120
+
121
+ ## What's Next
122
+
123
+ - [ ] Windows binary (Windows Subsystem for Linux compatible)
124
+ - [ ] Kotlin support (JVM ecosystem)
125
+ - [ ] GraphQL support
126
+ - [ ] API documentation generation
127
+
128
+ ---
129
+
130
+ **Made for:** AI agents, SaaS integrators, enterprises calling legacy APIs without OpenAPI specs.
@@ -0,0 +1,95 @@
1
+ # vclient — Auto-Generate API Clients
2
+
3
+ Auto-discover OpenAPI endpoints and generate **type-safe client libraries in 6 languages** instantly.
4
+
5
+ ## Features
6
+
7
+ ✨ **Single Command:**
8
+ ```bash
9
+ vclient infer https://api.example.com
10
+ ```
11
+
12
+ Generates:
13
+ - 🐍 **Python** (httpx + Pydantic models)
14
+ - 📘 **TypeScript** (Fetch API + Zod validation)
15
+ - 🐹 **Go** (net/http)
16
+ - 🦀 **Rust** (reqwest + serde)
17
+ - ☕ **Java** (HttpClient)
18
+ - 💎 **Ruby** (Faraday)
19
+
20
+ ## How It Works
21
+
22
+ 1. **Discover** — Probe API endpoints and extract OpenAPI specs
23
+ 2. **Infer** — Analyze responses → infer complete JSON schemas
24
+ 3. **Cache** — Store schemas, detect drift on next run
25
+ 4. **Generate** — Produce idiomatic clients for all 6 languages
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ # via PyPI
31
+ pip install vclient
32
+
33
+ # via Homebrew (after setup)
34
+ brew install yc-trails/vclient/vclient
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```bash
40
+ # Infer schema from live API
41
+ vclient infer https://be.vyonica.com --max-endpoints 25 \
42
+ --gen-python --gen-typescript --gen-go --gen-rust --gen-java --gen-ruby
43
+
44
+ # Generates: schema.json + client.py + client.ts + client.go + client.rs + Client.java + client.rb
45
+ ```
46
+
47
+ ## Generate from Existing Schema
48
+
49
+ ```bash
50
+ vclient codegen schema.json \
51
+ --python client.py \
52
+ --typescript client.ts \
53
+ --go client.go \
54
+ --rust client.rs \
55
+ --java Client.java \
56
+ --ruby client.rb
57
+ ```
58
+
59
+ ## Architecture
60
+
61
+ ```
62
+ vclient/
63
+ ├── src/
64
+ │ ├── cli.py # Command-line interface
65
+ │ ├── sampler.py # Endpoint discovery + HTTP sampling
66
+ │ ├── inferrer.py # Schema inference from responses
67
+ │ ├── codegen.py # 6-language code generation
68
+ │ └── cache.py # SQLite schema caching + drift detection
69
+ ├── tests/ # 55+ unit tests
70
+ ├── test-clients/ # Generated examples (153 Vyonica endpoints)
71
+ └── Formula/vclient.rb # Homebrew formula
72
+ ```
73
+
74
+ ## Testing
75
+
76
+ All generators tested on real-world APIs:
77
+
78
+ ```bash
79
+ # Run test suite
80
+ pytest tests/ -v
81
+
82
+ # Test with Vyonica API (153 endpoints)
83
+ vclient infer https://be.vyonica.com --no-cache
84
+ ```
85
+
86
+ ## What's Next
87
+
88
+ - [ ] Windows binary (Windows Subsystem for Linux compatible)
89
+ - [ ] Kotlin support (JVM ecosystem)
90
+ - [ ] GraphQL support
91
+ - [ ] API documentation generation
92
+
93
+ ---
94
+
95
+ **Made for:** AI agents, SaaS integrators, enterprises calling legacy APIs without OpenAPI specs.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=65", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vclient"
7
+ version = "0.2.0"
8
+ description = "Auto-discover API schemas and generate type-safe clients in 6 languages"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [
12
+ {name = "Dynamic API Adapter", email = "dev@example.com"}
13
+ ]
14
+ license = {text = "MIT"}
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+ dependencies = [
25
+ "httpx>=0.27.0",
26
+ "aiohttp>=3.9.5",
27
+ "pydantic>=2.7.0",
28
+ "click>=8.1.7",
29
+ "jinja2>=3.1.2",
30
+ "jsonschema>=4.21.1",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=7.4.4",
36
+ "pytest-asyncio>=0.23.0",
37
+ "pytest-cov>=4.1.0",
38
+ "pyinstaller>=6.0.0",
39
+ "wheel>=0.42.0",
40
+ ]
41
+
42
+ [project.scripts]
43
+ vclient = "src.cli:cli"
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/yc-trails/dynamic-api-adapter"
47
+ Repository = "https://github.com/yc-trails/dynamic-api-adapter"
48
+
49
+ [tool.setuptools]
50
+ packages = ["src"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ asyncio_mode = "auto"
55
+
56
+ [tool.pyinstaller]
57
+ # PyInstaller configuration
58
+ hidden_imports = ["jinja2"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
vclient-0.2.0/setup.py ADDED
@@ -0,0 +1,48 @@
1
+ """Setup configuration for Dynamic API Adapter."""
2
+ from setuptools import setup, find_packages
3
+
4
+ with open("README.md", "r", encoding="utf-8") as f:
5
+ long_description = f.read()
6
+
7
+ setup(
8
+ name="vclient",
9
+ version="0.2.0",
10
+ description="Auto-discover API schemas and generate type-safe clients in 6 languages",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ author="Dynamic API Adapter",
14
+ url="https://github.com/yc-trails/dynamic-api-adapter",
15
+ packages=find_packages(),
16
+ python_requires=">=3.10",
17
+ install_requires=[
18
+ "httpx>=0.27.0",
19
+ "aiohttp>=3.9.5",
20
+ "pydantic>=2.7.0",
21
+ "click>=8.1.7",
22
+ "jinja2>=3.1.2",
23
+ "jsonschema>=4.21.1",
24
+ ],
25
+ extras_require={
26
+ "dev": [
27
+ "pytest>=7.4.4",
28
+ "pytest-asyncio>=0.23.0",
29
+ "pytest-cov>=4.1.0",
30
+ "pyinstaller>=6.0.0",
31
+ "wheel>=0.42.0",
32
+ ],
33
+ },
34
+ entry_points={
35
+ "console_scripts": [
36
+ "vclient=src.cli:cli",
37
+ ],
38
+ },
39
+ classifiers=[
40
+ "Development Status :: 3 - Alpha",
41
+ "Intended Audience :: Developers",
42
+ "License :: OSI Approved :: MIT License",
43
+ "Programming Language :: Python :: 3",
44
+ "Programming Language :: Python :: 3.10",
45
+ "Programming Language :: Python :: 3.11",
46
+ "Programming Language :: Python :: 3.12",
47
+ ],
48
+ )
@@ -0,0 +1,15 @@
1
+ """Dynamic API Adapter - Auto-discover and adapt to API schemas."""
2
+
3
+ from .inferrer import SchemaInferrer, infer_property_schema, infer_object_schema
4
+ from .sampler import EndpointSampler
5
+ from .codegen import ClientGenerator
6
+
7
+ __all__ = [
8
+ "SchemaInferrer",
9
+ "EndpointSampler",
10
+ "ClientGenerator",
11
+ "infer_property_schema",
12
+ "infer_object_schema",
13
+ ]
14
+
15
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Entry point for vclient CLI when run as a module or executable."""
2
+ from src.cli import cli
3
+
4
+ if __name__ == "__main__":
5
+ cli()
@@ -0,0 +1,159 @@
1
+ """SQLite-based schema caching."""
2
+ import sqlite3
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Any, Dict, Optional
7
+
8
+ DEFAULT_DB_PATH = Path.home() / ".api-adapter" / "cache.db"
9
+
10
+
11
+ class SchemaCache:
12
+ """Persistent schema cache using SQLite."""
13
+
14
+ def __init__(self, db_path: str | Path | None = None) -> None:
15
+ self.db_path = Path(db_path) if db_path else DEFAULT_DB_PATH
16
+ self._conn: sqlite3.Connection | None = None
17
+
18
+ def __enter__(self) -> "SchemaCache":
19
+ self.connect()
20
+ return self
21
+
22
+ def __exit__(self, *args) -> None:
23
+ self.close()
24
+
25
+ def connect(self) -> None:
26
+ """Open connection and initialize schema."""
27
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
28
+ self._conn = sqlite3.connect(str(self.db_path))
29
+ self._conn.row_factory = sqlite3.Row
30
+ self._init_schema()
31
+
32
+ def close(self) -> None:
33
+ """Commit and close connection."""
34
+ if self._conn:
35
+ self._conn.commit()
36
+ self._conn.close()
37
+ self._conn = None
38
+
39
+ def _init_schema(self) -> None:
40
+ """Create tables if they don't exist."""
41
+ if not self._conn:
42
+ raise RuntimeError("Not connected to database")
43
+
44
+ cursor = self._conn.cursor()
45
+
46
+ cursor.execute("""
47
+ CREATE TABLE IF NOT EXISTS schema_snapshots (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ base_url TEXT NOT NULL,
50
+ captured_at TEXT NOT NULL,
51
+ schema_json TEXT NOT NULL
52
+ )
53
+ """)
54
+
55
+ cursor.execute("""
56
+ CREATE INDEX IF NOT EXISTS idx_snapshots_url
57
+ ON schema_snapshots(base_url)
58
+ """)
59
+
60
+ cursor.execute("""
61
+ CREATE TABLE IF NOT EXISTS drift_events (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ base_url TEXT NOT NULL,
64
+ detected_at TEXT NOT NULL,
65
+ old_snapshot_id INTEGER NOT NULL REFERENCES schema_snapshots(id),
66
+ new_snapshot_id INTEGER NOT NULL REFERENCES schema_snapshots(id),
67
+ similarity REAL NOT NULL,
68
+ diff_json TEXT NOT NULL
69
+ )
70
+ """)
71
+
72
+ self._conn.commit()
73
+
74
+ def save_snapshot(self, base_url: str, schema: Dict[str, Any]) -> int:
75
+ """Save schema snapshot and return row id."""
76
+ if not self._conn:
77
+ raise RuntimeError("Not connected to database")
78
+
79
+ cursor = self._conn.cursor()
80
+ now = datetime.now(timezone.utc).isoformat()
81
+ schema_json = json.dumps(schema, default=str)
82
+
83
+ cursor.execute(
84
+ "INSERT INTO schema_snapshots (base_url, captured_at, schema_json) VALUES (?, ?, ?)",
85
+ (base_url, now, schema_json)
86
+ )
87
+ self._conn.commit()
88
+ return cursor.lastrowid
89
+
90
+ def latest_snapshot(self, base_url: str) -> Optional[Dict[str, Any]]:
91
+ """Get most recent snapshot for base_url."""
92
+ if not self._conn:
93
+ raise RuntimeError("Not connected to database")
94
+
95
+ cursor = self._conn.cursor()
96
+ cursor.execute(
97
+ "SELECT schema_json FROM schema_snapshots WHERE base_url=? ORDER BY captured_at DESC LIMIT 1",
98
+ (base_url,)
99
+ )
100
+ row = cursor.fetchone()
101
+ if row:
102
+ return json.loads(row[0])
103
+ return None
104
+
105
+ def all_snapshots(self, base_url: str) -> list[Dict[str, Any]]:
106
+ """Get all snapshots for base_url ordered by captured_at DESC."""
107
+ if not self._conn:
108
+ raise RuntimeError("Not connected to database")
109
+
110
+ cursor = self._conn.cursor()
111
+ cursor.execute(
112
+ "SELECT schema_json FROM schema_snapshots WHERE base_url=? ORDER BY captured_at DESC",
113
+ (base_url,)
114
+ )
115
+ return [json.loads(row[0]) for row in cursor.fetchall()]
116
+
117
+ def save_drift_event(
118
+ self,
119
+ base_url: str,
120
+ old_id: int,
121
+ new_id: int,
122
+ similarity: float,
123
+ diff: Dict[str, Any],
124
+ ) -> int:
125
+ """Save drift event and return row id."""
126
+ if not self._conn:
127
+ raise RuntimeError("Not connected to database")
128
+
129
+ cursor = self._conn.cursor()
130
+ now = datetime.now(timezone.utc).isoformat()
131
+ diff_json = json.dumps(diff, default=str)
132
+
133
+ cursor.execute(
134
+ "INSERT INTO drift_events (base_url, detected_at, old_snapshot_id, new_snapshot_id, similarity, diff_json) "
135
+ "VALUES (?, ?, ?, ?, ?, ?)",
136
+ (base_url, now, old_id, new_id, similarity, diff_json)
137
+ )
138
+ self._conn.commit()
139
+ return cursor.lastrowid
140
+
141
+ def drift_history(self, base_url: str) -> list[Dict[str, Any]]:
142
+ """Get all drift events for base_url ordered by detected_at DESC."""
143
+ if not self._conn:
144
+ raise RuntimeError("Not connected to database")
145
+
146
+ cursor = self._conn.cursor()
147
+ cursor.execute(
148
+ "SELECT id, detected_at, similarity, diff_json FROM drift_events WHERE base_url=? ORDER BY detected_at DESC",
149
+ (base_url,)
150
+ )
151
+ results = []
152
+ for row in cursor.fetchall():
153
+ results.append({
154
+ "id": row[0],
155
+ "detected_at": row[1],
156
+ "similarity": row[2],
157
+ "diff": json.loads(row[3])
158
+ })
159
+ return results