Skip to content

Commit

Permalink
fix http_client to accept files as local paths and return .output
Browse files Browse the repository at this point in the history
  • Loading branch information
nikochiko committed Sep 11, 2024
1 parent 021be76 commit 6ffcbcc
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Specify files that shouldn't be modified by Fern

src/gooey/core/http_client.py
src/gooey/core/pydantic_utilities.py
94 changes: 86 additions & 8 deletions src/gooey/core/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import asyncio
import email.utils
import json
import json as json_module
import os
import re
import time
import typing
Expand Down Expand Up @@ -144,7 +145,7 @@ def get_request_body(
json_body = maybe_filter_request_body(json, request_options, omit)

# If you have an empty JSON body, you should just send None
return (json_body if json_body != {} else None), data_body if data_body != {} else None
return (json_body if json_body != {} else None), (data_body if data_body != {} else None)


class HttpClient:
Expand Down Expand Up @@ -190,6 +191,7 @@ def request(
else self.base_timeout
)

path, json, data, files = gooey_process_request_params(path=path, data=data, files=files, json=json, omit=omit)
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)

response = self.httpx_client.request(
Expand Down Expand Up @@ -224,7 +226,7 @@ def request(
json=json_body,
data=data_body,
content=content,
files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
files=(convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None),
timeout=timeout,
)

Expand All @@ -246,7 +248,21 @@ def request(
omit=omit,
)

return response
# custom gooey code
location = response.headers.get("location")
if not location:
return response

while True:
response = self.request(location, method="get", headers=headers, request_options=request_options)
if not response.is_success:
return response
else:
body = response.json()
if body.get("status") in ["starting", "running"]:
continue
else: # failed, completed, or something not in the spec
return response

@contextmanager
def stream(
Expand Down Expand Up @@ -306,7 +322,7 @@ def stream(
json=json_body,
data=data_body,
content=content,
files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
files=(convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None),
timeout=timeout,
) as stream:
yield stream
Expand Down Expand Up @@ -355,6 +371,7 @@ async def request(
else self.base_timeout
)

path, json, data, files = gooey_process_request_params(path=path, data=data, files=files, json=json, omit=omit)
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)

# Add the input to each of these and do None-safety checks
Expand Down Expand Up @@ -390,7 +407,7 @@ async def request(
json=json_body,
data=data_body,
content=content,
files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
files=(convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None),
timeout=timeout,
)

Expand All @@ -411,7 +428,22 @@ async def request(
retries=retries + 1,
omit=omit,
)
return response

# custom gooey code
location = response.headers.get("location")
if not location:
return response

while True:
response = await self.request(location, method="get", headers=headers, request_options=request_options)
if not response.is_success:
return response
else:
body = response.json()
if body.get("status") in ["starting", "running"]:
continue
else: # failed, completed, or something not in the spec
return response

@asynccontextmanager
async def stream(
Expand Down Expand Up @@ -471,7 +503,53 @@ async def stream(
json=json_body,
data=data_body,
content=content,
files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
files=(convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None),
timeout=timeout,
) as stream:
yield stream


class GooeyRequestParams(typing.NamedTuple):
"""Custom Request Parameters for Gooey"""

path: str | None
json: typing.Optional[typing.Any]
data: typing.Optional[typing.Any]
files: typing.Optional[typing.Any]


def gooey_process_request_params(
*,
path: str | None,
json: typing.Optional[typing.Any],
data: typing.Optional[typing.Any],
files: typing.Optional[typing.Any],
omit: typing.Any,
) -> GooeyRequestParams:
"""
Hack to allow providing filepaths as strings in the SDK.
"""
if json or not isinstance(data, typing.MutableMapping) or not path or not path.rstrip("/").endswith("/async"):
return GooeyRequestParams(path=path, json=json, data=data, files=files)

if files and isinstance(files, typing.MutableMapping):
for k, v in files.items():
if v and isinstance(v, list) and all(isinstance(item, str) and os.path.exists(item) for item in v):
files[k] = [open(item, "rb") for item in v]
elif v and isinstance(v, str) and os.path.exists(v):
files[k] = open(v, "rb")
elif isinstance(v, str) or v is omit or not v:
# a URL, None, or omitted value
data[k] = files.pop(k)

if files:
return GooeyRequestParams(
path=path.rstrip("/") + "/form",
json=None,
data={
"json": json_module.dumps(maybe_filter_request_body(data, request_options=None, omit=omit)),
},
files=files,
)
else:
return GooeyRequestParams(path=path, json=data, data=None, files=None)
7 changes: 7 additions & 0 deletions src/gooey/core/pydantic_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@


def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T:
if type_.__name__.endswith("Output") and isinstance(object_, typing.Mapping) and "output" in object_:
return _parse_obj_as(type_, object_["output"])

return _parse_obj_as(type_, object_)


def _parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T:
if IS_PYDANTIC_V2:
adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2
return adapter.validate_python(object_)
Expand Down

0 comments on commit 6ffcbcc

Please sign in to comment.