From c573946bbe5e475dd5b7379beaa62572f31d1409 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 2 May 2026 11:33:07 -0700 Subject: [PATCH 1/4] fix: remove debug import and fix SQLitePlatform alter sql mutation - Remove stale `from dumpdie import dd` debug import from MorphTo.py - Fix compile_alter_sql mutating diff.from_table.added_columns in place via .pop() and .update(), causing KeyError and wrong column lists when to_sql() is called more than once (e.g. __aexit__ + test assertion). Work on dict copies so each call produces correct idempotent output. Co-Authored-By: Claude Sonnet 4.6 --- .../masoniteorm/relationships/MorphTo.py | 1 - .../masoniteorm/schema/platforms/SQLitePlatform.py | 14 +++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/relationships/MorphTo.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/relationships/MorphTo.py index dda53cfa..bdc7270a 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/relationships/MorphTo.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/relationships/MorphTo.py @@ -1,4 +1,3 @@ -from dumpdie import dd from fastapi_startkit.masoniteorm.models import registry from .BaseRelationship import BaseRelationship from ..collection import Collection diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/platforms/SQLitePlatform.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/platforms/SQLitePlatform.py index 190c0c03..5dcfcf21 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/platforms/SQLitePlatform.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/platforms/SQLitePlatform.py @@ -211,23 +211,21 @@ def compile_alter_sql(self, diff): or diff.changed_columns or diff.added_foreign_keys ): - original_columns = diff.from_table.added_columns + original_columns = dict(diff.from_table.added_columns) # pop off the dropped columns. No need for them here for column in diff.dropped_columns: - original_columns.pop(column) + original_columns.pop(column, None) sql.append( "CREATE TEMPORARY TABLE __temp__{table} AS SELECT {original_column_names} FROM {table}".format( table=diff.name, - original_column_names=", ".join( - diff.from_table.added_columns.keys() - ), + original_column_names=", ".join(original_columns.keys()), ) ) sql.append("DROP TABLE {table}".format(table=self.wrap_table(diff.name))) - columns = diff.from_table.added_columns + columns = dict(original_columns) columns.update(diff.renamed_columns) columns.update(diff.changed_columns) @@ -264,9 +262,7 @@ def compile_alter_sql(self, diff): quoted_table=self.wrap_table(diff.name), table=diff.name, new_columns=", ".join(self.columnize_names(columns)), - original_column_names=", ".join( - diff.from_table.added_columns.keys() - ), + original_column_names=", ".join(columns.keys()), ) ) sql.append("DROP TABLE __temp__{table}".format(table=diff.name)) From 5a1daa9b36d0beae48fbe9d2d32a7605e657277e Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 2 May 2026 14:03:18 -0700 Subject: [PATCH 2/4] feat: test fix --- example/config-app/uv.lock | 1 + example/database-app/uv.lock | 1 + .../src/fastapi_startkit/application.py | 7 +++++++ .../fastapi_startkit/environment/environment.py | 16 ++++++++++++---- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/example/config-app/uv.lock b/example/config-app/uv.lock index b4cec0e5..4404d604 100644 --- a/example/config-app/uv.lock +++ b/example/config-app/uv.lock @@ -198,6 +198,7 @@ dev = [ { name = "dumpdie", specifier = ">=1.5.0" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.9.0" }, { name = "twine", specifier = ">=6.2.0" }, ] diff --git a/example/database-app/uv.lock b/example/database-app/uv.lock index e2071d70..e4bb9324 100644 --- a/example/database-app/uv.lock +++ b/example/database-app/uv.lock @@ -546,6 +546,7 @@ dev = [ { name = "dumpdie", specifier = ">=1.5.0" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.9.0" }, { name = "twine", specifier = ">=6.2.0" }, ] diff --git a/fastapi_startkit/src/fastapi_startkit/application.py b/fastapi_startkit/src/fastapi_startkit/application.py index 3249fbd7..3701052a 100644 --- a/fastapi_startkit/src/fastapi_startkit/application.py +++ b/fastapi_startkit/src/fastapi_startkit/application.py @@ -67,6 +67,12 @@ def set_environment(self, env: str): self.env = env return self + def load_environment(self): + """Reload environment variables for the current self.env.""" + Environment.load_base(base_path=self.base_path) + Environment.load(self.env, base_path=self.base_path) + return self + def configure_exception_handler(self): self.exception_manager: ExceptionHandler = self._exception_handler_class( application=self @@ -176,6 +182,7 @@ def __call__(self, *args, **kwargs): def resolve_environment(self): self.env = Environment.resolve_environment(base_path=self.base_path, env=self.env) + Environment.load_base(base_path=self.base_path) Environment.load(self.env, base_path=self.base_path) def is_debug(self) -> bool: diff --git a/fastapi_startkit/src/fastapi_startkit/environment/environment.py b/fastapi_startkit/src/fastapi_startkit/environment/environment.py index af993008..3cbd5721 100644 --- a/fastapi_startkit/src/fastapi_startkit/environment/environment.py +++ b/fastapi_startkit/src/fastapi_startkit/environment/environment.py @@ -13,17 +13,18 @@ def resolve_environment(base_path=None, env: str | None = None): if "PYTEST_CURRENT_TEST" in os.environ: return "testing" - if os.environ.get("APP_ENV"): - return os.environ["APP_ENV"] - + # Explicit env parameter takes priority over os.environ APP_ENV if env: return env + if os.environ.get("APP_ENV"): + return os.environ["APP_ENV"] + path = base_path / ".env" if not path.exists(): raise ValueError("Unable to determine environment.") - load_dotenv(path) + load_dotenv(path, override=True) env = os.environ.get("APP_ENV") if not env: @@ -31,6 +32,13 @@ def resolve_environment(base_path=None, env: str | None = None): return env + @staticmethod + def load_base(base_path=None): + """Load the base .env file, resetting vars to their default values.""" + path = base_path / ".env" + if path.exists(): + load_dotenv(path, override=True) + @staticmethod def load(env: str, override=True, only=None, base_path=None): path = base_path / f".env.{env}" From 3c3af293d7ee90e553459d6b7b294eb72ad7ca41 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 2 May 2026 14:07:13 -0700 Subject: [PATCH 3/4] fix: track example/config-app/.env so CI can resolve APP_ENV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root .gitignore had **/.env which prevented the example app .env from being committed. CI clones a fresh repo with no .env files, so resolve_environment raised "Unable to determine environment." for all config-app tests. Added !example/**/.env negation and force-tracked the config-app .env (safe to commit — it contains only demo values). Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + example/config-app/.env | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 example/config-app/.env diff --git a/.gitignore b/.gitignore index e0930bcd..68289914 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ **/*.sqlite3 **/*.log **/.env +!example/**/.env **/*.sqlite **/*.vite fastapi_startkit/dist diff --git a/example/config-app/.env b/example/config-app/.env new file mode 100644 index 00000000..fe4bbb09 --- /dev/null +++ b/example/config-app/.env @@ -0,0 +1,5 @@ +APP_ENV=development + +REDIS_HOST=host.default +REDIS_PORT=0000 +REDIS_DB=0 From af0119d395d300b5205c166a67f67cf3f9563df9 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 2 May 2026 14:29:14 -0700 Subject: [PATCH 4/4] fix: copy .env.example to .env in CI for config-app tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of tracking .env directly, add .env.example with safe defaults and copy it in the CI workflow before running tests. This follows the standard convention — .env stays gitignored, .env.example is the committed template. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/test.yml | 4 ++++ .gitignore | 1 - example/config-app/{.env => .env.example} | 0 3 files changed, 4 insertions(+), 1 deletion(-) rename example/config-app/{.env => .env.example} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21d3b308..4dbd792e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,10 @@ jobs: working-directory: example/config-app run: uv sync + - name: Copy .env.example to .env (config-app) + working-directory: example/config-app + run: cp .env.example .env + - name: Run tests (config-app) working-directory: example/config-app run: uv run pytest tests/ -v diff --git a/.gitignore b/.gitignore index 68289914..e0930bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ **/*.sqlite3 **/*.log **/.env -!example/**/.env **/*.sqlite **/*.vite fastapi_startkit/dist diff --git a/example/config-app/.env b/example/config-app/.env.example similarity index 100% rename from example/config-app/.env rename to example/config-app/.env.example