瀏覽代碼

Workflow angepasst - Upload ohne Anmeldung, nur mit vorhandenem Token

gc-server3 1 周之前
父節點
當前提交
dbb8f2173d
共有 9 個文件被更改,包括 194 次插入29 次删除
  1. 37 0
      README.md
  2. 1 1
      config/nasa_config.crypt
  3. 1 1
      mazda_archive.py
  4. 1 1
      mazda_export.py
  5. 22 5
      mazda_upload.py
  6. 2 21
      mazda_webservice.py
  7. 68 0
      mazda_workflow.py
  8. 1 0
      pyproject.toml
  9. 61 0
      uv.lock

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# Mazda-Schnittstellen
+
+## Installation
+
+Zunächst muss `uv`, der Python-Paketmanager, installiert werden:
+
+```cmd
+powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
+```
+
+Im Anschluss git-Repository klonen (am Beispiel von Ordner `C:\Projekte` - im Grunde beliebig):
+
+```cmd
+cd C:\Projekte
+git clone https://git.global-cube.com/GlobalCube/mazda.git
+```
+
+Git erstellt einen Unterordner `C:\Projekte\mazda`, in dem das gesamte Projekt abgelegt ist.
+Jetzt müssen alle Requirements erfüllt werden: passende Python-Version, benötigte Module mit entsprechenden Abhängigkeiten.
+Das übernimmt `uv`
+
+```cmd
+cd C:\Projekte\mazda
+uv run main.py
+```
+
+Das Repository ist nun bereit für die Bearbeitung.
+
+## NASA-Schnittstelle
+
+lorem ipsum
+
+## DSR-Schnittstelle
+
+[https://mapps.mazdaeur.com/dsr/](https://mapps.mazdaeur.com/dsr/)
+
+[https://mapps.mazdaeur.com/dogma-restapi-dms/api/swagger-ui.html](https://mapps.mazdaeur.com/dogma-restapi-dms/api/swagger-ui.html)

+ 1 - 1
config/nasa_config.crypt

@@ -1 +1 @@
-gAAAAABoQWIa4roqF0jn7s8lJl-3Q5AIZAIyilEjIbqbt1I6EM1iEM2W6KCBObaD2O0NX8WHOk0R-6V_H7imTREMreewkJxymTdxDKIS025QA_tMTfYR8fm745mjRjSjHYCoTPlV8RZe9iQU5m_uVpSazVM9d5ptZrT4K_zehJT1f-DOZQxQg4WB4WEPGCD4n9Pt_43X3Smr7P7J3MBnFYd6BNzmY81g8kkxGCs6-HcEzughCsa4tDDQXflTKc8PPlj26anlZBiVmHNxqQEl6PMHWSn_WpFxOX8o11DoceqqdLLKRmHo8RDeokavBqNwRf5ueOpLC7OuxuzFfm8cYZ2QFfES9SmjoGUKEYdVrHZHv3M1-wu6zHnH83IBA_NSeBLmJPGjV6HKgcDM0H0DhgzkVnIvvyFoVRcbBQui2AZqK8BGsaq_nOs0yaTS1KnzIwSLiP04JhNbK3KI8zEXBdpL8xt1sCnb7JbxNBxuzEzpQt3565_35qFQBLfZI1uyR7RVRYew0Qv4DzZNyeM_usa-VAj9Z4TyK-uRmUOORPc_duSQXxnCy3qH1w78BkDfba1ga26On20o
+gAAAAABoQWhOqEGNW6izttbDhgdYx0rENdx2jCFf87bj7laMc0oLM2lj2hKOJ0zChhZK6Xz1AXTU1zoO2I8TV0Sn9PxcvE6WhPOd5NfN5BIgZ1VaJ9CD130C7DvxKxWoTtb_uEzq2hHfTq-Ec1zUEpaAbRCcE2G_keZQXJq0wxVxLXfq6xF8HQoyOgpZ6jIfhYm91JyykCJV3o5VIF2WOJhoUg8Mg1RfidJgOHsjJMkQudmxcngLuBXEzWEIdomLaaPkSXstn6bmjJ9LHlEhAoMfdGVYeV2Fsmr_bm0fQrgkyYVqJv3AvSL0p9krPV21eSsWMQ44U66IEb5_zvw5iEHnED6bKkKdDZw9qnqzyLv6nzacNWO2ylRoQbH6bXC1au5UjpXgPfc6XKTHBWpoNog2gdTXe0ElRJsgjDzsZF9Fl3iVCcD95k5neAIyaDtWqalKkRuxDdSOFuXZhh0QIoNIV6SAOkcmtDLOVK9pEZ5UYeJkbPZVqbjGy4EhSZejNvevr5wjVHBivlpUq29DrvH7Fp0KlD6l9OyADlf4BEpF9YnAtxeln5J5Wc1JJQxCI14JXEUEpWJC

+ 1 - 1
mazda_archive.py

@@ -22,7 +22,7 @@ def calculate_sha256(data: str) -> str:
 def archive_files(export_dir: str):
     last_week = (datetime.now() - timedelta(days=6)).timestamp()
     for file in Path(export_dir).glob("*.json"):
-        if file.stat().st_ctime < last_week:
+        if file.stat().st_birthtime < last_week:
             file.unlink()
 
     archive_path = Path(export_dir + "/Archiv")

+ 1 - 1
mazda_export.py

@@ -177,7 +177,7 @@ def export_orders(data, export_dir):
         data["orders"] = [order]
         order_no = order["orderNumber"]
         data["invoices"] = [i for i in invoices if i["invoiceItems"][0]["orderNumber"] == order_no]
-        json.dump(data, open(f"{export_dir}/order-report_{order_no}_{period}_{timestamp}.json", "w"), indent=2)
+        json.dump(data, open(f"{export_dir}\\order-report_{order_no}_{period}_{timestamp}.json", "w"), indent=2)
 
 
 def main():

+ 22 - 5
mazda_upload.py

@@ -7,6 +7,23 @@ from requests_oauthlib import OAuth2Session
 from mazda_export import convert_csv
 
 
+@dataclass
+class Token:
+    access_token: str
+    token_type: str
+    refresh_token: str
+    expires_in: int
+    sub: str
+    iss: str
+    iat: int
+    defaultDomain: str
+    userGuid: str
+    organisations: list[str]
+    jti: str
+    scope: list[str]
+    expires_at: float
+
+
 @dataclass
 class MazdaConfig:
     domain: str
@@ -75,7 +92,7 @@ def upload(cfg: MazdaConfig, oauth: OAuth2Session, data):
                 fwh.write(r.content)
 
 
-def upload_files(cfg: MazdaConfig, oauth: OAuth2Session):
+def upload_files(cfg: MazdaConfig, oauth: OAuth2Session, export_dir: str):
     headers = {
         "accept": "application/vnd.mazdaeur.dms.v5+json",
         "x-mme-organisation": cfg.dealer_number,
@@ -85,7 +102,7 @@ def upload_files(cfg: MazdaConfig, oauth: OAuth2Session):
     result = []
     print("Upload Files")
 
-    for order_file in Path(base_dir + "export/Mazda").glob("*.json"):
+    for order_file in Path(export_dir).glob("*.json"):
         data = json.load(order_file.open("r"))
 
         r = oauth.post(cfg.domain + cfg.webservice + cfg.module, json.dumps(data), headers=headers)
@@ -94,11 +111,11 @@ def upload_files(cfg: MazdaConfig, oauth: OAuth2Session):
         if r.status_code in [200, 202]:
             # OK
             # order_file.unlink()
-            order_file.rename(base_dir + "export/Mazda/success/" + order_file.name)
+            order_file.rename(f"{export_dir}\\success\\{order_file.name}")
         else:
-            with open(base_dir + f"logs/{order_file.name}.log", "wb") as fwh:
+            with open(f"{base_dir}\\logs\\{order_file.name}.log", "wb") as fwh:
                 fwh.write(r.content)
-            order_file.rename(base_dir + "export/Mazda/error/" + order_file.name)
+            order_file.rename(f"{export_dir}\\error\\{order_file.name}")
 
     return result
 

+ 2 - 21
mazda_webservice.py

@@ -1,6 +1,5 @@
 import json
 import os
-from dataclasses import dataclass
 
 from flask import Flask, redirect, request, session
 from oauthlib.oauth2.rfc6749.errors import OAuth2Error
@@ -10,24 +9,6 @@ import mazda_upload
 
 # from datetime import datetime
 
-
-@dataclass
-class Token:
-    access_token: str
-    token_type: str
-    refresh_token: str
-    expires_in: int
-    sub: str
-    iss: str
-    iat: int
-    defaultDomain: str
-    userGuid: str
-    organisations: list[str]
-    jti: str
-    scope: list[str]
-    expires_at: float
-
-
 app = Flask(__name__)
 
 PROD = True
@@ -42,7 +23,7 @@ def token_save(token):
         json.dump(token, fwh, indent=2)
 
 
-def token_load() -> Token:
+def token_load() -> mazda_upload.Token:
     try:
         with open(base_dir + "temp/token.json", "r") as frh:
             return json.load(frh)
@@ -50,7 +31,7 @@ def token_load() -> Token:
         return None
 
 
-def get_token() -> Token:
+def get_token() -> mazda_upload.Token:
     if session.get("oauth_token") is None:
         session["oauth_token"] = token_load()
 

+ 68 - 0
mazda_workflow.py

@@ -0,0 +1,68 @@
+import json
+import os
+from datetime import datetime
+from pathlib import Path
+
+from requests_oauthlib import OAuth2Session
+
+import mazda_upload
+from mazda_archive import archive_files
+from mazda_export import convert_csv, export_orders
+
+
+def get_config():
+    base_dir = str(Path.cwd())
+    cfg = {
+        "timestamp": "",
+        "base_dir": base_dir,
+        "data_dir": base_dir + "\\data",
+        "export_dir": base_dir + "\\export\\Mazda",
+        "temp_dir": base_dir + "\\export\\Mazda\\temp",
+    }
+    os.makedirs(cfg["temp_dir"], exist_ok=True)
+    os.makedirs(cfg["data_dir"], exist_ok=True)
+    os.makedirs(f"{cfg["export_dir"]}\\error", exist_ok=True)
+    os.makedirs(f"{cfg["export_dir"]}\\success", exist_ok=True)
+    os.makedirs(f"{cfg["base_dir"]}\\logs", exist_ok=True)
+    os.makedirs(f"{cfg["base_dir"]}\\temp", exist_ok=True)
+    return cfg
+
+
+def token_load(config) -> mazda_upload.Token:
+    try:
+        with open(config["base_dir"] + "\\temp\\token.json", "r") as frh:
+            return json.load(frh)
+    except FileNotFoundError:
+        return None
+
+
+def submit_changes(config):
+    oauth = OAuth2Session(mazda_upload.cfg_prod.client_id, token=token_load(config))
+    if oauth.authorized:
+        mazda_upload.upload_files(mazda_upload.cfg_prod, oauth, config["export_dir"])
+    else:
+        print("OAuth2 session not authorized. Please check your credentials.")
+        return
+
+
+def main():
+    config = get_config()
+    dt = datetime.now()
+    config["timestamp"] = dt.strftime("%Y%m%d_%H%M%S")
+    print("Start: " + dt.strftime("%d.%m.%Y %H:%M:%S"))
+
+    print("Export")
+    data = convert_csv(
+        config["data_dir"] + "\\Workshop_Order_Report.csv", config["temp_dir"] + "\\mazda_export.json", 2024, 1
+    )
+    export_orders(data, config["temp_dir"])
+
+    print("Archivierung")
+    archive_files(config["export_dir"])
+
+    print("Upload")
+    submit_changes(config)
+
+
+if __name__ == "__main__":
+    main()

+ 1 - 0
pyproject.toml

@@ -5,6 +5,7 @@ description = "Add your description here"
 readme = "README.md"
 requires-python = ">=3.13"
 dependencies = [
+    "black>=25.1.0",
     "cryptography>=45.0.3",
     "flask>=3.1.1",
     "numpy>=2.2.6",

+ 61 - 0
uv.lock

@@ -2,6 +2,26 @@ version = 1
 revision = 1
 requires-python = ">=3.13"
 
+[[package]]
+name = "black"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "mypy-extensions" },
+    { name = "packaging" },
+    { name = "pathspec" },
+    { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 },
+    { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 },
+    { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 },
+    { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 },
+    { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 },
+]
+
 [[package]]
 name = "blinker"
 version = "1.9.0"
@@ -237,6 +257,7 @@ name = "mazda"
 version = "0.1.0"
 source = { virtual = "." }
 dependencies = [
+    { name = "black" },
     { name = "cryptography" },
     { name = "flask" },
     { name = "numpy" },
@@ -252,6 +273,7 @@ dependencies = [
 
 [package.metadata]
 requires-dist = [
+    { name = "black", specifier = ">=25.1.0" },
     { name = "cryptography", specifier = ">=45.0.3" },
     { name = "flask", specifier = ">=3.1.1" },
     { name = "numpy", specifier = ">=2.2.6" },
@@ -274,6 +296,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
 ]
 
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
+]
+
 [[package]]
 name = "numpy"
 version = "2.2.6"
@@ -311,6 +342,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 },
 ]
 
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
+]
+
 [[package]]
 name = "pandas"
 version = "2.3.0"
@@ -324,17 +364,38 @@ dependencies = [
 sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490 }
 wheels = [
     { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913 },
+    { url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249 },
     { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359 },
     { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789 },
+    { url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734 },
     { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381 },
     { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135 },
     { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356 },
     { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674 },
     { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876 },
     { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182 },
+    { url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686 },
     { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847 },
 ]
 
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 },
+]
+
 [[package]]
 name = "pyarrow"
 version = "20.0.0"