Skip to content

Commit

Permalink
Add spec generator (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsv1 authored Oct 14, 2024
1 parent f190ec7 commit b8f14ad
Show file tree
Hide file tree
Showing 22 changed files with 479 additions and 22 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
- name: Build
run: make build
- name: Publish
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -34,6 +34,7 @@ jobs:
- name: Test
run: make coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Changelog

## v0.5.0 (2024-10-14)

- Add spec generator [#2](https://github.com/vedro-universe/vedro-httpx/pull/2)

## v0.4.0 (2024-04-07)

- Add request recording [#1](https://github.com/vedro-universe/vedro-httpx/pull/1)
- Add request recording feature [#1](https://github.com/vedro-universe/vedro-httpx/pull/1)

## v0.3.0 (2023-07-28)

- Add render width [#a56356b](https://github.com/vedro-universe/vedro-httpx/commit/a56356b089522f6381018bad9ead0f9f5226d939)
- Added functionality to control the rendering width of HTTP responses [#a56356b](https://github.com/vedro-universe/vedro-httpx/commit/a56356b089522f6381018bad9ead0f9f5226d939)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# vedro-httpx

[![Codecov](https://img.shields.io/codecov/c/github/vedro-universe/vedro-httpx/master.svg?style=flat-square)](https://codecov.io/gh/vedro-universe/vedro-httpx)
[![Codecov](https://img.shields.io/codecov/c/github/vedro-universe/vedro-httpx/main.svg?style=flat-square)](https://codecov.io/gh/vedro-universe/vedro-httpx)
[![PyPI](https://img.shields.io/pypi/v/vedro-httpx.svg?style=flat-square)](https://pypi.python.org/pypi/vedro-httpx/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/vedro-httpx?style=flat-square)](https://pypi.python.org/pypi/vedro-httpx/)
[![Python Version](https://img.shields.io/pypi/pyversions/vedro-httpx.svg?style=flat-square)](https://pypi.python.org/pypi/vedro-httpx/)
Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ coverage:
status:
project:
default:
threshold: 5%
threshold: 15%
patch:
default:
threshold: 100%
Expand Down
15 changes: 8 additions & 7 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
baby-steps==1.3.0
baby-steps==1.3.1
bump2version==1.0.1
codecov==2.1.13
coverage==7.4.4
flake8==7.0.0
coverage==7.6.1
flake8==7.1.1
isort==5.13.2
mypy==1.9.0
pytest==8.1.1
pytest-asyncio==0.23.6
mypy==1.12.0
pytest==8.3.3
pytest-asyncio==0.24.0
pytest-clarity==1.0.1
pytest-cov==5.0.0
types-Pygments==2.17.0.20240310
types-Pygments==2.18.0.20240506
types-PyYAML==6.0.12.20240917
respx==0.21.1
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
httpx>=0.24,<1.0
vedro>=1.7,<2.0
PyYAML>6.0,<7.0
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def find_dev_required():
license="Apache-2.0",
packages=find_packages(exclude=["tests", "tests.*"]),
package_data={"vedro_httpx": ["py.typed"]},
entry_points={
"console_scripts": [
"vedro-httpx = vedro_httpx.__main__:main",
"vedro_httpx = vedro_httpx.__main__:main",
]
},
install_requires=find_required(),
tests_require=find_dev_required(),
classifiers=[
Expand Down
Empty file.
30 changes: 30 additions & 0 deletions tests/spec_generator/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest
from baby_steps import given, then, when

from vedro_httpx.spec_generator._utils import humanize_identifier


@pytest.mark.parametrize(("name", "expected"), [
("", ""),
("client id", "Client id"),
("client_id", "Client id"),
("_client_id_", "Client id"),
("clientId", "Client id"),
("ClientName", "Client name"),
("clientHTTPStatus", "Client http status"),
("HTTPResponse", "Http response"),
("StatusOK", "Status ok"),
("version2Name", "Version2 name"),
("CLIENT_ID", "Client id"),
("singleA", "Single a"),
("X-Client-ID", "X client id"),
])
def test_humanize_identifier(name: str, expected: str):
with given:
name = name

with when:
result = humanize_identifier(name)

with then:
assert result == expected
46 changes: 46 additions & 0 deletions vedro_httpx/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import sys

from vedro_httpx.spec_generator import APISpecBuilder, HARReader, OpenAPISpecGenerator


def generate_spec(har_directory: str) -> str:
"""
Generate an OpenAPI specification from HAR files in a specified directory.
This function reads all HAR files from the provided directory, extracts
HTTP request and response data, builds an API specification, and converts
it into an OpenAPI specification format.
:param har_directory: The directory path where HAR files are located.
:return: The generated OpenAPI specification as a YAML string.
"""
har_reader = HARReader(har_directory)
entries = har_reader.get_entries()

api_spec_builder = APISpecBuilder()
api_spec = api_spec_builder.build_spec(entries)

open_api_spec_generator = OpenAPISpecGenerator()
open_api_spec = open_api_spec_generator.generate_spec(api_spec)

return open_api_spec


def main() -> None:
"""
Main entry point of the script.
This function checks for the correct usage of the script by verifying
the command-line arguments, generates an OpenAPI specification from the
HAR directory provided as input, and prints the result.
"""
if len(sys.argv) != 2:
print("Usage: vedro-httpx <har_directory>")
sys.exit(1)

open_api_spec = generate_spec(sys.argv[1])
print(open_api_spec)


if __name__ == "__main__":
main()
13 changes: 12 additions & 1 deletion vedro_httpx/_async_http_interface.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Optional, Union, cast
from typing import Any, Dict, Optional, Union, cast

import vedro
from httpx import AsyncClient as _AsyncClient
Expand Down Expand Up @@ -101,6 +101,7 @@ async def _request(self,
follow_redirects: Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
extensions: Optional[RequestExtensions] = None,
segments: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> Response:
"""
Expand All @@ -126,6 +127,16 @@ async def _request(self,
:param kwargs: Additional keyword arguments to pass to the request method.
:return: A `Response` object containing the server's response to the HTTP request.
"""
if segments:
if extensions is None:
extensions = {}

parameterized_url = str(url)
if self._base_url:
parameterized_url = str(self._base_url).lstrip("/") + parameterized_url
extensions["vedro_httpx_parameterized_url"] = parameterized_url
url = str(url).format(**segments)

async with self._client() as client:
return cast(Response, await client.request(
method=method,
Expand Down
13 changes: 12 additions & 1 deletion vedro_httpx/_sync_http_interface.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Optional, Union, cast
from typing import Any, Dict, Optional, Union, cast

import vedro
from httpx import Client as _SyncClient
Expand Down Expand Up @@ -100,6 +100,7 @@ def _request(self,
follow_redirects: Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
extensions: Optional[RequestExtensions] = None,
segments: Optional[Dict[str, Any]] = None,
**kwargs: Any
) -> Response:
"""
Expand All @@ -125,6 +126,16 @@ def _request(self,
:param kwargs: Additional keyword arguments to pass to the request method.
:return: A `Response` object containing the server's response to the HTTP request.
"""
if segments:
if extensions is None:
extensions = {}

parameterized_url = str(url)
if self._base_url:
parameterized_url = str(self._base_url).lstrip("/") + parameterized_url
extensions["vedro_httpx_parameterized_url"] = parameterized_url
url = str(url).format(**segments)

with self._client() as client:
return cast(Response, client.request(
method=method,
Expand Down
3 changes: 3 additions & 0 deletions vedro_httpx/recorder/_async_har_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ async def format_request(self, request: httpx.Request, *,
else:
post_data = None

parameterized_url = request.extensions.get("vedro_httpx_parameterized_url", None)

return self._builder.build_request(
method=request.method,
url=str(request.url),
Expand All @@ -75,6 +77,7 @@ async def format_request(self, request: httpx.Request, *,
headers=self._format_headers(request.headers),
cookies=self._format_cookies(self._get_request_cookies(request.headers)),
post_data=post_data,
parameterized_url=parameterized_url,
)

async def format_response(self, response: httpx.Response) -> har.Response:
Expand Down
5 changes: 4 additions & 1 deletion vedro_httpx/recorder/_har_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def build_request(self,
query_string: List[har.QueryParam],
headers: List[har.Header],
cookies: List[har.Cookie],
post_data: Optional[har.PostData] = None) -> har.Request:
post_data: Optional[har.PostData] = None,
parameterized_url: Optional[str] = None) -> har.Request:
"""
Construct a request component for a HAR entry.
Expand All @@ -125,6 +126,8 @@ def build_request(self,
}
if post_data is not None:
request["postData"] = post_data
if parameterized_url is not None:
request["_parameterized_url"] = parameterized_url
return request

def build_query_param(self, name: str, value: str) -> har.QueryParam:
Expand Down
3 changes: 3 additions & 0 deletions vedro_httpx/recorder/_sync_har_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def format_request(self, request: httpx.Request, *,
else:
post_data = None

parameterized_url = request.extensions.get("vedro_httpx_parameterized_url", None)

return self._builder.build_request(
method=request.method,
url=str(request.url),
Expand All @@ -75,6 +77,7 @@ def format_request(self, request: httpx.Request, *,
headers=self._format_headers(request.headers),
cookies=self._format_cookies(self._get_request_cookies(request.headers)),
post_data=post_data,
parameterized_url=parameterized_url,
)

def format_response(self, response: httpx.Response) -> har.Response:
Expand Down
2 changes: 2 additions & 0 deletions vedro_httpx/recorder/har/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ class Request(TypedDict):
# Request method (GET, POST, ...).
method: str

_parameterized_url: NotRequired[str]

# Absolute URL of the request (fragments are not included).
url: str

Expand Down
5 changes: 5 additions & 0 deletions vedro_httpx/spec_generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ._api_spec_builder import APISpecBuilder
from ._har_reader import HARReader
from ._open_api_spec_generator import OpenAPISpecGenerator

__all__ = ("OpenAPISpecGenerator", "APISpecBuilder", "HARReader",)
Loading

0 comments on commit b8f14ad

Please sign in to comment.