mygenx-fastql 0.0.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.
Files changed (55) hide show
  1. mygenx_fastql-0.0.1/.gitignore +13 -0
  2. mygenx_fastql-0.0.1/CHANGELOG.md +14 -0
  3. mygenx_fastql-0.0.1/PKG-INFO +214 -0
  4. mygenx_fastql-0.0.1/README.md +170 -0
  5. mygenx_fastql-0.0.1/fastql/__init__.py +143 -0
  6. mygenx_fastql-0.0.1/fastql/__main__.py +6 -0
  7. mygenx_fastql-0.0.1/fastql/cli.py +130 -0
  8. mygenx_fastql-0.0.1/fastql/context.py +211 -0
  9. mygenx_fastql-0.0.1/fastql/dataloader.py +229 -0
  10. mygenx_fastql-0.0.1/fastql/decorators/__init__.py +41 -0
  11. mygenx_fastql-0.0.1/fastql/decorators/annotations.py +215 -0
  12. mygenx_fastql-0.0.1/fastql/decorators/construct.py +92 -0
  13. mygenx_fastql-0.0.1/fastql/decorators/definition.py +294 -0
  14. mygenx_fastql-0.0.1/fastql/decorators/enum.py +38 -0
  15. mygenx_fastql-0.0.1/fastql/decorators/field.py +164 -0
  16. mygenx_fastql-0.0.1/fastql/decorators/object.py +31 -0
  17. mygenx_fastql-0.0.1/fastql/decorators/operations.py +39 -0
  18. mygenx_fastql-0.0.1/fastql/decorators/registry.py +76 -0
  19. mygenx_fastql-0.0.1/fastql/decorators/scalar.py +45 -0
  20. mygenx_fastql-0.0.1/fastql/decorators/union.py +41 -0
  21. mygenx_fastql-0.0.1/fastql/errors.py +76 -0
  22. mygenx_fastql-0.0.1/fastql/execution/__init__.py +11 -0
  23. mygenx_fastql-0.0.1/fastql/execution/collect_fields.py +107 -0
  24. mygenx_fastql-0.0.1/fastql/execution/execute.py +796 -0
  25. mygenx_fastql-0.0.1/fastql/execution/values.py +224 -0
  26. mygenx_fastql-0.0.1/fastql/extensions.py +157 -0
  27. mygenx_fastql-0.0.1/fastql/integrations/__init__.py +26 -0
  28. mygenx_fastql-0.0.1/fastql/integrations/_sync.py +23 -0
  29. mygenx_fastql-0.0.1/fastql/integrations/asgi.py +87 -0
  30. mygenx_fastql-0.0.1/fastql/integrations/django.py +101 -0
  31. mygenx_fastql-0.0.1/fastql/integrations/fastapi.py +68 -0
  32. mygenx_fastql-0.0.1/fastql/integrations/flask.py +62 -0
  33. mygenx_fastql-0.0.1/fastql/integrations/http.py +421 -0
  34. mygenx_fastql-0.0.1/fastql/integrations/starlette.py +56 -0
  35. mygenx_fastql-0.0.1/fastql/introspection.py +320 -0
  36. mygenx_fastql-0.0.1/fastql/language/__init__.py +18 -0
  37. mygenx_fastql-0.0.1/fastql/language/ast.py +235 -0
  38. mygenx_fastql-0.0.1/fastql/language/lexer.py +314 -0
  39. mygenx_fastql-0.0.1/fastql/language/parser.py +393 -0
  40. mygenx_fastql-0.0.1/fastql/language/printer.py +172 -0
  41. mygenx_fastql-0.0.1/fastql/language/source.py +63 -0
  42. mygenx_fastql-0.0.1/fastql/playground.py +63 -0
  43. mygenx_fastql-0.0.1/fastql/registry.py +30 -0
  44. mygenx_fastql-0.0.1/fastql/schema_builder.py +370 -0
  45. mygenx_fastql-0.0.1/fastql/sdl.py +149 -0
  46. mygenx_fastql-0.0.1/fastql/server.py +221 -0
  47. mygenx_fastql-0.0.1/fastql/testing.py +93 -0
  48. mygenx_fastql-0.0.1/fastql/types/__init__.py +47 -0
  49. mygenx_fastql-0.0.1/fastql/types/definition.py +168 -0
  50. mygenx_fastql-0.0.1/fastql/types/scalars.py +113 -0
  51. mygenx_fastql-0.0.1/fastql/types/schema.py +164 -0
  52. mygenx_fastql-0.0.1/fastql/types/wrappers.py +33 -0
  53. mygenx_fastql-0.0.1/fastql/validation/__init__.py +5 -0
  54. mygenx_fastql-0.0.1/fastql/validation/rules.py +696 -0
  55. mygenx_fastql-0.0.1/pyproject.toml +77 -0
@@ -0,0 +1,13 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+
11
+ # Build
12
+ /build/
13
+ /dist/
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - A shared dependency-free GraphQL-over-HTTP handler and request-aware `HTTPContext`.
8
+ - Generic ASGI, Starlette, FastAPI, Flask, and Django integrations.
9
+ - Independent `asgi`, `starlette`, `fastapi`, `flask`, and `django` installation extras, plus `all`.
10
+ - Configurable GraphiQL, SDL, and introspection routes for production adapters.
11
+
12
+ ### Deferred
13
+
14
+ - WebSocket subscriptions, multipart uploads, batching, persisted queries, and incremental delivery remain outside the initial integration contract.
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: mygenx-fastql
3
+ Version: 0.0.1
4
+ Summary: A code-first, decorator-driven GraphQL framework for Python with a hand-built engine and a web-framework-agnostic core.
5
+ Project-URL: Homepage, https://github.com/MyGenX/FastQL
6
+ Project-URL: Documentation, https://github.com/MyGenX/FastQL/tree/main/docs
7
+ Project-URL: Repository, https://github.com/MyGenX/FastQL
8
+ Project-URL: Issues, https://github.com/MyGenX/FastQL/issues
9
+ Author: FastQL
10
+ License: MIT
11
+ Keywords: async,decorators,framework-agnostic,graphql,schema
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Provides-Extra: all
24
+ Requires-Dist: django<6,>=5.2; extra == 'all'
25
+ Requires-Dist: fastapi<1,>=0.129; extra == 'all'
26
+ Requires-Dist: flask<4,>=3.1; extra == 'all'
27
+ Requires-Dist: starlette<2,>=0.50; extra == 'all'
28
+ Provides-Extra: asgi
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2; extra == 'dev'
31
+ Requires-Dist: hatchling>=1.26; extra == 'dev'
32
+ Requires-Dist: httpx>=0.28; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Provides-Extra: django
36
+ Requires-Dist: django<6,>=5.2; extra == 'django'
37
+ Provides-Extra: fastapi
38
+ Requires-Dist: fastapi<1,>=0.129; extra == 'fastapi'
39
+ Provides-Extra: flask
40
+ Requires-Dist: flask<4,>=3.1; extra == 'flask'
41
+ Provides-Extra: starlette
42
+ Requires-Dist: starlette<2,>=0.50; extra == 'starlette'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # FastQL
46
+
47
+ A code-first, decorator-driven GraphQL framework for Python with a **hand-built engine**
48
+ (lexer → parser → validator → executor), a built-in **dependency-injection / context** layer,
49
+ and a **web-framework-agnostic core** — zero runtime dependencies, Python 3.11+.
50
+
51
+ > Status: early development. The core engine (parse → build → validate → execute, with
52
+ > dependency injection and introspection) is in place.
53
+
54
+ **Documentation:** Start with the [FastQL documentation](docs/index.mdx), then use the
55
+ [capability catalog](docs/specifications/capability-catalog.mdx) to trace documented
56
+ behavior to its canonical OpenSpec requirements.
57
+
58
+ ## Quickstart
59
+
60
+ Define types and operations with decorators — field and argument types come from your
61
+ Python type hints. Resolvers are plain functions; the executor injects arguments, the
62
+ parent object, resolve `info`, and the `Context` based on each resolver's signature.
63
+
64
+ ```python
65
+ import asyncio
66
+ from typing import Annotated
67
+ from fastql import Argument, Context, Field, Query, Schema, Type, execute
68
+
69
+
70
+ @Type
71
+ class User: # just fields — constructor/repr/eq are auto-generated
72
+ id: int
73
+ full_name: str
74
+
75
+ @Field
76
+ def loud_name(self) -> str: # exposed as loudName by default
77
+ return self.full_name.upper()
78
+
79
+
80
+ class AppContext(Context):
81
+ def __init__(self, users):
82
+ self.users = users
83
+
84
+
85
+ @Query # group related queries on a class…
86
+ class Queries:
87
+ @Field
88
+ async def user(
89
+ self,
90
+ user_id: Annotated[int, Argument(name="id")],
91
+ ctx: Context,
92
+ ) -> "User | None":
93
+ return ctx.users.get(user_id)
94
+
95
+ @Field
96
+ def ping(self) -> str: # sync resolvers work alongside async ones
97
+ return "pong"
98
+
99
+
100
+ schema = Schema(query=Queries)
101
+
102
+
103
+ async def main():
104
+ ctx = AppContext(users={1: User(1, "Ada Lovelace")})
105
+ result = await execute(schema, "{ user(id: 1) { id fullName loudName } ping }", context=ctx)
106
+ print(result.data) # {'user': {'id': 1, 'fullName': 'Ada Lovelace', 'loudName': 'ADA LOVELACE'}, 'ping': 'pong'}
107
+
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ `@Type` and `@Input` classes get generated constructors, `repr`, and equality unless they define
113
+ their own methods. Python snake_case fields and arguments become GraphQL camelCase by default;
114
+ pass `SchemaConfig(auto_camel_case=False)` to `Schema` to preserve Python names. Explicit `name=`
115
+ metadata always wins. `build_schema()` remains available for applications that intentionally merge
116
+ multiple globally decorated root classes.
117
+
118
+ `execute` returns an `ExecutionResult` with `data`, `errors`, and `extensions`, and a
119
+ `.formatted()` helper that produces the GraphQL-over-HTTP response shape. Introspection
120
+ (`__schema`, `__type`, `__typename`) is built in.
121
+
122
+ A fuller, runnable version — covering every type kind, mutations, subscriptions,
123
+ DataLoaders, permissions, and extensions — lives in [`examples/app`](examples/app), the
124
+ showcase schema that the per-framework projects under [`examples/projects`](examples/projects)
125
+ all reuse.
126
+
127
+ The documentation quickstart and first-schema examples are also executed by the test
128
+ suite from [`docs/snippets`](docs/snippets).
129
+
130
+ ## Dev server & playground
131
+
132
+ Try a schema in the browser with the built-in, zero-dependency dev server:
133
+
134
+ ```bash
135
+ python -m fastql serve examples.app:schema # http://127.0.0.1:7691
136
+ ```
137
+
138
+ It serves, on the default port **7691**:
139
+
140
+ | Route | Description |
141
+ | ----------------- | -------------------------------------------- |
142
+ | `GET /` | GraphiQL IDE (loaded from CDN) |
143
+ | `POST/GET /graphql` | the GraphQL endpoint (`{data, errors}` JSON) |
144
+ | `GET /schema.graphql` | the schema as SDL |
145
+ | `GET /schema.json` | the schema as an introspection result |
146
+
147
+ Override the binding with `--host` / `--port`. If your resolvers need a `Context`,
148
+ point `--context` at a value or zero-arg factory:
149
+
150
+ ```bash
151
+ python -m fastql serve examples.app:schema --context examples.app:make_context
152
+ ```
153
+
154
+ Or call it programmatically:
155
+
156
+ ```python
157
+ import fastql
158
+ from examples.app import schema, make_context
159
+
160
+ fastql.serve(schema, port=7691, context_factory=make_context) # blocking; Ctrl-C to stop
161
+ ```
162
+
163
+ The dev server lives outside the agnostic core (in `fastql.server`) and only
164
+ consumes `build_schema` / `execute` — it is a developer convenience, not a
165
+ production transport (no TLS, auth, or subscriptions).
166
+
167
+ ## Design at a glance
168
+
169
+ ```
170
+ @Type / @Input / root class decorators ← unified type-hint-driven authoring
171
+
172
+ Schema(query=...) / build_schema() ← compiles decorators into the type-system IR
173
+
174
+ Type-system IR (Schema, ObjectType, Field, scalars, wrappers)
175
+
176
+ execute() ── validation ── coercion ── async resolution + DI/context
177
+
178
+ language: Source → Lexer → Parser → AST ← hand-built front-end
179
+ ```
180
+
181
+ The core never imports an HTTP framework. Transports (an optional built-in dev server,
182
+ plus FastAPI/Django/Flask/ASGI adapters) plug in on top and consume `build_schema` / `execute`.
183
+
184
+ ## Web framework integrations
185
+
186
+ Install only the framework adapter an application uses:
187
+
188
+ ```bash
189
+ pip install mygenx-fastql[fastapi] # or starlette, flask, django
190
+ ```
191
+
192
+ ```python
193
+ from fastapi import FastAPI
194
+ from fastql.integrations.fastapi import create_fastapi_router
195
+
196
+ app = FastAPI()
197
+ app.include_router(create_fastapi_router(schema, graphiql=True))
198
+ ```
199
+
200
+ The base installation includes the dependency-free `GraphQLASGI` adapter. See
201
+ the [integration documentation](docs/integrations/overview.mdx) for mounting,
202
+ request context, endpoint configuration, and supported versions.
203
+
204
+ Runnable per-framework projects — FastAPI, Starlette, Flask, Django, and raw ASGI, each
205
+ mounting the **same** [`examples/app`](examples/app) schema — live under
206
+ [`examples/projects`](examples/projects). Reusing one schema across every adapter is the
207
+ proof that the core is framework-agnostic; each project differs only in a few lines of glue.
208
+
209
+ ## Development
210
+
211
+ ```bash
212
+ pip install -e ".[dev]"
213
+ pytest
214
+ ```
@@ -0,0 +1,170 @@
1
+ # FastQL
2
+
3
+ A code-first, decorator-driven GraphQL framework for Python with a **hand-built engine**
4
+ (lexer → parser → validator → executor), a built-in **dependency-injection / context** layer,
5
+ and a **web-framework-agnostic core** — zero runtime dependencies, Python 3.11+.
6
+
7
+ > Status: early development. The core engine (parse → build → validate → execute, with
8
+ > dependency injection and introspection) is in place.
9
+
10
+ **Documentation:** Start with the [FastQL documentation](docs/index.mdx), then use the
11
+ [capability catalog](docs/specifications/capability-catalog.mdx) to trace documented
12
+ behavior to its canonical OpenSpec requirements.
13
+
14
+ ## Quickstart
15
+
16
+ Define types and operations with decorators — field and argument types come from your
17
+ Python type hints. Resolvers are plain functions; the executor injects arguments, the
18
+ parent object, resolve `info`, and the `Context` based on each resolver's signature.
19
+
20
+ ```python
21
+ import asyncio
22
+ from typing import Annotated
23
+ from fastql import Argument, Context, Field, Query, Schema, Type, execute
24
+
25
+
26
+ @Type
27
+ class User: # just fields — constructor/repr/eq are auto-generated
28
+ id: int
29
+ full_name: str
30
+
31
+ @Field
32
+ def loud_name(self) -> str: # exposed as loudName by default
33
+ return self.full_name.upper()
34
+
35
+
36
+ class AppContext(Context):
37
+ def __init__(self, users):
38
+ self.users = users
39
+
40
+
41
+ @Query # group related queries on a class…
42
+ class Queries:
43
+ @Field
44
+ async def user(
45
+ self,
46
+ user_id: Annotated[int, Argument(name="id")],
47
+ ctx: Context,
48
+ ) -> "User | None":
49
+ return ctx.users.get(user_id)
50
+
51
+ @Field
52
+ def ping(self) -> str: # sync resolvers work alongside async ones
53
+ return "pong"
54
+
55
+
56
+ schema = Schema(query=Queries)
57
+
58
+
59
+ async def main():
60
+ ctx = AppContext(users={1: User(1, "Ada Lovelace")})
61
+ result = await execute(schema, "{ user(id: 1) { id fullName loudName } ping }", context=ctx)
62
+ print(result.data) # {'user': {'id': 1, 'fullName': 'Ada Lovelace', 'loudName': 'ADA LOVELACE'}, 'ping': 'pong'}
63
+
64
+
65
+ asyncio.run(main())
66
+ ```
67
+
68
+ `@Type` and `@Input` classes get generated constructors, `repr`, and equality unless they define
69
+ their own methods. Python snake_case fields and arguments become GraphQL camelCase by default;
70
+ pass `SchemaConfig(auto_camel_case=False)` to `Schema` to preserve Python names. Explicit `name=`
71
+ metadata always wins. `build_schema()` remains available for applications that intentionally merge
72
+ multiple globally decorated root classes.
73
+
74
+ `execute` returns an `ExecutionResult` with `data`, `errors`, and `extensions`, and a
75
+ `.formatted()` helper that produces the GraphQL-over-HTTP response shape. Introspection
76
+ (`__schema`, `__type`, `__typename`) is built in.
77
+
78
+ A fuller, runnable version — covering every type kind, mutations, subscriptions,
79
+ DataLoaders, permissions, and extensions — lives in [`examples/app`](examples/app), the
80
+ showcase schema that the per-framework projects under [`examples/projects`](examples/projects)
81
+ all reuse.
82
+
83
+ The documentation quickstart and first-schema examples are also executed by the test
84
+ suite from [`docs/snippets`](docs/snippets).
85
+
86
+ ## Dev server & playground
87
+
88
+ Try a schema in the browser with the built-in, zero-dependency dev server:
89
+
90
+ ```bash
91
+ python -m fastql serve examples.app:schema # http://127.0.0.1:7691
92
+ ```
93
+
94
+ It serves, on the default port **7691**:
95
+
96
+ | Route | Description |
97
+ | ----------------- | -------------------------------------------- |
98
+ | `GET /` | GraphiQL IDE (loaded from CDN) |
99
+ | `POST/GET /graphql` | the GraphQL endpoint (`{data, errors}` JSON) |
100
+ | `GET /schema.graphql` | the schema as SDL |
101
+ | `GET /schema.json` | the schema as an introspection result |
102
+
103
+ Override the binding with `--host` / `--port`. If your resolvers need a `Context`,
104
+ point `--context` at a value or zero-arg factory:
105
+
106
+ ```bash
107
+ python -m fastql serve examples.app:schema --context examples.app:make_context
108
+ ```
109
+
110
+ Or call it programmatically:
111
+
112
+ ```python
113
+ import fastql
114
+ from examples.app import schema, make_context
115
+
116
+ fastql.serve(schema, port=7691, context_factory=make_context) # blocking; Ctrl-C to stop
117
+ ```
118
+
119
+ The dev server lives outside the agnostic core (in `fastql.server`) and only
120
+ consumes `build_schema` / `execute` — it is a developer convenience, not a
121
+ production transport (no TLS, auth, or subscriptions).
122
+
123
+ ## Design at a glance
124
+
125
+ ```
126
+ @Type / @Input / root class decorators ← unified type-hint-driven authoring
127
+
128
+ Schema(query=...) / build_schema() ← compiles decorators into the type-system IR
129
+
130
+ Type-system IR (Schema, ObjectType, Field, scalars, wrappers)
131
+
132
+ execute() ── validation ── coercion ── async resolution + DI/context
133
+
134
+ language: Source → Lexer → Parser → AST ← hand-built front-end
135
+ ```
136
+
137
+ The core never imports an HTTP framework. Transports (an optional built-in dev server,
138
+ plus FastAPI/Django/Flask/ASGI adapters) plug in on top and consume `build_schema` / `execute`.
139
+
140
+ ## Web framework integrations
141
+
142
+ Install only the framework adapter an application uses:
143
+
144
+ ```bash
145
+ pip install mygenx-fastql[fastapi] # or starlette, flask, django
146
+ ```
147
+
148
+ ```python
149
+ from fastapi import FastAPI
150
+ from fastql.integrations.fastapi import create_fastapi_router
151
+
152
+ app = FastAPI()
153
+ app.include_router(create_fastapi_router(schema, graphiql=True))
154
+ ```
155
+
156
+ The base installation includes the dependency-free `GraphQLASGI` adapter. See
157
+ the [integration documentation](docs/integrations/overview.mdx) for mounting,
158
+ request context, endpoint configuration, and supported versions.
159
+
160
+ Runnable per-framework projects — FastAPI, Starlette, Flask, Django, and raw ASGI, each
161
+ mounting the **same** [`examples/app`](examples/app) schema — live under
162
+ [`examples/projects`](examples/projects). Reusing one schema across every adapter is the
163
+ proof that the core is framework-agnostic; each project differs only in a few lines of glue.
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ pip install -e ".[dev]"
169
+ pytest
170
+ ```
@@ -0,0 +1,143 @@
1
+ """FastQL — a code-first, decorator-driven GraphQL framework for Python.
2
+
3
+ Define types and operations with decorators, build a schema, and execute
4
+ operations — all without any web framework. The core turns Python values into a
5
+ :class:`~fastql.types.schema.Schema` and runs operations through a hand-built,
6
+ async-first engine.
7
+
8
+ from fastql import Type, Query, Schema, Context, execute
9
+
10
+ @Type
11
+ class User:
12
+ id: int
13
+ name: str
14
+
15
+ @Query
16
+ class QueryRoot:
17
+ @Field
18
+ async def user(self, id: int, ctx: Context) -> "User":
19
+ return ctx.users[id]
20
+
21
+ schema = Schema(query=QueryRoot)
22
+ result = await execute(schema, "{ user(id: 1) { id name } }", context=ctx)
23
+ """
24
+
25
+ from fastql.context import (
26
+ Context,
27
+ Info,
28
+ ResolveInfo,
29
+ provides,
30
+ register_dependency,
31
+ )
32
+ from fastql.dataloader import DataLoader, DataLoaderError, get_loader
33
+ from fastql.decorators import (
34
+ Arg,
35
+ Argument,
36
+ BasePermission,
37
+ Enum,
38
+ Field,
39
+ FieldExtension,
40
+ Input,
41
+ Interface,
42
+ Mutation,
43
+ Query,
44
+ Scalar,
45
+ Subscription,
46
+ Type,
47
+ Union,
48
+ )
49
+ from fastql.errors import GraphQLError, GraphQLSyntaxError, ValidationError
50
+ from fastql.execution import ExecutionResult, execute, subscribe
51
+ from fastql.extensions import SchemaExtension
52
+ from fastql.language import parse
53
+ from fastql.registry import TypeRegistry, default_registry
54
+ from fastql.schema_builder import SchemaBuildError, build_schema
55
+ from fastql.sdl import print_schema
56
+ from fastql.testing import GraphQLTestClient
57
+ from fastql.types import (
58
+ AppliedDirective,
59
+ Boolean,
60
+ Float,
61
+ ID,
62
+ Int,
63
+ ListType,
64
+ NonNull,
65
+ Schema,
66
+ SchemaConfig,
67
+ String,
68
+ )
69
+ from fastql.validation import validate
70
+
71
+ __version__ = "0.0.1"
72
+
73
+ __all__ = [
74
+ "__version__",
75
+ # Decorators
76
+ "Type",
77
+ "Input",
78
+ "Interface",
79
+ "Enum",
80
+ "Union",
81
+ "Scalar",
82
+ "Query",
83
+ "Mutation",
84
+ "Subscription",
85
+ "Field",
86
+ "Arg",
87
+ "Argument",
88
+ "AppliedDirective",
89
+ "BasePermission",
90
+ "FieldExtension",
91
+ "SchemaExtension",
92
+ # Context / DI
93
+ "Context",
94
+ "Info",
95
+ "ResolveInfo",
96
+ "register_dependency",
97
+ "provides",
98
+ # DataLoader (request-scoped batch loading)
99
+ "DataLoader",
100
+ "DataLoaderError",
101
+ "get_loader",
102
+ # Built-in scalars and wrappers
103
+ "Int",
104
+ "Float",
105
+ "String",
106
+ "Boolean",
107
+ "ID",
108
+ "NonNull",
109
+ "ListType",
110
+ # Schema building and execution
111
+ "Schema",
112
+ "SchemaConfig",
113
+ "build_schema",
114
+ "SchemaBuildError",
115
+ "execute",
116
+ "subscribe",
117
+ "ExecutionResult",
118
+ "validate",
119
+ "parse",
120
+ "print_schema",
121
+ "GraphQLTestClient",
122
+ # Dev server (lazy — keeps the agnostic core free of the HTTP layer)
123
+ "serve",
124
+ "start_server",
125
+ # Registry
126
+ "TypeRegistry",
127
+ "default_registry",
128
+ # Errors
129
+ "GraphQLError",
130
+ "GraphQLSyntaxError",
131
+ "ValidationError",
132
+ ]
133
+
134
+
135
+ def __getattr__(name: str):
136
+ # `serve` / `start_server` live in fastql.server, which imports asyncio HTTP
137
+ # machinery. Import them lazily so the agnostic core never pulls in the
138
+ # transport layer just by `import fastql`.
139
+ if name in ("serve", "start_server"):
140
+ from fastql import server
141
+
142
+ return getattr(server, name)
143
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,6 @@
1
+ """Enable ``python -m fastql ...``."""
2
+
3
+ from fastql.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,130 @@
1
+ """Command-line entry point: ``python -m fastql serve module:attr``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import importlib
8
+ import json
9
+ import sys
10
+ from typing import Any
11
+
12
+ from fastql.server import DEFAULT_HOST, DEFAULT_PORT, serve
13
+ from fastql.types.schema import Schema
14
+
15
+
16
+ def _build_parser() -> argparse.ArgumentParser:
17
+ parser = argparse.ArgumentParser(prog="fastql", description="FastQL CLI")
18
+ sub = parser.add_subparsers(dest="command", required=True)
19
+
20
+ serve_parser = sub.add_parser("serve", help="Run the dev server for a schema.")
21
+ serve_parser.add_argument(
22
+ "target",
23
+ help="Dotted path to the schema object, e.g. 'myapp.schema:schema'.",
24
+ )
25
+ serve_parser.add_argument("--host", default=DEFAULT_HOST)
26
+ serve_parser.add_argument("--port", type=int, default=DEFAULT_PORT)
27
+ serve_parser.add_argument(
28
+ "--context",
29
+ default=None,
30
+ help=(
31
+ "Dotted path to a per-request context value or zero-arg factory, "
32
+ "e.g. 'myapp:make_context'."
33
+ ),
34
+ )
35
+
36
+ export_parser = sub.add_parser(
37
+ "export-schema", help="Write a schema's SDL or introspection JSON."
38
+ )
39
+ export_parser.add_argument(
40
+ "target",
41
+ help="Dotted path to the schema object, e.g. 'myapp.schema:schema'.",
42
+ )
43
+ export_parser.add_argument(
44
+ "--output",
45
+ "-o",
46
+ default=None,
47
+ help="File to write to. Writes to stdout when omitted.",
48
+ )
49
+ export_parser.add_argument(
50
+ "--format",
51
+ choices=("sdl", "json"),
52
+ default="sdl",
53
+ help="Output format: 'sdl' (default) or 'json' (introspection result).",
54
+ )
55
+ return parser
56
+
57
+
58
+ def _load_schema(target: str) -> Any:
59
+ """Import and return the schema object named by a dotted ``module:attr`` path."""
60
+ if ":" in target:
61
+ module_name, attr = target.split(":", 1)
62
+ else:
63
+ module_name, _, attr = target.rpartition(".")
64
+ if not module_name or not attr:
65
+ raise SystemExit(
66
+ f"Invalid target {target!r}; expected 'module:attr' or 'module.attr'."
67
+ )
68
+ try:
69
+ module = importlib.import_module(module_name)
70
+ except ImportError as error:
71
+ raise SystemExit(f"Could not import module {module_name!r}: {error}")
72
+ try:
73
+ return getattr(module, attr)
74
+ except AttributeError:
75
+ raise SystemExit(f"Module {module_name!r} has no attribute {attr!r}.")
76
+
77
+
78
+ def _render_schema(schema: Any, fmt: str) -> str:
79
+ """Render ``schema`` as SDL or as an introspection-result JSON document."""
80
+ if not isinstance(schema, Schema):
81
+ raise SystemExit(
82
+ f"Loaded object is not a fastql Schema (got {type(schema).__name__})."
83
+ )
84
+ if fmt == "json":
85
+ from fastql.execution import execute
86
+ from fastql.integrations.http import INTROSPECTION_QUERY
87
+
88
+ result = asyncio.run(execute(schema, INTROSPECTION_QUERY))
89
+ if result.errors:
90
+ raise SystemExit(
91
+ f"Introspection failed: {result.errors[0].message}"
92
+ )
93
+ return json.dumps(result.data, indent=2)
94
+
95
+ from fastql.sdl import print_schema
96
+
97
+ return print_schema(schema)
98
+
99
+
100
+ def _export_schema(target: str, output: str | None, fmt: str) -> None:
101
+ schema = _load_schema(target)
102
+ rendered = _render_schema(schema, fmt)
103
+ if output:
104
+ with open(output, "w", encoding="utf-8") as handle:
105
+ handle.write(rendered + ("\n" if not rendered.endswith("\n") else ""))
106
+ else:
107
+ print(rendered)
108
+
109
+
110
+ def main(argv: list[str] | None = None) -> None:
111
+ parser = _build_parser()
112
+ args = parser.parse_args(argv)
113
+ if args.command == "serve":
114
+ schema = _load_schema(args.target)
115
+ context_factory = None
116
+ if args.context:
117
+ obj = _load_schema(args.context)
118
+ context_factory = obj if callable(obj) else (lambda value=obj: value)
119
+ serve(
120
+ schema,
121
+ host=args.host,
122
+ port=args.port,
123
+ context_factory=context_factory,
124
+ )
125
+ elif args.command == "export-schema":
126
+ _export_schema(args.target, args.output, args.format)
127
+
128
+
129
+ if __name__ == "__main__": # pragma: no cover
130
+ main()