csv-to-db-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. csv_to_db_py-0.1.0/.github/workflows/publish.yaml +19 -0
  2. csv_to_db_py-0.1.0/.github/workflows/workflow.yaml +43 -0
  3. csv_to_db_py-0.1.0/.gitignore +25 -0
  4. csv_to_db_py-0.1.0/.superpowers/sdd/.gitignore +1 -0
  5. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/final-review-fix-report.md +91 -0
  6. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/plan-path +1 -0
  7. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/progress.md +70 -0
  8. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-02798e6..bd3e9d5.diff +671 -0
  9. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-11102ec..f08c415.diff +501 -0
  10. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-27c87bb..11102ec.diff +144 -0
  11. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-49aa2f3..ff80dc8.diff +185 -0
  12. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-55479cd..27c87bb.diff +156 -0
  13. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-7ffe98c..f7751b0.diff +149 -0
  14. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-7ffe98c..fc851e2.diff +2058 -0
  15. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-8593195..49aa2f3.diff +90 -0
  16. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-ab9ee61..55479cd.diff +208 -0
  17. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-bd3e9d5..ab9ee61.diff +150 -0
  18. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-dbb469e..fc851e2.diff +136 -0
  19. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-f08c415..8593195.diff +27 -0
  20. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-f7751b0..02798e6.diff +129 -0
  21. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-fc851e2..9ee3e7a.diff +433 -0
  22. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/review-ff80dc8..dbb469e.diff +14 -0
  23. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-1-brief.md +12 -0
  24. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-1-report.md +53 -0
  25. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-10-brief.md +12 -0
  26. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-10-report.md +46 -0
  27. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-11-brief.md +15 -0
  28. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-11-report.md +35 -0
  29. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-12-brief.md +8 -0
  30. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-12-report.md +26 -0
  31. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-13-brief.md +7 -0
  32. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-13-report.md +25 -0
  33. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-2-brief.md +16 -0
  34. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-2-report.md +60 -0
  35. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-3-brief.md +15 -0
  36. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-3-report.md +77 -0
  37. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-4-brief.md +15 -0
  38. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-4-report.md +61 -0
  39. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-5-brief.md +16 -0
  40. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-5-report.md +33 -0
  41. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-6-brief.md +17 -0
  42. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-6-report.md +68 -0
  43. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-7-brief.md +15 -0
  44. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-7-report.md +55 -0
  45. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-8-brief.md +16 -0
  46. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-8-report.md +123 -0
  47. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-9-brief.md +8 -0
  48. csv_to_db_py-0.1.0/.superpowers/sdd/2026-10-08-csv-to-db-hardening/task-9-report.md +16 -0
  49. csv_to_db_py-0.1.0/.tool-versions +1 -0
  50. csv_to_db_py-0.1.0/.vscode/launch.json +24 -0
  51. csv_to_db_py-0.1.0/LICENSE.md +22 -0
  52. csv_to_db_py-0.1.0/PKG-INFO +276 -0
  53. csv_to_db_py-0.1.0/README.md +254 -0
  54. csv_to_db_py-0.1.0/compose.yml +11 -0
  55. csv_to_db_py-0.1.0/csv_to_db_py/__init__.py +4 -0
  56. csv_to_db_py-0.1.0/csv_to_db_py/config.py +44 -0
  57. csv_to_db_py-0.1.0/csv_to_db_py/core.py +90 -0
  58. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/__init__.py +5 -0
  59. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/base.py +14 -0
  60. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/mssql.py +17 -0
  61. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/mysql.py +16 -0
  62. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/oracle.py +17 -0
  63. csv_to_db_py-0.1.0/csv_to_db_py/db_connectors/postgres.py +17 -0
  64. csv_to_db_py-0.1.0/csv_to_db_py/logger.py +19 -0
  65. csv_to_db_py-0.1.0/csv_to_db_py/main.py +32 -0
  66. csv_to_db_py-0.1.0/csv_to_db_py/utils/__init__.py +7 -0
  67. csv_to_db_py-0.1.0/csv_to_db_py/utils/cleaned_csv.py +105 -0
  68. csv_to_db_py-0.1.0/csv_to_db_py/utils/csv_loader.py +265 -0
  69. csv_to_db_py-0.1.0/csv_to_db_py/utils/table_creator.py +19 -0
  70. csv_to_db_py-0.1.0/docs/superpowers/plans/2026-10-08-csv-to-db-hardening.md +206 -0
  71. csv_to_db_py-0.1.0/docs/superpowers/plans/2026-10-08-split-lib-cli.md +300 -0
  72. csv_to_db_py-0.1.0/input/test.csv +2 -0
  73. csv_to_db_py-0.1.0/mainConfig.json.example-flexible +15 -0
  74. csv_to_db_py-0.1.0/mainConfig.json.example-strict +15 -0
  75. csv_to_db_py-0.1.0/mainConfig.json.template +15 -0
  76. csv_to_db_py-0.1.0/pyproject.toml +50 -0
  77. csv_to_db_py-0.1.0/tests/date_formats_test.csv +11 -0
  78. csv_to_db_py-0.1.0/tests/test_batch_insertion.py +34 -0
  79. csv_to_db_py-0.1.0/tests/test_cli.py +21 -0
  80. csv_to_db_py-0.1.0/tests/test_config.py +53 -0
  81. csv_to_db_py-0.1.0/tests/test_connectors.py +36 -0
  82. csv_to_db_py-0.1.0/tests/test_core.py +52 -0
  83. csv_to_db_py-0.1.0/tests/test_datetime_convert.py +32 -0
  84. csv_to_db_py-0.1.0/tests/test_datetime_detection.py +48 -0
  85. csv_to_db_py-0.1.0/tests/test_delimiter.py +29 -0
  86. csv_to_db_py-0.1.0/tests/test_errors.py +100 -0
  87. csv_to_db_py-0.1.0/tests/test_filter.py +34 -0
  88. csv_to_db_py-0.1.0/tests/test_identifiers.py +48 -0
  89. csv_to_db_py-0.1.0/uv.lock +747 -0
@@ -0,0 +1,19 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '[0-9]*'
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ - run: uv build
19
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ ruff-format:
11
+ runs-on: ubuntu-latest
12
+ container:
13
+ image: python:3.13-slim
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - name: install ruff
17
+ run: pip install ruff
18
+ - name: lint
19
+ run: ruff format --check
20
+
21
+ ruff-check:
22
+ runs-on: ubuntu-latest
23
+ container:
24
+ image: python:3.13-slim
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - name: install ruff
28
+ run: pip install ruff
29
+ - name: lint
30
+ run: ruff check
31
+
32
+ test:
33
+ runs-on: ubuntu-latest
34
+ container:
35
+ image: python:3.13-slim
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - name: install uv
39
+ run: pip install uv
40
+ - name: install dependencies
41
+ run: uv sync --dev
42
+ - name: run tests
43
+ run: uv run pytest
@@ -0,0 +1,25 @@
1
+ mainConfig.json
2
+
3
+ # Python
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ build/
11
+ dist/
12
+ *.egg-info/
13
+
14
+ # Environments
15
+ .env
16
+ .venv
17
+ venv/
18
+
19
+ # Ruff
20
+ .ruff_cache/
21
+
22
+ # Logs
23
+ *.log
24
+
25
+ .serena/
@@ -0,0 +1,91 @@
1
+ # Final Review — Fix Report
2
+
3
+ Date: 2026-10-08
4
+ Branch: `fixes` (fix wave on top of fc851e2)
5
+ Scope: whole-branch final review findings (2 Important + minor batch)
6
+
7
+ ## Important 1 — Schema drift destroys old data before insert
8
+
9
+ **File:** `csv_to_db_py/utils/csv_loader.py`
10
+
11
+ `_truncate_if_exists` now takes `expected_columns`. When the table exists it
12
+ compares the DB column names (`inspect(engine).get_columns(table_name)` →
13
+ `.name`) against the expected columns **before** any `TRUNCATE`. On mismatch it
14
+ raises a `ValueError` explaining the schema drift and suggesting
15
+ `DROP TABLE ... CASCADE` + recreating dependent views; nothing is truncated.
16
+ Only when the names match is the table truncated. Return contract unchanged:
17
+ `True` if truncated, `False` if the table did not exist.
18
+
19
+ Call site updated in `overwrite_table_with_csv_data` to pass `csv_data.columns`.
20
+
21
+ ### Column-name comparison behavior (checked)
22
+
23
+ `create_table_from_csv` builds its `CREATE TABLE IF NOT EXISTS` with
24
+ `quote_identifier(column)` (double-quoted identifiers) over the already
25
+ normalized `dataframe.columns`. Quoted identifiers preserve the exact case on
26
+ Postgres, so on the normal overwrite path `get_columns(...)` returns the very
27
+ same names as `df.columns`. I therefore compare **exact** sorted name lists
28
+ without normalization. Rationale: the guard exists to avoid destroying data
29
+ when layouts differ; a normalized (fuzzy) comparison could mask a genuine
30
+ rename (e.g. `col name` → `colname` normalizing to the same key) and wrongly
31
+ truncate. Exact comparison is the correct, stricter choice. No normalization
32
+ applied.
33
+
34
+ A one-line `ponytail:` comment documents that TYPE drift is still not guarded
35
+ (it still fails loudly after truncate), which is acceptable since the name
36
+ guard covers the documented case.
37
+
38
+ **Tests** (`tests/test_errors.py`, +2 new):
39
+ - `test_truncate_raises_on_schema_drift_without_truncating`: stub inspector
40
+ with `has_table` → True and `get_columns` returning a column different from
41
+ the dataframe's → `_truncate_if_exists` raises `ValueError`; the stub engine
42
+ uses the existing `BoomConn` (raises `RuntimeError` on execute), so a
43
+ `ValueError` proves TRUNCATE never ran.
44
+ - `test_truncate_executes_when_columns_match`: stub inspector whose columns
45
+ match → TRUNCATE executes (asserted via a counting connection) and returns
46
+ `True`.
47
+ - Existing `test_truncate_if_exists_propagates_execute_error` updated: the
48
+ `FakeInspector` now also returns `get_columns` matching `["col"]`.
49
+
50
+ ## Important 2 — README Testing section stale
51
+
52
+ `README.md` Testing section now runs `uv run pytest` instead of
53
+ `uv run python tests/test_datetime_detection.py` (that file is a pytest module
54
+ without `__main__`), and the prose reflects the pytest reality.
55
+
56
+ ## Minor batch
57
+
58
+ 1. `csv_to_db_py/config.py`: `validate_config` guards `DATABASE_CONFIG` with
59
+ `isinstance(cfg.get("DATABASE_CONFIG"), dict)` → `ValueError` (was
60
+ AttributeError on `.get` of a non-dict). Error message concatenation merged
61
+ into a single f-string.
62
+ 2. `tests/test_datetime_detection.py`: `TEST_FILE` now
63
+ `Path(__file__).parent / "date_formats_test.csv"` (CWD-independent).
64
+ 3. `csv_to_db_py/main.py`: the two
65
+ `from csv_to_db_py.utils.cleaned_csv import ...` lines merged into one.
66
+ 4. `csv_to_db_py/main.py`: `parse_args` annotated
67
+ `-> argparse.Namespace`.
68
+ 5. `csv_to_db_py/utils/cleaned_csv.py`: `apply_filter` annotated
69
+ `(df: pl.DataFrame, filter_config: dict) -> pl.DataFrame`.
70
+ 6. `README.md`: Table Overwrite Strategy reworded — table is always
71
+ pre-created by `create_table_from_csv` (no longer "created automatically by
72
+ `to_sql`"); column-name drift now blocks before truncating, type drift
73
+ still fails loudly after truncate.
74
+ 7. `csv_to_db_py/utils/csv_loader.py`: stale "au moins 90%" comments replaced
75
+ with the configurable `datetime_detection_threshold` wording; one-line
76
+ docstring added to `convert_datetime_columns`.
77
+
78
+ ## Verification
79
+
80
+ ```
81
+ $ UV_PROJECT_ENVIRONMENT=/tmp/uv/csv-to-db-py uv run pytest -q
82
+ 47 passed, 80 warnings in 0.40s
83
+
84
+ $ UV_PROJECT_ENVIRONMENT=/tmp/uv/csv-to-db-py uv run ruff check .
85
+ All checks passed! (Ruff: No issues found)
86
+
87
+ $ UV_PROJECT_ENVIRONMENT=/tmp/uv/csv-to-db-py uv run ruff format .
88
+ 24 files left unchanged
89
+ ```
90
+
91
+ Baseline before the fix wave: 45 passed → 47 after (2 new drift tests).
@@ -0,0 +1 @@
1
+ docs/superpowers/plans/2026-10-08-csv-to-db-hardening.md
@@ -0,0 +1,70 @@
1
+ # SDD ledger — plan: docs/superpowers/plans/2026-10-08-csv-to-db-hardening.md
2
+
3
+ ## Pre-flight scan
4
+
5
+ | Tasks | Shared file | What one produces / other consumes | Found |
6
+ |---|---|---|---|
7
+ | T1 → T7 | `csv_to_db_py/main.py` | imports (T1) then argparse (T7) | sequential, fine |
8
+ | T1 → T5 | `table_creator.py`, `csv_loader.py` | import fixes, then error handling | sequential, fine |
9
+ | T5 → T6 | `csv_loader.py` | error propagation, then datetime refactor (different functions) | sequential, fine |
10
+ | T6 → T7 | `csv_loader.py` | `convert_datetime_columns`, then delete `polars_type_to_sql` | sequential, fine |
11
+ | T4 → T11 | `cleaned_csv.py` | `quote_identifier`, then `apply_filter` (different functions) | sequential, fine |
12
+ | T2 → T3 | `config.py` + tests | validation, then tests import prod modules | **CONFLICT: config.py reads `../mainConfig.json` at import; file is gitignored → absent in CI → tests cannot import** |
13
+ | T3 → T8 | `pyproject.toml` | pytest (dev), then drivers (deps) | sequential, fine |
14
+ | T6 test | config threshold | test needs threshold 0.7; local config has no key → default 1.0 | **NOTE: T6 test must monkeypatch `config["datetime_detection_threshold"] = 0.7`** |
15
+ | T9 | `compose.yml` | file is untracked (`?? compose.yml`) | commit will `git add` it whole; expected |
16
+
17
+ ## Pre-flight rulings
18
+
19
+ - `Ruling: config import resilience` — config.py must fall back to `mainConfig.json.example-strict` when `mainConfig.json` is absent, then validate. Without it, tests and CI crash on `import csv_to_db_py.config` (gitignored config missing in CI, and installed package can't find `../mainConfig.json`). Amend Task 2. — Cost if wrong: small rework in T2.
20
+ - `Ruling: T6 test threshold` — T6 test monkeypatches `config["datetime_detection_threshold"] = 0.7` before calling `convert_datetime_columns`; carried in T6 dispatch. — Cost if wrong: test silently uses default 1.0 and fails.
21
+
22
+ ## Tasks
23
+
24
+ Task 1: pending
25
+
26
+ Task 1: complete (commits 7ffe98c..f7751b0, review clean)
27
+ Task 1: minor (deferred): main.py:101-102 two csv_to_db_py.utils.cleaned_csv import lines could merge into one
28
+
29
+ Task 2: complete (commits f7751b0..02798e6, review clean)
30
+ Task 2: minor (deferred): config.py:28 DATABASE_CONFIG non-dict would raise AttributeError not ValueError; f-string for error message; fallback load path not covered by test
31
+
32
+ Task 3: complete (commits 02798e6..bd3e9d5, review clean; 22 tests passing confirmed by controller)
33
+ Task 3: minor (deferred): TEST_FILE path is CWD-relative (use Path(__file__).parent); batch test replicates prod formula (accepted, no importable helper)
34
+
35
+ Task 4: complete (commits bd3e9d5..ab9ee61, review clean)
36
+ Task 4: minor (deferred): long f-string in table_creator.py:86 readability
37
+
38
+ Task 5: complete (commits ab9ee61..55479cd, review clean)
39
+ Task 5: minor (deferred): table_creator.py has pre-existing duplicate `columns = dataframe.columns` assignment (out of scope)
40
+
41
+ Task 6: complete (commits 55479cd..27c87bb, review clean; fix round 1/5: test data 4 values at threshold 0.7 per ruling)
42
+ Task 6: minor (deferred): stale "90%" comments in csv_loader.py:110,172 (pre-existing); convert_datetime_columns lacks docstring
43
+
44
+ Task 7: complete (commits 27c87bb..11102ec, review clean)
45
+ Task 7: minor (deferred): parse_args lacks return type annotation (cosmetic)
46
+
47
+ Task 8: fix round 1/5 (oracle driver) — in progress
48
+ Task 8: Ruling: cx_Oracle cannot build on Python 3.13 (deprecated C extension, no cp313 wheel; sandbox build failed on missing pkg_resources). Its official successor python-oracledb ships cp313 wheels and SQLAlchemy supports dialect `oracle+oracledb`. Replace cx_oracle with oracledb so the connector installs everywhere (including CI uv sync) and the oracle test can run. — Cost if wrong: dialect name differs from some users' expectations; small.
49
+
50
+ Task 8: complete (commits 11102ec..f08c415, review clean; fix round 1/5: cx_oracle → oracledb per ruling)
51
+ Task 8: minor (deferred): make_url identity vs string round-trip in connector test (matches brief, adequate)
52
+
53
+ Task 9: complete (commits f08c415..8593195, review clean)
54
+
55
+ Task 10: complete (commits 8593195..49aa2f3, review clean)
56
+
57
+ Task 11: complete (commits 49aa2f3..ff80dc8, review clean)
58
+ Task 11: minor (deferred): apply_filter lacks type hints; FILTER empty-string value now runs filter (was truthy-skipped) — harmless
59
+
60
+ Task 12: complete (commits ff80dc8..dbb469e, review clean)
61
+
62
+ Task 13: complete (commits dbb469e..fc851e2, review clean)
63
+ Task 13: minor (deferred): README "Good for most cases" 100000 framing vs new 10000 default; launch.json windows python path (pre-existing)
64
+ ALL TASKS COMPLETE. Launching final whole-branch review (base 7ffe98c, head fc851e2).
65
+
66
+ Final review: "With fixes" — 2 Important + minor batch. Dispatching ONE fix subagent (fix base fc851e2).
67
+
68
+ Final review fix wave: complete (fc851e2..9ee3e7a, re-review clean — all 9 findings addressed, no new Critical/Important breakage)
69
+ Out-of-scope note for user: quote_identifier uses postgres double-quote syntax on all backends (MySQL expects backticks) — pre-existing, not introduced by this branch.
70
+ Final review: clean. Workspace cleanup next.