jentic-openapi-common 1.0.0a30__py3-none-any.whl
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.
- jentic/apitools/openapi/common/path_security.py +176 -0
- jentic/apitools/openapi/common/py.typed +0 -0
- jentic/apitools/openapi/common/subproc.py +145 -0
- jentic/apitools/openapi/common/uri.py +341 -0
- jentic/apitools/openapi/common/version_detection.py +223 -0
- jentic_openapi_common-1.0.0a30.dist-info/METADATA +322 -0
- jentic_openapi_common-1.0.0a30.dist-info/RECORD +10 -0
- jentic_openapi_common-1.0.0a30.dist-info/WHEEL +4 -0
- jentic_openapi_common-1.0.0a30.dist-info/licenses/LICENSE +202 -0
- jentic_openapi_common-1.0.0a30.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""OpenAPI version detection utilities.
|
|
2
|
+
|
|
3
|
+
This module provides functions to detect OpenAPI specification versions
|
|
4
|
+
from both text (YAML/JSON strings) and Mapping objects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"get_version",
|
|
14
|
+
"is_openapi_20",
|
|
15
|
+
"is_openapi_30",
|
|
16
|
+
"is_openapi_31",
|
|
17
|
+
"is_openapi_32",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Regex patterns for detecting OpenAPI versions in text
|
|
22
|
+
# Matches both YAML and JSON formats
|
|
23
|
+
# YAML: swagger: 2.0 (with optional quotes)
|
|
24
|
+
# JSON: "swagger": "2.0"
|
|
25
|
+
_OPENAPI_20_PATTERN = re.compile(
|
|
26
|
+
r'(?P<YAML>^(["\']?)swagger\2\s*:\s*(["\']?)(?P<version_yaml>2\.0)\3(?:\s+|$))'
|
|
27
|
+
r'|(?P<JSON>"swagger"\s*:\s*"(?P<version_json>2\.0)")',
|
|
28
|
+
re.MULTILINE,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# YAML: openapi: 3.0.x (with optional quotes)
|
|
32
|
+
# JSON: "openapi": "3.0.x"
|
|
33
|
+
_OPENAPI_30_PATTERN = re.compile(
|
|
34
|
+
r'(?P<YAML>^(["\']?)openapi\2\s*:\s*(["\']?)(?P<version_yaml>3\.0\.(?:[1-9]\d*|0))\3(?:\s+|$))'
|
|
35
|
+
r'|(?P<JSON>"openapi"\s*:\s*"(?P<version_json>3\.0\.(?:[1-9]\d*|0))")',
|
|
36
|
+
re.MULTILINE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_OPENAPI_31_PATTERN = re.compile(
|
|
40
|
+
r'(?P<YAML>^(["\']?)openapi\2\s*:\s*(["\']?)(?P<version_yaml>3\.1\.(?:[1-9]\d*|0))\3(?:\s+|$))'
|
|
41
|
+
r'|(?P<JSON>"openapi"\s*:\s*"(?P<version_json>3\.1\.(?:[1-9]\d*|0))")',
|
|
42
|
+
re.MULTILINE,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_OPENAPI_32_PATTERN = re.compile(
|
|
46
|
+
r'(?P<YAML>^(["\']?)openapi\2\s*:\s*(["\']?)(?P<version_yaml>3\.2\.(?:[1-9]\d*|0))\3(?:\s+|$))'
|
|
47
|
+
r'|(?P<JSON>"openapi"\s*:\s*"(?P<version_json>3\.2\.(?:[1-9]\d*|0))")',
|
|
48
|
+
re.MULTILINE,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# All patterns to try for version extraction
|
|
52
|
+
_ALL_PATTERNS = [
|
|
53
|
+
_OPENAPI_20_PATTERN,
|
|
54
|
+
_OPENAPI_30_PATTERN,
|
|
55
|
+
_OPENAPI_31_PATTERN,
|
|
56
|
+
_OPENAPI_32_PATTERN,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_version(document: str | Mapping[str, Any]) -> str | None:
|
|
61
|
+
"""Extract the OpenAPI/Swagger version from a document.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
document: Either a text string (YAML/JSON) or a Mapping object
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The version string if found, None otherwise
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
>>> get_version("swagger: 2.0\\ninfo:\\n title: API")
|
|
71
|
+
'2.0'
|
|
72
|
+
>>> get_version("openapi: 3.0.4\\ninfo:\\n title: API")
|
|
73
|
+
'3.0.4'
|
|
74
|
+
>>> get_version('{"openapi": "3.1.2"}')
|
|
75
|
+
'3.1.2'
|
|
76
|
+
>>> get_version({"openapi": "3.2.0"})
|
|
77
|
+
'3.2.0'
|
|
78
|
+
>>> get_version({"swagger": "2.0"})
|
|
79
|
+
'2.0'
|
|
80
|
+
>>> get_version("no version here")
|
|
81
|
+
None
|
|
82
|
+
"""
|
|
83
|
+
if isinstance(document, str):
|
|
84
|
+
# Try all patterns and extract version from named groups
|
|
85
|
+
for pattern in _ALL_PATTERNS:
|
|
86
|
+
match = pattern.search(document)
|
|
87
|
+
if match:
|
|
88
|
+
# Try to get version from either YAML or JSON group
|
|
89
|
+
version = (
|
|
90
|
+
match.group("version_yaml")
|
|
91
|
+
if match.group("version_yaml")
|
|
92
|
+
else match.group("version_json")
|
|
93
|
+
)
|
|
94
|
+
return version
|
|
95
|
+
return None
|
|
96
|
+
elif isinstance(document, Mapping):
|
|
97
|
+
# Return whatever version string is present, without validation
|
|
98
|
+
# Validation can be done separately with is_openapi_*() predicates
|
|
99
|
+
version = document.get("openapi") or document.get("swagger")
|
|
100
|
+
if isinstance(version, str):
|
|
101
|
+
return version
|
|
102
|
+
return None
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_openapi_20(document: str | Mapping[str, Any]) -> bool:
|
|
107
|
+
"""Check if document is OpenAPI 2.0 (Swagger 2.0) specification.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
document: Either a text string (YAML/JSON) or a Mapping object
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if document is OpenAPI 2.0, False otherwise
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
>>> is_openapi_20("swagger: 2.0\\ninfo:\\n title: API")
|
|
117
|
+
True
|
|
118
|
+
>>> is_openapi_20('{"swagger": "2.0"}')
|
|
119
|
+
True
|
|
120
|
+
>>> is_openapi_20({"swagger": "2.0"})
|
|
121
|
+
True
|
|
122
|
+
>>> is_openapi_20({"openapi": "3.0.4"})
|
|
123
|
+
False
|
|
124
|
+
"""
|
|
125
|
+
if isinstance(document, str):
|
|
126
|
+
return bool(_OPENAPI_20_PATTERN.search(document))
|
|
127
|
+
elif isinstance(document, Mapping):
|
|
128
|
+
version = document.get("swagger")
|
|
129
|
+
if isinstance(version, str):
|
|
130
|
+
# Construct YAML-like string and reuse text pattern
|
|
131
|
+
test_string = f"swagger: {version}"
|
|
132
|
+
return bool(_OPENAPI_20_PATTERN.search(test_string))
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def is_openapi_30(document: str | Mapping[str, Any]) -> bool:
|
|
137
|
+
"""Check if document is OpenAPI 3.0.x specification.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
document: Either a text string (YAML/JSON) or a Mapping object
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
True if document is OpenAPI 3.0.x, False otherwise
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
>>> is_openapi_30("openapi: 3.0.4\\ninfo:\\n title: API")
|
|
147
|
+
True
|
|
148
|
+
>>> is_openapi_30('{"openapi": "3.0.4"}')
|
|
149
|
+
True
|
|
150
|
+
>>> is_openapi_30({"openapi": "3.0.4"})
|
|
151
|
+
True
|
|
152
|
+
>>> is_openapi_30({"openapi": "3.1.0"})
|
|
153
|
+
False
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(document, str):
|
|
156
|
+
return bool(_OPENAPI_30_PATTERN.search(document))
|
|
157
|
+
elif isinstance(document, Mapping):
|
|
158
|
+
version = document.get("openapi")
|
|
159
|
+
if isinstance(version, str):
|
|
160
|
+
# Construct YAML-like string and reuse text pattern
|
|
161
|
+
test_string = f"openapi: {version}"
|
|
162
|
+
return bool(_OPENAPI_30_PATTERN.search(test_string))
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def is_openapi_31(document: str | Mapping[str, Any]) -> bool:
|
|
167
|
+
"""Check if document is OpenAPI 3.1.x specification.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
document: Either a text string (YAML/JSON) or a Mapping object
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if document is OpenAPI 3.1.x, False otherwise
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> is_openapi_31("openapi: 3.1.2\\ninfo:\\n title: API")
|
|
177
|
+
True
|
|
178
|
+
>>> is_openapi_31('{"openapi": "3.1.2"}')
|
|
179
|
+
True
|
|
180
|
+
>>> is_openapi_31({"openapi": "3.1.2"})
|
|
181
|
+
True
|
|
182
|
+
>>> is_openapi_31({"openapi": "3.0.4"})
|
|
183
|
+
False
|
|
184
|
+
"""
|
|
185
|
+
if isinstance(document, str):
|
|
186
|
+
return bool(_OPENAPI_31_PATTERN.search(document))
|
|
187
|
+
elif isinstance(document, Mapping):
|
|
188
|
+
version = document.get("openapi")
|
|
189
|
+
if isinstance(version, str):
|
|
190
|
+
# Construct YAML-like string and reuse text pattern
|
|
191
|
+
test_string = f"openapi: {version}"
|
|
192
|
+
return bool(_OPENAPI_31_PATTERN.search(test_string))
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def is_openapi_32(document: str | Mapping[str, Any]) -> bool:
|
|
197
|
+
"""Check if document is OpenAPI 3.2.x specification.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
document: Either a text string (YAML/JSON) or a Mapping object
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
True if document is OpenAPI 3.2.x, False otherwise
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
>>> is_openapi_32("openapi: 3.2.0\\ninfo:\\n title: API")
|
|
207
|
+
True
|
|
208
|
+
>>> is_openapi_32('{"openapi": "3.2.0"}')
|
|
209
|
+
True
|
|
210
|
+
>>> is_openapi_32({"openapi": "3.2.0"})
|
|
211
|
+
True
|
|
212
|
+
>>> is_openapi_32({"openapi": "3.1.0"})
|
|
213
|
+
False
|
|
214
|
+
"""
|
|
215
|
+
if isinstance(document, str):
|
|
216
|
+
return bool(_OPENAPI_32_PATTERN.search(document))
|
|
217
|
+
elif isinstance(document, Mapping):
|
|
218
|
+
version = document.get("openapi")
|
|
219
|
+
if isinstance(version, str):
|
|
220
|
+
# Construct YAML-like string and reuse text pattern
|
|
221
|
+
test_string = f"openapi: {version}"
|
|
222
|
+
return bool(_OPENAPI_32_PATTERN.search(test_string))
|
|
223
|
+
return False
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jentic-openapi-common
|
|
3
|
+
Version: 1.0.0a30
|
|
4
|
+
Summary: Jentic OpenAPI Common
|
|
5
|
+
Author: Jentic
|
|
6
|
+
Author-email: Jentic <hello@jentic.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Project-URL: Homepage, https://github.com/jentic/jentic-openapi-tools
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# jentic-openapi-common
|
|
15
|
+
|
|
16
|
+
Common utilities for OpenAPI tools packages. This package provides shared functionality using PEP 420 namespace packages as contribution points.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv add jentic-openapi-common
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Modules
|
|
25
|
+
|
|
26
|
+
### uri
|
|
27
|
+
|
|
28
|
+
URI/URL/path utilities for working with OpenAPI document references.
|
|
29
|
+
|
|
30
|
+
**Available functions:**
|
|
31
|
+
|
|
32
|
+
- `is_uri_like(s: str | None) -> bool` - Check if a string looks like a URI/URL/path
|
|
33
|
+
- `is_http_https_url(s: str | None) -> bool` - Check if string is an HTTP(S) URL
|
|
34
|
+
- `is_file_uri(s: str | None) -> bool` - Check if string is a file:// URI
|
|
35
|
+
- `is_path(s: str | None) -> bool` - Check if string is a filesystem path (not a URL)
|
|
36
|
+
- `resolve_to_absolute(uri: str, base_uri: str | None = None) -> str` - Resolve relative URIs to absolute
|
|
37
|
+
|
|
38
|
+
**Exceptions:**
|
|
39
|
+
|
|
40
|
+
- `URIResolutionError` - Raised when URI resolution fails
|
|
41
|
+
|
|
42
|
+
### path_security
|
|
43
|
+
|
|
44
|
+
Path security utilities for validating and securing filesystem access. Provides defense-in-depth protection against path traversal attacks, directory escapes, and unauthorized file access.
|
|
45
|
+
|
|
46
|
+
**Available functions:**
|
|
47
|
+
|
|
48
|
+
- `validate_path(path, *, allowed_base=None, allowed_extensions=None, resolve_symlinks=True, as_string=True) -> str | Path` - Validate and canonicalize a filesystem path with security checks. Returns `str` by default, or `Path` when `as_string=False`
|
|
49
|
+
|
|
50
|
+
**Exceptions:**
|
|
51
|
+
|
|
52
|
+
- `PathSecurityError` - Base exception for path security violations
|
|
53
|
+
- `PathTraversalError` - Path attempts to escape allowed base directory
|
|
54
|
+
- `InvalidExtensionError` - Path has disallowed file extension
|
|
55
|
+
- `SymlinkSecurityError` - Path contains symlinks when not allowed or symlink escapes boundary
|
|
56
|
+
|
|
57
|
+
### subproc
|
|
58
|
+
|
|
59
|
+
Subprocess execution utilities with enhanced error handling and cross-platform support.
|
|
60
|
+
|
|
61
|
+
### version_detection
|
|
62
|
+
|
|
63
|
+
OpenAPI/Swagger version detection utilities. Provides functions to detect and extract version information from OpenAPI documents in text (YAML/JSON) or Mapping formats.
|
|
64
|
+
|
|
65
|
+
**Available functions:**
|
|
66
|
+
|
|
67
|
+
- `get_version(document: str | Mapping[str, Any]) -> str | None` - Extract version string from document (e.g., "3.0.4", "2.0")
|
|
68
|
+
- `is_openapi_20(document: str | Mapping[str, Any]) -> bool` - Check if document is OpenAPI 2.0 (Swagger 2.0)
|
|
69
|
+
- `is_openapi_30(document: str | Mapping[str, Any]) -> bool` - Check if document is OpenAPI 3.0.x
|
|
70
|
+
- `is_openapi_31(document: str | Mapping[str, Any]) -> bool` - Check if document is OpenAPI 3.1.x
|
|
71
|
+
- `is_openapi_32(document: str | Mapping[str, Any]) -> bool` - Check if document is OpenAPI 3.2.x
|
|
72
|
+
|
|
73
|
+
**Version Detection Behavior:**
|
|
74
|
+
|
|
75
|
+
- **Text input**: Validates against regex patterns, only returns/matches valid versions per specification
|
|
76
|
+
- **Mapping input**:
|
|
77
|
+
- `get_version()` returns whatever version string is present (for extraction/inspection)
|
|
78
|
+
- `is_openapi_*()` validates against patterns (for version checking)
|
|
79
|
+
|
|
80
|
+
## Usage Examples
|
|
81
|
+
|
|
82
|
+
### URI Utilities
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from jentic.apitools.openapi.common.uri import (
|
|
86
|
+
is_uri_like,
|
|
87
|
+
is_http_https_url,
|
|
88
|
+
is_file_uri,
|
|
89
|
+
is_path,
|
|
90
|
+
resolve_to_absolute,
|
|
91
|
+
URIResolutionError,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Check URI types
|
|
95
|
+
is_uri_like("https://example.com/spec.yaml") # True
|
|
96
|
+
is_http_https_url("https://example.com/spec.yaml") # True
|
|
97
|
+
is_file_uri("file:///home/user/spec.yaml") # True
|
|
98
|
+
is_path("/home/user/spec.yaml") # True
|
|
99
|
+
is_path("https://example.com/spec.yaml") # False
|
|
100
|
+
|
|
101
|
+
# Resolve relative URIs
|
|
102
|
+
absolute = resolve_to_absolute("../spec.yaml", "/home/user/project/docs/")
|
|
103
|
+
# Returns: "/home/user/project/spec.yaml"
|
|
104
|
+
|
|
105
|
+
absolute = resolve_to_absolute("spec.yaml") # Resolves against current working directory
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Path Security
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
from jentic.apitools.openapi.common.path_security import (
|
|
113
|
+
validate_path,
|
|
114
|
+
PathSecurityError,
|
|
115
|
+
PathTraversalError,
|
|
116
|
+
InvalidExtensionError,
|
|
117
|
+
SymlinkSecurityError,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Basic validation - converts to absolute path (returns string by default)
|
|
121
|
+
safe_path = validate_path("./specs/openapi.yaml")
|
|
122
|
+
print(safe_path) # '/current/working/dir/specs/openapi.yaml'
|
|
123
|
+
print(type(safe_path)) # <class 'str'>
|
|
124
|
+
|
|
125
|
+
# Request Path object with as_string=False
|
|
126
|
+
safe_path_obj = validate_path("./specs/openapi.yaml", as_string=False)
|
|
127
|
+
print(safe_path_obj) # Path('/current/working/dir/specs/openapi.yaml')
|
|
128
|
+
print(type(safe_path_obj)) # <class 'pathlib.Path'>
|
|
129
|
+
|
|
130
|
+
# Return type control with as_string parameter
|
|
131
|
+
# - as_string=True (default): Returns str - best for subprocess commands
|
|
132
|
+
# - as_string=False: Returns Path - best for file operations with pathlib
|
|
133
|
+
|
|
134
|
+
# Example: Using with subprocess commands (default string return)
|
|
135
|
+
import subprocess
|
|
136
|
+
doc_path = validate_path("./specs/openapi.yaml")
|
|
137
|
+
subprocess.run(["cat", doc_path]) # Works directly, no str() conversion needed
|
|
138
|
+
|
|
139
|
+
# Example: Using with pathlib operations (Path return)
|
|
140
|
+
from pathlib import Path
|
|
141
|
+
doc_path = validate_path("./specs/openapi.yaml", as_string=False)
|
|
142
|
+
if doc_path.exists():
|
|
143
|
+
content = doc_path.read_text() # Path methods available
|
|
144
|
+
|
|
145
|
+
# Boundary enforcement - restrict access to specific directory
|
|
146
|
+
try:
|
|
147
|
+
safe_path = validate_path(
|
|
148
|
+
"/var/app/data/spec.yaml",
|
|
149
|
+
allowed_base="/var/app",
|
|
150
|
+
)
|
|
151
|
+
print(f"Access granted: {safe_path}")
|
|
152
|
+
except PathTraversalError as e:
|
|
153
|
+
print(f"Access denied: {e}")
|
|
154
|
+
|
|
155
|
+
# Block directory traversal attacks
|
|
156
|
+
try:
|
|
157
|
+
safe_path = validate_path(
|
|
158
|
+
"/var/app/../../../etc/passwd",
|
|
159
|
+
allowed_base="/var/app",
|
|
160
|
+
)
|
|
161
|
+
except PathTraversalError:
|
|
162
|
+
print("Path traversal attack blocked!")
|
|
163
|
+
|
|
164
|
+
# Extension validation - whitelist approach
|
|
165
|
+
try:
|
|
166
|
+
safe_path = validate_path(
|
|
167
|
+
"spec.yaml",
|
|
168
|
+
allowed_extensions=(".yaml", ".yml", ".json"),
|
|
169
|
+
)
|
|
170
|
+
print(f"Valid extension: {safe_path}")
|
|
171
|
+
except InvalidExtensionError:
|
|
172
|
+
print("Invalid file extension")
|
|
173
|
+
|
|
174
|
+
# Combined security checks (recommended for web services)
|
|
175
|
+
try:
|
|
176
|
+
safe_path = validate_path(
|
|
177
|
+
user_provided_path,
|
|
178
|
+
allowed_base="/var/app/uploads",
|
|
179
|
+
allowed_extensions=(".yaml", ".yml", ".json"),
|
|
180
|
+
resolve_symlinks=True, # Default: resolve and check symlinks
|
|
181
|
+
)
|
|
182
|
+
# Safe to use safe_path for file operations
|
|
183
|
+
with open(safe_path) as f:
|
|
184
|
+
content = f.read()
|
|
185
|
+
except PathSecurityError as e:
|
|
186
|
+
print(f"Security validation failed: {e}")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Subprocess Execution
|
|
190
|
+
|
|
191
|
+
#### Basic Command Execution
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from jentic.apitools.openapi.common.subproc import run_subprocess
|
|
195
|
+
|
|
196
|
+
# Simple command
|
|
197
|
+
result = run_subprocess(["echo", "hello"])
|
|
198
|
+
print(result.stdout) # "hello\n"
|
|
199
|
+
print(result.returncode) # 0
|
|
200
|
+
|
|
201
|
+
# Command with working directory
|
|
202
|
+
result = run_subprocess(["pwd"], cwd="/tmp")
|
|
203
|
+
print(result.stdout.strip()) # "/tmp"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Error Handling
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from jentic.apitools.openapi.common.subproc import (
|
|
210
|
+
run_subprocess,
|
|
211
|
+
SubprocessExecutionError
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Handle errors manually
|
|
215
|
+
result = run_subprocess(["false"]) # Command that exits with code 1
|
|
216
|
+
if result.returncode != 0:
|
|
217
|
+
print(f"Command failed with code {result.returncode}")
|
|
218
|
+
|
|
219
|
+
# Automatic error handling
|
|
220
|
+
try:
|
|
221
|
+
result = run_subprocess(["false"], fail_on_error=True)
|
|
222
|
+
except SubprocessExecutionError as e:
|
|
223
|
+
print(f"Command {e.cmd} failed: {e}")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Advanced Usage
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from jentic.apitools.openapi.common.subproc import (
|
|
230
|
+
run_subprocess,
|
|
231
|
+
SubprocessExecutionError
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Timeout handling
|
|
235
|
+
try:
|
|
236
|
+
result = run_subprocess(["sleep", "10"], timeout=1)
|
|
237
|
+
except SubprocessExecutionError as e:
|
|
238
|
+
print("Command timed out")
|
|
239
|
+
|
|
240
|
+
# Custom encoding
|
|
241
|
+
result = run_subprocess(["python", "-c", "print('ñ')"], encoding="utf-8")
|
|
242
|
+
print(result.stdout) # "ñ\n"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Version Detection
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from jentic.apitools.openapi.common.version_detection import (
|
|
249
|
+
get_version,
|
|
250
|
+
is_openapi_20,
|
|
251
|
+
is_openapi_30,
|
|
252
|
+
is_openapi_31,
|
|
253
|
+
is_openapi_32,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Extract version from text (YAML/JSON)
|
|
257
|
+
yaml_doc = """
|
|
258
|
+
openapi: 3.0.4
|
|
259
|
+
info:
|
|
260
|
+
title: Pet Store API
|
|
261
|
+
version: 1.0.0
|
|
262
|
+
"""
|
|
263
|
+
version = get_version(yaml_doc)
|
|
264
|
+
print(version) # "3.0.4"
|
|
265
|
+
|
|
266
|
+
json_doc = '{"openapi": "3.1.2", "info": {"title": "API", "version": "1.0.0"}}'
|
|
267
|
+
version = get_version(json_doc)
|
|
268
|
+
print(version) # "3.1.2"
|
|
269
|
+
|
|
270
|
+
# Extract version from Mapping (returns any version string, even if unsupported)
|
|
271
|
+
doc = {"openapi": "3.0.4"}
|
|
272
|
+
version = get_version(doc)
|
|
273
|
+
print(version) # "3.0.4"
|
|
274
|
+
|
|
275
|
+
# Even unsupported versions are returned from Mapping
|
|
276
|
+
doc = {"openapi": "3.0.4-rc1"}
|
|
277
|
+
version = get_version(doc)
|
|
278
|
+
print(version) # "3.0.4-rc1" (suffix returned as-is)
|
|
279
|
+
|
|
280
|
+
# But text input validates with regex
|
|
281
|
+
version = get_version("openapi: 3.0.4-rc1")
|
|
282
|
+
print(version) # None (suffix doesn't match pattern)
|
|
283
|
+
|
|
284
|
+
# Version checking with predicates (validates for both text and Mapping)
|
|
285
|
+
doc_20 = {"swagger": "2.0"}
|
|
286
|
+
print(is_openapi_20(doc_20)) # True
|
|
287
|
+
print(is_openapi_30(doc_20)) # False
|
|
288
|
+
|
|
289
|
+
doc_30 = {"openapi": "3.0.4"}
|
|
290
|
+
print(is_openapi_30(doc_30)) # True
|
|
291
|
+
print(is_openapi_31(doc_30)) # False
|
|
292
|
+
|
|
293
|
+
doc_31 = {"openapi": "3.1.2"}
|
|
294
|
+
print(is_openapi_31(doc_31)) # True
|
|
295
|
+
print(is_openapi_32(doc_31)) # False
|
|
296
|
+
|
|
297
|
+
doc_32 = {"openapi": "3.2.0"}
|
|
298
|
+
print(is_openapi_32(doc_32)) # True
|
|
299
|
+
|
|
300
|
+
# Predicates validate strictly
|
|
301
|
+
doc_suffix = {"openapi": "3.0.4-rc1"}
|
|
302
|
+
print(is_openapi_30(doc_suffix)) # False (suffix rejected)
|
|
303
|
+
|
|
304
|
+
doc_unsupported = {"openapi": "3.3.0"}
|
|
305
|
+
print(is_openapi_32(doc_unsupported)) # False (unsupported version)
|
|
306
|
+
|
|
307
|
+
# Works with YAML text too
|
|
308
|
+
yaml_text = "openapi: 3.0.4\ninfo:\n title: API"
|
|
309
|
+
print(is_openapi_30(yaml_text)) # True
|
|
310
|
+
|
|
311
|
+
# Works with JSON text
|
|
312
|
+
json_text = '{"openapi": "3.1.2"}'
|
|
313
|
+
print(is_openapi_31(json_text)) # True
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Testing
|
|
317
|
+
|
|
318
|
+
Run the test suite:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
uv run --package jentic-openapi-common pytest packages/jentic-openapi-common -v
|
|
322
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
jentic/apitools/openapi/common/path_security.py,sha256=TN32F7LiKQQJAIsq-T8rhdRJcXjR7Hs6Q7SBYOP7xeI,6374
|
|
2
|
+
jentic/apitools/openapi/common/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
jentic/apitools/openapi/common/subproc.py,sha256=KowpIfK1tZxjXKGpUJmX4Eiu8CP3rNMwpA6a6yo2Lfk,4983
|
|
4
|
+
jentic/apitools/openapi/common/uri.py,sha256=jZ-US7GanWsqwYPTSjALxcBtrnq3r9fKaHfVjBhxRDw,11193
|
|
5
|
+
jentic/apitools/openapi/common/version_detection.py,sha256=dxs9x6_pOfNfhMyBFWMCI24phVYHsnkanTDaLJtXw1Y,7114
|
|
6
|
+
jentic_openapi_common-1.0.0a30.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
7
|
+
jentic_openapi_common-1.0.0a30.dist-info/licenses/NOTICE,sha256=pAOGW-rGw9KNc2cuuLWZkfx0GSTV4TicbgBKZSLPMIs,168
|
|
8
|
+
jentic_openapi_common-1.0.0a30.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
9
|
+
jentic_openapi_common-1.0.0a30.dist-info/METADATA,sha256=JssI8E98ohz-pxOJzHavdjsT2pE1eobG_xcRzELgkag,9830
|
|
10
|
+
jentic_openapi_common-1.0.0a30.dist-info/RECORD,,
|