From 6bf05dcaa698905a69a4b0a2dd39a9464f369bc2 Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 19:03:56 -0500 Subject: [PATCH 1/8] adding gitignore --- .gitignore | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43b5e0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,216 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# pdm +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ +*.iml +*.iws +.idea_modules/ + +# VS Code +.vscode/ +*.code-workspace + +# Sublime Text +*.sublime-project +*.sublime-workspace + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini +$RECYCLE.BIN/ +*.lnk + +# Linux +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* + +# Project-specific +*.log +*.csv +*.xlsx +*.docx +*.pdf +demo.yml +test_data/ +uploads/ +downloads/ +data/ +exemple/data/ +temp/ +tmp/ + +# API keys and secrets (safety net) +*apikey* +*api_key* +*secret* +*password* +*.pem +*.key +*.crt + +# Dataverse uploader specific +DVUploaderLog_* +refresh.txt + +# Backup files +*.bak +*.swp +*.swo +*~ + +# Archive files (unless intentionally tracked) +*.zip +*.tar.gz +*.tar.bz2 +*.tar.xz +*.7z +*.rar \ No newline at end of file From c0a69bfb55c55a069195e1541200de4f94a00c2b Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 19:05:21 -0500 Subject: [PATCH 2/8] removing pycache --- .../csv_to_dataverse_json.cpython-312.pyc | Bin 30623 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __pycache__/csv_to_dataverse_json.cpython-312.pyc diff --git a/__pycache__/csv_to_dataverse_json.cpython-312.pyc b/__pycache__/csv_to_dataverse_json.cpython-312.pyc deleted file mode 100644 index e031203d25b78a921ee5d726d84eea894d345e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30623 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 From 2bee5bb20e83978985afdc9340ff6a801cf9f986 Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 19:11:32 -0500 Subject: [PATCH 3/8] add python manager --- main.py | 6 ++ pyproject.toml | 15 +++++ uv.lock | 163 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/main.py b/main.py new file mode 100644 index 0000000..c5d7109 --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from csv-to-dataverse-json!") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..aded6eb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "csv-to-dataverse-json" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "pandas>=1.3.0", +] + +[dependency-groups] +dev = [ + "black (>=25.12.0,<26.0.0)", + "pytest (>=9.0.2,<10.0.0)" +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..ff90d24 --- /dev/null +++ b/uv.lock @@ -0,0 +1,163 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "csv-to-dataverse-json" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pandas" }, +] + +[package.metadata] +requires-dist = [{ name = "pandas", specifier = ">=1.3.0" }] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] From 024c97d42317db8b6a506734fe8956299d775be2 Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 20:02:08 -0500 Subject: [PATCH 4/8] deleting file --- main.py | 6 ------ pyproject.toml | 16 +++++++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 main.py diff --git a/main.py b/main.py deleted file mode 100644 index c5d7109..0000000 --- a/main.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - print("Hello from csv-to-dataverse-json!") - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index aded6eb..c3d3c0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,21 @@ [project] name = "csv-to-dataverse-json" -version = "0.1.0" -description = "Add your description here" +version = "1.0.0" +description = "Simple tool to convert CSV files into Dataverse/Borealis JSON format" readme = "README.md" requires-python = ">=3.12" dependencies = [ "pandas>=1.3.0", ] -[dependency-groups] +[project.optional-dependencies] dev = [ - "black (>=25.12.0,<26.0.0)", - "pytest (>=9.0.2,<10.0.0)" + "black>=24.0.0", ] + +[project.scripts] +csv-to-dataverse = "csv_to_dataverse_json:main" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" From 2e902a1b3a8df3c8a17610a01762287c4ae9ea57 Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 20:03:04 -0500 Subject: [PATCH 5/8] blacking that file --- csv_to_dataverse_json.py | 840 ++++++++++++++++++++++++++++++--------- 1 file changed, 644 insertions(+), 196 deletions(-) diff --git a/csv_to_dataverse_json.py b/csv_to_dataverse_json.py index 30a4198..3be34ed 100644 --- a/csv_to_dataverse_json.py +++ b/csv_to_dataverse_json.py @@ -18,30 +18,38 @@ def ensure_required_fields(dataset_json, row, defaults=None): """Ensure Dataverse-required fields exist; fill with placeholders if missing.""" try: - citation_fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + citation_fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"][ + "fields" + ] except Exception: return def find_field(name): for f in citation_fields: - if f.get('typeName') == name: + if f.get("typeName") == name: return f return None # 1) Author (authorName required) - author_field = find_field('author') - if not author_field or not author_field.get('value'): + author_field = find_field("author") + if not author_field or not author_field.get("value"): # try pull from depositor or CSV 'author' raw string raw_author = None - if 'author' in row and not pd.isna(row['author']): - raw_author = str(row['author']).split(';')[0].split('|')[0].strip() - elif 'depositor' in row and not pd.isna(row['depositor']): - raw_author = str(row['depositor']).strip() + if "author" in row and not pd.isna(row["author"]): + raw_author = str(row["author"]).split(";")[0].split("|")[0].strip() + elif "depositor" in row and not pd.isna(row["depositor"]): + raw_author = str(row["depositor"]).strip() else: # fallback to provided default author, environment, or generic - raw_author = (defaults.get('author') if defaults and defaults.get('author') else - os.getenv('DATAVERSE_DEFAULT_AUTHOR') if os.getenv('DATAVERSE_DEFAULT_AUTHOR') else - 'Unknown Author') + raw_author = ( + defaults.get("author") + if defaults and defaults.get("author") + else ( + os.getenv("DATAVERSE_DEFAULT_AUTHOR") + if os.getenv("DATAVERSE_DEFAULT_AUTHOR") + else "Unknown Author" + ) + ) new_author = { "typeName": "author", @@ -49,9 +57,14 @@ def find_field(name): "typeClass": "compound", "value": [ { - "authorName": {"typeName": "authorName", "multiple": False, "typeClass": "primitive", "value": raw_author} + "authorName": { + "typeName": "authorName", + "multiple": False, + "typeClass": "primitive", + "value": raw_author, + } } - ] + ], } # replace or append if author_field: @@ -59,29 +72,51 @@ def find_field(name): citation_fields.append(new_author) # 2) Dataset contact email (datasetContact -> datasetContactEmail required) - contact_field = find_field('datasetContact') - if not contact_field or not contact_field.get('value') or not any('datasetContactEmail' in v for v in (contact_field.get('value') or [])): + contact_field = find_field("datasetContact") + if ( + not contact_field + or not contact_field.get("value") + or not any( + "datasetContactEmail" in v for v in (contact_field.get("value") or []) + ) + ): # try CSV values contact_name = None contact_email = None - if 'datasetContact' in row and not pd.isna(row['datasetContact']): - parts = [p.strip() for p in str(row['datasetContact']).split(';')] + if "datasetContact" in row and not pd.isna(row["datasetContact"]): + parts = [p.strip() for p in str(row["datasetContact"]).split(";")] if len(parts) >= 1: contact_name = parts[0] if len(parts) >= 3: contact_email = parts[2] - if not contact_name and 'depositor' in row and not pd.isna(row['depositor']): - contact_name = str(row['depositor']).strip() - if not contact_email and 'datasetContactEmail' in row and not pd.isna(row['datasetContactEmail']): - contact_email = str(row['datasetContactEmail']).strip() + if not contact_name and "depositor" in row and not pd.isna(row["depositor"]): + contact_name = str(row["depositor"]).strip() + if ( + not contact_email + and "datasetContactEmail" in row + and not pd.isna(row["datasetContactEmail"]) + ): + contact_email = str(row["datasetContactEmail"]).strip() if not contact_email: - contact_email = (defaults.get('email') if defaults and defaults.get('email') else - os.getenv('DATAVERSE_DEFAULT_EMAIL') if os.getenv('DATAVERSE_DEFAULT_EMAIL') else - 'no-reply@example.com') + contact_email = ( + defaults.get("email") + if defaults and defaults.get("email") + else ( + os.getenv("DATAVERSE_DEFAULT_EMAIL") + if os.getenv("DATAVERSE_DEFAULT_EMAIL") + else "no-reply@example.com" + ) + ) if not contact_name: - contact_name = (defaults.get('author') if defaults and defaults.get('author') else - os.getenv('DATAVERSE_DEFAULT_AUTHOR') if os.getenv('DATAVERSE_DEFAULT_AUTHOR') else - 'Dataset Contact') + contact_name = ( + defaults.get("author") + if defaults and defaults.get("author") + else ( + os.getenv("DATAVERSE_DEFAULT_AUTHOR") + if os.getenv("DATAVERSE_DEFAULT_AUTHOR") + else "Dataset Contact" + ) + ) new_contact = { "typeName": "datasetContact", @@ -89,28 +124,53 @@ def find_field(name): "typeClass": "compound", "value": [ { - "datasetContactName": {"typeName": "datasetContactName", "multiple": False, "typeClass": "primitive", "value": contact_name}, - "datasetContactAffiliation": {"typeName": "datasetContactAffiliation", "multiple": False, "typeClass": "primitive", "value": str(row.get('datasetContactAffiliation','')).strip()}, - "datasetContactEmail": {"typeName": "datasetContactEmail", "multiple": False, "typeClass": "primitive", "value": contact_email} + "datasetContactName": { + "typeName": "datasetContactName", + "multiple": False, + "typeClass": "primitive", + "value": contact_name, + }, + "datasetContactAffiliation": { + "typeName": "datasetContactAffiliation", + "multiple": False, + "typeClass": "primitive", + "value": str(row.get("datasetContactAffiliation", "")).strip(), + }, + "datasetContactEmail": { + "typeName": "datasetContactEmail", + "multiple": False, + "typeClass": "primitive", + "value": contact_email, + }, } - ] + ], } if contact_field: citation_fields.remove(contact_field) citation_fields.append(new_contact) # 3) Description (dsDescription -> dsDescriptionValue required) - desc_field = find_field('dsDescription') - if not desc_field or not desc_field.get('value') or not any('dsDescriptionValue' in v for v in (desc_field.get('value') or [])): + desc_field = find_field("dsDescription") + if ( + not desc_field + or not desc_field.get("value") + or not any("dsDescriptionValue" in v for v in (desc_field.get("value") or [])) + ): desc_text = None - if 'dsDescription' in row and not pd.isna(row['dsDescription']): - desc_text = str(row['dsDescription']).split(';')[0].strip() - elif 'citation' in row and not pd.isna(row['citation']): - desc_text = str(row['citation']).strip() + if "dsDescription" in row and not pd.isna(row["dsDescription"]): + desc_text = str(row["dsDescription"]).split(";")[0].strip() + elif "citation" in row and not pd.isna(row["citation"]): + desc_text = str(row["citation"]).strip() else: - desc_text = (defaults.get('description') if defaults and defaults.get('description') else - os.getenv('DATAVERSE_DEFAULT_DESCRIPTION') if os.getenv('DATAVERSE_DEFAULT_DESCRIPTION') else - 'No description provided.') + desc_text = ( + defaults.get("description") + if defaults and defaults.get("description") + else ( + os.getenv("DATAVERSE_DEFAULT_DESCRIPTION") + if os.getenv("DATAVERSE_DEFAULT_DESCRIPTION") + else "No description provided." + ) + ) new_desc = { "typeName": "dsDescription", @@ -118,9 +178,14 @@ def find_field(name): "typeClass": "compound", "value": [ { - "dsDescriptionValue": {"typeName": "dsDescriptionValue", "multiple": False, "typeClass": "primitive", "value": desc_text} + "dsDescriptionValue": { + "typeName": "dsDescriptionValue", + "multiple": False, + "typeClass": "primitive", + "value": desc_text, + } } - ] + ], } if desc_field: citation_fields.remove(desc_field) @@ -137,59 +202,198 @@ def csv_to_dataverse_json(csv_file_path, output_json_path, defaults=None): # Field type directory - defines structure for all citation fields directory = { - 'title': {"typeName": "title", "multiple": False, "typeClass": "primitive"}, - 'subtitle': {"typeName": "subtitle", "multiple": False, "typeClass": "primitive"}, - 'alternativeTitle': {"typeName": "alternativeTitle", "multiple": True, "typeClass": "primitive"}, - 'otherId': {"typeName": "otherId", "multiple": True, "typeClass": "compound"}, - 'author': {"typeName": "author", "multiple": True, "typeClass": "compound"}, - 'datasetContact': {"typeName": "datasetContact", "multiple": True, "typeClass": "compound"}, - 'dsDescription': {"typeName": "dsDescription", "multiple": True, "typeClass": "compound"}, - 'subject': {"typeName": "subject", "multiple": True, "typeClass": "controlledVocabulary"}, - 'keyword': {"typeName": "keyword", "multiple": True, "typeClass": "compound"}, - 'topicClassification': {"typeName": "topicClassification", "multiple": True, "typeClass": "compound"}, - 'publication': {"typeName": "publication", "multiple": True, "typeClass": "compound"}, - 'notesText': {"typeName": "notesText", "multiple": False, "typeClass": "primitive"}, - 'language': {"typeName": "language", "multiple": True, "typeClass": "controlledVocabulary"}, - 'producer': {"typeName": "producer", "multiple": True, "typeClass": "compound"}, - 'productionDate': {"typeName": "productionDate", "multiple": False, "typeClass": "primitive"}, - 'productionPlace': {"typeName": "productionPlace", "multiple": True, "typeClass": "primitive"}, - 'contributor': {"typeName": "contributor", "multiple": True, "typeClass": "compound"}, - 'grantNumber': {"typeName": "grantNumber", "multiple": True, "typeClass": "compound"}, - 'distributor': {"typeName": "distributor", "multiple": True, "typeClass": "compound"}, - 'distributionDate': {"typeName": "distributionDate", "multiple": False, "typeClass": "primitive"}, - 'depositor': {"typeName": "depositor", "multiple": False, "typeClass": "primitive"}, - 'dateOfDeposit': {"typeName": "dateOfDeposit", "multiple": False, "typeClass": "primitive"}, - 'timePeriodCovered': {"typeName": "timePeriodCovered", "multiple": True, "typeClass": "compound"}, - 'dateOfCollection': {"typeName": "dateOfCollection", "multiple": True, "typeClass": "compound"}, - 'kindOfData': {"typeName": "kindOfData", "multiple": True, "typeClass": "primitive"}, - 'series': {"typeName": "series", "multiple": True, "typeClass": "compound"}, - 'software': {"typeName": "software", "multiple": True, "typeClass": "compound"}, - 'relatedMaterial': {"typeName": "relatedMaterial", "multiple": True, "typeClass": "primitive"}, - 'relatedDatasets': {"typeName": "relatedDatasets", "multiple": True, "typeClass": "primitive"}, - 'otherReferences': {"typeName": "otherReferences", "multiple": True, "typeClass": "primitive"}, - 'dataSources': {"typeName": "dataSources", "multiple": True, "typeClass": "primitive"}, - 'originOfSources': {"typeName": "originOfSources", "multiple": False, "typeClass": "primitive"}, - 'characteristicOfSources': {"typeName": "characteristicOfSources", "multiple": False, "typeClass": "primitive"}, - 'accessToSources': {"typeName": "accessToSources", "multiple": False, "typeClass": "primitive"} + "title": {"typeName": "title", "multiple": False, "typeClass": "primitive"}, + "subtitle": { + "typeName": "subtitle", + "multiple": False, + "typeClass": "primitive", + }, + "alternativeTitle": { + "typeName": "alternativeTitle", + "multiple": True, + "typeClass": "primitive", + }, + "otherId": {"typeName": "otherId", "multiple": True, "typeClass": "compound"}, + "author": {"typeName": "author", "multiple": True, "typeClass": "compound"}, + "datasetContact": { + "typeName": "datasetContact", + "multiple": True, + "typeClass": "compound", + }, + "dsDescription": { + "typeName": "dsDescription", + "multiple": True, + "typeClass": "compound", + }, + "subject": { + "typeName": "subject", + "multiple": True, + "typeClass": "controlledVocabulary", + }, + "keyword": {"typeName": "keyword", "multiple": True, "typeClass": "compound"}, + "topicClassification": { + "typeName": "topicClassification", + "multiple": True, + "typeClass": "compound", + }, + "publication": { + "typeName": "publication", + "multiple": True, + "typeClass": "compound", + }, + "notesText": { + "typeName": "notesText", + "multiple": False, + "typeClass": "primitive", + }, + "language": { + "typeName": "language", + "multiple": True, + "typeClass": "controlledVocabulary", + }, + "producer": {"typeName": "producer", "multiple": True, "typeClass": "compound"}, + "productionDate": { + "typeName": "productionDate", + "multiple": False, + "typeClass": "primitive", + }, + "productionPlace": { + "typeName": "productionPlace", + "multiple": True, + "typeClass": "primitive", + }, + "contributor": { + "typeName": "contributor", + "multiple": True, + "typeClass": "compound", + }, + "grantNumber": { + "typeName": "grantNumber", + "multiple": True, + "typeClass": "compound", + }, + "distributor": { + "typeName": "distributor", + "multiple": True, + "typeClass": "compound", + }, + "distributionDate": { + "typeName": "distributionDate", + "multiple": False, + "typeClass": "primitive", + }, + "depositor": { + "typeName": "depositor", + "multiple": False, + "typeClass": "primitive", + }, + "dateOfDeposit": { + "typeName": "dateOfDeposit", + "multiple": False, + "typeClass": "primitive", + }, + "timePeriodCovered": { + "typeName": "timePeriodCovered", + "multiple": True, + "typeClass": "compound", + }, + "dateOfCollection": { + "typeName": "dateOfCollection", + "multiple": True, + "typeClass": "compound", + }, + "kindOfData": { + "typeName": "kindOfData", + "multiple": True, + "typeClass": "primitive", + }, + "series": {"typeName": "series", "multiple": True, "typeClass": "compound"}, + "software": {"typeName": "software", "multiple": True, "typeClass": "compound"}, + "relatedMaterial": { + "typeName": "relatedMaterial", + "multiple": True, + "typeClass": "primitive", + }, + "relatedDatasets": { + "typeName": "relatedDatasets", + "multiple": True, + "typeClass": "primitive", + }, + "otherReferences": { + "typeName": "otherReferences", + "multiple": True, + "typeClass": "primitive", + }, + "dataSources": { + "typeName": "dataSources", + "multiple": True, + "typeClass": "primitive", + }, + "originOfSources": { + "typeName": "originOfSources", + "multiple": False, + "typeClass": "primitive", + }, + "characteristicOfSources": { + "typeName": "characteristicOfSources", + "multiple": False, + "typeClass": "primitive", + }, + "accessToSources": { + "typeName": "accessToSources", + "multiple": False, + "typeClass": "primitive", + }, } # Compound field subfield mappings compound_fields = { - 'otherId': ['otherIdAgency', 'otherIdValue'], - 'author': ['authorName', 'authorAffiliation', 'authorIdentifierScheme', 'authorIdentifier'], - 'datasetContact': ['datasetContactName', 'datasetContactAffiliation', 'datasetContactEmail'], - 'dsDescription': ['dsDescriptionValue', 'dsDescriptionDate'], - 'keyword': ['keywordValue', 'keywordVocabulary', 'keywordVocabularyURI'], - 'topicClassification': ['topicClassValue', 'topicClassVocab', 'topicClassVocabURI'], - 'publication': ['publicationRelationType', 'publicationCitation', 'publicationIDType', 'publicationIDNumber', 'publicationURL'], - 'producer': ['producerName', 'producerAffiliation', 'producerAbbreviation', 'producerURL', 'producerLogoURL'], - 'contributor': ['contributorType', 'contributorName'], - 'grantNumber': ['grantNumberAgency', 'grantNumberValue'], - 'distributor': ['distributorName', 'distributorAffiliation', 'distributorAbbreviation', 'distributorURL', 'distributorLogoURL'], - 'timePeriodCovered': ['timePeriodCoveredStart', 'timePeriodCoveredEnd'], - 'dateOfCollection': ['dateOfCollectionStart', 'dateOfCollectionEnd'], - 'series': ['seriesName', 'seriesInformation'], - 'software': ['softwareName', 'softwareVersion'] + "otherId": ["otherIdAgency", "otherIdValue"], + "author": [ + "authorName", + "authorAffiliation", + "authorIdentifierScheme", + "authorIdentifier", + ], + "datasetContact": [ + "datasetContactName", + "datasetContactAffiliation", + "datasetContactEmail", + ], + "dsDescription": ["dsDescriptionValue", "dsDescriptionDate"], + "keyword": ["keywordValue", "keywordVocabulary", "keywordVocabularyURI"], + "topicClassification": [ + "topicClassValue", + "topicClassVocab", + "topicClassVocabURI", + ], + "publication": [ + "publicationRelationType", + "publicationCitation", + "publicationIDType", + "publicationIDNumber", + "publicationURL", + ], + "producer": [ + "producerName", + "producerAffiliation", + "producerAbbreviation", + "producerURL", + "producerLogoURL", + ], + "contributor": ["contributorType", "contributorName"], + "grantNumber": ["grantNumberAgency", "grantNumberValue"], + "distributor": [ + "distributorName", + "distributorAffiliation", + "distributorAbbreviation", + "distributorURL", + "distributorLogoURL", + ], + "timePeriodCovered": ["timePeriodCoveredStart", "timePeriodCoveredEnd"], + "dateOfCollection": ["dateOfCollectionStart", "dateOfCollectionEnd"], + "series": ["seriesName", "seriesInformation"], + "software": ["softwareName", "softwareVersion"], } # Get current date and time for defaults @@ -206,7 +410,7 @@ def format_date_to_year(date_value): date_str = str(date_value).strip() # Extract year using regex - year_match = re.search(r'\b(19|20)\d{2}\b', date_str) + year_match = re.search(r"\b(19|20)\d{2}\b", date_str) if year_match: return year_match.group(0) @@ -218,77 +422,244 @@ def format_date_to_year(date_value): for idx, row in df.iterrows(): # Generate unique IDs if not provided - dataset_id = int(row.get('id', 0)) if row.get('id') and not pd.isna(row.get('id')) else 1000 + idx - version_id = int(row.get('versionId', 0)) if row.get('versionId') and not pd.isna(row.get('versionId')) else 2000 + idx - + dataset_id = ( + int(row.get("id", 0)) + if row.get("id") and not pd.isna(row.get("id")) + else 1000 + idx + ) + version_id = ( + int(row.get("versionId", 0)) + if row.get("versionId") and not pd.isna(row.get("versionId")) + else 2000 + idx + ) + # Generate identifier/DOI if not provided - if row.get('identifier') and not pd.isna(row.get('identifier')): - identifier = str(row.get('identifier')) + if row.get("identifier") and not pd.isna(row.get("identifier")): + identifier = str(row.get("identifier")) else: identifier = f"FK2/{uuid.uuid4().hex[:8].upper()}" # Get protocol and authority - protocol = str(row.get('protocol', 'doi')).strip() if row.get('protocol') and not pd.isna(row.get('protocol')) else 'doi' - authority = str(row.get('authority', '10.70122')).strip() if row.get('authority') and not pd.isna(row.get('authority')) else '10.70122' - + protocol = ( + str(row.get("protocol", "doi")).strip() + if row.get("protocol") and not pd.isna(row.get("protocol")) + else "doi" + ) + authority = ( + str(row.get("authority", "10.70122")).strip() + if row.get("authority") and not pd.isna(row.get("authority")) + else "10.70122" + ) + # Build complete JSON structure with all top-level fields dataset_json = { "id": dataset_id, "identifier": identifier, - "persistentUrl": f"https://doi.org/{authority}/{identifier}" if protocol == "doi" else f"hdl:{authority}/{identifier}", + "persistentUrl": ( + f"https://doi.org/{authority}/{identifier}" + if protocol == "doi" + else f"hdl:{authority}/{identifier}" + ), "protocol": protocol, "authority": authority, "separator": "/", - "publisher": str(row.get('publisher', 'Dataverse')).strip() if row.get('publisher') and not pd.isna(row.get('publisher')) else 'Dataverse', - "publicationDate": str(row.get('publicationDate', current_date)).strip() if row.get('publicationDate') and not pd.isna(row.get('publicationDate')) else current_date, - "storageIdentifier": f"s3://{authority}/{identifier}" if row.get('storageIdentifier') is None or pd.isna(row.get('storageIdentifier')) else str(row.get('storageIdentifier')), - "datasetType": str(row.get('datasetType', 'dataset')).strip() if row.get('datasetType') and not pd.isna(row.get('datasetType')) else 'dataset', + "publisher": ( + str(row.get("publisher", "Dataverse")).strip() + if row.get("publisher") and not pd.isna(row.get("publisher")) + else "Dataverse" + ), + "publicationDate": ( + str(row.get("publicationDate", current_date)).strip() + if row.get("publicationDate") + and not pd.isna(row.get("publicationDate")) + else current_date + ), + "storageIdentifier": ( + f"s3://{authority}/{identifier}" + if row.get("storageIdentifier") is None + or pd.isna(row.get("storageIdentifier")) + else str(row.get("storageIdentifier")) + ), + "datasetType": ( + str(row.get("datasetType", "dataset")).strip() + if row.get("datasetType") and not pd.isna(row.get("datasetType")) + else "dataset" + ), "datasetVersion": { "id": version_id, "datasetId": dataset_id, "datasetPersistentId": f"{protocol}:{authority}/{identifier}", - "datasetType": str(row.get('datasetType', 'dataset')).strip() if row.get('datasetType') and not pd.isna(row.get('datasetType')) else 'dataset', - "storageIdentifier": f"s3://{authority}:{uuid.uuid4().hex[:12]}-{uuid.uuid4().hex[:12]}" if row.get('storageIdentifier') is None or pd.isna(row.get('storageIdentifier')) else str(row.get('storageIdentifier')), - "versionNumber": int(row.get('versionNumber', 1)) if row.get('versionNumber') and not pd.isna(row.get('versionNumber')) else 1, - "internalVersionNumber": int(row.get('internalVersionNumber', 1)) if row.get('internalVersionNumber') and not pd.isna(row.get('internalVersionNumber')) else 1, - "versionMinorNumber": int(row.get('versionMinorNumber', 0)) if row.get('versionMinorNumber') and not pd.isna(row.get('versionMinorNumber')) else 0, - "versionState": str(row.get('versionState', 'DRAFT')).strip() if row.get('versionState') and not pd.isna(row.get('versionState')) else 'DRAFT', - "latestVersionPublishingState": str(row.get('latestVersionPublishingState', 'DRAFT')).strip() if row.get('latestVersionPublishingState') and not pd.isna(row.get('latestVersionPublishingState')) else 'DRAFT', - "UNF": str(row.get('UNF', '')).strip() if row.get('UNF') and not pd.isna(row.get('UNF')) else '', - "lastUpdateTime": str(row.get('lastUpdateTime', current_datetime)).strip() if row.get('lastUpdateTime') and not pd.isna(row.get('lastUpdateTime')) else current_datetime, - "releaseTime": str(row.get('releaseTime', '')).strip() if row.get('releaseTime') and not pd.isna(row.get('releaseTime')) else '', - "createTime": str(row.get('createTime', current_datetime)).strip() if row.get('createTime') and not pd.isna(row.get('createTime')) else current_datetime, - "publicationDate": str(row.get('publicationDate', current_date)).strip() if row.get('publicationDate') and not pd.isna(row.get('publicationDate')) else current_date, - "citationDate": str(row.get('citationDate', current_date)).strip() if row.get('citationDate') and not pd.isna(row.get('citationDate')) else current_date, - "termsOfUse": str(row.get('termsOfUse', '')).strip() if row.get('termsOfUse') and not pd.isna(row.get('termsOfUse')) else '', - "citationRequirements": str(row.get('citationRequirements', '')).strip() if row.get('citationRequirements') and not pd.isna(row.get('citationRequirements')) else '', - "conditions": str(row.get('conditions', '')).strip() if row.get('conditions') and not pd.isna(row.get('conditions')) else '', - "termsOfAccess": str(row.get('termsOfAccess', '')).strip() if row.get('termsOfAccess') and not pd.isna(row.get('termsOfAccess')) else '', + "datasetType": ( + str(row.get("datasetType", "dataset")).strip() + if row.get("datasetType") and not pd.isna(row.get("datasetType")) + else "dataset" + ), + "storageIdentifier": ( + f"s3://{authority}:{uuid.uuid4().hex[:12]}-{uuid.uuid4().hex[:12]}" + if row.get("storageIdentifier") is None + or pd.isna(row.get("storageIdentifier")) + else str(row.get("storageIdentifier")) + ), + "versionNumber": ( + int(row.get("versionNumber", 1)) + if row.get("versionNumber") + and not pd.isna(row.get("versionNumber")) + else 1 + ), + "internalVersionNumber": ( + int(row.get("internalVersionNumber", 1)) + if row.get("internalVersionNumber") + and not pd.isna(row.get("internalVersionNumber")) + else 1 + ), + "versionMinorNumber": ( + int(row.get("versionMinorNumber", 0)) + if row.get("versionMinorNumber") + and not pd.isna(row.get("versionMinorNumber")) + else 0 + ), + "versionState": ( + str(row.get("versionState", "DRAFT")).strip() + if row.get("versionState") and not pd.isna(row.get("versionState")) + else "DRAFT" + ), + "latestVersionPublishingState": ( + str(row.get("latestVersionPublishingState", "DRAFT")).strip() + if row.get("latestVersionPublishingState") + and not pd.isna(row.get("latestVersionPublishingState")) + else "DRAFT" + ), + "UNF": ( + str(row.get("UNF", "")).strip() + if row.get("UNF") and not pd.isna(row.get("UNF")) + else "" + ), + "lastUpdateTime": ( + str(row.get("lastUpdateTime", current_datetime)).strip() + if row.get("lastUpdateTime") + and not pd.isna(row.get("lastUpdateTime")) + else current_datetime + ), + "releaseTime": ( + str(row.get("releaseTime", "")).strip() + if row.get("releaseTime") and not pd.isna(row.get("releaseTime")) + else "" + ), + "createTime": ( + str(row.get("createTime", current_datetime)).strip() + if row.get("createTime") and not pd.isna(row.get("createTime")) + else current_datetime + ), + "publicationDate": ( + str(row.get("publicationDate", current_date)).strip() + if row.get("publicationDate") + and not pd.isna(row.get("publicationDate")) + else current_date + ), + "citationDate": ( + str(row.get("citationDate", current_date)).strip() + if row.get("citationDate") and not pd.isna(row.get("citationDate")) + else current_date + ), + "termsOfUse": ( + str(row.get("termsOfUse", "")).strip() + if row.get("termsOfUse") and not pd.isna(row.get("termsOfUse")) + else "" + ), + "citationRequirements": ( + str(row.get("citationRequirements", "")).strip() + if row.get("citationRequirements") + and not pd.isna(row.get("citationRequirements")) + else "" + ), + "conditions": ( + str(row.get("conditions", "")).strip() + if row.get("conditions") and not pd.isna(row.get("conditions")) + else "" + ), + "termsOfAccess": ( + str(row.get("termsOfAccess", "")).strip() + if row.get("termsOfAccess") + and not pd.isna(row.get("termsOfAccess")) + else "" + ), "license": { - "name": str(row.get('licenseName', 'CC0 1.0')).strip() if row.get('licenseName') and not pd.isna(row.get('licenseName')) else 'CC0 1.0', - "uri": str(row.get('licenseUri', 'http://creativecommons.org/publicdomain/zero/1.0')).strip() if row.get('licenseUri') and not pd.isna(row.get('licenseUri')) else 'http://creativecommons.org/publicdomain/zero/1.0', - "iconUri": str(row.get('licenseIconUri', 'https://licensebuttons.net/p/zero/1.0/88x31.png')).strip() if row.get('licenseIconUri') and not pd.isna(row.get('licenseIconUri')) else 'https://licensebuttons.net/p/zero/1.0/88x31.png', - "rightsIdentifier": str(row.get('rightsIdentifier', 'CC0-1.0')).strip() if row.get('rightsIdentifier') and not pd.isna(row.get('rightsIdentifier')) else 'CC0-1.0', - "rightsIdentifierScheme": str(row.get('rightsIdentifierScheme', 'SPDX')).strip() if row.get('rightsIdentifierScheme') and not pd.isna(row.get('rightsIdentifierScheme')) else 'SPDX', - "schemeUri": str(row.get('schemeUri', 'https://spdx.org/licenses/')).strip() if row.get('schemeUri') and not pd.isna(row.get('schemeUri')) else 'https://spdx.org/licenses/', - "languageCode": str(row.get('languageCode', 'en')).strip() if row.get('languageCode') and not pd.isna(row.get('languageCode')) else 'en' + "name": ( + str(row.get("licenseName", "CC0 1.0")).strip() + if row.get("licenseName") + and not pd.isna(row.get("licenseName")) + else "CC0 1.0" + ), + "uri": ( + str( + row.get( + "licenseUri", + "http://creativecommons.org/publicdomain/zero/1.0", + ) + ).strip() + if row.get("licenseUri") and not pd.isna(row.get("licenseUri")) + else "http://creativecommons.org/publicdomain/zero/1.0" + ), + "iconUri": ( + str( + row.get( + "licenseIconUri", + "https://licensebuttons.net/p/zero/1.0/88x31.png", + ) + ).strip() + if row.get("licenseIconUri") + and not pd.isna(row.get("licenseIconUri")) + else "https://licensebuttons.net/p/zero/1.0/88x31.png" + ), + "rightsIdentifier": ( + str(row.get("rightsIdentifier", "CC0-1.0")).strip() + if row.get("rightsIdentifier") + and not pd.isna(row.get("rightsIdentifier")) + else "CC0-1.0" + ), + "rightsIdentifierScheme": ( + str(row.get("rightsIdentifierScheme", "SPDX")).strip() + if row.get("rightsIdentifierScheme") + and not pd.isna(row.get("rightsIdentifierScheme")) + else "SPDX" + ), + "schemeUri": ( + str(row.get("schemeUri", "https://spdx.org/licenses/")).strip() + if row.get("schemeUri") and not pd.isna(row.get("schemeUri")) + else "https://spdx.org/licenses/" + ), + "languageCode": ( + str(row.get("languageCode", "en")).strip() + if row.get("languageCode") + and not pd.isna(row.get("languageCode")) + else "en" + ), }, - "fileAccessRequest": bool(row.get('fileAccessRequest', True)) if row.get('fileAccessRequest') and not pd.isna(row.get('fileAccessRequest')) else True, + "fileAccessRequest": ( + bool(row.get("fileAccessRequest", True)) + if row.get("fileAccessRequest") + and not pd.isna(row.get("fileAccessRequest")) + else True + ), "metadataBlocks": { "citation": { "displayName": "Citation Metadata", "name": "citation", - "fields": [] + "fields": [], } - } - } + }, + }, } fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] # Process each metadata field for field_name, field_config in directory.items(): - if field_name not in row or pd.isna(row[field_name]) or row[field_name] == "": + if ( + field_name not in row + or pd.isna(row[field_name]) + or row[field_name] == "" + ): continue value = str(row[field_name]).strip() @@ -299,68 +670,96 @@ def format_date_to_year(date_value): field_entry = { "typeName": field_config["typeName"], "multiple": field_config["multiple"], - "typeClass": field_config["typeClass"] + "typeClass": field_config["typeClass"], } # Process based on type if field_config["typeClass"] == "primitive": # Convert date fields to year-only format - if field_name in ['productionDate', 'distributionDate', 'dateOfDeposit']: + if field_name in [ + "productionDate", + "distributionDate", + "dateOfDeposit", + ]: value = format_date_to_year(value) if field_config["multiple"]: # Multiple primitive: split by pipe - field_entry["value"] = [v.strip() for v in value.split('|') if v.strip()] + field_entry["value"] = [ + v.strip() for v in value.split("|") if v.strip() + ] else: # Single primitive field_entry["value"] = value elif field_config["typeClass"] == "controlledVocabulary": # Controlled vocabulary: split by pipe - field_entry["value"] = [v.strip() for v in value.split('|') if v.strip()] + field_entry["value"] = [ + v.strip() for v in value.split("|") if v.strip() + ] elif field_config["typeClass"] == "compound": # Compound: parse with subfields - field_entry["value"] = parse_compound(value, field_name, compound_fields) + field_entry["value"] = parse_compound( + value, field_name, compound_fields + ) # Add to fields list if field_entry.get("value"): fields.append(field_entry) # Add geospatial metadata block if present - if any(col in row for col in ['geographicCoverage', 'geographicUnit', 'geographicBoundingBox']): + if any( + col in row + for col in ["geographicCoverage", "geographicUnit", "geographicBoundingBox"] + ): geo_block = create_geospatial_block(row) if geo_block: - dataset_json["datasetVersion"]["metadataBlocks"]["geospatial"] = geo_block + dataset_json["datasetVersion"]["metadataBlocks"][ + "geospatial" + ] = geo_block # Add social science metadata block if present - if any(col in row for col in ['unitOfAnalysis', 'universe', 'timeMethod', 'samplingProcedure']): + if any( + col in row + for col in ["unitOfAnalysis", "universe", "timeMethod", "samplingProcedure"] + ): social_block = create_socialscience_block(row) if social_block: - dataset_json["datasetVersion"]["metadataBlocks"]["socialscience"] = social_block + dataset_json["datasetVersion"]["metadataBlocks"][ + "socialscience" + ] = social_block # Add files array if present - if 'files' in row and row['files'] and not pd.isna(row['files']): + if "files" in row and row["files"] and not pd.isna(row["files"]): try: - files_data = json.loads(row['files']) if isinstance(row['files'], str) else row['files'] - dataset_json["datasetVersion"]["files"] = files_data if isinstance(files_data, list) else [files_data] + files_data = ( + json.loads(row["files"]) + if isinstance(row["files"], str) + else row["files"] + ) + dataset_json["datasetVersion"]["files"] = ( + files_data if isinstance(files_data, list) else [files_data] + ) except json.JSONDecodeError: print(f" ⚠ Warning: Could not parse files JSON in row {idx + 1}") # Add citation field if present - if 'citation' in row and row['citation'] and not pd.isna(row['citation']): - dataset_json["citation"] = str(row['citation']).strip() + if "citation" in row and row["citation"] and not pd.isna(row["citation"]): + dataset_json["citation"] = str(row["citation"]).strip() # Ensure required fields exist (author, datasetContact email, description) ensure_required_fields(dataset_json, row, defaults) all_datasets.append(dataset_json) - print(f"✓ Row {idx + 1}: Dataset ID={dataset_id}, Processed {len(fields)} citation fields") + print( + f"✓ Row {idx + 1}: Dataset ID={dataset_id}, Processed {len(fields)} citation fields" + ) # Write output JSON file # If single row, write as single object; if multiple rows, write as array output_data = all_datasets[0] if len(all_datasets) == 1 else all_datasets - with open(output_json_path, 'w', encoding='utf-8') as f: + with open(output_json_path, "w", encoding="utf-8") as f: json.dump(output_data, f, indent=2, ensure_ascii=False) print(f"\n✓ Successfully converted CSV to JSON: {output_json_path}") @@ -377,18 +776,18 @@ def parse_compound(value, field_name, compound_fields): return [] subfield_names = compound_fields[field_name] - entries = [e.strip() for e in value.split('|') if e.strip()] + entries = [e.strip() for e in value.split("|") if e.strip()] result = [] for entry in entries: - parts = [p.strip() for p in entry.split(';')] + parts = [p.strip() for p in entry.split(";")] entry_obj = {} for i, subfield in enumerate(subfield_names): - if i < len(parts) and parts[i] and parts[i].lower() != 'nan': + if i < len(parts) and parts[i] and parts[i].lower() != "nan": # Special handling for dsDescriptionDate - convert to year - if subfield == 'dsDescriptionDate': - year_match = re.search(r'\b(19|20)\d{2}\b', parts[i]) + if subfield == "dsDescriptionDate": + year_match = re.search(r"\b(19|20)\d{2}\b", parts[i]) if year_match: parts[i] = year_match.group(0) else: @@ -398,7 +797,7 @@ def parse_compound(value, field_name, compound_fields): "typeName": subfield, "multiple": False, "typeClass": "primitive", - "value": parts[i] + "value": parts[i], } if entry_obj: @@ -412,29 +811,52 @@ def create_geospatial_block(row): geospatial = { "displayName": "Geospatial Metadata", "name": "geospatial", - "fields": [] + "fields": [], } # Geographic Coverage - if 'geographicCoverage' in row and row['geographicCoverage'] and not pd.isna(row['geographicCoverage']): - countries = [c.strip() for c in str(row['geographicCoverage']).split('|') if c.strip()] - geospatial["fields"].append({ - "typeName": "geographicCoverage", - "multiple": True, - "typeClass": "compound", - "value": [{"country": {"typeName": "country", "multiple": False, - "typeClass": "controlledVocabulary", "value": c}} for c in countries] - }) + if ( + "geographicCoverage" in row + and row["geographicCoverage"] + and not pd.isna(row["geographicCoverage"]) + ): + countries = [ + c.strip() for c in str(row["geographicCoverage"]).split("|") if c.strip() + ] + geospatial["fields"].append( + { + "typeName": "geographicCoverage", + "multiple": True, + "typeClass": "compound", + "value": [ + { + "country": { + "typeName": "country", + "multiple": False, + "typeClass": "controlledVocabulary", + "value": c, + } + } + for c in countries + ], + } + ) # Geographic Unit - if 'geographicUnit' in row and row['geographicUnit'] and not pd.isna(row['geographicUnit']): - units = [u.strip() for u in str(row['geographicUnit']).split('|') if u.strip()] - geospatial["fields"].append({ - "typeName": "geographicUnit", - "multiple": True, - "typeClass": "primitive", - "value": units - }) + if ( + "geographicUnit" in row + and row["geographicUnit"] + and not pd.isna(row["geographicUnit"]) + ): + units = [u.strip() for u in str(row["geographicUnit"]).split("|") if u.strip()] + geospatial["fields"].append( + { + "typeName": "geographicUnit", + "multiple": True, + "typeClass": "primitive", + "value": units, + } + ) return geospatial if geospatial["fields"] else None @@ -444,31 +866,33 @@ def create_socialscience_block(row): socialscience = { "displayName": "Social Science and Humanities Metadata", "name": "socialscience", - "fields": [] + "fields": [], } # Simple fields mapping simple_fields = { - 'unitOfAnalysis': {'multiple': True, 'typeClass': 'primitive'}, - 'universe': {'multiple': True, 'typeClass': 'primitive'}, - 'timeMethod': {'multiple': False, 'typeClass': 'primitive'}, - 'frequencyOfDataCollection': {'multiple': False, 'typeClass': 'primitive'}, - 'samplingProcedure': {'multiple': False, 'typeClass': 'primitive'}, - 'collectionMode': {'multiple': True, 'typeClass': 'primitive'}, - 'dataCollectionSituation': {'multiple': False, 'typeClass': 'primitive'}, - 'weighting': {'multiple': False, 'typeClass': 'primitive'} + "unitOfAnalysis": {"multiple": True, "typeClass": "primitive"}, + "universe": {"multiple": True, "typeClass": "primitive"}, + "timeMethod": {"multiple": False, "typeClass": "primitive"}, + "frequencyOfDataCollection": {"multiple": False, "typeClass": "primitive"}, + "samplingProcedure": {"multiple": False, "typeClass": "primitive"}, + "collectionMode": {"multiple": True, "typeClass": "primitive"}, + "dataCollectionSituation": {"multiple": False, "typeClass": "primitive"}, + "weighting": {"multiple": False, "typeClass": "primitive"}, } for field_name, config in simple_fields.items(): if field_name in row and row[field_name] and not pd.isna(row[field_name]): field_entry = { "typeName": field_name, - "multiple": config['multiple'], - "typeClass": config['typeClass'] + "multiple": config["multiple"], + "typeClass": config["typeClass"], } - if config['multiple']: - field_entry["value"] = [v.strip() for v in str(row[field_name]).split('|') if v.strip()] + if config["multiple"]: + field_entry["value"] = [ + v.strip() for v in str(row[field_name]).split("|") if v.strip() + ] else: field_entry["value"] = str(row[field_name]).strip() @@ -480,21 +904,45 @@ def create_socialscience_block(row): # Main execution if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Convert CSV to Dataverse JSON with optional defaults') - parser.add_argument('csv_input', nargs='?', default='Csv_to_json - Citation.csv', help='Input CSV file') - parser.add_argument('json_output', nargs='?', default='output_metadata.json', help='Output JSON file') - parser.add_argument('--default-author', dest='default_author', help='Default author name if none provided') - parser.add_argument('--default-email', dest='default_email', help='Default contact email if none provided') - parser.add_argument('--default-description', dest='default_description', help='Default description if none provided') + parser = argparse.ArgumentParser( + description="Convert CSV to Dataverse JSON with optional defaults" + ) + parser.add_argument( + "csv_input", + nargs="?", + default="Csv_to_json - Citation.csv", + help="Input CSV file", + ) + parser.add_argument( + "json_output", + nargs="?", + default="output_metadata.json", + help="Output JSON file", + ) + parser.add_argument( + "--default-author", + dest="default_author", + help="Default author name if none provided", + ) + parser.add_argument( + "--default-email", + dest="default_email", + help="Default contact email if none provided", + ) + parser.add_argument( + "--default-description", + dest="default_description", + help="Default description if none provided", + ) args = parser.parse_args() defaults = {} if args.default_author: - defaults['author'] = args.default_author + defaults["author"] = args.default_author if args.default_email: - defaults['email'] = args.default_email + defaults["email"] = args.default_email if args.default_description: - defaults['description'] = args.default_description + defaults["description"] = args.default_description csv_to_dataverse_json(args.csv_input, args.json_output, defaults=defaults) From 1b91410a241dbc54dc84322bea57a1cf75d2086e Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 20:06:08 -0500 Subject: [PATCH 6/8] removing black --- uv.lock | 163 -------------------------------------------------------- 1 file changed, 163 deletions(-) delete mode 100644 uv.lock diff --git a/uv.lock b/uv.lock deleted file mode 100644 index ff90d24..0000000 --- a/uv.lock +++ /dev/null @@ -1,163 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.12" - -[[package]] -name = "csv-to-dataverse-json" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "pandas" }, -] - -[package.metadata] -requires-dist = [{ name = "pandas", specifier = ">=1.3.0" }] - -[[package]] -name = "numpy" -version = "2.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, -] - -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "tzdata" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, -] From 36bb6c160d1c3838e8d5be088100c502c30c52f9 Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 20:30:16 -0500 Subject: [PATCH 7/8] add utf8 character on NoteText --- Csv_to_json - Citation.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Csv_to_json - Citation.csv b/Csv_to_json - Citation.csv index e3ad0e4..78a74ef 100644 --- a/Csv_to_json - Citation.csv +++ b/Csv_to_json - Citation.csv @@ -1,2 +1,2 @@ doi,title,subtitle,alternativeTitle,otherId: otherIdAgency,author: authorName; authorAffiliation,datasetContact: datasetContactName; datasetContactAffiliation,dsDescription: dsDescriptionValue; dsDescriptionDate,subject,keyword: keywordValue; keywordVocabulary,topicClassification: topicClassValue; topicClassVocab,publication: publicationRelationType; publicationCitation; publicationIDType; publicationIDNumber,notesText,language,producer: producerName; producerAffiliation; producerAbbreviation,productionDate,productionPlace,contributor: contributorType; contributorName,grantNumber: grantNumberAgency; grantNumberValue,distributor: distributorName; distributorAffiliation; distributorAbbreviation; distributorURL,distributionDate,depositor,dateOfDeposit,timePeriodCovered: timePeriodCoveredStart; timePeriodCoveredEnd,dateOfCollection: dateOfCollectionStart; dateOfCollectionEnd,kindOfData,series: seriesName; seriesInformation,software: softwareName,relatedMaterial,relatedDatasets,otherReferences,dataSources,originOfSources,characteristicOfSources,accessToSources,citation -,Test: csv to json file,conversion,alternativeTitle,Agency,zeynep Cevik; University of Toronto,University of toronto,2025,Social Sciences,csv: json: test,test,test1,notest1,English,statscan,2025,Canada,zeynep cevik,,,2025,Zeynep Cevik,2025,,,micro,Json,,,,,,,,, +,Test: csv to json file,conversion,alternativeTitle,Agency,zeynep Cevik; University of Toronto,University of toronto,2025,Social Sciences,csv: json: test,test,test1,"L'enquête a révélé que plusieurs ménages n'avaient pas reçu le courriel initial à cause d'une défaillance technique.",English,statscan,2025,Canada,zeynep cevik,,,2025,Zeynep Cevik,2025,,,micro,Json,,,,,,,,, From c7b20190b87c75e7470029e56d7e5858096c94ab Mon Sep 17 00:00:00 2001 From: Guinsly Mond Date: Thu, 11 Dec 2025 21:10:19 -0500 Subject: [PATCH 8/8] adding test files --- TEST_README.md | 169 +++++++++++++ csv_to_dataverse_json.py | 47 +++- pyproject.toml | 2 + test_csv_to_dataverse_json.py | 459 ++++++++++++++++++++++++++++++++++ 4 files changed, 666 insertions(+), 11 deletions(-) create mode 100644 TEST_README.md create mode 100644 test_csv_to_dataverse_json.py diff --git a/TEST_README.md b/TEST_README.md new file mode 100644 index 0000000..e782c4e --- /dev/null +++ b/TEST_README.md @@ -0,0 +1,169 @@ +# Test Documentation + +This document describes the test suite for the CSV to Dataverse JSON converter. + +## Running Tests + +### Prerequisites + +Install the required test dependencies: + +```bash +pip install --break-system-packages pytest pandas +``` + +Or install all dev dependencies: + +```bash +pip install --break-system-packages -e ".[dev]" +``` + +### Basic Test Commands + +**Run all tests:** +```bash +python -m pytest test_csv_to_dataverse_json.py +``` + +**Run with verbose output:** +```bash +python -m pytest test_csv_to_dataverse_json.py -v +``` + +**Run with coverage report:** +```bash +python -m pytest test_csv_to_dataverse_json.py --cov=csv_to_dataverse_json +``` + +**Run a specific test class:** +```bash +python -m pytest test_csv_to_dataverse_json.py::TestUTF8Encoding -v +``` + +**Run a specific test:** +```bash +python -m pytest test_csv_to_dataverse_json.py::TestIntegration::test_full_conversion_with_all_fields -v +``` + +## Test Structure + +The test suite is organized into the following test classes: + +### TestUTF8Encoding +Tests UTF-8 character encoding handling. + +- `test_utf8_french_text_in_csv` - Verifies French accented characters are preserved +- `test_utf8_special_characters` - Tests various special characters (Japanese, Chinese, Arabic, etc.) + +### TestParseCompound +Tests the `parse_compound()` function for parsing compound field values. + +- `test_parse_author_single` - Single author with name and affiliation +- `test_parse_author_multiple` - Multiple authors separated by pipe +- `test_parse_contact_with_email` - Contact with name, affiliation, and email +- `test_parse_empty_subfields` - Handling of empty subfield values + +### TestEnsureRequiredFields +Tests the `ensure_required_fields()` function for adding missing required fields. + +- `test_adds_default_author_when_missing` - Adds default author when none provided +- `test_uses_depositor_as_fallback_author` - Uses depositor field as fallback +- `test_adds_default_contact_email_when_missing` - Adds default contact email +- `test_adds_default_description_when_missing` - Adds default description + +### TestIntegration +Integration tests for the complete conversion process. + +- `test_full_conversion_with_all_fields` - Tests conversion with various field types including custom subfield headers +- `test_conversion_with_defaults` - Tests conversion with default values +- `test_multiple_rows_conversion` - Tests converting CSV with multiple rows + +### TestGeospatialBlock +Tests the `create_geospatial_block()` function. + +- `test_create_geospatial_block_with_coverage` - Creates geospatial block with geographic coverage +- `test_create_geospatial_block_returns_none_when_empty` - Returns None when no geospatial fields present + +### TestSocialscienceBlock +Tests the `create_socialscience_block()` function. + +- `test_create_socialscience_block_with_unit` - Creates social science block with unit of analysis + +## Test Features + +### Custom Subfield Headers +The converter supports CSV headers with custom subfield specifications: + +```csv +"author: authorName; authorAffiliation","keyword: keywordValue; keywordVocabulary" +``` + +This allows you to specify which subfields to use for compound fields directly in the CSV header. + +### Temporary Files +Tests use Python's `tempfile` module to create temporary CSV and JSON files, which are automatically cleaned up after each test. + +### UTF-8 Encoding +All tests verify that UTF-8 encoding is properly preserved throughout the conversion process. + +## Expected Test Results + +When all tests pass, you should see: + +``` +============================= test session starts ============================== +... +test_csv_to_dataverse_json.py::TestUTF8Encoding::test_utf8_french_text_in_csv PASSED +test_csv_to_dataverse_json.py::TestUTF8Encoding::test_utf8_special_characters PASSED +... +============================== 16 passed in X.XXs ============================== +``` + +## Troubleshooting + +**Import errors:** +Make sure `csv_to_dataverse_json.py` is in the same directory as the test file, or install the package in development mode: +```bash +pip install --break-system-packages -e . +``` + +**Missing dependencies:** +Install pytest and pandas: +```bash +pip install --break-system-packages pytest pandas +``` + +**Permission errors:** +If you get permission errors, use the `--break-system-packages` flag or create a virtual environment: +```bash +python3 -m venv venv +source venv/bin/activate +pip install pytest pandas +``` + +## Writing New Tests + +To add new tests: + +1. Create a new test class or add to an existing one +2. Test method names must start with `test_` +3. Use descriptive docstrings to explain what the test does +4. Clean up any temporary files in a `finally` block +5. Use assertions to verify expected behavior + +Example: +```python +class TestMyFeature: + """Test description""" + + def test_my_new_feature(self): + """Test that my feature works correctly""" + # Arrange + input_data = "test" + + # Act + result = my_function(input_data) + + # Assert + assert result == "expected_output" +``` diff --git a/csv_to_dataverse_json.py b/csv_to_dataverse_json.py index 3be34ed..65ba616 100644 --- a/csv_to_dataverse_json.py +++ b/csv_to_dataverse_json.py @@ -418,6 +418,25 @@ def format_date_to_year(date_value): # Read CSV file and process each row df = pd.read_csv(csv_file_path) + + # Parse column headers to handle "field: subfield1; subfield2" format + column_mapping = {} # Maps actual column name to (base_field_name, custom_subfields) + for col in df.columns: + if ': ' in col: + # Extract base field name and custom subfields + base_field, subfields_str = col.split(': ', 1) + base_field = base_field.strip() + custom_subfields = [s.strip() for s in subfields_str.split(';') if s.strip()] + column_mapping[col] = (base_field, custom_subfields) + else: + column_mapping[col] = (col.strip(), None) + + # Create a temporary compound_fields_override for this CSV + compound_fields_override = compound_fields.copy() + for col, (base_field, custom_subfields) in column_mapping.items(): + if custom_subfields and base_field in directory: + compound_fields_override[base_field] = custom_subfields + all_datasets = [] for idx, row in df.iterrows(): @@ -653,19 +672,25 @@ def format_date_to_year(date_value): fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] - # Process each metadata field - for field_name, field_config in directory.items(): - if ( - field_name not in row - or pd.isna(row[field_name]) - or row[field_name] == "" - ): + # Process each CSV column + for col in df.columns: + # Get the base field name and whether it has custom subfields + base_field, custom_subfields = column_mapping.get(col, (col, None)) + + # Check if this field is in our directory + if base_field not in directory: + continue + + # Check if row has a value for this column + if col not in row or pd.isna(row[col]) or row[col] == "": continue - value = str(row[field_name]).strip() + value = str(row[col]).strip() if not value: continue + field_config = directory[base_field] + # Build field structure field_entry = { "typeName": field_config["typeName"], @@ -676,7 +701,7 @@ def format_date_to_year(date_value): # Process based on type if field_config["typeClass"] == "primitive": # Convert date fields to year-only format - if field_name in [ + if base_field in [ "productionDate", "distributionDate", "dateOfDeposit", @@ -698,9 +723,9 @@ def format_date_to_year(date_value): ] elif field_config["typeClass"] == "compound": - # Compound: parse with subfields + # Compound: parse with subfields (use override if custom subfields specified) field_entry["value"] = parse_compound( - value, field_name, compound_fields + value, base_field, compound_fields_override ) # Add to fields list diff --git a/pyproject.toml b/pyproject.toml index c3d3c0e..8482349 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,8 @@ dependencies = [ [project.optional-dependencies] dev = [ "black>=24.0.0", + "pytest>=7.0.0", + "pytest-cov>=4.0.0", ] [project.scripts] diff --git a/test_csv_to_dataverse_json.py b/test_csv_to_dataverse_json.py new file mode 100644 index 0000000..338f75d --- /dev/null +++ b/test_csv_to_dataverse_json.py @@ -0,0 +1,459 @@ +# -*- coding: utf-8 -*- +""" +Tests for CSV to Dataverse JSON Converter +""" + +import os +import json +import tempfile +import pytest +import pandas as pd +from csv_to_dataverse_json import ( + ensure_required_fields, + parse_compound, + csv_to_dataverse_json, + create_geospatial_block, + create_socialscience_block, +) + + +class TestUTF8Encoding: + """Test UTF-8 encoding handling""" + + def test_utf8_french_text_in_csv(self): + """Test that French UTF-8 characters are correctly processed""" + # Create a temporary CSV file with French text + csv_content = """title,notesText +"Test Dataset","L'enquête a révélé que plusieurs ménages n'avaient pas reçu le courriel initial à cause d'une défaillance technique." +""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".csv", delete=False, encoding="utf-8" + ) as f: + f.write(csv_content) + csv_path = f.name + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as f: + json_path = f.name + + try: + # Convert CSV to JSON + csv_to_dataverse_json(csv_path, json_path) + + # Read the output JSON + with open(json_path, "r", encoding="utf-8") as f: + result = json.load(f) + + # Check that the French text is preserved + notes_field = None + for field in result["datasetVersion"]["metadataBlocks"]["citation"][ + "fields" + ]: + if field["typeName"] == "notesText": + notes_field = field + break + + assert notes_field is not None + assert ( + "L'enquête a révélé" + in notes_field["value"] + ) + assert "ménages" in notes_field["value"] + assert "défaillance" in notes_field["value"] + + finally: + # Cleanup + os.unlink(csv_path) + os.unlink(json_path) + + def test_utf8_special_characters(self): + """Test various UTF-8 special characters""" + special_chars = "Ñoño: café, naïve, 日本語, 中文, العربية" + csv_content = f"""title,notesText +"Test Dataset","{special_chars}" +""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".csv", delete=False, encoding="utf-8" + ) as f: + f.write(csv_content) + csv_path = f.name + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as f: + json_path = f.name + + try: + csv_to_dataverse_json(csv_path, json_path) + + with open(json_path, "r", encoding="utf-8") as f: + result = json.load(f) + + notes_field = None + for field in result["datasetVersion"]["metadataBlocks"]["citation"][ + "fields" + ]: + if field["typeName"] == "notesText": + notes_field = field + break + + assert notes_field is not None + assert special_chars in notes_field["value"] + + finally: + os.unlink(csv_path) + os.unlink(json_path) + + +class TestParseCompound: + """Test parse_compound function""" + + def test_parse_author_single(self): + """Test parsing single author with name and affiliation""" + compound_fields = { + "author": ["authorName", "authorAffiliation"], + } + + result = parse_compound( + "John Smith; Harvard University", "author", compound_fields + ) + + assert len(result) == 1 + assert result[0]["authorName"]["value"] == "John Smith" + assert result[0]["authorAffiliation"]["value"] == "Harvard University" + + def test_parse_author_multiple(self): + """Test parsing multiple authors separated by pipe""" + compound_fields = { + "author": ["authorName", "authorAffiliation"], + } + + result = parse_compound( + "John Smith; Harvard University | Jane Doe; MIT", "author", compound_fields + ) + + assert len(result) == 2 + assert result[0]["authorName"]["value"] == "John Smith" + assert result[0]["authorAffiliation"]["value"] == "Harvard University" + assert result[1]["authorName"]["value"] == "Jane Doe" + assert result[1]["authorAffiliation"]["value"] == "MIT" + + def test_parse_contact_with_email(self): + """Test parsing dataset contact with name, affiliation, and email""" + compound_fields = { + "datasetContact": [ + "datasetContactName", + "datasetContactAffiliation", + "datasetContactEmail", + ], + } + + result = parse_compound( + "Contact Person; University; contact@email.com", + "datasetContact", + compound_fields, + ) + + assert len(result) == 1 + assert result[0]["datasetContactName"]["value"] == "Contact Person" + assert result[0]["datasetContactAffiliation"]["value"] == "University" + assert result[0]["datasetContactEmail"]["value"] == "contact@email.com" + + def test_parse_empty_subfields(self): + """Test parsing with empty subfields""" + compound_fields = { + "keyword": ["keywordValue", "keywordVocabulary"], + } + + result = parse_compound("sample; | test;", "keyword", compound_fields) + + assert len(result) == 2 + assert result[0]["keywordValue"]["value"] == "sample" + # keywordVocabulary should be omitted when empty + assert "keywordVocabulary" not in result[0] + + +class TestEnsureRequiredFields: + """Test ensure_required_fields function""" + + def test_adds_default_author_when_missing(self): + """Test that default author is added when missing""" + dataset_json = { + "datasetVersion": {"metadataBlocks": {"citation": {"fields": []}}} + } + row = pd.Series({}) + defaults = {"author": "Default Author"} + + ensure_required_fields(dataset_json, row, defaults) + + fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + author_field = next((f for f in fields if f["typeName"] == "author"), None) + + assert author_field is not None + assert author_field["value"][0]["authorName"]["value"] == "Default Author" + + def test_uses_depositor_as_fallback_author(self): + """Test that depositor is used as fallback author""" + dataset_json = { + "datasetVersion": {"metadataBlocks": {"citation": {"fields": []}}} + } + row = pd.Series({"depositor": "Jane Depositor"}) + + ensure_required_fields(dataset_json, row) + + fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + author_field = next((f for f in fields if f["typeName"] == "author"), None) + + assert author_field is not None + assert author_field["value"][0]["authorName"]["value"] == "Jane Depositor" + + def test_adds_default_contact_email_when_missing(self): + """Test that default contact email is added when missing""" + dataset_json = { + "datasetVersion": {"metadataBlocks": {"citation": {"fields": []}}} + } + row = pd.Series({}) + defaults = {"email": "default@example.com"} + + ensure_required_fields(dataset_json, row, defaults) + + fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + contact_field = next( + (f for f in fields if f["typeName"] == "datasetContact"), None + ) + + assert contact_field is not None + assert ( + contact_field["value"][0]["datasetContactEmail"]["value"] + == "default@example.com" + ) + + def test_adds_default_description_when_missing(self): + """Test that default description is added when missing""" + dataset_json = { + "datasetVersion": {"metadataBlocks": {"citation": {"fields": []}}} + } + row = pd.Series({}) + defaults = {"description": "Default description"} + + ensure_required_fields(dataset_json, row, defaults) + + fields = dataset_json["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + desc_field = next( + (f for f in fields if f["typeName"] == "dsDescription"), None + ) + + assert desc_field is not None + assert ( + desc_field["value"][0]["dsDescriptionValue"]["value"] + == "Default description" + ) + + +class TestIntegration: + """Integration tests for the full conversion process""" + + def test_full_conversion_with_all_fields(self): + """Test complete conversion with various field types""" + csv_content = """title,subtitle,"author: authorName; authorAffiliation",subject,"keyword: keywordValue; keywordVocabulary" +"Research Dataset","Subtitle","John Smith; University | Jane Doe; MIT","Social Sciences","research; | data;" +""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".csv", delete=False, encoding="utf-8" + ) as f: + f.write(csv_content) + csv_path = f.name + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as f: + json_path = f.name + + try: + csv_to_dataverse_json(csv_path, json_path) + + with open(json_path, "r", encoding="utf-8") as f: + result = json.load(f) + + # Verify top-level structure + assert "id" in result + assert "identifier" in result + assert "datasetVersion" in result + + # Verify citation metadata + fields = result["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + + # Check title + title_field = next((f for f in fields if f["typeName"] == "title"), None) + assert title_field is not None + assert title_field["value"] == "Research Dataset" + + # Check subtitle + subtitle_field = next( + (f for f in fields if f["typeName"] == "subtitle"), None + ) + assert subtitle_field is not None + assert subtitle_field["value"] == "Subtitle" + + # Check authors + author_field = next((f for f in fields if f["typeName"] == "author"), None) + assert author_field is not None + assert len(author_field["value"]) == 2 + assert author_field["value"][0]["authorName"]["value"] == "John Smith" + assert ( + author_field["value"][0]["authorAffiliation"]["value"] == "University" + ) + + # Check subject + subject_field = next( + (f for f in fields if f["typeName"] == "subject"), None + ) + assert subject_field is not None + assert "Social Sciences" in subject_field["value"] + + finally: + os.unlink(csv_path) + os.unlink(json_path) + + def test_conversion_with_defaults(self): + """Test conversion with default values""" + csv_content = """title +"Minimal Dataset" +""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".csv", delete=False, encoding="utf-8" + ) as f: + f.write(csv_content) + csv_path = f.name + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as f: + json_path = f.name + + try: + defaults = { + "author": "Test Author", + "email": "test@example.com", + "description": "Test description", + } + csv_to_dataverse_json(csv_path, json_path, defaults) + + with open(json_path, "r", encoding="utf-8") as f: + result = json.load(f) + + fields = result["datasetVersion"]["metadataBlocks"]["citation"]["fields"] + + # Check that defaults were applied + author_field = next((f for f in fields if f["typeName"] == "author"), None) + assert author_field is not None + assert author_field["value"][0]["authorName"]["value"] == "Test Author" + + contact_field = next( + (f for f in fields if f["typeName"] == "datasetContact"), None + ) + assert contact_field is not None + assert ( + contact_field["value"][0]["datasetContactEmail"]["value"] + == "test@example.com" + ) + + finally: + os.unlink(csv_path) + os.unlink(json_path) + + def test_multiple_rows_conversion(self): + """Test converting CSV with multiple rows""" + csv_content = """title,subject +"Dataset 1","Social Sciences" +"Dataset 2","Medicine, Health and Life Sciences" +""" + with tempfile.NamedTemporaryFile( + mode="w", suffix=".csv", delete=False, encoding="utf-8" + ) as f: + f.write(csv_content) + csv_path = f.name + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as f: + json_path = f.name + + try: + csv_to_dataverse_json(csv_path, json_path) + + with open(json_path, "r", encoding="utf-8") as f: + result = json.load(f) + + # Should be a list of datasets + assert isinstance(result, list) + assert len(result) == 2 + + # Check first dataset + fields1 = result[0]["datasetVersion"]["metadataBlocks"]["citation"][ + "fields" + ] + title1 = next((f for f in fields1 if f["typeName"] == "title"), None) + assert title1["value"] == "Dataset 1" + + # Check second dataset + fields2 = result[1]["datasetVersion"]["metadataBlocks"]["citation"][ + "fields" + ] + title2 = next((f for f in fields2 if f["typeName"] == "title"), None) + assert title2["value"] == "Dataset 2" + + finally: + os.unlink(csv_path) + os.unlink(json_path) + + +class TestGeospatialBlock: + """Test geospatial metadata block creation""" + + def test_create_geospatial_block_with_coverage(self): + """Test creating geospatial block with geographic coverage""" + row = pd.Series({"geographicCoverage": "Canada | United States"}) + + result = create_geospatial_block(row) + + assert result is not None + assert result["name"] == "geospatial" + assert "fields" in result + # Check that geographicCoverage field exists + geo_field = next( + (f for f in result["fields"] if f["typeName"] == "geographicCoverage"), + None, + ) + assert geo_field is not None + assert len(geo_field["value"]) == 2 + assert geo_field["value"][0]["country"]["value"] == "Canada" + + def test_create_geospatial_block_returns_none_when_empty(self): + """Test that None is returned when no geospatial fields present""" + row = pd.Series({"title": "Test Dataset"}) + + result = create_geospatial_block(row) + + assert result is None + + +class TestSocialscienceBlock: + """Test social science metadata block creation""" + + def test_create_socialscience_block_with_unit(self): + """Test creating social science block with unit of analysis""" + row = pd.Series({"unitOfAnalysis": "Individual"}) + + result = create_socialscience_block(row) + + assert result is not None + assert result["name"] == "socialscience" + assert "fields" in result + # Check that unitOfAnalysis field exists + unit_field = next( + (f for f in result["fields"] if f["typeName"] == "unitOfAnalysis"), None + ) + assert unit_field is not None + assert "Individual" in unit_field["value"]