jentic-openapi-validator 1.0.0a27__tar.gz → 1.0.0a29__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 (17) hide show
  1. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/PKG-INFO +49 -10
  2. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/README.md +45 -6
  3. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/pyproject.toml +4 -4
  4. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/core/openapi_validator.py +92 -18
  5. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/LICENSE +0 -0
  6. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/NOTICE +0 -0
  7. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/base.py +0 -0
  8. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/default/__init__.py +0 -0
  9. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/default/rules/__init__.py +0 -0
  10. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/default/rules/security.py +0 -0
  11. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/default/rules/server.py +0 -0
  12. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/default/rules/structural.py +0 -0
  13. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/openapi_spec.py +0 -0
  14. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/backends/py.typed +0 -0
  15. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/core/__init__.py +0 -0
  16. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/core/diagnostics.py +0 -0
  17. {jentic_openapi_validator-1.0.0a27 → jentic_openapi_validator-1.0.0a29}/src/jentic/apitools/openapi/validator/core/py.typed +0 -0
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jentic-openapi-validator
3
- Version: 1.0.0a27
3
+ Version: 1.0.0a29
4
4
  Summary: Jentic OpenAPI Validator
5
5
  Author: Jentic
6
6
  Author-email: Jentic <hello@jentic.com>
7
7
  License-Expression: Apache-2.0
8
8
  License-File: LICENSE
9
9
  License-File: NOTICE
10
- Requires-Dist: jentic-openapi-parser~=1.0.0a27
10
+ Requires-Dist: jentic-openapi-parser~=1.0.0a29
11
11
  Requires-Dist: openapi-spec-validator~=0.7.2
12
12
  Requires-Dist: lsprotocol~=2025.0.0
13
- Requires-Dist: jentic-openapi-validator-redocly~=1.0.0a27 ; extra == 'redocly'
14
- Requires-Dist: jentic-openapi-validator-spectral~=1.0.0a27 ; extra == 'spectral'
13
+ Requires-Dist: jentic-openapi-validator-redocly~=1.0.0a29 ; extra == 'redocly'
14
+ Requires-Dist: jentic-openapi-validator-spectral~=1.0.0a29 ; extra == 'spectral'
15
15
  Requires-Python: >=3.11
16
16
  Project-URL: Homepage, https://github.com/jentic/jentic-openapi-tools
17
17
  Provides-Extra: redocly
@@ -26,6 +26,7 @@ A Python library for validating OpenAPI documents using pluggable validator back
26
26
 
27
27
  - **Pluggable Backend Architecture**: Support for multiple validation strategies via entry points
28
28
  - **Multiple Input Formats**: Validate OpenAPI documents from file URIs, JSON/YAML strings, or Python dictionaries
29
+ - **Parallel Execution**: Run multiple backends concurrently using `ProcessPoolExecutor` for improved performance
29
30
  - **Aggregated Results**: Collect diagnostics from all configured backends into a single result
30
31
  - **Type Safety**: Full type hints with comprehensive docstrings
31
32
  - **Extensible Design**: Easy integration of third-party validator backends
@@ -47,6 +48,12 @@ For advanced validation with Spectral:
47
48
  pip install jentic-openapi-validator-spectral
48
49
  ```
49
50
 
51
+ For validation with Redocly:
52
+
53
+ ```bash
54
+ pip install jentic-openapi-validator-redocly
55
+ ```
56
+
50
57
  ## Quick Start
51
58
 
52
59
  ### Basic Validation
@@ -132,6 +139,26 @@ parser = OpenAPIParser()
132
139
  validator = OpenAPIValidator(parser=parser)
133
140
  ```
134
141
 
142
+ ### Parallel Execution
143
+
144
+ When using multiple backends, you can run them in parallel for improved performance:
145
+
146
+ ```python
147
+ # Run backends in parallel using ProcessPoolExecutor
148
+ validator = OpenAPIValidator(backends=["default", "openapi-spec", "spectral"])
149
+ result = validator.validate(document, parallel=True)
150
+
151
+ # Limit the number of worker processes
152
+ result = validator.validate(document, parallel=True, max_workers=2)
153
+ ```
154
+
155
+ Parallel execution uses `ProcessPoolExecutor` for true parallelism that bypasses Python's GIL. This is particularly beneficial when using multiple backends, especially I/O-bound backends like Spectral and Redocly that spawn subprocesses.
156
+
157
+ **Notes:**
158
+ - `parallel=False` by default (opt-in)
159
+ - With a single backend, `parallel=True` has no effect (runs sequentially)
160
+ - Diagnostics from all backends are aggregated regardless of execution mode
161
+
135
162
  ## Working with ValidationResult
136
163
 
137
164
  The `ValidationResult` class provides convenient methods for working with validation diagnostics:
@@ -175,24 +202,28 @@ The package includes integration tests for backend discovery and validation. Tes
175
202
  class OpenAPIValidator:
176
203
  def __init__(
177
204
  self,
178
- backends: list[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
205
+ backends: Sequence[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
179
206
  parser: OpenAPIParser | None = None,
180
207
  ) -> None
181
208
  ```
182
209
 
183
210
  **Parameters:**
184
- - `backends`: List of validator backends to use. Each item can be:
185
- - `str`: Name of a backend registered via entry points (e.g., "openapi-spec", "spectral")
211
+ - `backends`: Sequence of validator backends to use. Each item can be:
212
+ - `str`: Name of a backend registered via entry points (e.g., "default", "openapi-spec", "redocly", "spectral")
186
213
  - `BaseValidatorBackend`: Instance of a validator backend
187
214
  - `Type[BaseValidatorBackend]`: Class of a validator backend (will be instantiated)
188
- - Defaults to `["openapi-spec"]` if `None`
215
+ - Defaults to `["default"]` if `None`
189
216
  - `parser`: Custom OpenAPIParser instance (optional)
190
217
 
191
218
  **Methods:**
192
219
 
193
- - `validate(document: str | dict) -> ValidationResult`
220
+ - `validate(document: str | dict, *, base_url: str | None = None, target: str | None = None, parallel: bool = False, max_workers: int | None = None) -> ValidationResult`
194
221
  - Validates an OpenAPI document using all configured backends
195
222
  - `document`: File URI, JSON/YAML string, or dictionary
223
+ - `base_url`: Optional base URL for resolving relative references
224
+ - `target`: Optional target identifier for validation context
225
+ - `parallel`: If `True` and multiple backends are configured, run validation in parallel using `ProcessPoolExecutor`. Defaults to `False`.
226
+ - `max_workers`: Maximum number of worker processes for parallel execution. If `None`, defaults to the number of processors on the machine. Only used when `parallel=True`.
196
227
  - Returns: `ValidationResult` with aggregated diagnostics
197
228
 
198
229
  ### ValidationResult
@@ -218,7 +249,15 @@ class ValidationResult:
218
249
  ### default
219
250
  Basic validation backend that checks for required OpenAPI fields and structure. Suitable for basic document validation.
220
251
 
252
+ ### openapi-spec
253
+ Validation backend using the `openapi-spec-validator` library for JSON Schema-based validation of OpenAPI documents. CPU-bound validation.
254
+
255
+ ### redocly (Optional)
256
+ Validation backend using Redocly CLI for comprehensive OpenAPI linting and validation. I/O-bound (spawns Node.js subprocess).
257
+
258
+ Install: `pip install jentic-openapi-validator-redocly`
259
+
221
260
  ### spectral (Optional)
222
- Advanced validation backend using Spectral CLI with comprehensive rule checking.
261
+ Advanced validation backend using Spectral CLI with comprehensive rule checking. I/O-bound (spawns Node.js subprocess).
223
262
 
224
263
  Install: `pip install jentic-openapi-validator-spectral`
@@ -6,6 +6,7 @@ A Python library for validating OpenAPI documents using pluggable validator back
6
6
 
7
7
  - **Pluggable Backend Architecture**: Support for multiple validation strategies via entry points
8
8
  - **Multiple Input Formats**: Validate OpenAPI documents from file URIs, JSON/YAML strings, or Python dictionaries
9
+ - **Parallel Execution**: Run multiple backends concurrently using `ProcessPoolExecutor` for improved performance
9
10
  - **Aggregated Results**: Collect diagnostics from all configured backends into a single result
10
11
  - **Type Safety**: Full type hints with comprehensive docstrings
11
12
  - **Extensible Design**: Easy integration of third-party validator backends
@@ -27,6 +28,12 @@ For advanced validation with Spectral:
27
28
  pip install jentic-openapi-validator-spectral
28
29
  ```
29
30
 
31
+ For validation with Redocly:
32
+
33
+ ```bash
34
+ pip install jentic-openapi-validator-redocly
35
+ ```
36
+
30
37
  ## Quick Start
31
38
 
32
39
  ### Basic Validation
@@ -112,6 +119,26 @@ parser = OpenAPIParser()
112
119
  validator = OpenAPIValidator(parser=parser)
113
120
  ```
114
121
 
122
+ ### Parallel Execution
123
+
124
+ When using multiple backends, you can run them in parallel for improved performance:
125
+
126
+ ```python
127
+ # Run backends in parallel using ProcessPoolExecutor
128
+ validator = OpenAPIValidator(backends=["default", "openapi-spec", "spectral"])
129
+ result = validator.validate(document, parallel=True)
130
+
131
+ # Limit the number of worker processes
132
+ result = validator.validate(document, parallel=True, max_workers=2)
133
+ ```
134
+
135
+ Parallel execution uses `ProcessPoolExecutor` for true parallelism that bypasses Python's GIL. This is particularly beneficial when using multiple backends, especially I/O-bound backends like Spectral and Redocly that spawn subprocesses.
136
+
137
+ **Notes:**
138
+ - `parallel=False` by default (opt-in)
139
+ - With a single backend, `parallel=True` has no effect (runs sequentially)
140
+ - Diagnostics from all backends are aggregated regardless of execution mode
141
+
115
142
  ## Working with ValidationResult
116
143
 
117
144
  The `ValidationResult` class provides convenient methods for working with validation diagnostics:
@@ -155,24 +182,28 @@ The package includes integration tests for backend discovery and validation. Tes
155
182
  class OpenAPIValidator:
156
183
  def __init__(
157
184
  self,
158
- backends: list[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
185
+ backends: Sequence[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
159
186
  parser: OpenAPIParser | None = None,
160
187
  ) -> None
161
188
  ```
162
189
 
163
190
  **Parameters:**
164
- - `backends`: List of validator backends to use. Each item can be:
165
- - `str`: Name of a backend registered via entry points (e.g., "openapi-spec", "spectral")
191
+ - `backends`: Sequence of validator backends to use. Each item can be:
192
+ - `str`: Name of a backend registered via entry points (e.g., "default", "openapi-spec", "redocly", "spectral")
166
193
  - `BaseValidatorBackend`: Instance of a validator backend
167
194
  - `Type[BaseValidatorBackend]`: Class of a validator backend (will be instantiated)
168
- - Defaults to `["openapi-spec"]` if `None`
195
+ - Defaults to `["default"]` if `None`
169
196
  - `parser`: Custom OpenAPIParser instance (optional)
170
197
 
171
198
  **Methods:**
172
199
 
173
- - `validate(document: str | dict) -> ValidationResult`
200
+ - `validate(document: str | dict, *, base_url: str | None = None, target: str | None = None, parallel: bool = False, max_workers: int | None = None) -> ValidationResult`
174
201
  - Validates an OpenAPI document using all configured backends
175
202
  - `document`: File URI, JSON/YAML string, or dictionary
203
+ - `base_url`: Optional base URL for resolving relative references
204
+ - `target`: Optional target identifier for validation context
205
+ - `parallel`: If `True` and multiple backends are configured, run validation in parallel using `ProcessPoolExecutor`. Defaults to `False`.
206
+ - `max_workers`: Maximum number of worker processes for parallel execution. If `None`, defaults to the number of processors on the machine. Only used when `parallel=True`.
176
207
  - Returns: `ValidationResult` with aggregated diagnostics
177
208
 
178
209
  ### ValidationResult
@@ -198,7 +229,15 @@ class ValidationResult:
198
229
  ### default
199
230
  Basic validation backend that checks for required OpenAPI fields and structure. Suitable for basic document validation.
200
231
 
232
+ ### openapi-spec
233
+ Validation backend using the `openapi-spec-validator` library for JSON Schema-based validation of OpenAPI documents. CPU-bound validation.
234
+
235
+ ### redocly (Optional)
236
+ Validation backend using Redocly CLI for comprehensive OpenAPI linting and validation. I/O-bound (spawns Node.js subprocess).
237
+
238
+ Install: `pip install jentic-openapi-validator-redocly`
239
+
201
240
  ### spectral (Optional)
202
- Advanced validation backend using Spectral CLI with comprehensive rule checking.
241
+ Advanced validation backend using Spectral CLI with comprehensive rule checking. I/O-bound (spawns Node.js subprocess).
203
242
 
204
243
  Install: `pip install jentic-openapi-validator-spectral`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "jentic-openapi-validator"
3
- version = "1.0.0-alpha.27"
3
+ version = "1.0.0-alpha.29"
4
4
  description = "Jentic OpenAPI Validator"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Jentic", email = "hello@jentic.com" }]
@@ -8,14 +8,14 @@ license = "Apache-2.0"
8
8
  license-files = ["LICENSE", "NOTICE"]
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
11
- "jentic-openapi-parser~=1.0.0-alpha.27",
11
+ "jentic-openapi-parser~=1.0.0-alpha.29",
12
12
  "openapi-spec-validator~=0.7.2",
13
13
  "lsprotocol~=2025.0.0"
14
14
  ]
15
15
 
16
16
  [project.optional-dependencies]
17
- spectral = ["jentic-openapi-validator-spectral~=1.0.0-alpha.27"]
18
- redocly = ["jentic-openapi-validator-redocly~=1.0.0-alpha.27"]
17
+ spectral = ["jentic-openapi-validator-spectral~=1.0.0-alpha.29"]
18
+ redocly = ["jentic-openapi-validator-redocly~=1.0.0-alpha.29"]
19
19
 
20
20
  [project.urls]
21
21
  Homepage = "https://github.com/jentic/jentic-openapi-tools"
@@ -1,6 +1,8 @@
1
1
  import importlib.metadata
2
2
  import json
3
3
  import warnings
4
+ from collections.abc import Sequence
5
+ from concurrent.futures import ProcessPoolExecutor, as_completed
4
6
  from typing import Type
5
7
 
6
8
  from jentic.apitools.openapi.parser.core import OpenAPIParser
@@ -40,7 +42,7 @@ class OpenAPIValidator:
40
42
 
41
43
  def __init__(
42
44
  self,
43
- backends: list[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
45
+ backends: Sequence[str | BaseValidatorBackend | Type[BaseValidatorBackend]] | None = None,
44
46
  parser: OpenAPIParser | None = None,
45
47
  ):
46
48
  """
@@ -78,7 +80,13 @@ class OpenAPIValidator:
78
80
  raise TypeError("Invalid backend type: must be name or backend class/instance")
79
81
 
80
82
  def validate(
81
- self, document: str | dict, *, base_url: str | None = None, target: str | None = None
83
+ self,
84
+ document: str | dict,
85
+ *,
86
+ base_url: str | None = None,
87
+ target: str | None = None,
88
+ parallel: bool = False,
89
+ max_workers: int | None = None,
82
90
  ) -> ValidationResult:
83
91
  """
84
92
  Validate an OpenAPI document using all configured backends.
@@ -94,6 +102,11 @@ class OpenAPIValidator:
94
102
  - Python dictionary
95
103
  base_url: Optional base URL for resolving relative references in the document
96
104
  target: Optional target identifier for validation context
105
+ parallel: If True and multiple backends are configured, run validation
106
+ in parallel using ProcessPoolExecutor. Defaults to False.
107
+ max_workers: Maximum number of worker processes for parallel execution.
108
+ If None, defaults to the number of processors on the machine.
109
+ Only used when parallel=True.
97
110
 
98
111
  Returns:
99
112
  ValidationResult containing aggregated diagnostics from all backends.
@@ -103,7 +116,6 @@ class OpenAPIValidator:
103
116
  TypeError: If document is not a str or dict
104
117
  """
105
118
 
106
- diagnostics: list[JenticDiagnostic] = []
107
119
  document_is_uri: bool = False
108
120
  document_text: str = ""
109
121
  document_dict: dict | None = None
@@ -134,22 +146,41 @@ class OpenAPIValidator:
134
146
  f"Expected str (URI or JSON/YAML) or dict."
135
147
  )
136
148
 
137
- # Run validation through all backends
138
- for backend in self.backends:
139
- accepted = backend.accepts()
140
- backend_document = None
141
-
142
- # Determine which format to pass to this backend
143
- if document_is_uri and "uri" in accepted:
144
- backend_document = document
145
- elif "dict" in accepted and document_dict is not None:
146
- backend_document = document_dict
147
- elif "text" in accepted:
148
- backend_document = document_text
149
+ diagnostics: list[JenticDiagnostic] = []
149
150
 
150
- if backend_document is not None:
151
- result = backend.validate(backend_document, base_url=base_url, target=target)
152
- diagnostics.extend(result.diagnostics)
151
+ # Run validation through all backends
152
+ if parallel and len(self.backends) > 1:
153
+ # Parallel execution using ProcessPoolExecutor
154
+ with ProcessPoolExecutor(max_workers=max_workers) as executor:
155
+ futures = [
156
+ executor.submit(
157
+ _validate_single_backend,
158
+ backend,
159
+ document,
160
+ document_dict,
161
+ document_text,
162
+ document_is_uri,
163
+ base_url,
164
+ target,
165
+ )
166
+ for backend in self.backends
167
+ ]
168
+ for future in as_completed(futures):
169
+ diagnostics.extend(future.result())
170
+ else:
171
+ # Sequential execution (default)
172
+ for backend in self.backends:
173
+ diagnostics.extend(
174
+ _validate_single_backend(
175
+ backend,
176
+ document,
177
+ document_dict,
178
+ document_text,
179
+ document_is_uri,
180
+ base_url,
181
+ target,
182
+ )
183
+ )
153
184
 
154
185
  return ValidationResult(diagnostics=diagnostics)
155
186
 
@@ -189,3 +220,46 @@ class OpenAPIValidator:
189
220
  ['default', 'spectral']
190
221
  """
191
222
  return list(_VALIDATOR_BACKENDS.keys())
223
+
224
+
225
+ def _validate_single_backend(
226
+ backend: BaseValidatorBackend,
227
+ document: str | dict,
228
+ document_dict: dict | None,
229
+ document_text: str,
230
+ document_is_uri: bool,
231
+ base_url: str | None,
232
+ target: str | None,
233
+ ) -> list[JenticDiagnostic]:
234
+ """
235
+ Validate document with a single backend.
236
+
237
+ This is a module-level function (not a method) to ensure it's picklable
238
+ for use with ProcessPoolExecutor.
239
+
240
+ Args:
241
+ backend: The validator backend to use
242
+ document: The original document (URI or text)
243
+ document_dict: Parsed document as dict (if available)
244
+ document_text: Document as text string
245
+ document_is_uri: Whether document is a URI
246
+ base_url: Optional base URL for resolving references
247
+ target: Optional target identifier
248
+
249
+ Returns:
250
+ List of diagnostics from the backend
251
+ """
252
+ accepted = backend.accepts()
253
+ backend_document: str | dict | None = None
254
+
255
+ if document_is_uri and "uri" in accepted:
256
+ backend_document = document
257
+ elif "dict" in accepted and document_dict is not None:
258
+ backend_document = document_dict
259
+ elif "text" in accepted:
260
+ backend_document = document_text
261
+
262
+ if backend_document is not None:
263
+ result = backend.validate(backend_document, base_url=base_url, target=target)
264
+ return list(result.diagnostics)
265
+ return []