mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-26 07:18:32 -06:00
remove httpx dependency
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
- New styles for input boxes: "rounded" and "pill"
|
||||
- Improved mobile support: Dragging is now much smoother
|
||||
- Improved tables
|
||||
- `rio run` now also works when using `as_fastapi`
|
||||
|
||||
## 0.10
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ dependencies = [
|
||||
"fastapi~=0.110",
|
||||
"fuzzywuzzy~=0.18",
|
||||
"gitignore-parser==0.1.11",
|
||||
"httpx~=0.27.2",
|
||||
"introspection~=1.9.2",
|
||||
"isort~=5.13",
|
||||
"keyring~=24.3",
|
||||
|
||||
171
rio/arequests.py
Normal file
171
rio/arequests.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import asyncio
|
||||
import json as json_module
|
||||
import typing as t
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import urllib.response
|
||||
|
||||
|
||||
class HttpError(Exception):
|
||||
"""
|
||||
Raised when an HTTP request fails.
|
||||
|
||||
The status code is `None` if the error wasn't caused by HTTP, but some other
|
||||
reason like a network error.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
status_code: int | None,
|
||||
) -> None:
|
||||
super().__init__(message, status_code)
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def status_code(self) -> int | None:
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class HttpResponse:
|
||||
"""
|
||||
Represents an HTTP response.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
status_code: int,
|
||||
headers: dict[str, str],
|
||||
content: bytes,
|
||||
) -> None:
|
||||
self.status_code = status_code
|
||||
self.headers = headers
|
||||
self._content = content
|
||||
|
||||
def read(self) -> bytes:
|
||||
"""
|
||||
Returns the response body as bytes.
|
||||
"""
|
||||
return self._content
|
||||
|
||||
def json(self) -> t.Any:
|
||||
"""
|
||||
Returns the response body as a JSON object. Raises a
|
||||
`json.JSONDecodeError` if the response body is not valid JSON.
|
||||
"""
|
||||
|
||||
try:
|
||||
return json_module.loads(self._content)
|
||||
except UnicodeDecodeError:
|
||||
raise json_module.JSONDecodeError(
|
||||
"The response body is not valid UTF-8",
|
||||
"",
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
def _request_sync(
|
||||
method: t.Literal["GET", "POST"],
|
||||
url: str,
|
||||
*,
|
||||
content: str | bytes | None = None,
|
||||
json: dict[str, t.Any] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> HttpResponse:
|
||||
"""
|
||||
Makes an HTTP request with the specified parameters. Returns the response
|
||||
headers and body.
|
||||
"""
|
||||
|
||||
# Prepare the request
|
||||
req = urllib.request.Request(url, method=method)
|
||||
|
||||
if headers:
|
||||
for key, value in headers.items():
|
||||
req.add_header(key, value)
|
||||
|
||||
if json:
|
||||
if content is not None:
|
||||
raise ValueError("Cannot specify both `content` and `json`")
|
||||
|
||||
content = json_module.dumps(json)
|
||||
|
||||
if content:
|
||||
if isinstance(content, str):
|
||||
content = content.encode("utf-8")
|
||||
|
||||
req.data = content
|
||||
|
||||
# Make the request
|
||||
try:
|
||||
with urllib.request.urlopen(req) as response:
|
||||
# Check the status code
|
||||
if response.status >= 300:
|
||||
raise HttpError(
|
||||
response.reason,
|
||||
response.status,
|
||||
)
|
||||
|
||||
# Epic success!
|
||||
return HttpResponse(
|
||||
status_code=response.status,
|
||||
headers={
|
||||
key.lower(): value for key, value in response.getheaders()
|
||||
},
|
||||
content=response.read(),
|
||||
)
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
raise HttpError(
|
||||
e.reason,
|
||||
e.code,
|
||||
) from None
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
raise HttpError(
|
||||
str(e.reason),
|
||||
None,
|
||||
) from None
|
||||
|
||||
|
||||
async def request(
|
||||
method: t.Literal["GET", "POST"],
|
||||
url: str,
|
||||
*,
|
||||
content: bytes | None = None,
|
||||
json: dict[str, t.Any] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> HttpResponse:
|
||||
"""
|
||||
Makes an HTTP request with the specified parameters. Returns the response
|
||||
headers and body.
|
||||
"""
|
||||
|
||||
return await asyncio.to_thread(
|
||||
_request_sync,
|
||||
method,
|
||||
url,
|
||||
content=content,
|
||||
json=json,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
response = await request(
|
||||
"GET",
|
||||
"https://postman-echo.com/get?foo=bar",
|
||||
)
|
||||
|
||||
print(response.status_code)
|
||||
print(response.headers)
|
||||
print(response.json())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -7,12 +7,12 @@ import os
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import typing_extensions as te
|
||||
from PIL.Image import Image
|
||||
from yarl import URL
|
||||
|
||||
import rio
|
||||
import rio.arequests as arequests
|
||||
|
||||
from .self_serializing import SelfSerializing
|
||||
from .utils import ImageLike
|
||||
@@ -257,17 +257,17 @@ class UrlAsset(Asset):
|
||||
|
||||
async def try_fetch_as_blob(self) -> tuple[bytes, str | None]:
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(str(self._url))
|
||||
response = await arequests.request("GET", str(self._url))
|
||||
|
||||
content_type = response.headers.get("Content-Type")
|
||||
if content_type is None:
|
||||
content_type = "application/octet-stream"
|
||||
else:
|
||||
content_type, _, _ = content_type.partition(";")
|
||||
content_type = response.headers.get("content-type")
|
||||
|
||||
return response.read(), content_type
|
||||
except httpx.HTTPError:
|
||||
if isinstance(content_type, str):
|
||||
content_type, _, _ = content_type.partition(";")
|
||||
else:
|
||||
content_type = "application/octet-stream"
|
||||
|
||||
return response.read(), content_type
|
||||
except arequests.HttpError:
|
||||
raise ValueError(f"Could not fetch asset from {self._url}")
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import typing as t
|
||||
from datetime import timedelta
|
||||
|
||||
import httpx
|
||||
import rio.arequests as arequests
|
||||
|
||||
BASE_URL = "https://rio.dev/api"
|
||||
|
||||
@@ -35,9 +35,8 @@ class RioApi:
|
||||
def __init__(
|
||||
self,
|
||||
access_token: str | None = None,
|
||||
):
|
||||
) -> None:
|
||||
self._access_token = access_token
|
||||
self._http_client = httpx.AsyncClient()
|
||||
|
||||
@property
|
||||
def is_logged_in(self) -> bool:
|
||||
@@ -51,7 +50,7 @@ class RioApi:
|
||||
|
||||
async def close(self) -> None:
|
||||
"""
|
||||
Log out, if logged in and close the HTTP client.
|
||||
Log out, if currently logged in.
|
||||
"""
|
||||
if self.is_logged_in:
|
||||
try:
|
||||
@@ -59,8 +58,6 @@ class RioApi:
|
||||
except ApiException:
|
||||
pass
|
||||
|
||||
await self._http_client.aclose()
|
||||
|
||||
async def request(
|
||||
self,
|
||||
endpoint: str,
|
||||
@@ -85,7 +82,7 @@ class RioApi:
|
||||
# Make the request
|
||||
#
|
||||
# TODO: Which exceptions can this throw?
|
||||
response = await self._http_client.request(
|
||||
response = await arequests.request(
|
||||
method,
|
||||
f"{BASE_URL}/{endpoint}",
|
||||
headers=headers,
|
||||
|
||||
@@ -10,11 +10,11 @@ import typing as t
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import revel
|
||||
from revel import print
|
||||
|
||||
import rio.app_server.fastapi_server
|
||||
import rio.arequests as arequests
|
||||
import rio.cli
|
||||
import rio.snippets
|
||||
|
||||
@@ -187,21 +187,17 @@ class Arbiter:
|
||||
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://pypi.org/pypi/rio-ui/json",
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
response = await arequests.request(
|
||||
"GET",
|
||||
"https://pypi.org/pypi/rio-ui/json",
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
return data["info"]["version"]
|
||||
data = response.json()
|
||||
return data["info"]["version"]
|
||||
|
||||
# Oh muh gawd soo many errors
|
||||
except (
|
||||
httpx.HTTPError,
|
||||
httpx.StreamError,
|
||||
httpx.ProtocolError,
|
||||
arequests.HttpError,
|
||||
json.JSONDecodeError,
|
||||
) as exc:
|
||||
raise ValueError(f"Failed to fetch the latest Rio version: {exc}")
|
||||
|
||||
@@ -121,7 +121,16 @@ class DevelComponent(FundamentalComponent):
|
||||
|
||||
tasks = [
|
||||
("sass", str(scss_path), str(css_path)),
|
||||
("tsc", str(ts_path), "--outFile", str(js_path)),
|
||||
(
|
||||
"tsc",
|
||||
"--lib",
|
||||
"dom,es6",
|
||||
str(ts_path),
|
||||
"--target",
|
||||
"ES6",
|
||||
"--outFile",
|
||||
str(js_path),
|
||||
),
|
||||
]
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
|
||||
23
rio/utils.py
23
rio/utils.py
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import mimetypes
|
||||
import os
|
||||
@@ -8,6 +9,7 @@ import secrets
|
||||
import socket
|
||||
import typing as t
|
||||
from dataclasses import dataclass
|
||||
from http.client import HTTPSConnection
|
||||
from io import BytesIO, StringIO
|
||||
from pathlib import Path
|
||||
|
||||
@@ -565,3 +567,24 @@ def soft_sort(
|
||||
|
||||
for element, _, _ in keyed_elements:
|
||||
elements.append(element)
|
||||
|
||||
|
||||
async def async_http_request(url: str) -> Tuple[Dict[str, str], bytes]:
|
||||
"""Performs an async HTTP GET request to the given URL and returns headers and content blob."""
|
||||
loop = asyncio.get_running_loop()
|
||||
host, path = url.split("/", 1)
|
||||
|
||||
def fetch() -> Tuple[Dict[str, str], bytes]:
|
||||
conn = HTTPSConnection(host)
|
||||
conn.request("GET", f"/{path}")
|
||||
response = conn.getresponse()
|
||||
headers = dict(response.getheaders())
|
||||
blob = response.read()
|
||||
conn.close()
|
||||
return headers, blob
|
||||
|
||||
return await loop.run_in_executor(None, fetch)
|
||||
|
||||
|
||||
# Example usage:
|
||||
# headers, blob = asyncio.run(async_http_request("example.com/some-path"))
|
||||
|
||||
Reference in New Issue
Block a user