From b9a0c000603efa539bff0ece861ba9fdebef85c7 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 28 May 2026 15:35:32 -0700 Subject: [PATCH 01/10] test: add utility function test coverage Co-Authored-By: Claude Sonnet 4.6 --- fastapi_startkit/tests/utils/__init__.py | 0 .../tests/utils/test_collections.py | 253 ++++++++++++++++++ .../tests/utils/test_filesystem_utils.py | 100 +++++++ .../tests/utils/test_str_utils.py | 155 +++++++++++ .../tests/utils/test_time_utils.py | 96 +++++++ 5 files changed, 604 insertions(+) create mode 100644 fastapi_startkit/tests/utils/__init__.py create mode 100644 fastapi_startkit/tests/utils/test_collections.py create mode 100644 fastapi_startkit/tests/utils/test_filesystem_utils.py create mode 100644 fastapi_startkit/tests/utils/test_str_utils.py create mode 100644 fastapi_startkit/tests/utils/test_time_utils.py diff --git a/fastapi_startkit/tests/utils/__init__.py b/fastapi_startkit/tests/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/utils/test_collections.py b/fastapi_startkit/tests/utils/test_collections.py new file mode 100644 index 00000000..9d873bc4 --- /dev/null +++ b/fastapi_startkit/tests/utils/test_collections.py @@ -0,0 +1,253 @@ +"""Tests for Collection utility class (task #15).""" + +import pytest + +from fastapi_startkit.utils.collections import Collection, collect, flatten + + +class TestCollectionBasics: + def test_empty_collection(self): + c = Collection() + assert c.count() == 0 + assert c.is_empty() + + def test_count(self): + c = Collection([1, 2, 3]) + assert c.count() == 3 + + def test_all_returns_items(self): + c = Collection([10, 20]) + assert c.all() == [10, 20] + + def test_iteration(self): + items = [1, 2, 3] + c = Collection(items) + assert list(c) == items + + def test_getitem(self): + c = Collection(["a", "b", "c"]) + assert c[0] == "a" + assert c[-1] == "c" + + +class TestCollectionFirstLast: + def test_first_without_callback(self): + assert Collection([5, 6, 7]).first() == 5 + + def test_first_with_callback(self): + c = Collection([1, 2, 3, 4]) + result = c.first(lambda x: x > 2) + assert result == 3 + + def test_last_without_callback(self): + assert Collection([1, 2, 3]).last() == 3 + + def test_last_with_callback(self): + c = Collection([1, 2, 3, 4]) + result = c.last(lambda x: x < 3) + assert result == 2 + + def test_first_returns_none_for_empty(self): + assert Collection([]).first() is None + + +class TestCollectionMap: + def test_map_transforms_items(self): + c = Collection([1, 2, 3]) + result = c.map(lambda x: x * 2) + assert result.all() == [2, 4, 6] + + def test_map_returns_new_collection(self): + c = Collection([1, 2, 3]) + result = c.map(lambda x: x) + assert isinstance(result, Collection) + + +class TestCollectionFilter: + def test_filter_keeps_matching_items(self): + c = Collection([1, 2, 3, 4, 5]) + result = c.filter(lambda x: x % 2 == 0) + assert result.all() == [2, 4] + + def test_filter_raises_on_non_callable(self): + with pytest.raises(ValueError): + Collection([1, 2]).filter("not a callable") + + +class TestCollectionPluck: + def test_pluck_values_from_dicts(self): + c = Collection([{"name": "Alice"}, {"name": "Bob"}]) + result = c.pluck("name") + assert result.all() == ["Alice", "Bob"] + + def test_pluck_with_key(self): + c = Collection([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]) + result = c.pluck("name", "id") + assert result.all() == {1: "Alice", 2: "Bob"} + + +class TestCollectionChunk: + def test_chunk_even(self): + c = Collection([1, 2, 3, 4]) + chunks = c.chunk(2) + result = [ch.all() for ch in chunks] + assert result == [[1, 2], [3, 4]] + + def test_chunk_uneven(self): + c = Collection([1, 2, 3, 4, 5]) + chunks = c.chunk(2) + result = [ch.all() for ch in chunks] + assert result == [[1, 2], [3, 4], [5]] + + def test_chunk_size_larger_than_collection(self): + c = Collection([1, 2]) + chunks = c.chunk(10) + result = [ch.all() for ch in chunks] + assert result == [[1, 2]] + + +class TestCollectionGroupBy: + def test_group_by_key(self): + c = Collection( + [ + {"category": "A", "val": 1}, + {"category": "B", "val": 2}, + {"category": "A", "val": 3}, + ] + ) + result = c.group_by("category") + grouped = result.all() + assert "A" in grouped + assert "B" in grouped + assert len(grouped["A"]) == 2 + assert len(grouped["B"]) == 1 + + +class TestCollectionSum: + def test_sum_numbers(self): + assert Collection([1, 2, 3]).sum() == 6 + + def test_sum_key(self): + c = Collection([{"price": 10}, {"price": 20}]) + assert c.sum("price") == 30 + + def test_sum_empty(self): + assert Collection([]).sum() == 0 + + +class TestCollectionImplode: + def test_implode_strings(self): + result = Collection(["a", "b", "c"]).implode(", ") + assert result == "a, b, c" + + def test_implode_numbers(self): + result = Collection([1, 2, 3]).implode("-") + assert result == "1-2-3" + + +class TestCollectionMerge: + def test_merge_adds_items(self): + c = Collection([1, 2]) + c.merge([3, 4]) + assert c.all() == [1, 2, 3, 4] + + def test_merge_raises_on_non_list(self): + with pytest.raises(ValueError): + Collection([1]).merge("not a list") + + +class TestCollectionUnique: + def test_unique_primitives(self): + result = Collection([1, 2, 2, 3, 3]).unique() + assert len(result.all()) == 3 + + def test_unique_by_key(self): + c = Collection([{"id": 1, "x": "a"}, {"id": 1, "x": "b"}, {"id": 2, "x": "c"}]) + result = c.unique("id") + assert result.count() == 2 + + +class TestCollectionWhere: + def test_where_equals(self): + c = Collection([{"age": 10}, {"age": 20}, {"age": 10}]) + result = c.where("age", 10) + assert result.count() == 2 + + def test_where_greater_than(self): + c = Collection([{"n": 1}, {"n": 5}, {"n": 10}]) + result = c.where("n", ">", 4) + assert result.count() == 2 + + +class TestCollectionContains: + def test_contains_primitive(self): + c = Collection([1, 2, 3]) + assert c.contains(2) is True + assert c.contains(99) is False + + def test_contains_with_callback(self): + c = Collection([1, 2, 3]) + assert c.contains(lambda x: x > 2) is True + assert c.contains(lambda x: x > 10) is False + + +class TestCollect: + def test_collect_returns_collection(self): + result = collect([1, 2, 3]) + assert isinstance(result, Collection) + assert result.all() == [1, 2, 3] + + +class TestFlatten: + def test_flatten_nested_lists(self): + result = flatten([[1, 2], [3, [4, 5]]]) + assert result == [1, 2, 3, 4, 5] + + def test_flatten_already_flat(self): + result = flatten([1, 2, 3]) + assert result == [1, 2, 3] + + def test_flatten_empty(self): + assert flatten([]) == [] + + def test_flatten_deeply_nested(self): + result = flatten([[[1]], [2, [3]]]) + assert result == [1, 2, 3] + + +class TestCollectionHTTPUtils: + def test_http_status_200_ok(self): + from fastapi_startkit.utils.http import HTTP_STATUS_CODES + + assert HTTP_STATUS_CODES[200] == "200 OK" + + def test_http_status_404_not_found(self): + from fastapi_startkit.utils.http import HTTP_STATUS_CODES + + assert "404" in HTTP_STATUS_CODES[404] + + def test_http_status_500_internal_server_error(self): + from fastapi_startkit.utils.http import HTTP_STATUS_CODES + + assert "500" in HTTP_STATUS_CODES[500] + + def test_generate_wsgi_defaults(self): + from fastapi_startkit.utils.http import generate_wsgi + + env = generate_wsgi() + assert env["REQUEST_METHOD"] == "GET" + assert env["PATH_INFO"] == "/" + assert env["SERVER_PORT"] == "8000" + + def test_generate_wsgi_custom_path_and_method(self): + from fastapi_startkit.utils.http import generate_wsgi + + env = generate_wsgi(path="/users", method="POST") + assert env["PATH_INFO"] == "/users" + assert env["REQUEST_METHOD"] == "POST" + + def test_generate_wsgi_custom_query_string(self): + from fastapi_startkit.utils.http import generate_wsgi + + env = generate_wsgi(query_string="page=1&limit=10") + assert env["QUERY_STRING"] == "page=1&limit=10" diff --git a/fastapi_startkit/tests/utils/test_filesystem_utils.py b/fastapi_startkit/tests/utils/test_filesystem_utils.py new file mode 100644 index 00000000..9a075ffb --- /dev/null +++ b/fastapi_startkit/tests/utils/test_filesystem_utils.py @@ -0,0 +1,100 @@ +"""Tests for filesystem utility functions (task #15).""" + +import os + + +from fastapi_startkit.utils.filesystem import ( + file_exists, + get_extension, + make_directory, + make_full_directory, + modified_date, +) + + +class TestMakeDirectory: + def test_creates_parent_directories(self, tmp_path): + target = str(tmp_path / "sub" / "dir" / "file.txt") + make_directory(target) + assert os.path.exists(str(tmp_path / "sub" / "dir")) + + def test_returns_true_when_created(self, tmp_path): + target = str(tmp_path / "newdir" / "file.txt") + result = make_directory(target) + assert result is True + + def test_returns_false_for_existing_file(self, tmp_path): + f = tmp_path / "existing.txt" + f.write_text("data") + result = make_directory(str(f)) + assert result is False + + +class TestMakeFullDirectory: + def test_creates_full_directory_tree(self, tmp_path): + target = str(tmp_path / "a" / "b" / "c") + make_full_directory(target) + assert os.path.exists(target) + + def test_returns_true_when_created(self, tmp_path): + target = str(tmp_path / "fresh_dir") + result = make_full_directory(target) + assert result is True + + def test_existing_directory_returns_true(self, tmp_path): + result = make_full_directory(str(tmp_path)) + assert result is True + + def test_returns_false_for_existing_file(self, tmp_path): + f = tmp_path / "file.txt" + f.write_text("x") + result = make_full_directory(str(f)) + assert result is False + + +class TestFileExists: + def test_returns_true_for_existing_directory(self, tmp_path): + f = tmp_path / "sub" / "file.txt" + f.parent.mkdir(parents=True) + assert file_exists(str(f)) is True + + def test_returns_false_for_nonexistent_directory(self, tmp_path): + f = tmp_path / "no_such_dir" / "file.txt" + assert file_exists(str(f)) is False + + +class TestModifiedDate: + def test_returns_numeric_timestamp(self, tmp_path): + f = tmp_path / "test.txt" + f.write_text("hello") + ts = modified_date(str(f)) + assert isinstance(ts, float) + assert ts > 0 + + +class TestGetExtension: + def test_simple_extension(self): + assert get_extension("file.txt") == ".txt" + + def test_no_extension(self): + assert get_extension("noextension") == "" + + def test_hidden_file_no_content_extension(self): + # .hidden files (no secondary extension) return "" + result = get_extension(".hidden") + assert result == "" + + def test_double_extension_tar_gz(self): + result = get_extension("archive.tar.gz") + assert result == ".tar.gz" + + def test_image_extensions(self): + assert get_extension("photo.jpg") == ".jpg" + assert get_extension("graphic.png") == ".png" + + def test_without_dot(self): + result = get_extension("file.txt", without_dot=True) + assert result == "txt" + + def test_path_with_directory(self): + assert get_extension("/some/path/to/file.py") == ".py" diff --git a/fastapi_startkit/tests/utils/test_str_utils.py b/fastapi_startkit/tests/utils/test_str_utils.py new file mode 100644 index 00000000..6e6c1742 --- /dev/null +++ b/fastapi_startkit/tests/utils/test_str_utils.py @@ -0,0 +1,155 @@ +"""Tests for string utility functions (task #15).""" + +from fastapi_startkit.utils.str import ( + add_query_params, + as_filepath, + get_controller_name, + match, + modularize, + random_string, + removeprefix, + removesuffix, +) + + +class TestRandomString: + def test_default_length_is_4(self): + s = random_string() + assert len(s) == 4 + + def test_custom_length(self): + assert len(random_string(10)) == 10 + + def test_returns_uppercase_alphanumeric(self): + s = random_string(100) + assert s.isalnum() + assert s == s.upper() + + def test_zero_length(self): + assert random_string(0) == "" + + +class TestModularize: + def test_forward_slash_replaced_by_dot(self): + assert modularize("app/controllers/user") == "app.controllers.user" + + def test_removes_py_suffix(self): + assert modularize("app/models/user.py") == "app.models.user" + + def test_backslash_replaced_by_dot(self): + assert modularize("app\\controllers\\user") == "app.controllers.user" + + def test_no_extension_unchanged(self): + assert modularize("app/views/home", suffix=".py") == "app.views.home" + + +class TestAsFilepath: + def test_dots_replaced_by_slashes(self): + assert as_filepath("app.controllers.user") == "app/controllers/user" + + def test_no_dots_unchanged(self): + assert as_filepath("app") == "app" + + +class TestRemoveprefix: + def test_removes_matching_prefix(self): + assert removeprefix("hello world", "hello ") == "world" + + def test_no_match_returns_original(self): + assert removeprefix("hello world", "bye") == "hello world" + + def test_empty_prefix(self): + assert removeprefix("hello", "") == "hello" + + def test_prefix_equals_string(self): + assert removeprefix("abc", "abc") == "" + + +class TestRemovesuffix: + def test_removes_matching_suffix(self): + assert removesuffix("hello.py", ".py") == "hello" + + def test_no_match_returns_original(self): + assert removesuffix("hello.py", ".txt") == "hello.py" + + def test_empty_suffix(self): + assert removesuffix("hello", "") == "hello" + + def test_suffix_equals_string(self): + assert removesuffix("abc", "abc") == "" + + +class TestMatch: + def test_exact_match(self): + assert match("hello", "hello") is True + + def test_exact_no_match(self): + assert match("hello", "world") is False + + def test_wildcard_suffix(self): + assert match("hello_world", "hello*") is True + assert match("goodbye_world", "hello*") is False + + def test_wildcard_prefix(self): + assert match("hello_world", "*world") is True + assert match("hello_earth", "*world") is False + + def test_wildcard_middle(self): + assert match("hello_middle_world", "hello*world") is True + assert match("hello_middle_earth", "hello*world") is False + + +class TestAddQueryParams: + def test_adds_params_to_path(self): + result = add_query_params("/search", {"q": "python"}) + assert "q=python" in result + + def test_merges_with_existing_params(self): + result = add_query_params("/search?q=python", {"page": "2"}) + assert "q=python" in result + assert "page=2" in result + + def test_preserves_fragment(self): + result = add_query_params("/page#section", {"ref": "home"}) + assert "#section" in result + assert "ref=home" in result + + def test_full_url_with_domain(self): + result = add_query_params("http://example.com/path", {"key": "val"}) + assert "key=val" in result + assert "example.com" in result + + def test_empty_params_dict_unchanged_path(self): + result = add_query_params("/no-params", {}) + assert result == "/no-params" + + +class TestGetControllerName: + def test_string_passthrough(self): + assert get_controller_name("UserController@index") == "UserController@index" + + def test_class_with_method(self): + class MyController: + def show(self): + pass + + result = get_controller_name(MyController.show) + assert "MyController" in result + assert "show" in result + + def test_class_without_method_uses_call(self): + # Top-level class (no dots in __qualname__) gets @__call__ appended + + # Create a class with a simple qualname (no dots) to avoid test scope nesting + TopLevel = type("TopLevel", (), {}) + TopLevel.__qualname__ = "TopLevel" # force simple qualname + result = get_controller_name(TopLevel) + assert "TopLevel" in result + assert "__call__" in result + + def test_instance_uses_class_qualname(self): + class MyController: + pass + + result = get_controller_name(MyController()) + assert "MyController" in result diff --git a/fastapi_startkit/tests/utils/test_time_utils.py b/fastapi_startkit/tests/utils/test_time_utils.py new file mode 100644 index 00000000..c58f7501 --- /dev/null +++ b/fastapi_startkit/tests/utils/test_time_utils.py @@ -0,0 +1,96 @@ +"""Tests for time utility functions (task #15).""" + +import pendulum + +from fastapi_startkit.utils.time import cookie_expire_time, migration_timestamp, parse_human_time + + +class TestParseHumanTime: + def test_now_returns_current_time(self): + result = parse_human_time("now") + assert result is not None + diff = abs(pendulum.now("GMT").diff(result).in_seconds()) + assert diff < 5 + + def test_expired_returns_past_date(self): + result = parse_human_time("expired") + assert result < pendulum.now("GMT") + + def test_1_second(self): + before = pendulum.now("GMT") + result = parse_human_time("1 second") + assert result > before + + def test_5_minutes(self): + result = parse_human_time("5 minutes") + diff = result.diff(pendulum.now("GMT")).in_minutes() + assert abs(diff - 5) <= 1 + + def test_2_hours(self): + result = parse_human_time("2 hours") + diff = result.diff(pendulum.now("GMT")).in_hours() + assert abs(diff - 2) <= 1 + + def test_1_day(self): + result = parse_human_time("1 day") + diff = result.diff(pendulum.now("GMT")).in_days() + assert abs(diff - 1) <= 1 + + def test_1_week(self): + result = parse_human_time("1 week") + diff = result.diff(pendulum.now("GMT")).in_weeks() + assert abs(diff - 1) <= 1 + + def test_1_month(self): + result = parse_human_time("1 month") + diff = result.diff(pendulum.now("GMT")).in_months() + assert abs(diff - 1) <= 1 + + def test_1_year(self): + result = parse_human_time("1 year") + diff = result.diff(pendulum.now("GMT")).in_years() + assert abs(diff - 1) <= 1 + + def test_plural_seconds(self): + result = parse_human_time("30 seconds") + assert result is not None + + def test_plural_days(self): + result = parse_human_time("3 days") + diff = result.diff(pendulum.now("GMT")).in_days() + assert abs(diff - 3) <= 1 + + +class TestCookieExpireTime: + def test_returns_cookie_formatted_string(self): + result = cookie_expire_time("1 day") + # Cookie format: "Thu, 21 Oct 2021 07:28:00" + assert isinstance(result, str) + assert len(result) > 0 + # Should contain a comma (day-of-week, date) + assert "," in result + + def test_now_returns_string(self): + result = cookie_expire_time("now") + assert isinstance(result, str) + + def test_expired_returns_past_string(self): + result = cookie_expire_time("expired") + assert isinstance(result, str) + + +class TestMigrationTimestamp: + def test_returns_string(self): + ts = migration_timestamp() + assert isinstance(ts, str) + + def test_format_matches_pattern(self): + import re + + ts = migration_timestamp() + assert re.match(r"\d{4}_\d{2}_\d{2}_\d{6}", ts), f"Unexpected format: {ts}" + + def test_contains_current_year(self): + ts = migration_timestamp() + year = str(pendulum.now().year) + assert ts.startswith(year) From c10b123e36f3a3a50153ccec5ec0bc2309f664ad Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Fri, 29 May 2026 16:37:44 -0700 Subject: [PATCH 02/10] feat: refactored and improving tests --- .../fastapi_startkit/exceptions.backup/DD.py | 38 -- .../exceptions.backup/ExceptionHandler.py | 70 --- .../exceptions.backup/__init__.py | 38 -- .../exceptionite/__init__.py | 0 .../exceptions.backup/exceptionite/blocks.py | 101 ---- .../exceptionite/controllers.py | 13 - .../exceptionite/solutions.py | 66 --- .../exceptions.backup/exceptionite/tabs.py | 19 - .../handlers/DumpExceptionHandler.py | 102 ---- .../handlers/HttpExceptionHandler.py | 26 - .../handlers/ModelNotFoundHandler.py | 11 - .../src/fastapi_startkit/facades/Auth.py | 5 - .../src/fastapi_startkit/facades/Auth.pyi | 32 -- .../src/fastapi_startkit/facades/Inertia.py | 5 - .../src/fastapi_startkit/facades/__init__.py | 1 - .../logging/channels/DailyChannel.py | 7 +- .../logging/channels/SingleChannel.py | 2 +- .../logging/channels/SyslogChannel.py | 2 +- .../logging/drivers/LogTerminalDriver.py | 16 +- .../src/fastapi_startkit/logging/file.py | 15 + .../{utils => storage}/data/mime.types | 0 .../src/fastapi_startkit/storage/helper.py | 33 ++ .../{collection => support}/__init__.py | 2 + .../{collection => support}/collection.py | 0 .../{helpers => support}/string.py | 0 .../src/fastapi_startkit/utils/__init__.py | 0 .../src/fastapi_startkit/utils/collections.py | 543 ------------------ .../src/fastapi_startkit/utils/console.py | 39 -- .../src/fastapi_startkit/utils/filesystem.py | 100 ---- .../src/fastapi_startkit/utils/http.py | 100 ---- .../src/fastapi_startkit/utils/location.py | 91 --- .../src/fastapi_startkit/utils/str.py | 116 ---- .../src/fastapi_startkit/utils/time.py | 59 -- .../tests/utils/test_collections.py | 40 +- .../tests/utils/test_filesystem_utils.py | 100 ---- .../tests/utils/test_str_utils.py | 155 ----- .../tests/utils/test_time_utils.py | 96 ---- 37 files changed, 70 insertions(+), 1973 deletions(-) delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/DD.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/ExceptionHandler.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/__init__.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/__init__.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/blocks.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/controllers.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/solutions.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/tabs.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/handlers/DumpExceptionHandler.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/handlers/HttpExceptionHandler.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/exceptions.backup/handlers/ModelNotFoundHandler.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/facades/Auth.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/facades/Auth.pyi delete mode 100644 fastapi_startkit/src/fastapi_startkit/facades/Inertia.py create mode 100644 fastapi_startkit/src/fastapi_startkit/logging/file.py rename fastapi_startkit/src/fastapi_startkit/{utils => storage}/data/mime.types (100%) create mode 100644 fastapi_startkit/src/fastapi_startkit/storage/helper.py rename fastapi_startkit/src/fastapi_startkit/{collection => support}/__init__.py (57%) rename fastapi_startkit/src/fastapi_startkit/{collection => support}/collection.py (100%) rename fastapi_startkit/src/fastapi_startkit/{helpers => support}/string.py (100%) delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/__init__.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/collections.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/console.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/filesystem.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/http.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/location.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/str.py delete mode 100644 fastapi_startkit/src/fastapi_startkit/utils/time.py delete mode 100644 fastapi_startkit/tests/utils/test_filesystem_utils.py delete mode 100644 fastapi_startkit/tests/utils/test_str_utils.py delete mode 100644 fastapi_startkit/tests/utils/test_time_utils.py diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/DD.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/DD.py deleted file mode 100644 index 972c8cc9..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/DD.py +++ /dev/null @@ -1,38 +0,0 @@ -import inspect -import warnings - -from .exceptions import DumpException - - -warnings.warn( - "DD class will be removed in Masonite 5. Please use Dump facade instead.", - DeprecationWarning, -) - - -class DD: - def __init__(self, container): - self.app = container - - def die_and_dump(self, *args): - """Dump all provided args and die, ie raise a DumpException.""" - self.dump(*args) - raise DumpException - - def dump(self, *args): - """Dump all provided args and let flow continue. This does not raise a DumpException.""" - print( - inspect.stack()[1].function, - inspect.stack()[1].filename, - inspect.stack()[1].lineno, - ) - if self.app.has("ObjDumpList"): - dump_list = self.app.make("ObjDumpList") - else: - dump_list = [] - start = len(dump_list) - for i, obj in enumerate(args): - dump_name = f"ObjDump{start + i}" - self.app.bind(dump_name, obj) - dump_list.append(dump_name) - self.app.bind("ObjDumpList", dump_list) diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/ExceptionHandler.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/ExceptionHandler.py deleted file mode 100644 index 43aefb4a..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/ExceptionHandler.py +++ /dev/null @@ -1,70 +0,0 @@ -class ExceptionHandler: - def __init__(self, application, driver_config=None): - self.application = application - self.drivers = {} - self.driver_config = driver_config or {} - self.options = {} - - def set_options(self, options): - self.options = options - return self - - def add_driver(self, name, driver): - self.drivers.update({name: driver}) - - def set_configuration(self, config): - self.driver_config = config - return self - - def get_driver(self, name=None): - if name is None: - return self.drivers[self.driver_config.get("default")] - return self.drivers[name] - - def get_config_options(self, driver=None): - if driver is None: - return self.driver_config[self.driver_config.get("default")] - - return self.driver_config.get(driver, {}) - - def handle(self, exception): - response = self.application.make("response") - request = self.application.make("request") - - self.application.make("event").fire(f"masonite.exception.{exception.__class__.__name__}", exception) - - # add headers to response if any - if hasattr(exception, "get_headers"): - headers = exception.get_headers() - response.with_headers(headers) - - # if an exception handler is registered for this exception, use it instead - # add headers to response if any - if hasattr(exception, "get_headers"): - headers = exception.get_headers() - response.with_headers(headers) - - if self.application.has(f"{exception.__class__.__name__}Handler"): - return self.application.make(f"{exception.__class__.__name__}Handler").handle(exception) - - # handle exception in production - if not self.application.is_debug(): - # for HTTP error codes (500, 404, 403...) a specific page should be displayed - # if a renderable exception is raised let it be displayed - if hasattr(exception, "is_http_exception") or hasattr(exception, "get_response"): - return self.application.make("HttpExceptionHandler").handle(exception) - - # else fallback to an unknown exception that should be displayed as a 500 error - exception.get_status = lambda: 500 - exception.get_response = lambda: str(exception) or "Unknown error" - return self.application.make("HttpExceptionHandler").handle(exception) - - # handle exception in development mode with Exceptionite - exceptionite = self.get_driver("exceptionite") - exceptionite.start(exception) - exceptionite.render("terminal") - - if request.accepts_json(): - return response.view(exceptionite.render("json"), status=500) - else: - return response.view(exceptionite.render("web"), status=500) diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/__init__.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/__init__.py deleted file mode 100644 index 590a19b7..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -from .ExceptionHandler import ExceptionHandler -from .handlers.DumpExceptionHandler import DumpExceptionHandler -from .handlers.HttpExceptionHandler import HttpExceptionHandler -from .handlers.ModelNotFoundHandler import ModelNotFoundHandler -from .DD import DD -from .exceptions import ( - AuthorizationException, - InvalidRouteCompileException, - RouteMiddlewareNotFound, - ContainerError, - MissingContainerBindingNotFound, - StrictContainerException, - ResponseError, - InvalidHTTPStatusCode, - RequiredContainerBindingNotFound, - ViewException, - RouteNotFoundException, - DumpException, - InvalidSecretKey, - InvalidCSRFToken, - NotificationException, - InvalidToken, - ProjectLimitReached, - ProjectProviderTimeout, - ProjectProviderHttpError, - ProjectTargetNotEmpty, - MixFileNotFound, - MixManifestNotFound, - InvalidConfigurationLocation, - InvalidConfigurationSetup, - InvalidPackageName, - LoaderNotFound, - QueueException, - AmbiguousError, - MethodNotAllowedException, - ModelNotFoundException, - ThrottleRequestsException, -) diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/__init__.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/blocks.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/blocks.py deleted file mode 100644 index 0236b09d..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/blocks.py +++ /dev/null @@ -1,101 +0,0 @@ -from exceptionite import Block - -from ... import __version__ -from ...helpers import optional -from ...utils.str import get_controller_name - - -def recursive_serializer(data): - if isinstance(data, (int, bool, str, bytes)): - return data - elif isinstance(data, (list, tuple)): - return [recursive_serializer(item) for item in data] - elif isinstance(data, dict): - return {key: recursive_serializer(val) for key, val in data.items()} - elif callable(data): - return str(data) - elif hasattr(data, "serialize"): - return data.serialize() - else: - return str(data) - - -class AppBlock(Block): - id = "application" - name = "Application" - icon = "DesktopComputerIcon" - has_sections = True - - def build(self): - request = self.handler.app.make("request") - route = request.get_route() - - data = { - "Info": { - "Masonite Version": __version__, - "Environment": self.handler.app.environment(), - "Debug": self.handler.app.is_debug(), - } - } - - # add app route data - if route: - data.update( - { - "Route": { - "Controller": get_controller_name(route.controller), - "Name": route.get_name(), - "Middlewares": route.get_middlewares(), - } - } - ) - - # add user route data - user = request.user() - if user: - data.update( - { - "User": { - "E-mail": optional(user).email, - "ID": optional(user).id, - } - } - ) - - return data - - -class RequestBlock(Block): - id = "request" - name = "Request" - icon = "SwitchHorizontalIcon" - has_sections = True - - def build(self): - request = self.handler.app.make("request") - # serialize inputs (e.g. in case of file) - inputs = {} - for name, value in request.all().items(): - inputs[name] = recursive_serializer(value) - return { - "Parameters": { - "Path": request.get_path(), - "Input": inputs or None, - "Request Method": request.get_request_method(), - }, - "Headers": request.header_bag.to_dict(), - } - - -class ConfigBlock(Block): - id = "config" - name = "Configuration" - icon = "CogIcon" - has_sections = True - - def build(self): - data = {} - for section, config_data in self.handler.app.make("config").all().items(): - section_name = section.title() - data[section_name] = recursive_serializer(config_data) - return data diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/controllers.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/controllers.py deleted file mode 100644 index 35fd676c..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/controllers.py +++ /dev/null @@ -1,13 +0,0 @@ -from ...request import Request -from ...controllers import Controller -from ...response import Response - - -class ExceptioniteController(Controller): - def run_action(self, request: Request, response: Response): - handler = request.app.make("exception_handler").get_driver("exceptionite") - data = handler.run_action(request.input("action_id"), request.input("options")) - try: - return response.json({"message": "ok", "data": data}, 200) - except: # noqa: E722 - return response.json({"message": "An error happened", "data": data}, 400) diff --git a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/solutions.py b/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/solutions.py deleted file mode 100644 index b5b8bf6d..00000000 --- a/fastapi_startkit/src/fastapi_startkit/exceptions.backup/exceptionite/solutions.py +++ /dev/null @@ -1,66 +0,0 @@ -class TableNotFound: - def title(self): - return "Table Not Found" - - def description(self): - return "You are trying to make a query on a table that cannot be found. Check that :table migration exists and that migrations have been ran with 'python craft migrate' command." - - def regex(self): - return r"no such table: (?P(\w+))" - - -class MissingCSRFToken: - def title(self): - return "Missing CSRF Token" - - def description(self): - return "You are trying to make a sensitive request without providing a CSRF token. Your request might be vulnerable to Cross Site Request Forgery. To resolve this issue you should use {{ csrf_field }} in HTML forms or add X-CSRF-TOKEN header in AJAX requests." - - def regex(self): - return r"Missing CSRF Token" - - -class InvalidCSRFToken: - def title(self): - return "The session does not match the CSRF token" - - def description(self): - return "Try clearing your cookies for the localhost domain in your browsers developer tools." - - def regex(self): - return r"Invalid CSRF Token" - - -class TemplateNotFound: - def title(self): - return "Template Not Found" - - def description(self): - return """':template.html' view file has not been found in registered view locations. Please verify the spelling of the template and that it exists in locations declared in Kernel file. You can check - available view locations with app.make('view.locations').""" - - def regex(self): - return r"Template '(?P