From bf07951ba75cfba3ead43e30a1e5c2ce4c2e56c2 Mon Sep 17 00:00:00 2001 From: Stig Ofstad Date: Fri, 20 Sep 2024 13:01:30 +0200 Subject: [PATCH] fix: replace xlrd with pandas --- ...f-the-standard-genetic-algorithm-GA-33.png | Bin 0 -> 41094 bytes api/poetry.lock | 135 +++++++++++++++--- api/pyproject.toml | 3 +- api/src/tests/create_product_row_test.py | 12 +- api/src/util/azure_blobs.py | 28 ++-- api/src/util/excel.py | 37 ----- 6 files changed, 139 insertions(+), 76 deletions(-) create mode 100644 api/doc/Flowchart-of-the-standard-genetic-algorithm-GA-33.png delete mode 100644 api/src/util/excel.py diff --git a/api/doc/Flowchart-of-the-standard-genetic-algorithm-GA-33.png b/api/doc/Flowchart-of-the-standard-genetic-algorithm-GA-33.png new file mode 100644 index 0000000000000000000000000000000000000000..22b0e6ead3c25723f5d7262e74696d207fbd99ea GIT binary patch literal 41094 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 oDCy1Pf9^D%3Re{(<+r)Zpc% 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 Dt_+v- literal 0 HcmV?d00001 diff --git a/api/poetry.lock b/api/poetry.lock index f8d5e29..5c772f3 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -568,6 +568,17 @@ files = [ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + [[package]] name = "flask" version = "3.0.3" @@ -1060,6 +1071,20 @@ files = [ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + [[package]] name = "packaging" version = "24.0" @@ -1071,6 +1096,75 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pandas" +version = "2.2.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pathspec" version = "0.12.1" @@ -1300,6 +1394,17 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -1325,6 +1430,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1460,6 +1566,17 @@ files = [ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "urllib3" version = "2.2.1" @@ -1494,23 +1611,7 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] -[[package]] -name = "xlrd" -version = "2.0.1" -description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, - {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, -] - -[package.extras] -build = ["twine", "wheel"] -docs = ["sphinx"] -test = ["pytest", "pytest-cov"] - [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "604120036546c26b2947af0d4c11942eadfe72c3ba4582ca82f3b620cc532b75" +content-hash = "7ad73acbffc941c86463f3fd965daee40a5db1db962dc12caf84c6e57fa4059b" diff --git a/api/pyproject.toml b/api/pyproject.toml index 158f508..9e44754 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -9,13 +9,14 @@ license = "MIT" python = "^3.12" azure-cosmosdb-table = "^1.0.6" numpy = "^1.26.2" -xlrd = "^2.0.1" azure-storage-blob = "^12.19.0" PyJWT = "^2.8.0" cachetools = "^5.3.2" matplotlib = "^3.8.2" gunicorn = "^21.2.0" Flask = "^3.0.0" +pandas = "^2.2.2" +openpyxl = "^3.1.5" [tool.poetry.dev-dependencies] pytest = "^7.1.3" diff --git a/api/src/tests/create_product_row_test.py b/api/src/tests/create_product_row_test.py index 61e14d5..a5a86a5 100644 --- a/api/src/tests/create_product_row_test.py +++ b/api/src/tests/create_product_row_test.py @@ -1,20 +1,16 @@ import unittest -from unittest import skip from tests.utils import read_file from util.azure_table import process_meta_blob -from util.excel import excel_raw_file_to_sheet, sheet_to_bridge_dict +from util.excel import excel_bytes_to_dataframe -@skip("No time to fix") class CreateProductTableRow(unittest.TestCase): @staticmethod def test_create_row(): with open("src/test_data/metadata.csv") as meta_file: - productdata = process_meta_blob(meta_file) + process_meta_blob(meta_file) product_bridge_file = read_file("src/test_data/flow-carb10.xlsx") - product_sheet = excel_raw_file_to_sheet(product_bridge_file) - product_data = sheet_to_bridge_dict(product_sheet) - - productdata[0]["cumulative"] = product_data["cumulative"] + product_df = excel_bytes_to_dataframe(product_bridge_file) + product_df.Cumulative.to_list() diff --git a/api/src/util/azure_blobs.py b/api/src/util/azure_blobs.py index 17a1668..db07252 100644 --- a/api/src/util/azure_blobs.py +++ b/api/src/util/azure_blobs.py @@ -3,11 +3,10 @@ from pathlib import Path from azure.storage.blob import BlobProperties, ContainerClient -from xlrd.sheet import Sheet +from pandas import DataFrame, read_excel from config import Config from util.azure_table import process_meta_blob, sanitize_row_key -from util.excel import excel_raw_file_to_sheet, sheet_to_bridge_dict def get_container_client() -> ContainerClient: @@ -18,17 +17,25 @@ def get_container_client() -> ContainerClient: ) -def from_blobs_to_excel(blobs: Iterator[BlobProperties], container_client: ContainerClient) -> dict[str, Sheet]: - sheets = {} +def excel_bytes_to_dataframe(file: bytes) -> DataFrame: + file_io = io.BytesIO(file) + df = read_excel(file_io) + return df + + +def from_excel_blobs_to_data_frame( + blobs: Iterator[BlobProperties], container_client: ContainerClient +) -> dict[str, DataFrame]: + products: dict[str, DataFrame] = {} for blob in blobs: if Path(blob.name).suffix != ".xlsx": continue blob_client = container_client.get_blob_client(blob) raw_blob = blob_client.download_blob().readall() product_id = sanitize_row_key(Path(blob.name).stem) - sheets[product_id] = excel_raw_file_to_sheet(raw_blob) + products[product_id] = excel_bytes_to_dataframe(raw_blob) - return sheets + return products def get_metadata_blob_data() -> list[dict]: @@ -41,10 +48,5 @@ def get_metadata_blob_data() -> list[dict]: def get_product_blobs_data() -> dict[str, dict]: container_client = get_container_client() all_blobs = container_client.list_blobs() - sheets = from_blobs_to_excel(all_blobs, container_client) - - table_data = {} - for filename, sheet in sheets.items(): - table_data[filename] = sheet_to_bridge_dict(sheet) - - return table_data + dfs = from_excel_blobs_to_data_frame(all_blobs, container_client) + return {filename: {"cumulative": data_frame.Cumulative.to_list()} for filename, data_frame in dfs.items()} diff --git a/api/src/util/excel.py b/api/src/util/excel.py deleted file mode 100644 index 4715ecc..0000000 --- a/api/src/util/excel.py +++ /dev/null @@ -1,37 +0,0 @@ -import xlrd -from xlrd.sheet import Sheet - - -def get_column_values(sheet: Sheet, column_name: str) -> list: - column_index = _find_column_index(sheet, column_name) - - if column_index == -1: - raise Exception(f"Sheet does not contain column {column_name}") - - values = [] - for i in range(1, sheet.nrows): - values.append(sheet.cell_value(i, column_index)) - return values - - -def _find_column_index(sheet: Sheet, column_name: str) -> int: - distribution_index = -1 - - for i in range(sheet.ncols): - if sheet.cell_value(0, i) == column_name: - distribution_index = i - break - - return distribution_index - - -def excel_raw_file_to_sheet(file: bytes) -> Sheet: - workbook = xlrd.open_workbook(file_contents=file) - sheet = workbook.sheet_by_index(0) - return sheet - - -def sheet_to_bridge_dict(sheet: Sheet) -> dict: - return { - "cumulative": get_column_values(sheet, "Cumulative"), - }