Skip to content

Commit 031d4d3

Browse files
authored
Merge pull request #35 from Unstructured-IO/jj/suggest_url_if_401
chore: suggest url if 401
2 parents 94e71aa + a624e46 commit 031d4d3

File tree

5 files changed

+162
-76
lines changed

5 files changed

+162
-76
lines changed

Diff for: _test_unstructured_client/test__decorators.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import pytest
2+
3+
from unstructured_client import UnstructuredClient
4+
from unstructured_client.models import shared
5+
from unstructured_client.models.errors import SDKError
6+
7+
8+
FAKE_KEY = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
9+
10+
11+
@pytest.mark.parametrize(
12+
"server_url",
13+
[
14+
# -- well-formed url --
15+
"https://unstructured-000mock.api.unstructuredapp.io",
16+
# -- common malformed urls --
17+
"unstructured-000mock.api.unstructuredapp.io",
18+
"http://unstructured-000mock.api.unstructuredapp.io/general/v0/general",
19+
"https://unstructured-000mock.api.unstructuredapp.io/general/v0/general",
20+
"unstructured-000mock.api.unstructuredapp.io/general/v0/general",
21+
],
22+
)
23+
def test_clean_server_url_fixes_malformed_paid_api_url(server_url: str):
24+
client = UnstructuredClient(
25+
server_url=server_url,
26+
api_key_auth=FAKE_KEY,
27+
)
28+
assert (
29+
client.general.sdk_configuration.server_url
30+
== "https://unstructured-000mock.api.unstructuredapp.io"
31+
)
32+
33+
34+
@pytest.mark.parametrize(
35+
"server_url",
36+
[
37+
# -- well-formed url --
38+
"http://localhost:8000",
39+
# -- common malformed urls --
40+
"localhost:8000",
41+
"localhost:8000/general/v0/general",
42+
"http://localhost:8000/general/v0/general",
43+
],
44+
)
45+
def test_clean_server_url_fixes_malformed_localhost_url(server_url: str):
46+
client = UnstructuredClient(
47+
server_url=server_url,
48+
api_key_auth=FAKE_KEY,
49+
)
50+
assert client.general.sdk_configuration.server_url == "http://localhost:8000"
51+
52+
53+
def test_clean_server_url_returns_empty_string_given_empty_string():
54+
client = UnstructuredClient( server_url="", api_key_auth=FAKE_KEY)
55+
assert client.general.sdk_configuration.server_url == ""
56+
57+
58+
def test_clean_server_url_returns_None_given_no_server_url():
59+
client = UnstructuredClient(
60+
api_key_auth=FAKE_KEY,
61+
)
62+
assert client.general.sdk_configuration.server_url == None
63+
64+
65+
@pytest.mark.parametrize(
66+
"server_url",
67+
[
68+
# -- well-formed url --
69+
"https://unstructured-000mock.api.unstructuredapp.io",
70+
# -- malformed url --
71+
"unstructured-000mock.api.unstructuredapp.io/general/v0/general",
72+
],
73+
)
74+
def test_clean_server_url_fixes_malformed_urls_with_positional_arguments(
75+
server_url: str,
76+
):
77+
client = UnstructuredClient(
78+
FAKE_KEY,
79+
"",
80+
server_url,
81+
)
82+
assert (
83+
client.general.sdk_configuration.server_url
84+
== "https://unstructured-000mock.api.unstructuredapp.io"
85+
)
86+
87+
88+
def test_suggest_defining_url_issues_a_warning_on_a_401():
89+
client = UnstructuredClient(
90+
api_key_auth=FAKE_KEY,
91+
)
92+
93+
filename = "_sample_docs/layout-parser-paper-fast.pdf"
94+
95+
with open(filename, "rb") as f:
96+
files = shared.Files(
97+
content=f.read(),
98+
file_name=filename,
99+
)
100+
101+
req = shared.PartitionParameters(
102+
files=files,
103+
)
104+
105+
with pytest.raises(SDKError, match="API error occurred: Status 401"):
106+
with pytest.warns(
107+
UserWarning,
108+
match="If intending to use the paid API, please define `server_url` in your request.",
109+
):
110+
client.general.partition(req)

Diff for: _test_unstructured_client/test_check_url_protocol.py

-70
This file was deleted.

Diff for: src/unstructured_client/general.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from typing import Any, List, Optional
55
from unstructured_client import utils
66
from unstructured_client.models import errors, operations, shared
7+
from unstructured_client.utils._decorators import suggest_defining_url_if_401 # human code
8+
79

810
class General:
911
sdk_configuration: SDKConfiguration
@@ -12,7 +14,7 @@ def __init__(self, sdk_config: SDKConfiguration) -> None:
1214
self.sdk_configuration = sdk_config
1315

1416

15-
17+
@suggest_defining_url_if_401 # human code
1618
def partition(self, request: Optional[shared.PartitionParameters], retries: Optional[utils.RetryConfig] = None) -> operations.PartitionResponse:
1719
r"""Pipeline 1"""
1820
base_url = utils.template_url(*self.sdk_configuration.get_server_details())

Diff for: src/unstructured_client/sdk.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
from typing import Callable, Dict, Union
77
from unstructured_client import utils
88
from unstructured_client.models import shared
9-
from unstructured_client.utils._decorators import clean_server_url
9+
from unstructured_client.utils._decorators import clean_server_url # human code
1010

1111
class UnstructuredClient:
1212
r"""Unstructured Pipeline API: Partition documents with the Unstructured library"""
1313
general: General
1414

1515
sdk_configuration: SDKConfiguration
1616

17-
@clean_server_url
17+
@clean_server_url # human code
1818
def __init__(self,
1919
api_key_auth: Union[str, Callable[[], str]],
2020
server: str = None,

Diff for: src/unstructured_client/utils/_decorators.py

+47-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
from __future__ import annotations
22

33
import functools
4-
from typing import cast, Callable, Optional
4+
from typing import cast, Callable, TYPE_CHECKING, Optional
55
from typing_extensions import ParamSpec
66
from urllib.parse import urlparse, urlunparse, ParseResult
7+
import warnings
8+
9+
from unstructured_client.models import errors, operations
10+
11+
if TYPE_CHECKING:
12+
from unstructured_client.general import General
713

814

915
_P = ParamSpec("_P")
1016

1117

1218
def clean_server_url(func: Callable[_P, None]) -> Callable[_P, None]:
19+
"""A decorator for fixing common types of malformed 'server_url' arguments.
20+
21+
This decorator addresses the common problem of users omitting or using the wrong url scheme
22+
and/or adding the '/general/v0/general' path to the 'server_url'. The decorator should be
23+
manually applied to the __init__ method of UnstructuredClient after merging a PR from Speakeasy.
24+
"""
1325

1426
@functools.wraps(func)
1527
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
@@ -39,8 +51,40 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
3951
if url_is_in_kwargs:
4052
kwargs["server_url"] = urlunparse(cleaned_url)
4153
else:
42-
args = args[:SERVER_URL_ARG_IDX] + (urlunparse(cleaned_url),) + args[SERVER_URL_ARG_IDX+1:] # type: ignore
43-
54+
args = (
55+
args[:SERVER_URL_ARG_IDX]
56+
+ (urlunparse(cleaned_url),)
57+
+ args[SERVER_URL_ARG_IDX + 1 :]
58+
) # type: ignore
59+
4460
return func(*args, **kwargs)
4561

4662
return wrapper
63+
64+
65+
def suggest_defining_url_if_401(
66+
func: Callable[_P, operations.PartitionResponse]
67+
) -> Callable[_P, operations.PartitionResponse]:
68+
"""A decorator to suggest defining the 'server_url' parameter if a 401 Unauthorized error is
69+
encountered.
70+
71+
This decorator addresses the common problem of users not passing in the 'server_url' when
72+
using their paid api key. The decorator should be manually applied to General.partition after
73+
merging a PR from Speakeasy.
74+
"""
75+
76+
@functools.wraps(func)
77+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> operations.PartitionResponse:
78+
try:
79+
return func(*args, **kwargs)
80+
except errors.SDKError as e:
81+
if e.status_code == 401:
82+
general_obj: General = args[0] # type: ignore
83+
if not general_obj.sdk_configuration.server_url:
84+
warnings.warn(
85+
"If intending to use the paid API, please define `server_url` in your request."
86+
)
87+
88+
return func(*args, **kwargs)
89+
90+
return wrapper

0 commit comments

Comments
 (0)