Skip to main content

Jentic OpenAPI Common

Project description

jentic-openapi-common

Common utilities for OpenAPI tools packages. This package provides shared functionality using PEP 420 namespace packages as contribution points.

Installation

uv add jentic-openapi-common

Modules

uri

URI/URL/path utilities for working with OpenAPI document references.

Available functions:

  • is_uri_like(s: str | None) -> bool - Check if a string looks like a URI/URL/path
  • is_http_https_url(s: str | None) -> bool - Check if string is an HTTP(S) URL
  • is_file_uri(s: str | None) -> bool - Check if string is a file:// URI
  • is_path(s: str | None) -> bool - Check if string is a filesystem path (not a URL)
  • resolve_to_absolute(uri: str, base_uri: str | None = None) -> str - Resolve relative URIs to absolute

Exceptions:

  • URIResolutionError - Raised when URI resolution fails

path_security

Path security utilities for validating and securing filesystem access. Provides defense-in-depth protection against path traversal attacks, directory escapes, and unauthorized file access.

Available functions:

  • 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

Exceptions:

  • PathSecurityError - Base exception for path security violations
  • PathTraversalError - Path attempts to escape allowed base directory
  • InvalidExtensionError - Path has disallowed file extension
  • SymlinkSecurityError - Path contains symlinks when not allowed or symlink escapes boundary

subproc

Subprocess execution utilities with enhanced error handling and cross-platform support.

version_detection

OpenAPI/Swagger version detection utilities. Provides functions to detect and extract version information from OpenAPI documents in text (YAML/JSON) or Mapping formats.

Available functions:

  • get_version(document: str | Mapping[str, Any]) -> str | None - Extract version string from document (e.g., "3.0.4", "2.0")
  • is_openapi_20(document: str | Mapping[str, Any]) -> bool - Check if document is OpenAPI 2.0 (Swagger 2.0)
  • is_openapi_30(document: str | Mapping[str, Any]) -> bool - Check if document is OpenAPI 3.0.x
  • is_openapi_31(document: str | Mapping[str, Any]) -> bool - Check if document is OpenAPI 3.1.x
  • is_openapi_32(document: str | Mapping[str, Any]) -> bool - Check if document is OpenAPI 3.2.x

Version Detection Behavior:

  • Text input: Validates against regex patterns, only returns/matches valid versions per specification
  • Mapping input:
    • get_version() returns whatever version string is present (for extraction/inspection)
    • is_openapi_*() validates against patterns (for version checking)

Usage Examples

URI Utilities

from jentic.apitools.openapi.common.uri import (
    is_uri_like,
    is_http_https_url,
    is_file_uri,
    is_path,
    resolve_to_absolute,
    URIResolutionError,
)

# Check URI types
is_uri_like("https://example.com/spec.yaml")  # True
is_http_https_url("https://example.com/spec.yaml")  # True
is_file_uri("file:///home/user/spec.yaml")  # True
is_path("/home/user/spec.yaml")  # True
is_path("https://example.com/spec.yaml")  # False

# Resolve relative URIs
absolute = resolve_to_absolute("../spec.yaml", "/home/user/project/docs/")
# Returns: "/home/user/project/spec.yaml"

absolute = resolve_to_absolute("spec.yaml")  # Resolves against current working directory

Path Security

from pathlib import Path
from jentic.apitools.openapi.common.path_security import (
    validate_path,
    PathSecurityError,
    PathTraversalError,
    InvalidExtensionError,
    SymlinkSecurityError,
)

# Basic validation - converts to absolute path (returns string by default)
safe_path = validate_path("./specs/openapi.yaml")
print(safe_path)  # '/current/working/dir/specs/openapi.yaml'
print(type(safe_path))  # <class 'str'>

# Request Path object with as_string=False
safe_path_obj = validate_path("./specs/openapi.yaml", as_string=False)
print(safe_path_obj)  # Path('/current/working/dir/specs/openapi.yaml')
print(type(safe_path_obj))  # <class 'pathlib.Path'>

# Return type control with as_string parameter
# - as_string=True (default): Returns str - best for subprocess commands
# - as_string=False: Returns Path - best for file operations with pathlib

# Example: Using with subprocess commands (default string return)
import subprocess
doc_path = validate_path("./specs/openapi.yaml")
subprocess.run(["cat", doc_path])  # Works directly, no str() conversion needed

# Example: Using with pathlib operations (Path return)
from pathlib import Path
doc_path = validate_path("./specs/openapi.yaml", as_string=False)
if doc_path.exists():
    content = doc_path.read_text()  # Path methods available

# Boundary enforcement - restrict access to specific directory
try:
    safe_path = validate_path(
        "/var/app/data/spec.yaml",
        allowed_base="/var/app",
    )
    print(f"Access granted: {safe_path}")
except PathTraversalError as e:
    print(f"Access denied: {e}")

# Block directory traversal attacks
try:
    safe_path = validate_path(
        "/var/app/../../../etc/passwd",
        allowed_base="/var/app",
    )
except PathTraversalError:
    print("Path traversal attack blocked!")

# Extension validation - whitelist approach
try:
    safe_path = validate_path(
        "spec.yaml",
        allowed_extensions=(".yaml", ".yml", ".json"),
    )
    print(f"Valid extension: {safe_path}")
except InvalidExtensionError:
    print("Invalid file extension")

# Combined security checks (recommended for web services)
try:
    safe_path = validate_path(
        user_provided_path,
        allowed_base="/var/app/uploads",
        allowed_extensions=(".yaml", ".yml", ".json"),
        resolve_symlinks=True,  # Default: resolve and check symlinks
    )
    # Safe to use safe_path for file operations
    with open(safe_path) as f:
        content = f.read()
except PathSecurityError as e:
    print(f"Security validation failed: {e}")

Subprocess Execution

Basic Command Execution

from jentic.apitools.openapi.common.subproc import run_subprocess

# Simple command
result = run_subprocess(["echo", "hello"])
print(result.stdout)  # "hello\n"
print(result.returncode)  # 0

# Command with working directory
result = run_subprocess(["pwd"], cwd="/tmp")
print(result.stdout.strip())  # "/tmp"

Error Handling

from jentic.apitools.openapi.common.subproc import (
    run_subprocess,
    SubprocessExecutionError
)

# Handle errors manually
result = run_subprocess(["false"])  # Command that exits with code 1
if result.returncode != 0:
    print(f"Command failed with code {result.returncode}")

# Automatic error handling
try:
    result = run_subprocess(["false"], fail_on_error=True)
except SubprocessExecutionError as e:
    print(f"Command {e.cmd} failed: {e}")

Advanced Usage

from jentic.apitools.openapi.common.subproc import (
    run_subprocess,
    SubprocessExecutionError
)

# Timeout handling
try:
    result = run_subprocess(["sleep", "10"], timeout=1)
except SubprocessExecutionError as e:
    print("Command timed out")

# Custom encoding
result = run_subprocess(["python", "-c", "print('ñ')"], encoding="utf-8")
print(result.stdout)  # "ñ\n"

# Custom environment variables (replaces inherited environment)
import os
custom_env = {**os.environ, "MY_VAR": "value"}
result = run_subprocess(["printenv", "MY_VAR"], env=custom_env)
print(result.stdout.strip())  # "value"

Version Detection

from jentic.apitools.openapi.common.version_detection import (
    get_version,
    is_openapi_20,
    is_openapi_30,
    is_openapi_31,
    is_openapi_32,
)

# Extract version from text (YAML/JSON)
yaml_doc = """
openapi: 3.0.4
info:
  title: Pet Store API
  version: 1.0.0
"""
version = get_version(yaml_doc)
print(version)  # "3.0.4"

json_doc = '{"openapi": "3.1.2", "info": {"title": "API", "version": "1.0.0"}}'
version = get_version(json_doc)
print(version)  # "3.1.2"

# Extract version from Mapping (returns any version string, even if unsupported)
doc = {"openapi": "3.0.4"}
version = get_version(doc)
print(version)  # "3.0.4"

# Even unsupported versions are returned from Mapping
doc = {"openapi": "3.0.4-rc1"}
version = get_version(doc)
print(version)  # "3.0.4-rc1" (suffix returned as-is)

# But text input validates with regex
version = get_version("openapi: 3.0.4-rc1")
print(version)  # None (suffix doesn't match pattern)

# Version checking with predicates (validates for both text and Mapping)
doc_20 = {"swagger": "2.0"}
print(is_openapi_20(doc_20))  # True
print(is_openapi_30(doc_20))  # False

doc_30 = {"openapi": "3.0.4"}
print(is_openapi_30(doc_30))  # True
print(is_openapi_31(doc_30))  # False

doc_31 = {"openapi": "3.1.2"}
print(is_openapi_31(doc_31))  # True
print(is_openapi_32(doc_31))  # False

doc_32 = {"openapi": "3.2.0"}
print(is_openapi_32(doc_32))  # True

# Predicates validate strictly
doc_suffix = {"openapi": "3.0.4-rc1"}
print(is_openapi_30(doc_suffix))  # False (suffix rejected)

doc_unsupported = {"openapi": "3.3.0"}
print(is_openapi_32(doc_unsupported))  # False (unsupported version)

# Works with YAML text too
yaml_text = "openapi: 3.0.4\ninfo:\n  title: API"
print(is_openapi_30(yaml_text))  # True

# Works with JSON text
json_text = '{"openapi": "3.1.2"}'
print(is_openapi_31(json_text))  # True

Testing

Run the test suite:

uv run --package jentic-openapi-common pytest packages/jentic-openapi-common -v

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jentic_openapi_common-1.0.0a49.tar.gz (15.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jentic_openapi_common-1.0.0a49-py3-none-any.whl (18.3 kB view details)

Uploaded Python 3

File details

Details for the file jentic_openapi_common-1.0.0a49.tar.gz.

File metadata

  • Download URL: jentic_openapi_common-1.0.0a49.tar.gz
  • Upload date:
  • Size: 15.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jentic_openapi_common-1.0.0a49.tar.gz
Algorithm Hash digest
SHA256 d0557543b36773b839de69fbcb28551a30f56f7cad1d508146a4fe1665e43e97
MD5 0a0d0eec01e32e81e2f1c50a3e98fd52
BLAKE2b-256 bcc2b349c9b2498f6f1966a8554a99611524cf0b1459e541f4433e7b9f4acbd4

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_common-1.0.0a49.tar.gz:

Publisher: release.yml on jentic/jentic-openapi-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jentic_openapi_common-1.0.0a49-py3-none-any.whl.

File metadata

File hashes

Hashes for jentic_openapi_common-1.0.0a49-py3-none-any.whl
Algorithm Hash digest
SHA256 b1b18c8113aac3887f92b0f126680b5b61803dbb50d7080c4ac9073fe7017539
MD5 079f3a3d67dfa63d352035ccd009a8a8
BLAKE2b-256 ed66b4d7092907712a74fc0ddc8c53bd0233cf2b3f69c1876dc1b9fe8356705e

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_common-1.0.0a49-py3-none-any.whl:

Publisher: release.yml on jentic/jentic-openapi-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page