Skip to content

Commit 7a8faf1

Browse files
authored
Dev (#291)
* Start work on HTTPX utils improvements (#290) * Update demo(). Add vscode workspace. Update httpx validators. Move file_utils -> path_utils
1 parent e04feff commit 7a8faf1

17 files changed

+236
-92
lines changed

.vscode/red-utils.code-workspace

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "..",
5+
"name": "Git Root"
6+
},
7+
{
8+
"path": "../red_utils/",
9+
"name": "Red-Utils App"
10+
}
11+
]
12+
}

demo.py

+15-49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from __future__ import annotations
2+
import sys
3+
4+
sys.path.append(".")
25

36
import pkgutil
47
import shutil
@@ -9,7 +12,7 @@
912
from red_utils.std import (
1013
context_managers,
1114
dict_utils,
12-
file_utils,
15+
path_utils,
1316
hash_utils,
1417
time_utils,
1518
uuid_utils,
@@ -41,11 +44,12 @@
4144
from red_utils import CustomException
4245
from red_utils.ext.context_managers import cli_spinners
4346

47+
4448
def test_file_utils_list() -> list[Path]:
4549
cwd = Path.cwd()
4650
search_dir = f"{cwd}/red_utils"
4751

48-
list_files_test = file_utils.list_files(in_dir=search_dir, ext_filter=".py")
52+
list_files_test = path_utils.list_files(in_dir=search_dir, ext_filter=".py")
4953
print(f".py files found in {search_dir}: {len(list_files_test)}")
5054

5155
rand_index = random.randint(0, len(list_files_test) - 1)
@@ -410,68 +414,28 @@ def delete_test_remnants(delete_dirs=delete_dirs, delete_files=delete_files):
410414
for _d in delete_dirs:
411415
d_path: Path = Path(_d)
412416

413-
try:
414-
if d_path.exists():
415-
print(f"Deleting directory: {str(d_path)}")
416-
shutil.rmtree(d_path, ignore_errors=True)
417-
418-
except FileNotFoundError as fnf:
419-
print(
420-
FileNotFoundError(
421-
f"Could not find dir {str(d_path)}. Details: {fnf}"
422-
)
423-
)
424-
except PermissionError as perm:
425-
print(
426-
PermissionError(
427-
f"Insufficient permissions to delete dir {str(d_path)}. Details: {perm}"
428-
)
429-
)
430-
except Exception as exc:
431-
print(
432-
Exception(
433-
f"Unhandled exception deleting dir {str(d_path)}. Details: {exc}"
434-
)
435-
)
417+
path_utils.delete_path(_d)
436418

437419
for _f in delete_files:
438420
f_path: Path = Path(_f)
439421

440-
try:
441-
if f_path.exists():
442-
print(f"Deleting file {str(f_path)}")
443-
f_path.unlink()
444-
445-
except FileNotFoundError as fnf:
446-
print(
447-
FileNotFoundError(
448-
f"Could not find file {str(f_path)}. Details: {fnf}"
449-
)
450-
)
451-
except PermissionError as perm:
452-
print(
453-
PermissionError(
454-
f"Insufficient permissions to delete file {str(f_path)}. Details: {perm}"
455-
)
456-
)
457-
except Exception as exc:
458-
print(
459-
Exception(
460-
f"Unhandled exception deleting file {str(f_path)}. Details: {exc}"
461-
)
462-
)
422+
path_utils.delete_path(f_path)
463423

464424
delete_test_remnants()
465425

466426

427+
def test_ensuredirs(_dirs: list[Path] = [Path("test"), Path("test/testing")]):
428+
path_utils.ensure_dirs_exist(ensure_dirs=_dirs)
429+
430+
467431
def main():
468432
"""Main function to control flow of demo.
469433
470434
Comment functions you don't want to execute.
471435
"""
472436
## Delete lists for cleanup function
473437
cleanup_dirs: list[str] = [".cache", ".db", ".serialize"]
474-
cleanup_files: list[str] = ["test.db"]
438+
cleanup_files: list[str] = ["test.db", "test"]
475439

476440
print(test_file_utils_list())
477441

@@ -496,6 +460,8 @@ def main():
496460

497461
test_sqlalchemy_utils()
498462

463+
test_ensuredirs()
464+
499465
with cli_spinners.SimpleSpinner("Cleaning up..."):
500466
post_test_cleanup(delete_files=cleanup_files, delete_dirs=cleanup_dirs)
501467
time.sleep(0.5)

red_utils/core/__init__.py

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

3-
from . import dataclass_utils
3+
from . import dataclass_utils, constants
4+
5+
from .constants import DATA_DIR, CACHE_DIR, SERIALIZE_DIR, JSON_DIR, ENSURE_EXIST_DIRS

red_utils/core/constants.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pathlib import Path
2+
3+
DATA_DIR: Path = Path(".data")
4+
CACHE_DIR: Path = Path(".cache")
5+
SERIALIZE_DIR: Path = Path(".serialize")
6+
JSON_DIR: Path = Path(f"{DATA_DIR}/json")
7+
8+
ENSURE_EXIST_DIRS: list[Path] = [DATA_DIR, CACHE_DIR, SERIALIZE_DIR]

red_utils/ext/httpx_utils/__init__.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
from __future__ import annotations
22

3+
import sys
4+
5+
sys.path.append(".")
6+
37
from . import constants, operations, validators
4-
from .constants import default_headers, valid_methods
8+
from .constants import default_headers
59
from .operations import get_req_client, make_request, merge_headers, update_headers
6-
from .validators import validate_client, validate_headers, validate_method
10+
from .validators import (
11+
validate_headers,
12+
valid_methods,
13+
validate_client,
14+
validate_method,
15+
)

red_utils/ext/httpx_utils/classes.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Union
2+
3+
from red_utils.core.dataclass_utils import DictMixin
4+
5+
from red_utils.validators.ext.httpx_validators import (
6+
valid_methods,
7+
validate_client,
8+
validate_headers,
9+
validate_method,
10+
)
11+
from .constants import default_headers
12+
13+
from dataclasses import dataclass, field
14+
15+
import httpx
16+
17+
18+
@dataclass
19+
class SimpleHTTPXClientBase:
20+
method: str | None = field(default="GET")
21+
headers: dict | None = field(default=default_headers)
22+
timeout: int | None = field(default=5)
23+
data: Union[dict, str] | None = field(default=None)
24+
25+
def __post_init__(self):
26+
self.method = self.method.upper()
27+
validate_method(self.method)
28+
validate_headers(self.headers)
29+
30+
def client(self) -> httpx.Client:
31+
try:
32+
_client = httpx.Client(headers=self.headers, timeout=self.timeout)
33+
34+
return _client
35+
except Exception as exc:
36+
raise Exception(f"Unhandled exception getting HTTPX Client. Details: {exc}")
37+
38+
39+
@dataclass
40+
class SimpleHTTPXClient(SimpleHTTPXClientBase):
41+
pass
-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
from __future__ import annotations
22

3-
valid_methods: list[str] = ["GET", "POST", "PUT", "UPDATE", "DELETE"]
43
default_headers: dict[str, str] = {"Content-Type": "application/json"}

red_utils/ext/httpx_utils/operations.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
from .constants import (
44
default_headers,
5-
valid_methods,
65
)
7-
from .validators import validate_client, validate_headers, validate_method
6+
from .validators import (
7+
validate_client,
8+
validate_headers,
9+
validate_method,
10+
)
811

912
import httpx
1013

1114
from httpx import Client
1215

16+
1317
def merge_headers(
1418
original_headers: dict[str, str] = default_headers,
1519
update_vals: dict[str, str] = None,

red_utils/ext/httpx_utils/validators.py

+30-16
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
1-
from __future__ import annotations
2-
31
from typing import Union
2+
from httpx import AsyncClient, Client
43

5-
from .constants import valid_methods
4+
valid_methods: list[str] = ["GET", "POST", "PUT", "UPDATE", "DELETE"]
5+
6+
7+
def validate_method(method: str = None) -> str:
8+
"""Validate an HTTPX request method.
9+
10+
Params:
11+
-------
12+
- method (str): The method to validate.
13+
"""
14+
if method is None:
15+
raise ValueError("Missing a method to validate")
16+
if not isinstance(method, str):
17+
try:
18+
method: str = str(method).upper()
19+
except Exception as exc:
20+
raise Exception(
21+
f"Unable to coerce method value to string: ({type(method)}) - {method}. Details: {exc}"
22+
)
23+
else:
24+
method: str = method.upper()
25+
26+
if method not in valid_methods:
27+
raise ValueError(f"Invalid method: {method}. Must be one of {valid_methods}")
28+
29+
return method
630

7-
from httpx import AsyncClient, Client
831

932
def validate_client(
1033
client: Union[Client, AsyncClient] = None
1134
) -> Union[Client, AsyncClient]:
35+
"""Validate HTTPX Client/AsyncClient object."""
1236
if not client:
1337
raise ValueError("Missing client to evaluate")
1438

@@ -20,20 +44,10 @@ def validate_client(
2044
return client
2145

2246

23-
def validate_method(method: str = None) -> str:
24-
"""Validate HTTP method."""
25-
if not method:
26-
raise ValueError("Missing method to evaluate")
27-
28-
if method not in valid_methods:
29-
raise TypeError(f"Invalid method: {method}. Must be one of {valid_methods}")
30-
31-
return method
32-
33-
3447
def validate_headers(headers: dict[str, str] = None) -> dict[str, str]:
3548
if not headers:
36-
raise ValueError("Missing headers to evaluate")
49+
# raise ValueError("Missing headers to evaluate")
50+
return
3751

3852
if not isinstance(headers, dict):
3953
raise TypeError(

red_utils/std/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
context_managers,
55
dataclass_mixins,
66
dict_utils,
7-
file_utils,
7+
path_utils,
88
hash_utils,
99
time_utils,
1010
uuid_utils,

red_utils/std/file_utils/__init__.py

-5
This file was deleted.

red_utils/std/file_utils/constants.py

-12
This file was deleted.

red_utils/std/path_utils/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
from . import constants, operations
4+
from .operations import (
5+
crawl_dir,
6+
export_json,
7+
list_files,
8+
ensure_dirs_exist,
9+
delete_path,
10+
)

red_utils/std/path_utils/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from __future__ import annotations

0 commit comments

Comments
 (0)