apiforge-sdk 0.1.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.
- apiforge_sdk-0.1.0/.gitignore +41 -0
- apiforge_sdk-0.1.0/Contributing.md +260 -0
- apiforge_sdk-0.1.0/LICENSE +21 -0
- apiforge_sdk-0.1.0/PKG-INFO +264 -0
- apiforge_sdk-0.1.0/README.md +203 -0
- apiforge_sdk-0.1.0/ROADMAP.md +135 -0
- apiforge_sdk-0.1.0/apiforge/__init__.py +40 -0
- apiforge_sdk-0.1.0/apiforge/cli/__init__.py +11 -0
- apiforge_sdk-0.1.0/apiforge/cli/main.py +197 -0
- apiforge_sdk-0.1.0/apiforge/core/__init__.py +29 -0
- apiforge_sdk-0.1.0/apiforge/core/client.py +255 -0
- apiforge_sdk-0.1.0/apiforge/core/config.py +62 -0
- apiforge_sdk-0.1.0/apiforge/core/credentials.py +259 -0
- apiforge_sdk-0.1.0/apiforge/core/discovery.py +105 -0
- apiforge_sdk-0.1.0/apiforge/core/exceptions.py +58 -0
- apiforge_sdk-0.1.0/apiforge/core/metadata.py +79 -0
- apiforge_sdk-0.1.0/apiforge/generators/__init__.py +78 -0
- apiforge_sdk-0.1.0/apiforge/mcp/__init__.py +12 -0
- apiforge_sdk-0.1.0/apiforge/mcp/adapter.py +92 -0
- apiforge_sdk-0.1.0/apiforge/mcp/generator.py +90 -0
- apiforge_sdk-0.1.0/apiforge/plugins/__init__.py +97 -0
- apiforge_sdk-0.1.0/apiforge/plugins/base.py +12 -0
- apiforge_sdk-0.1.0/apiforge/plugins/discord/__init__.py +88 -0
- apiforge_sdk-0.1.0/apiforge/plugins/github/__init__.py +122 -0
- apiforge_sdk-0.1.0/apiforge/plugins/notion/__init__.py +104 -0
- apiforge_sdk-0.1.0/docs/Architecture.md +270 -0
- apiforge_sdk-0.1.0/pyproject.toml +136 -0
- apiforge_sdk-0.1.0/tests/__init__.py +0 -0
- apiforge_sdk-0.1.0/tests/conftest.py +87 -0
- apiforge_sdk-0.1.0/tests/test_core/__init__.py +0 -0
- apiforge_sdk-0.1.0/tests/test_core/test_client.py +61 -0
- apiforge_sdk-0.1.0/tests/test_core/test_credentials.py +86 -0
- apiforge_sdk-0.1.0/tests/test_core/test_discovery.py +53 -0
- apiforge_sdk-0.1.0/tests/test_plugins/__init__.py +0 -0
- apiforge_sdk-0.1.0/tests/test_plugins/test_first_party.py +118 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Test / coverage artifacts
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
htmlcov/
|
|
23
|
+
coverage.xml
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
|
30
|
+
*.swp
|
|
31
|
+
*.swo
|
|
32
|
+
|
|
33
|
+
# OS
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
36
|
+
|
|
37
|
+
# Local config
|
|
38
|
+
.env
|
|
39
|
+
.env.local
|
|
40
|
+
config.local.toml
|
|
41
|
+
*.local.toml
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Contributing to APIForge
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving APIForge! This document walks
|
|
4
|
+
you through the plugin development workflow, the codebase layout,
|
|
5
|
+
the conventions we use, and how to send a pull request.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Code of conduct
|
|
10
|
+
|
|
11
|
+
Be respectful, assume good faith, and remember there is a person on
|
|
12
|
+
the other side of the screen. We follow the
|
|
13
|
+
[Contributor Covenant](https://www.contributor-covenant.org/).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Setting up your environment
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/apiforge/apiforge.git
|
|
21
|
+
cd apiforge
|
|
22
|
+
python -m venv .venv
|
|
23
|
+
source .venv/bin/activate
|
|
24
|
+
pip install -e ".[dev,test]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run the tests:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pytest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Lint:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ruff check apiforge tests
|
|
37
|
+
ruff format --check apiforge tests
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Type-check:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
mypy apiforge
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
All three checks run in CI; see `.github/workflows/ci.yml`.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Code layout
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
apiforge/
|
|
54
|
+
├── core/ ← client, config, credentials, discovery, metadata
|
|
55
|
+
├── plugins/ ← first-party plugins (GitHub, Discord, Notion)
|
|
56
|
+
├── mcp/ ← MCP adapter base + generator (Phase 4)
|
|
57
|
+
├── generators/ ← OpenAPI generator (Phase 2)
|
|
58
|
+
└── cli/ ← the `apiforge` command
|
|
59
|
+
|
|
60
|
+
tests/
|
|
61
|
+
├── test_core/
|
|
62
|
+
└── test_plugins/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The rule of thumb:
|
|
66
|
+
|
|
67
|
+
- Cross-cutting concerns go in `core/`.
|
|
68
|
+
- Each service has its own plugin directory under `plugins/`.
|
|
69
|
+
- Adapters and generators are interface-first today; they grow
|
|
70
|
+
alongside the plugins they serve.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Adding a new plugin
|
|
75
|
+
|
|
76
|
+
The fastest path: copy an existing plugin (`apiforge/plugins/github/`)
|
|
77
|
+
and adapt it. The full checklist:
|
|
78
|
+
|
|
79
|
+
### 1. Create the package
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
apiforge/plugins/<name>/__init__.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a standalone distribution, the directory name should be
|
|
86
|
+
`<name>.py` or a package. The class name must be unique.
|
|
87
|
+
|
|
88
|
+
### 2. Define metadata
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from apiforge.plugins.base import BasePlugin
|
|
92
|
+
from apiforge.core.metadata import AuthType, OperationMetadata, PluginMetadata
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class StripePlugin(BasePlugin):
|
|
96
|
+
metadata = PluginMetadata(
|
|
97
|
+
name="stripe",
|
|
98
|
+
version="0.1.0",
|
|
99
|
+
description="Stripe payments API.",
|
|
100
|
+
auth_type=AuthType.API_KEY,
|
|
101
|
+
base_url="https://api.stripe.com/v1",
|
|
102
|
+
operations=(
|
|
103
|
+
OperationMetadata(
|
|
104
|
+
name="create_charge",
|
|
105
|
+
description="Charge a customer.",
|
|
106
|
+
parameters={"amount": "int", "currency": "str"},
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`PluginMetadata` is strict — extra fields are rejected. The fields
|
|
113
|
+
you should fill in:
|
|
114
|
+
|
|
115
|
+
- `name` — unique, lowercase, matches the entry-point name.
|
|
116
|
+
- `version` — semver.
|
|
117
|
+
- `description` — one sentence, used in `apiforge list`.
|
|
118
|
+
- `auth_type` — one of `token`, `api_key`, `oauth2`, `basic`, `none`.
|
|
119
|
+
- `base_url` — the upstream root; some plugins don't have one.
|
|
120
|
+
- `operations` — at minimum the operations you implement.
|
|
121
|
+
- `openapi_url` — if the upstream publishes a spec.
|
|
122
|
+
|
|
123
|
+
### 3. Implement operations
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
async def create_charge(self, amount: int, currency: str) -> dict:
|
|
127
|
+
token = self.require_credential()
|
|
128
|
+
response = await self.http.post(
|
|
129
|
+
f"{self.metadata.base_url}/charges",
|
|
130
|
+
json={"amount": amount, "currency": currency},
|
|
131
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
132
|
+
)
|
|
133
|
+
response.raise_for_status()
|
|
134
|
+
return response.json()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Conventions:
|
|
138
|
+
|
|
139
|
+
- All operations are `async def`.
|
|
140
|
+
- Use `self.http`, not your own client — we share the pool.
|
|
141
|
+
- Use `self.require_credential()` if a credential is mandatory.
|
|
142
|
+
- Use `self.get_credential()` if it's optional (e.g. for read-only
|
|
143
|
+
endpoints).
|
|
144
|
+
- Don't catch `HTTPStatusError` — let it propagate so the CLI can
|
|
145
|
+
show the upstream status.
|
|
146
|
+
|
|
147
|
+
### 4. Wire the entry point
|
|
148
|
+
|
|
149
|
+
In your `pyproject.toml`:
|
|
150
|
+
|
|
151
|
+
```toml
|
|
152
|
+
[project.entry-points."apiforge.plugins"]
|
|
153
|
+
stripe = "apiforge_plugin_stripe:StripePlugin"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you're adding to APIForge itself, edit the top-level
|
|
157
|
+
`pyproject.toml` instead.
|
|
158
|
+
|
|
159
|
+
### 5. Add tests
|
|
160
|
+
|
|
161
|
+
Cover at minimum:
|
|
162
|
+
|
|
163
|
+
- The plugin's happy path, with `httpx` mocked via `respx`.
|
|
164
|
+
- The credential-missing path, if your plugin requires one.
|
|
165
|
+
- Auth headers — assert the right `Authorization` header is sent.
|
|
166
|
+
|
|
167
|
+
See `tests/test_plugins/test_first_party.py` for examples.
|
|
168
|
+
|
|
169
|
+
### 6. Document
|
|
170
|
+
|
|
171
|
+
Add a one-paragraph section to the README under "Plugins".
|
|
172
|
+
Reference the upstream API docs.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Adding an MCP adapter (Phase 4 preview)
|
|
177
|
+
|
|
178
|
+
When the MCP generation machinery lands, your plugin will need to
|
|
179
|
+
declare an adapter:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from apiforge.mcp.adapter import BaseMCPAdapter
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class StripeMCPAdapter(BaseMCPAdapter):
|
|
186
|
+
def tool_definitions(self) -> list[dict]:
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
"name": "stripe__create_charge",
|
|
190
|
+
"description": "Charge a customer.",
|
|
191
|
+
"inputSchema": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"properties": {
|
|
194
|
+
"amount": {"type": "integer"},
|
|
195
|
+
"currency": {"type": "string"},
|
|
196
|
+
},
|
|
197
|
+
"required": ["amount", "currency"],
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class StripePlugin(BasePlugin):
|
|
204
|
+
metadata = PluginMetadata(name="stripe", version="0.1.0", ...)
|
|
205
|
+
MCP_ADAPTER_CLASS = StripeMCPAdapter
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The contract is stable today; the generator walks the registry
|
|
209
|
+
looking for `MCP_ADAPTER_CLASS`.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Style
|
|
214
|
+
|
|
215
|
+
- **Python 3.12+** syntax — `type` aliases, `match`, generics.
|
|
216
|
+
- **Type hints everywhere** public. Internal helpers can use less
|
|
217
|
+
formal types where it helps readability.
|
|
218
|
+
- **Pydantic models** for any data that crosses a boundary (config,
|
|
219
|
+
metadata, request/response shapes).
|
|
220
|
+
- **Ruff** for linting and formatting. Run `ruff format` before
|
|
221
|
+
committing.
|
|
222
|
+
- **mypy --strict** is the type-check setting.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Pull request checklist
|
|
227
|
+
|
|
228
|
+
- [ ] Tests pass locally (`pytest`)
|
|
229
|
+
- [ ] Ruff is clean (`ruff check apiforge tests`)
|
|
230
|
+
- [ ] mypy is clean (`mypy apiforge`)
|
|
231
|
+
- [ ] New code is type-hinted
|
|
232
|
+
- [ ] New public surface is documented (docstrings + README update
|
|
233
|
+
if user-facing)
|
|
234
|
+
- [ ] If you added a plugin, the entry point is wired in
|
|
235
|
+
`pyproject.toml`
|
|
236
|
+
- [ ] CHANGELOG entry (we'll add one once we're out of 0.0.x)
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Release process
|
|
241
|
+
|
|
242
|
+
We use `hatch` for builds. To cut a release:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Bump version, update CHANGELOG.
|
|
246
|
+
# Tag and push:
|
|
247
|
+
git tag -a v0.2.0 -m "v0.2.0"
|
|
248
|
+
git push origin v0.2.0
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
CI builds the wheel and sdist; we publish from CI with PyPI trusted
|
|
252
|
+
publishing.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Questions?
|
|
257
|
+
|
|
258
|
+
Open a GitHub Discussion. Bug reports go in Issues. Security
|
|
259
|
+
disclosures: see `SECURITY.md` (TODO — please open an issue for now
|
|
260
|
+
and we'll route it).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 APIForge Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apiforge-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified Python interface for multiple APIs with plugin architecture
|
|
5
|
+
Project-URL: Homepage, https://github.com/apiforge/apiforge
|
|
6
|
+
Project-URL: Documentation, https://github.com/apiforge/apiforge/blob/main/README.md
|
|
7
|
+
Project-URL: Repository, https://github.com/apiforge/apiforge
|
|
8
|
+
Project-URL: Issues, https://github.com/apiforge/apiforge/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/apiforge/apiforge/releases
|
|
10
|
+
Author-email: APIForge Contributors <maintainers@apiforge.dev>
|
|
11
|
+
Maintainer-email: APIForge Contributors <maintainers@apiforge.dev>
|
|
12
|
+
License: MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2026 APIForge Contributors
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Keywords: api,discord,github,integration,mcp,notion,openapi,plugins,sdk
|
|
35
|
+
Classifier: Development Status :: 3 - Alpha
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Operating System :: OS Independent
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
43
|
+
Classifier: Typing :: Typed
|
|
44
|
+
Requires-Python: >=3.12
|
|
45
|
+
Requires-Dist: click>=8.1.7
|
|
46
|
+
Requires-Dist: httpx>=0.27.0
|
|
47
|
+
Requires-Dist: keyring>=24.3.0
|
|
48
|
+
Requires-Dist: pydantic-settings>=2.2.0
|
|
49
|
+
Requires-Dist: pydantic>=2.6.0
|
|
50
|
+
Requires-Dist: rich>=13.7.0
|
|
51
|
+
Provides-Extra: dev
|
|
52
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
53
|
+
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
54
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
55
|
+
Provides-Extra: test
|
|
56
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
57
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
|
|
58
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
59
|
+
Requires-Dist: respx>=0.21.0; extra == 'test'
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
|
|
62
|
+
# APIForge
|
|
63
|
+
|
|
64
|
+
> One Python interface for thousands of APIs.
|
|
65
|
+
|
|
66
|
+
[](LICENSE)
|
|
67
|
+
[](https://www.python.org/downloads/)
|
|
68
|
+
[](https://github.com/apiforge/apiforge/actions/workflows/ci.yml)
|
|
69
|
+
|
|
70
|
+
APIForge is a plugin-based Python framework that gives you a single,
|
|
71
|
+
consistent way to talk to many APIs. The first release ships with
|
|
72
|
+
plugins for **GitHub**, **Discord**, and **Notion**; the architecture
|
|
73
|
+
is designed to grow to thousands.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from apiforge import APIForge
|
|
77
|
+
|
|
78
|
+
client = APIForge()
|
|
79
|
+
client.configure(
|
|
80
|
+
github_token="ghp_...",
|
|
81
|
+
discord_token="...",
|
|
82
|
+
notion_token="...",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# All of these look the same — no separate SDK to learn per service.
|
|
86
|
+
user = client.github.get_user("octocat")
|
|
87
|
+
msg = client.discord.send_message(channel_id="...", content="hi")
|
|
88
|
+
pages = client.notion.list_pages()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The same client also exposes a CLI:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
apiforge list
|
|
95
|
+
apiforge plugins
|
|
96
|
+
apiforge info github
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Why APIForge?
|
|
102
|
+
|
|
103
|
+
If you integrate with five services, you install five SDKs, learn
|
|
104
|
+
five authentication schemes, and maintain five upgrade cycles.
|
|
105
|
+
APIForge collapses that into one.
|
|
106
|
+
|
|
107
|
+
- **One import.** `from apiforge import APIForge`.
|
|
108
|
+
- **One auth surface.** Pass tokens once; they reach every plugin.
|
|
109
|
+
- **One plugin contract.** Adding a new service is a small, well-defined job.
|
|
110
|
+
- **One way to fail.** A small exception hierarchy that you can catch in one place.
|
|
111
|
+
- **One direction.** Async-first under the hood, with sync wrappers for ergonomics.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Installation
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install apiforge
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The base package installs the three first-party plugins
|
|
122
|
+
(GitHub, Discord, Notion) automatically. Additional plugins are
|
|
123
|
+
distributed as separate packages, e.g.:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install apiforge-plugin-stripe
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Each one registers itself via the standard `apiforge.plugins`
|
|
130
|
+
entry-point group — there's nothing to wire up in your code.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Quickstart
|
|
135
|
+
|
|
136
|
+
### Sync usage
|
|
137
|
+
|
|
138
|
+
The simplest path. Every method on a plugin is awaitable under the
|
|
139
|
+
hood, but the client exposes a transparent sync wrapper, so you don't
|
|
140
|
+
need an event loop for one-off scripts:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from apiforge import APIForge
|
|
144
|
+
|
|
145
|
+
client = APIForge()
|
|
146
|
+
client.configure(github_token="ghp_...")
|
|
147
|
+
|
|
148
|
+
user = client.github.get_user("octocat")
|
|
149
|
+
print(user["login"])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Async usage
|
|
153
|
+
|
|
154
|
+
For servers, batched jobs, or anything that benefits from concurrency:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
from apiforge import APIForge
|
|
159
|
+
|
|
160
|
+
async def main() -> None:
|
|
161
|
+
async with APIForge() as client:
|
|
162
|
+
client.configure(github_token="ghp_...")
|
|
163
|
+
user, repos = await asyncio.gather(
|
|
164
|
+
client.github.get_user("octocat"),
|
|
165
|
+
client.github.list_repos("octocat", limit=5),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
asyncio.run(main())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Credentials
|
|
172
|
+
|
|
173
|
+
APIForge looks for credentials in this order:
|
|
174
|
+
|
|
175
|
+
1. **Explicit** — `client.configure(github_token="...")`
|
|
176
|
+
2. **Environment** — `APIFORGE_GITHUB_TOKEN=...`
|
|
177
|
+
3. **Keyring** — the OS secure store (`keyring` package)
|
|
178
|
+
4. **Config file** — `~/.config/apiforge/credentials.toml`
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Store a token in your OS keyring:
|
|
182
|
+
python -c "from apiforge import APIForge; APIForge().credentials.store('github', 'ghp_...')"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## CLI
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
apiforge list # all registered plugins
|
|
191
|
+
apiforge plugins # alias for `list`
|
|
192
|
+
apiforge info github # details on one plugin
|
|
193
|
+
apiforge info notion --json # machine-readable
|
|
194
|
+
|
|
195
|
+
# Coming in later phases:
|
|
196
|
+
apiforge generate openapi.yaml --name myplugin # Phase 2
|
|
197
|
+
apiforge install apiforge-plugin-stripe # Phase 5
|
|
198
|
+
apiforge update # Phase 5
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Writing a Plugin
|
|
204
|
+
|
|
205
|
+
A plugin is a class that subclasses `BasePlugin` and declares a
|
|
206
|
+
`PluginMetadata`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from apiforge.plugins.base import BasePlugin
|
|
210
|
+
from apiforge.core.metadata import AuthType, OperationMetadata, PluginMetadata
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class StripePlugin(BasePlugin):
|
|
214
|
+
metadata = PluginMetadata(
|
|
215
|
+
name="stripe",
|
|
216
|
+
version="0.1.0",
|
|
217
|
+
description="Stripe payments API.",
|
|
218
|
+
auth_type=AuthType.API_KEY,
|
|
219
|
+
base_url="https://api.stripe.com/v1",
|
|
220
|
+
operations=(
|
|
221
|
+
OperationMetadata(name="create_charge", parameters={"amount": "int"}),
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
async def setup(self) -> None:
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
async def create_charge(self, amount: int) -> dict:
|
|
229
|
+
...
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Then register it in your package's `pyproject.toml`:
|
|
233
|
+
|
|
234
|
+
```toml
|
|
235
|
+
[project.entry-points."apiforge.plugins"]
|
|
236
|
+
stripe = "apiforge_plugin_stripe:StripePlugin"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Once installed, `client.stripe.create_charge(...)` works out of the
|
|
240
|
+
box. See [Contributing.md](Contributing.md) for the full checklist.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Project Status
|
|
245
|
+
|
|
246
|
+
APIForge is in **alpha**. The core framework, the credential
|
|
247
|
+
manager, the CLI, and the three first-party plugins are stable
|
|
248
|
+
enough for everyday use, but the API may still evolve.
|
|
249
|
+
|
|
250
|
+
See [ROADMAP.md](ROADMAP.md) for what's next.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Documentation
|
|
255
|
+
|
|
256
|
+
- [Architecture.md](Architecture.md) — the design, the layers, the contracts.
|
|
257
|
+
- [Contributing.md](Contributing.md) — how to write and ship a plugin.
|
|
258
|
+
- [ROADMAP.md](ROADMAP.md) — Phase 1 through Phase 5.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT — see [LICENSE](LICENSE).
|