graphql-http 1.6.2__tar.gz → 2.0.2__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 (45) hide show
  1. graphql_http-2.0.2/.coverage +0 -0
  2. graphql_http-2.0.2/.github/workflows/python-package.yml +78 -0
  3. graphql_http-2.0.2/.gitignore +24 -0
  4. graphql_http-2.0.2/PKG-INFO +274 -0
  5. graphql_http-2.0.2/README.md +246 -0
  6. graphql_http-2.0.2/VERSION +1 -0
  7. graphql_http-2.0.2/examples/README.md +288 -0
  8. graphql_http-2.0.2/examples/advanced_server.py +411 -0
  9. graphql_http-2.0.2/examples/auth_server.py +234 -0
  10. graphql_http-2.0.2/examples/basic_server.py +139 -0
  11. graphql_http-2.0.2/examples/graphql_api_server.py +367 -0
  12. graphql_http-2.0.2/graphql_http/error.py +47 -0
  13. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http/helpers.py +126 -45
  14. graphql_http-2.0.2/graphql_http/server.py +608 -0
  15. graphql_http-2.0.2/graphql_http.egg-info/PKG-INFO +274 -0
  16. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http.egg-info/SOURCES.txt +15 -4
  17. graphql_http-2.0.2/graphql_http.egg-info/requires.txt +9 -0
  18. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http.egg-info/top_level.txt +0 -1
  19. graphql_http-2.0.2/pyproject.toml +77 -0
  20. graphql_http-2.0.2/setup.cfg +4 -0
  21. graphql_http-2.0.2/test_server.py +41 -0
  22. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/app.py +33 -12
  23. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/conftest.py +2 -1
  24. graphql_http-2.0.2/tests/test_auth.py +292 -0
  25. graphql_http-2.0.2/tests/test_cors.py +456 -0
  26. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/test_graphql_api.py +6 -3
  27. graphql_http-2.0.2/tests/test_server.py +392 -0
  28. graphql_http-2.0.2/uv.lock +969 -0
  29. graphql_http-1.6.2/MANIFEST.in +0 -4
  30. graphql_http-1.6.2/PKG-INFO +0 -40
  31. graphql_http-1.6.2/README.md +0 -3
  32. graphql_http-1.6.2/VERSION +0 -1
  33. graphql_http-1.6.2/graphql_http/error.py +0 -19
  34. graphql_http-1.6.2/graphql_http/server.py +0 -368
  35. graphql_http-1.6.2/graphql_http.egg-info/PKG-INFO +0 -40
  36. graphql_http-1.6.2/graphql_http.egg-info/requires.txt +0 -7
  37. graphql_http-1.6.2/setup.cfg +0 -12
  38. graphql_http-1.6.2/setup.py +0 -44
  39. {graphql_http-1.6.2 → graphql_http-2.0.2}/LICENSE +0 -0
  40. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http/__init__.py +0 -0
  41. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http/graphiql/index.html +0 -0
  42. {graphql_http-1.6.2 → graphql_http-2.0.2}/graphql_http.egg-info/dependency_links.txt +0 -0
  43. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/__init__.py +0 -0
  44. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/app_federation.py +0 -0
  45. {graphql_http-1.6.2 → graphql_http-2.0.2}/tests/test_federation.py +0 -0
Binary file
@@ -0,0 +1,78 @@
1
+ name: Python Package CI/CD
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main # Or your default branch
7
+ tags:
8
+ - '*.*.*' # For publishing to PyPI
9
+ pull_request:
10
+ branches:
11
+ - main # Or your default branch
12
+
13
+ jobs:
14
+ build_and_test:
15
+ name: Build and Test (Python ${{ matrix.python-version }})
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.11", "3.12"] # graphql-api requires Python 3.11+
21
+
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Set up Python ${{ matrix.python-version }}
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+
31
+ - name: Install UV
32
+ uses: astral-sh/setup-uv@v3
33
+
34
+ - name: Install dependencies
35
+ run: uv sync --dev
36
+
37
+ - name: Lint with Flake8
38
+ run: uv run flake8 graphql_http tests
39
+
40
+ - name: Test with Pytest
41
+ run: uv run pytest
42
+
43
+ publish:
44
+ name: Publish to PyPI
45
+ needs: build_and_test # Ensure tests pass before publishing
46
+ runs-on: ubuntu-latest
47
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
48
+ permissions:
49
+ id-token: write
50
+
51
+ steps:
52
+ - name: Validate tag format (e.g., 1.0.0)
53
+ run: |
54
+ TAG_NAME="${{ github.ref_name }}"
55
+ if ! [[ "$TAG_NAME" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
56
+ echo "Error: Tag '$TAG_NAME' is not in the required X.Y.Z numeric format (e.g., 1.0.0)."
57
+ exit 1
58
+ fi
59
+ echo "Tag '$TAG_NAME' is valid."
60
+ shell: bash
61
+
62
+ - name: Checkout repository
63
+ uses: actions/checkout@v4
64
+
65
+ - name: Set up Python
66
+ uses: actions/setup-python@v5
67
+ with:
68
+ python-version: '3.12' # Use compatible Python version for publishing
69
+
70
+ - name: Install UV
71
+ uses: astral-sh/setup-uv@v3
72
+
73
+ - name: Update VERSION file
74
+ run: echo "${{ github.ref_name }}" > VERSION
75
+ shell: bash
76
+
77
+ - name: Build and publish package to PyPI
78
+ run: uv build && uv publish
@@ -0,0 +1,24 @@
1
+ *.pyc
2
+ .DS_Store
3
+ Pipfile.lock
4
+
5
+ # Build artifacts
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+ *.swo
20
+
21
+ # Testing
22
+ .pytest_cache/
23
+ .coverage
24
+ htmlcov/
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: graphql_http
3
+ Version: 2.0.2
4
+ Summary: HTTP Server for GraphQL.
5
+ Author-email: Robert Parker <rob@parob.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/parob/graphql-http
8
+ Project-URL: Repository, https://gitlab.com/parob/graphql-http
9
+ Keywords: GraphQL,HTTP,Server,starlette
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: <3.13,>=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: graphql-core
19
+ Requires-Dist: graphql-api
20
+ Requires-Dist: packaging
21
+ Requires-Dist: graphql-schema-diff
22
+ Requires-Dist: pyjwt[crypto]
23
+ Requires-Dist: starlette
24
+ Requires-Dist: uvicorn
25
+ Requires-Dist: python-multipart
26
+ Requires-Dist: httpx
27
+ Dynamic: license-file
28
+
29
+ # GraphQL HTTP Server
30
+
31
+ A lightweight, production-ready HTTP server for GraphQL APIs built on top of Starlette/FastAPI. This server provides a simple yet powerful way to serve GraphQL schemas over HTTP with built-in support for authentication, CORS, GraphiQL integration, and more.
32
+
33
+ ## Features
34
+
35
+ - 🚀 **High Performance**: Built on Starlette/ASGI for excellent async performance
36
+ - 🔐 **JWT Authentication**: Built-in JWT authentication with JWKS support
37
+ - 🌐 **CORS Support**: Configurable CORS middleware for cross-origin requests
38
+ - 🎨 **GraphiQL Integration**: Interactive GraphQL IDE for development
39
+ - 📊 **Health Checks**: Built-in health check endpoints
40
+ - 🔄 **Batch Queries**: Support for batched GraphQL operations
41
+ - 🛡️ **Error Handling**: Comprehensive error handling and formatting
42
+ - 📝 **Type Safety**: Full TypeScript-style type hints for Python
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ uv add graphql_http
48
+ ```
49
+
50
+ Or with pip:
51
+ ```bash
52
+ pip install graphql_http
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ### Basic Usage
58
+
59
+ ```python
60
+ from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString
61
+ from graphql_http import GraphQLHTTP
62
+
63
+ # Define your GraphQL schema
64
+ schema = GraphQLSchema(
65
+ query=GraphQLObjectType(
66
+ name="Query",
67
+ fields={
68
+ "hello": GraphQLField(
69
+ GraphQLString,
70
+ resolve=lambda obj, info: "Hello, World!"
71
+ )
72
+ }
73
+ )
74
+ )
75
+
76
+ # Create the HTTP server
77
+ app = GraphQLHTTP(schema=schema)
78
+
79
+ # Run the server
80
+ if __name__ == "__main__":
81
+ app.run(host="0.0.0.0", port=8000)
82
+ ```
83
+
84
+ ### Using with GraphQL-API
85
+
86
+ For more advanced schemas, integrate with `graphql-api`:
87
+
88
+ ```python
89
+ from graphql_api import GraphQLAPI
90
+ from graphql_http import GraphQLHTTP
91
+
92
+ api = GraphQLAPI()
93
+
94
+ @api.type(is_root_type=True)
95
+ class Query:
96
+ @api.field
97
+ def hello(self, name: str = "World") -> str:
98
+ return f"Hello, {name}!"
99
+
100
+ # Create server from API
101
+ server = GraphQLHTTP.from_api(api)
102
+ server.run()
103
+ ```
104
+
105
+ ## Configuration Options
106
+
107
+ ### Basic Configuration
108
+
109
+ ```python
110
+ app = GraphQLHTTP(
111
+ schema=schema,
112
+ serve_graphiql=True, # Enable GraphiQL interface
113
+ graphiql_default_query="{ hello }", # Default query in GraphiQL
114
+ allow_cors=True, # Enable CORS
115
+ health_path="/health" # Health check endpoint
116
+ )
117
+ ```
118
+
119
+ ### Authentication Configuration
120
+
121
+ ```python
122
+ app = GraphQLHTTP(
123
+ schema=schema,
124
+ auth_enabled=True,
125
+ auth_jwks_uri="https://your-auth0-domain/.well-known/jwks.json",
126
+ auth_issuer="https://your-auth0-domain/",
127
+ auth_audience="your-api-audience",
128
+ auth_enabled_for_introspection=False # Allow introspection without auth
129
+ )
130
+ ```
131
+
132
+ ### Advanced Configuration
133
+
134
+ ```python
135
+ from graphql.execution import ExecutionContext
136
+
137
+ class CustomExecutionContext(ExecutionContext):
138
+ # Custom execution logic
139
+ pass
140
+
141
+ app = GraphQLHTTP(
142
+ schema=schema,
143
+ root_value={"version": "1.0"},
144
+ middleware=[your_middleware_function],
145
+ context_value=custom_context,
146
+ execution_context_class=CustomExecutionContext
147
+ )
148
+ ```
149
+
150
+ ## API Reference
151
+
152
+ ### GraphQLHTTP Class
153
+
154
+ #### Constructor Parameters
155
+
156
+ - `schema` (GraphQLSchema): The GraphQL schema to serve
157
+ - `root_value` (Any, optional): Root value passed to resolvers
158
+ - `middleware` (List[Callable], optional): List of middleware functions
159
+ - `context_value` (Any, optional): Context passed to resolvers
160
+ - `serve_graphiql` (bool, default: True): Whether to serve GraphiQL interface
161
+ - `graphiql_default_query` (str, optional): Default query for GraphiQL
162
+ - `allow_cors` (bool, default: False): Enable CORS middleware
163
+ - `health_path` (str, optional): Path for health check endpoint
164
+ - `execution_context_class` (Type[ExecutionContext], optional): Custom execution context
165
+ - `auth_enabled` (bool, default: False): Enable JWT authentication
166
+ - `auth_jwks_uri` (str, optional): JWKS URI for JWT validation
167
+ - `auth_issuer` (str, optional): Expected JWT issuer
168
+ - `auth_audience` (str, optional): Expected JWT audience
169
+ - `auth_enabled_for_introspection` (bool, default: False): Require auth for introspection
170
+
171
+ #### Methods
172
+
173
+ - `from_api(api, **kwargs)`: Create server from GraphQL-API instance
174
+ - `run(host, port, **kwargs)`: Run the server
175
+ - `client()`: Get test client for testing
176
+
177
+ ## HTTP Endpoints
178
+
179
+ ### POST /graphql
180
+
181
+ Execute GraphQL operations:
182
+
183
+ ```bash
184
+ curl -X POST http://localhost:8000/graphql \
185
+ -H "Content-Type: application/json" \
186
+ -d '{"query": "{ hello }"}'
187
+ ```
188
+
189
+ ### GET /graphql
190
+
191
+ Execute GraphQL queries via GET (with query parameter):
192
+
193
+ ```bash
194
+ curl "http://localhost:8000/graphql?query={hello}"
195
+ ```
196
+
197
+ Access GraphiQL interface in browser:
198
+
199
+ ```
200
+ http://localhost:8000/graphql
201
+ ```
202
+
203
+ ### GET /health
204
+
205
+ Health check endpoint (if configured):
206
+
207
+ ```bash
208
+ curl http://localhost:8000/health
209
+ ```
210
+
211
+ ## Authentication
212
+
213
+ When authentication is enabled, requests must include a valid JWT token:
214
+
215
+ ```bash
216
+ curl -X POST http://localhost:8000/graphql \
217
+ -H "Content-Type: application/json" \
218
+ -H "Authorization: Bearer YOUR_JWT_TOKEN" \
219
+ -d '{"query": "{ hello }"}'
220
+ ```
221
+
222
+ ## Testing
223
+
224
+ The server includes a built-in test client:
225
+
226
+ ```python
227
+ from graphql_http import GraphQLHTTP
228
+
229
+ server = GraphQLHTTP(schema=schema)
230
+ client = server.client()
231
+
232
+ response = client.post("/graphql", json={"query": "{ hello }"})
233
+ assert response.status_code == 200
234
+ assert response.json() == {"data": {"hello": "Hello, World!"}}
235
+ ```
236
+
237
+ ## Error Handling
238
+
239
+ The server provides comprehensive error handling:
240
+
241
+ - **400 Bad Request**: Malformed queries or invalid JSON
242
+ - **401 Unauthorized**: Invalid or missing authentication
243
+ - **405 Method Not Allowed**: Invalid HTTP method
244
+ - **500 Internal Server Error**: Server-side errors
245
+
246
+ ## Development
247
+
248
+ ### Running Tests
249
+
250
+ With UV:
251
+ ```bash
252
+ uv run pytest tests/ -v
253
+ ```
254
+
255
+ Or with Python directly:
256
+ ```bash
257
+ python -m pytest tests/ -v
258
+ ```
259
+
260
+ ### Contributing
261
+
262
+ 1. Fork the repository
263
+ 2. Create a feature branch
264
+ 3. Add tests for your changes
265
+ 4. Ensure all tests pass
266
+ 5. Submit a pull request
267
+
268
+ ## License
269
+
270
+ MIT License - see LICENSE file for details.
271
+
272
+ ## Changelog
273
+
274
+ See CHANGELOG.md for version history and updates.
@@ -0,0 +1,246 @@
1
+ # GraphQL HTTP Server
2
+
3
+ A lightweight, production-ready HTTP server for GraphQL APIs built on top of Starlette/FastAPI. This server provides a simple yet powerful way to serve GraphQL schemas over HTTP with built-in support for authentication, CORS, GraphiQL integration, and more.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **High Performance**: Built on Starlette/ASGI for excellent async performance
8
+ - 🔐 **JWT Authentication**: Built-in JWT authentication with JWKS support
9
+ - 🌐 **CORS Support**: Configurable CORS middleware for cross-origin requests
10
+ - 🎨 **GraphiQL Integration**: Interactive GraphQL IDE for development
11
+ - 📊 **Health Checks**: Built-in health check endpoints
12
+ - 🔄 **Batch Queries**: Support for batched GraphQL operations
13
+ - 🛡️ **Error Handling**: Comprehensive error handling and formatting
14
+ - 📝 **Type Safety**: Full TypeScript-style type hints for Python
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ uv add graphql_http
20
+ ```
21
+
22
+ Or with pip:
23
+ ```bash
24
+ pip install graphql_http
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### Basic Usage
30
+
31
+ ```python
32
+ from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString
33
+ from graphql_http import GraphQLHTTP
34
+
35
+ # Define your GraphQL schema
36
+ schema = GraphQLSchema(
37
+ query=GraphQLObjectType(
38
+ name="Query",
39
+ fields={
40
+ "hello": GraphQLField(
41
+ GraphQLString,
42
+ resolve=lambda obj, info: "Hello, World!"
43
+ )
44
+ }
45
+ )
46
+ )
47
+
48
+ # Create the HTTP server
49
+ app = GraphQLHTTP(schema=schema)
50
+
51
+ # Run the server
52
+ if __name__ == "__main__":
53
+ app.run(host="0.0.0.0", port=8000)
54
+ ```
55
+
56
+ ### Using with GraphQL-API
57
+
58
+ For more advanced schemas, integrate with `graphql-api`:
59
+
60
+ ```python
61
+ from graphql_api import GraphQLAPI
62
+ from graphql_http import GraphQLHTTP
63
+
64
+ api = GraphQLAPI()
65
+
66
+ @api.type(is_root_type=True)
67
+ class Query:
68
+ @api.field
69
+ def hello(self, name: str = "World") -> str:
70
+ return f"Hello, {name}!"
71
+
72
+ # Create server from API
73
+ server = GraphQLHTTP.from_api(api)
74
+ server.run()
75
+ ```
76
+
77
+ ## Configuration Options
78
+
79
+ ### Basic Configuration
80
+
81
+ ```python
82
+ app = GraphQLHTTP(
83
+ schema=schema,
84
+ serve_graphiql=True, # Enable GraphiQL interface
85
+ graphiql_default_query="{ hello }", # Default query in GraphiQL
86
+ allow_cors=True, # Enable CORS
87
+ health_path="/health" # Health check endpoint
88
+ )
89
+ ```
90
+
91
+ ### Authentication Configuration
92
+
93
+ ```python
94
+ app = GraphQLHTTP(
95
+ schema=schema,
96
+ auth_enabled=True,
97
+ auth_jwks_uri="https://your-auth0-domain/.well-known/jwks.json",
98
+ auth_issuer="https://your-auth0-domain/",
99
+ auth_audience="your-api-audience",
100
+ auth_enabled_for_introspection=False # Allow introspection without auth
101
+ )
102
+ ```
103
+
104
+ ### Advanced Configuration
105
+
106
+ ```python
107
+ from graphql.execution import ExecutionContext
108
+
109
+ class CustomExecutionContext(ExecutionContext):
110
+ # Custom execution logic
111
+ pass
112
+
113
+ app = GraphQLHTTP(
114
+ schema=schema,
115
+ root_value={"version": "1.0"},
116
+ middleware=[your_middleware_function],
117
+ context_value=custom_context,
118
+ execution_context_class=CustomExecutionContext
119
+ )
120
+ ```
121
+
122
+ ## API Reference
123
+
124
+ ### GraphQLHTTP Class
125
+
126
+ #### Constructor Parameters
127
+
128
+ - `schema` (GraphQLSchema): The GraphQL schema to serve
129
+ - `root_value` (Any, optional): Root value passed to resolvers
130
+ - `middleware` (List[Callable], optional): List of middleware functions
131
+ - `context_value` (Any, optional): Context passed to resolvers
132
+ - `serve_graphiql` (bool, default: True): Whether to serve GraphiQL interface
133
+ - `graphiql_default_query` (str, optional): Default query for GraphiQL
134
+ - `allow_cors` (bool, default: False): Enable CORS middleware
135
+ - `health_path` (str, optional): Path for health check endpoint
136
+ - `execution_context_class` (Type[ExecutionContext], optional): Custom execution context
137
+ - `auth_enabled` (bool, default: False): Enable JWT authentication
138
+ - `auth_jwks_uri` (str, optional): JWKS URI for JWT validation
139
+ - `auth_issuer` (str, optional): Expected JWT issuer
140
+ - `auth_audience` (str, optional): Expected JWT audience
141
+ - `auth_enabled_for_introspection` (bool, default: False): Require auth for introspection
142
+
143
+ #### Methods
144
+
145
+ - `from_api(api, **kwargs)`: Create server from GraphQL-API instance
146
+ - `run(host, port, **kwargs)`: Run the server
147
+ - `client()`: Get test client for testing
148
+
149
+ ## HTTP Endpoints
150
+
151
+ ### POST /graphql
152
+
153
+ Execute GraphQL operations:
154
+
155
+ ```bash
156
+ curl -X POST http://localhost:8000/graphql \
157
+ -H "Content-Type: application/json" \
158
+ -d '{"query": "{ hello }"}'
159
+ ```
160
+
161
+ ### GET /graphql
162
+
163
+ Execute GraphQL queries via GET (with query parameter):
164
+
165
+ ```bash
166
+ curl "http://localhost:8000/graphql?query={hello}"
167
+ ```
168
+
169
+ Access GraphiQL interface in browser:
170
+
171
+ ```
172
+ http://localhost:8000/graphql
173
+ ```
174
+
175
+ ### GET /health
176
+
177
+ Health check endpoint (if configured):
178
+
179
+ ```bash
180
+ curl http://localhost:8000/health
181
+ ```
182
+
183
+ ## Authentication
184
+
185
+ When authentication is enabled, requests must include a valid JWT token:
186
+
187
+ ```bash
188
+ curl -X POST http://localhost:8000/graphql \
189
+ -H "Content-Type: application/json" \
190
+ -H "Authorization: Bearer YOUR_JWT_TOKEN" \
191
+ -d '{"query": "{ hello }"}'
192
+ ```
193
+
194
+ ## Testing
195
+
196
+ The server includes a built-in test client:
197
+
198
+ ```python
199
+ from graphql_http import GraphQLHTTP
200
+
201
+ server = GraphQLHTTP(schema=schema)
202
+ client = server.client()
203
+
204
+ response = client.post("/graphql", json={"query": "{ hello }"})
205
+ assert response.status_code == 200
206
+ assert response.json() == {"data": {"hello": "Hello, World!"}}
207
+ ```
208
+
209
+ ## Error Handling
210
+
211
+ The server provides comprehensive error handling:
212
+
213
+ - **400 Bad Request**: Malformed queries or invalid JSON
214
+ - **401 Unauthorized**: Invalid or missing authentication
215
+ - **405 Method Not Allowed**: Invalid HTTP method
216
+ - **500 Internal Server Error**: Server-side errors
217
+
218
+ ## Development
219
+
220
+ ### Running Tests
221
+
222
+ With UV:
223
+ ```bash
224
+ uv run pytest tests/ -v
225
+ ```
226
+
227
+ Or with Python directly:
228
+ ```bash
229
+ python -m pytest tests/ -v
230
+ ```
231
+
232
+ ### Contributing
233
+
234
+ 1. Fork the repository
235
+ 2. Create a feature branch
236
+ 3. Add tests for your changes
237
+ 4. Ensure all tests pass
238
+ 5. Submit a pull request
239
+
240
+ ## License
241
+
242
+ MIT License - see LICENSE file for details.
243
+
244
+ ## Changelog
245
+
246
+ See CHANGELOG.md for version history and updates.
@@ -0,0 +1 @@
1
+ 2.0.2