Skip to content

Commit

Permalink
fix: Use save_to_disk
Browse files Browse the repository at this point in the history
  • Loading branch information
ll931217 committed Oct 1, 2024
1 parent 090b51e commit f386be9
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 79 deletions.
6 changes: 1 addition & 5 deletions src/posting/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,7 @@ def import_spec(spec_path: str, output: str | None, type: str) -> None:
collection = import_openapi_spec(spec_path)
elif type.lower() == "postman":
spec_type = "Postman"
console.print(
"Importing Postman collection haven't been implemented yet", style="red"
)
import_postman_spec(spec_path, output)
return
collection = import_postman_spec(spec_path, output)
else:
console.print(f"Unknown spec type: {type!r}", style="red")
return
Expand Down
177 changes: 103 additions & 74 deletions src/posting/importing/postman.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from pathlib import Path
from typing import Any, List, Optional
from typing import List, Optional
import json
import os
import re
import yaml

from pydantic import BaseModel

from rich.console import Console

from posting.collection import APIInfo, Collection
from posting.collection import (
APIInfo,
Collection,
FormItem,
Header,
QueryParam,
RequestBody,
RequestModel,
HttpRequestMethod,
)


class Variable(BaseModel):
Expand All @@ -22,9 +29,17 @@ class Variable(BaseModel):
disabled: Optional[bool] = None


class RawRequestOptions(BaseModel):
language: str


class RequestOptions(BaseModel):
raw: RawRequestOptions


class Body(BaseModel):
mode: str
options: Optional[dict] = None
options: Optional[RequestOptions] = None
raw: Optional[str] = None
formdata: Optional[List[Variable]] = None

Expand All @@ -37,7 +52,7 @@ class Url(BaseModel):


class PostmanRequest(BaseModel):
method: str
method: HttpRequestMethod
url: Optional[str | Url] = None
header: Optional[List[Variable]] = None
description: Optional[str] = None
Expand All @@ -52,100 +67,115 @@ class RequestItem(BaseModel):

class PostmanCollection(BaseModel):
info: dict[str, str]
item: List[RequestItem]
variable: List[Variable]

item: List[RequestItem]


# Converts variable names like userId to $USER_ID, or user-id to $USER_ID
def sanitize_variables(string):
underscore_case = re.sub(r"(?<!^)(?=[A-Z-])", "_", string).replace("-", "")
return underscore_case.upper()


def sanitize_str(string):
def replace_match(match):
value = match.group(1)
return f"${sanitize_variables(value)}"

transformed = re.sub(r"\{\{([\w-]+)\}\}", replace_match, string)
return transformed


def create_env_file(path: Path, env_filename: str, variables: List[Variable]) -> Path:
env_content: List[str] = []

for var in variables:
env_content.append(f"{transform_variables(var.key)}={var.value}")
env_content.append(f"{sanitize_variables(var.key)}={var.value}")

env_file = path / env_filename
env_file.write_text("\n".join(env_content))
return env_file


def generate_directory_structure(
def import_requests(
items: List[RequestItem], current_path: str = "", base_path: Path = Path("")
) -> List[str]:
directories = []
) -> List[RequestModel]:
requests: List[RequestModel] = []
for item in items:
if item.item is not None:
folder_name = item.name
new_path = f"{current_path}/{folder_name}" if current_path else folder_name
full_path = Path(base_path) / new_path
os.makedirs(str(full_path), exist_ok=True)
directories.append(str(full_path))
generate_directory_structure(item.item, new_path, base_path)
requests = requests + import_requests(item.item, new_path, base_path)
if item.request is not None:
request_name = re.sub(r"[^A-Za-z0-9\.]+", "", item.name)
file_name = f"{request_name}.posting.yaml"
file_name = re.sub(r"[^A-Za-z0-9\.]+", "", item.name)
full_path = Path(base_path) / current_path / file_name
create_request_file(full_path, item)
return directories
requests.append(format_request(str(full_path), item.request))

return requests

# Converts variable names like userId to $USER_ID, or user-id to $USER_ID
def transform_variables(string):
underscore_case = re.sub(r"(?<!^)(?=[A-Z-])", "_", string).replace("-", "")
return underscore_case.upper()


def transform_url(string):
def replace_match(match):
value = match.group(1)
return f"${transform_variables(value)}"

transformed = re.sub(r"\{\{([\w-]+)\}\}", replace_match, string)
return transformed


def create_request_file(file_path: Path, request_data: RequestItem):
yaml_content: dict[str, Any] = {
"name": request_data.name,
}

if request_data.request is not None:
yaml_content["method"] = request_data.request.method

if request_data.request.header is not None:
yaml_content["headers"] = [
{"name": header.key, "value": header.value}
for header in request_data.request.header
]

if request_data.request.url is not None:
if isinstance(request_data.request.url, Url):
yaml_content["url"] = transform_url(request_data.request.url.raw)
if request_data.request.url.query is not None:
yaml_content["params"] = [
{"name": param.key, "value": transform_url(param.value)}
for param in request_data.request.url.query
]
else:
yaml_content["url"] = transform_url((request_data.request.url))

if request_data.request.description is not None:
yaml_content["description"] = request_data.request.description
def format_request(name: str, request: PostmanRequest) -> RequestModel:
postingRequest = RequestModel(
name=name,
method=request.method,
description=request.description if request.description is not None else "",
url=sanitize_str(
request.url.raw if isinstance(request.url, Url) else request.url
)
if request.url is not None
else "",
)

if request.header is not None:
for header in request.header:
postingRequest.headers.append(
Header(
name=header.key,
value=header.value if header.value is not None else "",
enabled=True,
)
)

if (
request.url is not None
and isinstance(request.url, Url)
and request.url.query is not None
):
for param in request.url.query:
postingRequest.params.append(
QueryParam(
name=param.key,
value=param.value if param.value is not None else "",
enabled=param.disabled if param.disabled is not None else False,
)
)

if request.body is not None and request.body.raw is not None:
if (
request_data.request.body is not None
and request_data.request.body.raw is not None
request.body.mode == "raw"
and request.body.options is not None
and request.body.options.raw.language == "json"
):
yaml_content["body"] = {
"content": transform_url(request_data.request.body.raw)
}
postingRequest.body = RequestBody(content=sanitize_str(request.body.raw))
elif request.body.mode == "formdata" and request.body.formdata is not None:
form_data: list[FormItem] = [
FormItem(
name=data.key,
value=data.value if data.value is not None else "",
enabled=data.disabled is False,
)
for data in request.body.formdata
]
postingRequest.body = RequestBody(form_data=form_data)

# Write YAML file
with open(file_path, "w") as f:
yaml.dump(yaml_content, f, default_flow_style=False)
return postingRequest


def import_postman_spec(
spec_path: str | Path, output_path: str | Path | None
) -> tuple[Collection, Path, List[str]]:
) -> Collection:
console = Console()
console.print(f"Importing Postman spec from {spec_path!r}.")

Expand All @@ -166,15 +196,14 @@ def import_postman_spec(
if output_path is not None:
base_dir = Path(output_path) if isinstance(output_path, str) else output_path

console.print(f"Output path: {output_path!r}")
console.print(f"Output path: {str(base_dir)!r}")

env_file = create_env_file(base_dir, f"{info.title}.env", spec.variable)
console.print(f"Created environment file {str(env_file)!r}.")

main_collection = Collection(path=spec_path.parent, name="Postman Test")
main_collection = Collection(path=spec_path.parent, name=info.title)
main_collection.readme = main_collection.generate_readme(info)

# Create the directory structure like Postman's request folders
directories = generate_directory_structure(spec.item, base_path=base_dir)
console.print("Finished importing postman collection.")
main_collection.requests = import_requests(spec.item, base_path=base_dir)

return main_collection, env_file, directories
return main_collection

0 comments on commit f386be9

Please sign in to comment.