nabu-forge 1.0.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.
Files changed (102) hide show
  1. nabu_forge-1.0.0/.github/workflows/ci.yml +56 -0
  2. nabu_forge-1.0.0/.gitignore +58 -0
  3. nabu_forge-1.0.0/LICENSE +21 -0
  4. nabu_forge-1.0.0/PKG-INFO +228 -0
  5. nabu_forge-1.0.0/README.md +209 -0
  6. nabu_forge-1.0.0/architecture/assets/compiler-pipeline.drawio.png +0 -0
  7. nabu_forge-1.0.0/architecture/compiler-pipeline.md +128 -0
  8. nabu_forge-1.0.0/architecture/development-plan.md +151 -0
  9. nabu_forge-1.0.0/architecture/diagnostics-and-cli.md +142 -0
  10. nabu_forge-1.0.0/architecture/generated-package.md +234 -0
  11. nabu_forge-1.0.0/architecture/overview.md +44 -0
  12. nabu_forge-1.0.0/architecture/product-requirements.md +113 -0
  13. nabu_forge-1.0.0/pyproject.toml +43 -0
  14. nabu_forge-1.0.0/samples/university/nabu.toml +7 -0
  15. nabu_forge-1.0.0/samples/university/operations/courses.graphql +36 -0
  16. nabu_forge-1.0.0/samples/university/operations/fragments.graphql +42 -0
  17. nabu_forge-1.0.0/samples/university/operations/get_student.graphql +14 -0
  18. nabu_forge-1.0.0/samples/university/operations/get_student_details.graphql +38 -0
  19. nabu_forge-1.0.0/samples/university/operations/list_students.graphql +8 -0
  20. nabu_forge-1.0.0/samples/university/operations/mutations.graphql +80 -0
  21. nabu_forge-1.0.0/samples/university/operations/search.graphql +32 -0
  22. nabu_forge-1.0.0/samples/university/schema.graphqls +199 -0
  23. nabu_forge-1.0.0/src/nabu/__init__.py +0 -0
  24. nabu_forge-1.0.0/src/nabu/analysis/__init__.py +0 -0
  25. nabu_forge-1.0.0/src/nabu/analysis/analyser.py +289 -0
  26. nabu_forge-1.0.0/src/nabu/analysis/index.py +61 -0
  27. nabu_forge-1.0.0/src/nabu/backends/__init__.py +0 -0
  28. nabu_forge-1.0.0/src/nabu/backends/python/__init__.py +7 -0
  29. nabu_forge-1.0.0/src/nabu/backends/python/codegen/__init__.py +0 -0
  30. nabu_forge-1.0.0/src/nabu/backends/python/codegen/client_gen.py +124 -0
  31. nabu_forge-1.0.0/src/nabu/backends/python/codegen/engine.py +31 -0
  32. nabu_forge-1.0.0/src/nabu/backends/python/codegen/enum_gen.py +13 -0
  33. nabu_forge-1.0.0/src/nabu/backends/python/codegen/exceptions_gen.py +5 -0
  34. nabu_forge-1.0.0/src/nabu/backends/python/codegen/exports_gen.py +26 -0
  35. nabu_forge-1.0.0/src/nabu/backends/python/codegen/fields.py +115 -0
  36. nabu_forge-1.0.0/src/nabu/backends/python/codegen/input_gen.py +19 -0
  37. nabu_forge-1.0.0/src/nabu/backends/python/codegen/model_gen.py +44 -0
  38. nabu_forge-1.0.0/src/nabu/backends/python/codegen/operation_gen.py +271 -0
  39. nabu_forge-1.0.0/src/nabu/backends/python/codegen/ordering.py +36 -0
  40. nabu_forge-1.0.0/src/nabu/backends/python/codegen/scalars_gen.py +25 -0
  41. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/client.py.jinja +57 -0
  42. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/enum.py.jinja +11 -0
  43. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/exceptions.py.jinja +11 -0
  44. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/init.py.jinja +10 -0
  45. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/input.py.jinja +17 -0
  46. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/model.py.jinja +26 -0
  47. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/operation_model.py.jinja +35 -0
  48. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/scalars.py.jinja +10 -0
  49. nabu_forge-1.0.0/src/nabu/backends/python/codegen/templates/transport.py.jinja +25 -0
  50. nabu_forge-1.0.0/src/nabu/backends/python/codegen/transport_gen.py +5 -0
  51. nabu_forge-1.0.0/src/nabu/backends/python/codegen/writer.py +50 -0
  52. nabu_forge-1.0.0/src/nabu/backends/python/mapping/__init__.py +0 -0
  53. nabu_forge-1.0.0/src/nabu/backends/python/mapping/imports.py +47 -0
  54. nabu_forge-1.0.0/src/nabu/backends/python/mapping/names.py +14 -0
  55. nabu_forge-1.0.0/src/nabu/backends/python/mapping/scalars.py +11 -0
  56. nabu_forge-1.0.0/src/nabu/backends/python/mapping/type_mapper.py +15 -0
  57. nabu_forge-1.0.0/src/nabu/cli/__init__.py +17 -0
  58. nabu_forge-1.0.0/src/nabu/cli/callbacks.py +13 -0
  59. nabu_forge-1.0.0/src/nabu/cli/commands.py +75 -0
  60. nabu_forge-1.0.0/src/nabu/config/__init__.py +3 -0
  61. nabu_forge-1.0.0/src/nabu/config/loader.py +55 -0
  62. nabu_forge-1.0.0/src/nabu/context.py +96 -0
  63. nabu_forge-1.0.0/src/nabu/diagnostics/__init__.py +6 -0
  64. nabu_forge-1.0.0/src/nabu/diagnostics/codes.py +29 -0
  65. nabu_forge-1.0.0/src/nabu/diagnostics/diagnostic.py +45 -0
  66. nabu_forge-1.0.0/src/nabu/diagnostics/reporter.py +29 -0
  67. nabu_forge-1.0.0/src/nabu/diagnostics/result.py +16 -0
  68. nabu_forge-1.0.0/src/nabu/graphql/__init__.py +0 -0
  69. nabu_forge-1.0.0/src/nabu/graphql/schema_utils.py +15 -0
  70. nabu_forge-1.0.0/src/nabu/ir/__init__.py +0 -0
  71. nabu_forge-1.0.0/src/nabu/ir/definitions.py +62 -0
  72. nabu_forge-1.0.0/src/nabu/ir/document.py +26 -0
  73. nabu_forge-1.0.0/src/nabu/ir/location.py +17 -0
  74. nabu_forge-1.0.0/src/nabu/ir/operations.py +70 -0
  75. nabu_forge-1.0.0/src/nabu/ir/transformer.py +250 -0
  76. nabu_forge-1.0.0/src/nabu/ir/types.py +37 -0
  77. nabu_forge-1.0.0/src/nabu/loader/__init__.py +3 -0
  78. nabu_forge-1.0.0/src/nabu/loader/files.py +39 -0
  79. nabu_forge-1.0.0/src/nabu/log.py +3 -0
  80. nabu_forge-1.0.0/src/nabu/parser/__init__.py +5 -0
  81. nabu_forge-1.0.0/src/nabu/parser/operations.py +46 -0
  82. nabu_forge-1.0.0/src/nabu/parser/schema.py +30 -0
  83. nabu_forge-1.0.0/src/nabu/parser/traverse.py +65 -0
  84. nabu_forge-1.0.0/tests/__init__.py +0 -0
  85. nabu_forge-1.0.0/tests/analysis/__init__.py +0 -0
  86. nabu_forge-1.0.0/tests/analysis/test_analyser.py +237 -0
  87. nabu_forge-1.0.0/tests/backends/__init__.py +0 -0
  88. nabu_forge-1.0.0/tests/backends/python/__init__.py +0 -0
  89. nabu_forge-1.0.0/tests/backends/python/codegen/__init__.py +0 -0
  90. nabu_forge-1.0.0/tests/backends/python/codegen/test_client_gen.py +133 -0
  91. nabu_forge-1.0.0/tests/backends/python/codegen/test_generators.py +713 -0
  92. nabu_forge-1.0.0/tests/backends/python/mapping/__init__.py +0 -0
  93. nabu_forge-1.0.0/tests/backends/python/mapping/test_imports.py +59 -0
  94. nabu_forge-1.0.0/tests/backends/python/mapping/test_names.py +42 -0
  95. nabu_forge-1.0.0/tests/backends/python/mapping/test_type_mapper.py +82 -0
  96. nabu_forge-1.0.0/tests/ir/__init__.py +0 -0
  97. nabu_forge-1.0.0/tests/ir/test_transformer.py +175 -0
  98. nabu_forge-1.0.0/tests/ir/test_types.py +43 -0
  99. nabu_forge-1.0.0/tests/parser/__init__.py +0 -0
  100. nabu_forge-1.0.0/tests/parser/test_operations.py +91 -0
  101. nabu_forge-1.0.0/tests/parser/test_schema.py +53 -0
  102. nabu_forge-1.0.0/tests/test_cli.py +79 -0
@@ -0,0 +1,56 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ tags: [ "v*" ]
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: [ "3.11", "3.12", "3.13" ]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install project and dev dependencies
24
+ run: pip install -e ".[dev]"
25
+
26
+ - name: Lint (ruff)
27
+ run: ruff check src/
28
+
29
+ - name: Format check (ruff)
30
+ run: ruff format --check src/
31
+
32
+ - name: Tests
33
+ run: pytest -q
34
+
35
+ publish:
36
+ name: Publish to PyPI
37
+ needs: test
38
+ runs-on: ubuntu-latest
39
+ if: startsWith(github.ref, 'refs/tags/v')
40
+ environment: pypi
41
+ permissions:
42
+ id-token: write
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ python-version: "3.12"
49
+
50
+ - name: Build distribution
51
+ run: |
52
+ pip install build
53
+ python -m build
54
+
55
+ - name: Publish to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,58 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.egg
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ wheels/
11
+ sdist/
12
+ .eggs/
13
+ MANIFEST
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+ env/
19
+ ENV/
20
+
21
+ # uv
22
+ .uv/
23
+ uv.lock
24
+
25
+ # Distribution / packaging
26
+ pip-wheel-metadata/
27
+ share/python-wheels/
28
+
29
+ # Testing
30
+ .pytest_cache/
31
+ .coverage
32
+ coverage.xml
33
+ htmlcov/
34
+ .tox/
35
+
36
+ # Type checking
37
+ .mypy_cache/
38
+ .pyright/
39
+ pyrightconfig.json
40
+
41
+ # Ruff
42
+ .ruff_cache/
43
+
44
+ # IDEs
45
+ .idea/
46
+ .vscode/
47
+ *.iml
48
+
49
+ # macOS
50
+ .DS_Store
51
+
52
+ # Generated output (development artefacts, not committed)
53
+ generated_client/
54
+
55
+ architecture/phases/
56
+ architecture/assets/*.drawio
57
+
58
+ samples/euporie/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Nikolov
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,228 @@
1
+ Metadata-Version: 2.5
2
+ Name: nabu-forge
3
+ Version: 1.0.0
4
+ Summary: A schema-driven compiler that transforms GraphQL SDL and operation definitions into type-safe Python client packages.
5
+ Project-URL: Repository, https://github.com/ivanNikolov2910/nabu-forge
6
+ Author: Ivan Nikolov
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: graphql-core>=3.2
11
+ Requires-Dist: httpx>=0.26.2
12
+ Requires-Dist: jinja2>=3.1.6
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: typer>=0.27.1
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=9.1.1; extra == 'dev'
17
+ Requires-Dist: ruff>=0.16.1; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Nabu Forge
21
+
22
+ A schema-driven compiler that transforms GraphQL SDL and operation definitions into type-safe Python client packages.
23
+
24
+ ```text
25
+ GraphQL SDL + operations -> Nabu Forge -> Typed Python client package
26
+ ```
27
+
28
+ ## What it does
29
+
30
+ Given a GraphQL schema and operation documents, Nabu Forge generates a complete Python package with:
31
+
32
+ - Pydantic v2 models for every response shape, with correct list/nullability handling
33
+ - Discriminated unions for interfaces and polymorphic types (via `__typename`)
34
+ - Enum definitions (`str, Enum`)
35
+ - Input models with camelCase -> snake_case aliasing for correct wire serialization
36
+ - Async client methods with typed variables and response deserialization
37
+ - Custom scalar mappings via config
38
+ - Async httpx transport with configurable timeout and persistent connection
39
+ - Compiler-style diagnostics with file/line/column and hints
40
+
41
+ ### Example generated usage
42
+
43
+ ```python
44
+ from generated_client import Client
45
+
46
+ # basic usage
47
+ client = Client(url="https://example.com/graphql", headers={"Authorization": "Bearer token"})
48
+ result = await client.get_student(id="123")
49
+ print(result.student.id, result.student.status)
50
+
51
+ # async context manager for clean connection teardown
52
+ async with Client(url="https://example.com/graphql") as client:
53
+ result = await client.get_student(id="123")
54
+ ```
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install nabu-forge
60
+ ```
61
+
62
+ Or for development:
63
+
64
+ ```bash
65
+ cd nabu-forge
66
+ pip install -e .
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### 1. Write a `nabu.toml`
72
+
73
+ ```toml
74
+ schema = "schema.graphqls"
75
+ operations = "operations/"
76
+ output = "generated_client"
77
+
78
+ [scalars]
79
+ DateTime = "datetime.datetime"
80
+ JSON = "typing.Any"
81
+ Void = "None"
82
+ ```
83
+
84
+ ### 2. Run
85
+
86
+ ```bash
87
+ nabu validate --config nabu.toml # validate without writing files
88
+ nabu inspect --schema schema.graphqls # show schema structure
89
+ nabu generate --config nabu.toml # generate the client package
90
+ ```
91
+
92
+ ## Configuration
93
+
94
+ | Field | Description |
95
+ |--------------|-------------------------------------------------|
96
+ | `schema` | Path to the GraphQL SDL file |
97
+ | `operations` | Directory containing `.graphql` operation files |
98
+ | `output` | Output directory for the generated package |
99
+ | `[scalars]` | Python type mappings for custom scalars |
100
+
101
+ All paths are relative to `nabu.toml`.
102
+
103
+ ## GraphQL → Python type mapping
104
+
105
+ | GraphQL | Python |
106
+ |---------------|-----------------------------|
107
+ | `String!` | `str` |
108
+ | `String` | `str \| None` |
109
+ | `[String!]!` | `list[str]` |
110
+ | `[String]` | `list[str \| None] \| None` |
111
+ | `Int!` | `int` |
112
+ | `Float!` | `float` |
113
+ | `Boolean!` | `bool` |
114
+ | `ID!` | `str` |
115
+ | Custom scalar | configured via `[scalars]` |
116
+
117
+ camelCase GraphQL field names are converted to `snake_case`. A `Field(alias="camelCase")` is emitted automatically so
118
+ generated models deserialize real GraphQL responses correctly.
119
+
120
+ ## Polymorphic types (interfaces and unions)
121
+
122
+ Inline fragments generate discriminated union aliases using `__typename` as the discriminator. Shared interface-level
123
+ fields appear in every member class — you do not need to repeat them in each fragment.
124
+
125
+ ```graphql
126
+ query GetJob($id: String!) {
127
+ job(id: $id) {
128
+ jobId # shared — emitted in ALL member classes
129
+ state
130
+ ... on ExecutionJob { task { name } }
131
+ ... on CollectionJob { }
132
+ }
133
+ }
134
+ ```
135
+
136
+ Generated:
137
+
138
+ ```python
139
+ class GetJobJobExecutionJob(BaseModel):
140
+ model_config = ConfigDict(populate_by_name=True)
141
+ typename: Literal["ExecutionJob"] = Field(alias="__typename")
142
+ job_id: str = Field(..., alias="jobId") # shared
143
+ state: JobState # shared
144
+ task: GetJobJobExecutionJobTask | None
145
+
146
+
147
+ GetJobJob = Annotated[
148
+ GetJobJobExecutionJob | GetJobJobCollectionJob,
149
+ Field(discriminator="typename"),
150
+ ]
151
+ ```
152
+
153
+ `__typename` is injected automatically in the sent document — no need to add it to your `.graphql` files.
154
+
155
+ ## Diagnostics
156
+
157
+ Nabu Forge emits compiler-style diagnostics. Errors abort generation; warnings print and continue.
158
+
159
+ ```
160
+ error[E023]: Field 'ghost' does not exist on type 'Query'.
161
+ operations/bad.graphql:2:5
162
+
163
+ warning[E029]: Field 'job' returns polymorphic type 'Job' but has no inline fragments.
164
+ operations/get_job.graphql:3:5
165
+ hint: Add '... on ConcreteType { fields }' for each possible type.
166
+ ```
167
+
168
+ | Code | Severity | Meaning |
169
+ |------|----------|--------------------------------------------|
170
+ | E001 | error | Config file not found |
171
+ | E002 | error | Missing required config fields |
172
+ | E010 | error | GraphQL syntax error |
173
+ | E020 | error | Unknown type reference |
174
+ | E021 | error | Unmapped custom scalar |
175
+ | E022 | error | Unknown variable type |
176
+ | E023 | error | Unknown field / direct field on union type |
177
+ | E024 | error | Unknown fragment spread |
178
+ | E025 | error | Bad inline fragment target |
179
+ | E026 | error | Generated name collision |
180
+ | E028 | error | Unsupported feature (subscriptions) |
181
+ | E030 | error | File write error |
182
+
183
+ ## Compiler pipeline
184
+
185
+ <img src="architecture/assets/compiler-pipeline.drawio.png" alt="Nabu Forge compiler pipeline"/>
186
+
187
+ ## Supported / not supported
188
+
189
+ **Supported:**
190
+
191
+ - Object types, input types, enums, interfaces, unions
192
+ - Lists and nullability (all 8 combinations)
193
+ - Custom scalars via `[scalars]` config
194
+ - Queries and mutations
195
+ - Named fragments (inlined at codegen time)
196
+ - Inline fragments → discriminated unions via `__typename`
197
+ - Shared interface fields alongside inline fragments
198
+ - Field aliases including two aliases of the same underlying field
199
+ - camelCase → snake_case with `Field(alias=...)`
200
+ - Async httpx transport with `timeout`, persistent `AsyncClient`, `async with` support
201
+
202
+ **Not supported:**
203
+
204
+ - Subscriptions (E028 at semantic analysis)
205
+ - File uploads (multipart)
206
+ - Query batching
207
+ - `@skip` / `@include` directives
208
+ - Variable default values
209
+ - Introspection-based generation (SDL file required)
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ pip install -e .
215
+
216
+ ruff check src/ # lint
217
+ pytest # 144 tests
218
+ nabu generate --config samples/university/nabu.toml
219
+ ```
220
+
221
+ ## Architecture docs
222
+
223
+ - [`architecture/overview.md`](architecture/overview.md) — project summary and goals
224
+ - [`architecture/compiler-pipeline.md`](architecture/compiler-pipeline.md) — all pipeline stages
225
+ - [`architecture/generated-package.md`](architecture/generated-package.md) — generated package layout
226
+ - [`architecture/diagnostics-and-cli.md`](architecture/diagnostics-and-cli.md) — CLI and diagnostics design
227
+ - [`architecture/development-plan.md`](architecture/development-plan.md) — implementation phases
228
+ - [`architecture/product-requirements.md`](architecture/product-requirements.md) — scope and definition of done
@@ -0,0 +1,209 @@
1
+ # Nabu Forge
2
+
3
+ A schema-driven compiler that transforms GraphQL SDL and operation definitions into type-safe Python client packages.
4
+
5
+ ```text
6
+ GraphQL SDL + operations -> Nabu Forge -> Typed Python client package
7
+ ```
8
+
9
+ ## What it does
10
+
11
+ Given a GraphQL schema and operation documents, Nabu Forge generates a complete Python package with:
12
+
13
+ - Pydantic v2 models for every response shape, with correct list/nullability handling
14
+ - Discriminated unions for interfaces and polymorphic types (via `__typename`)
15
+ - Enum definitions (`str, Enum`)
16
+ - Input models with camelCase -> snake_case aliasing for correct wire serialization
17
+ - Async client methods with typed variables and response deserialization
18
+ - Custom scalar mappings via config
19
+ - Async httpx transport with configurable timeout and persistent connection
20
+ - Compiler-style diagnostics with file/line/column and hints
21
+
22
+ ### Example generated usage
23
+
24
+ ```python
25
+ from generated_client import Client
26
+
27
+ # basic usage
28
+ client = Client(url="https://example.com/graphql", headers={"Authorization": "Bearer token"})
29
+ result = await client.get_student(id="123")
30
+ print(result.student.id, result.student.status)
31
+
32
+ # async context manager for clean connection teardown
33
+ async with Client(url="https://example.com/graphql") as client:
34
+ result = await client.get_student(id="123")
35
+ ```
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install nabu-forge
41
+ ```
42
+
43
+ Or for development:
44
+
45
+ ```bash
46
+ cd nabu-forge
47
+ pip install -e .
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ### 1. Write a `nabu.toml`
53
+
54
+ ```toml
55
+ schema = "schema.graphqls"
56
+ operations = "operations/"
57
+ output = "generated_client"
58
+
59
+ [scalars]
60
+ DateTime = "datetime.datetime"
61
+ JSON = "typing.Any"
62
+ Void = "None"
63
+ ```
64
+
65
+ ### 2. Run
66
+
67
+ ```bash
68
+ nabu validate --config nabu.toml # validate without writing files
69
+ nabu inspect --schema schema.graphqls # show schema structure
70
+ nabu generate --config nabu.toml # generate the client package
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ | Field | Description |
76
+ |--------------|-------------------------------------------------|
77
+ | `schema` | Path to the GraphQL SDL file |
78
+ | `operations` | Directory containing `.graphql` operation files |
79
+ | `output` | Output directory for the generated package |
80
+ | `[scalars]` | Python type mappings for custom scalars |
81
+
82
+ All paths are relative to `nabu.toml`.
83
+
84
+ ## GraphQL → Python type mapping
85
+
86
+ | GraphQL | Python |
87
+ |---------------|-----------------------------|
88
+ | `String!` | `str` |
89
+ | `String` | `str \| None` |
90
+ | `[String!]!` | `list[str]` |
91
+ | `[String]` | `list[str \| None] \| None` |
92
+ | `Int!` | `int` |
93
+ | `Float!` | `float` |
94
+ | `Boolean!` | `bool` |
95
+ | `ID!` | `str` |
96
+ | Custom scalar | configured via `[scalars]` |
97
+
98
+ camelCase GraphQL field names are converted to `snake_case`. A `Field(alias="camelCase")` is emitted automatically so
99
+ generated models deserialize real GraphQL responses correctly.
100
+
101
+ ## Polymorphic types (interfaces and unions)
102
+
103
+ Inline fragments generate discriminated union aliases using `__typename` as the discriminator. Shared interface-level
104
+ fields appear in every member class — you do not need to repeat them in each fragment.
105
+
106
+ ```graphql
107
+ query GetJob($id: String!) {
108
+ job(id: $id) {
109
+ jobId # shared — emitted in ALL member classes
110
+ state
111
+ ... on ExecutionJob { task { name } }
112
+ ... on CollectionJob { }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Generated:
118
+
119
+ ```python
120
+ class GetJobJobExecutionJob(BaseModel):
121
+ model_config = ConfigDict(populate_by_name=True)
122
+ typename: Literal["ExecutionJob"] = Field(alias="__typename")
123
+ job_id: str = Field(..., alias="jobId") # shared
124
+ state: JobState # shared
125
+ task: GetJobJobExecutionJobTask | None
126
+
127
+
128
+ GetJobJob = Annotated[
129
+ GetJobJobExecutionJob | GetJobJobCollectionJob,
130
+ Field(discriminator="typename"),
131
+ ]
132
+ ```
133
+
134
+ `__typename` is injected automatically in the sent document — no need to add it to your `.graphql` files.
135
+
136
+ ## Diagnostics
137
+
138
+ Nabu Forge emits compiler-style diagnostics. Errors abort generation; warnings print and continue.
139
+
140
+ ```
141
+ error[E023]: Field 'ghost' does not exist on type 'Query'.
142
+ operations/bad.graphql:2:5
143
+
144
+ warning[E029]: Field 'job' returns polymorphic type 'Job' but has no inline fragments.
145
+ operations/get_job.graphql:3:5
146
+ hint: Add '... on ConcreteType { fields }' for each possible type.
147
+ ```
148
+
149
+ | Code | Severity | Meaning |
150
+ |------|----------|--------------------------------------------|
151
+ | E001 | error | Config file not found |
152
+ | E002 | error | Missing required config fields |
153
+ | E010 | error | GraphQL syntax error |
154
+ | E020 | error | Unknown type reference |
155
+ | E021 | error | Unmapped custom scalar |
156
+ | E022 | error | Unknown variable type |
157
+ | E023 | error | Unknown field / direct field on union type |
158
+ | E024 | error | Unknown fragment spread |
159
+ | E025 | error | Bad inline fragment target |
160
+ | E026 | error | Generated name collision |
161
+ | E028 | error | Unsupported feature (subscriptions) |
162
+ | E030 | error | File write error |
163
+
164
+ ## Compiler pipeline
165
+
166
+ <img src="architecture/assets/compiler-pipeline.drawio.png" alt="Nabu Forge compiler pipeline"/>
167
+
168
+ ## Supported / not supported
169
+
170
+ **Supported:**
171
+
172
+ - Object types, input types, enums, interfaces, unions
173
+ - Lists and nullability (all 8 combinations)
174
+ - Custom scalars via `[scalars]` config
175
+ - Queries and mutations
176
+ - Named fragments (inlined at codegen time)
177
+ - Inline fragments → discriminated unions via `__typename`
178
+ - Shared interface fields alongside inline fragments
179
+ - Field aliases including two aliases of the same underlying field
180
+ - camelCase → snake_case with `Field(alias=...)`
181
+ - Async httpx transport with `timeout`, persistent `AsyncClient`, `async with` support
182
+
183
+ **Not supported:**
184
+
185
+ - Subscriptions (E028 at semantic analysis)
186
+ - File uploads (multipart)
187
+ - Query batching
188
+ - `@skip` / `@include` directives
189
+ - Variable default values
190
+ - Introspection-based generation (SDL file required)
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ pip install -e .
196
+
197
+ ruff check src/ # lint
198
+ pytest # 144 tests
199
+ nabu generate --config samples/university/nabu.toml
200
+ ```
201
+
202
+ ## Architecture docs
203
+
204
+ - [`architecture/overview.md`](architecture/overview.md) — project summary and goals
205
+ - [`architecture/compiler-pipeline.md`](architecture/compiler-pipeline.md) — all pipeline stages
206
+ - [`architecture/generated-package.md`](architecture/generated-package.md) — generated package layout
207
+ - [`architecture/diagnostics-and-cli.md`](architecture/diagnostics-and-cli.md) — CLI and diagnostics design
208
+ - [`architecture/development-plan.md`](architecture/development-plan.md) — implementation phases
209
+ - [`architecture/product-requirements.md`](architecture/product-requirements.md) — scope and definition of done