diff --git a/backend/aci/server/app_connectors/microsoft_onedrive.py b/backend/aci/server/app_connectors/microsoft_onedrive.py index decdabec3..a7757cdce 100644 --- a/backend/aci/server/app_connectors/microsoft_onedrive.py +++ b/backend/aci/server/app_connectors/microsoft_onedrive.py @@ -1,5 +1,6 @@ import csv import io +import tempfile from typing import override import requests @@ -161,3 +162,189 @@ def create_excel_from_csv( except Exception as e: logger.error(f"Failed to create CSV file from CSV data: {e}") raise Exception(f"Failed to create CSV file: {e}") from e + + def create_docx_from_markdown( + self, markdown_data: str, parent_folder_id: str, filename: str | None = None + ) -> dict[str, str | int]: + """ + Convert Markdown text to a formatted DOCX document and save it to OneDrive. + Uses the md2docx-python library for robust conversion. + + Args: + markdown_data: The Markdown text as a string to convert + parent_folder_id: The identifier of the parent folder where the DOCX file will be created + filename: Optional custom name for the DOCX file (without .docx extension) + + Returns: + dict: Response containing the created DOCX file metadata + """ + logger.info(f"Creating DOCX file from Markdown on OneDrive, folder: {parent_folder_id}") + + try: + from md2docx_python.src.md2docx_python import markdown_to_word + + # Determine filename + if not filename: + filename = "converted_document" + + # Ensure .docx extension + if not filename.endswith(".docx"): + filename += ".docx" + + # Create temporary files for conversion + with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as md_file: + md_file.write(markdown_data) + md_file_path = md_file.name + + with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as docx_file: + docx_file_path = docx_file.name + + try: + # Convert markdown to DOCX using the well-maintained library + markdown_to_word(md_file_path, docx_file_path) + + # Read the generated DOCX file + with open(docx_file_path, "rb") as docx_file: + docx_bytes = docx_file.read() + + # Upload DOCX file to OneDrive + upload_url = ( + f"{self.base_url}/me/drive/items/{parent_folder_id}:/{filename}:/content" + ) + + headers = { + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + } + + upload_response = requests.put( + upload_url, headers=headers, data=docx_bytes, timeout=60 + ) + upload_response.raise_for_status() + + result = upload_response.json() + + # Count some basic stats for the response + lines = markdown_data.split("\n") + word_count = len(markdown_data.split()) + + logger.info( + f"Successfully created DOCX file: {filename}, ID: {result.get('id', '')}" + ) + + return { + "id": result.get("id", ""), + "name": result.get("name", ""), + "path": result.get("parentReference", {}).get("path", "") + + "/" + + result.get("name", ""), + "size": result.get("size", 0), + "mime_type": result.get("file", {}).get("mimeType", ""), + "created_datetime": result.get("createdDateTime", ""), + "modified_datetime": result.get("lastModifiedDateTime", ""), + "download_url": result.get("@microsoft.graph.downloadUrl", ""), + "lines_converted": len(lines), + "word_count": word_count, + "note": "DOCX file created successfully from Markdown using md2docx-python library.", + } + + finally: + # Clean up temporary files + import os + + try: + os.unlink(md_file_path) + os.unlink(docx_file_path) + except OSError: + pass # Files already cleaned up + + except Exception as e: + logger.error(f"Failed to create DOCX file from Markdown data: {e}") + raise Exception(f"Failed to create DOCX file: {e}") from e + + def read_markdown_from_docx(self, item_id: str) -> dict[str, str | int]: + """ + Convert a DOCX file from OneDrive to Markdown text. + Uses the md2docx-python library for robust conversion. + + Args: + item_id: The identifier of the DOCX file in OneDrive to convert + + Returns: + dict: Response containing the markdown content and metadata + """ + logger.info(f"Converting DOCX file to Markdown from OneDrive: {item_id}") + + try: + from md2docx_python.src.docx2md_python import word_to_markdown + + # Download the DOCX file from OneDrive + download_url = f"{self.base_url}/me/drive/items/{item_id}/content" + headers = {"Authorization": f"Bearer {self.access_token}"} + + download_response = requests.get(download_url, headers=headers, timeout=30) + download_response.raise_for_status() + + # Get file metadata for response details + metadata_url = f"{self.base_url}/me/drive/items/{item_id}" + metadata_response = requests.get(metadata_url, headers=headers, timeout=30) + metadata_response.raise_for_status() + metadata = metadata_response.json() + + # Verify it's a DOCX file + if not metadata.get("name", "").lower().endswith((".docx", ".doc")): + raise Exception(f"File '{metadata.get('name', '')}' is not a Word document") + + # Create temporary files for conversion + with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as docx_file: + docx_file.write(download_response.content) + docx_file_path = docx_file.name + + with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as md_file: + md_file_path = md_file.name + + try: + # Convert DOCX to Markdown using the well-maintained library + word_to_markdown(docx_file_path, md_file_path) + + # Read the generated Markdown file + with open(md_file_path, encoding="utf-8") as md_file: + markdown_content = md_file.read() + + # Count some basic stats for the response + lines = markdown_content.split("\n") + word_count = len(markdown_content.split()) + + logger.info( + f"Successfully converted DOCX to Markdown: {item_id}, {len(markdown_content)} characters" + ) + + return { + "content": markdown_content, + "id": metadata.get("id", ""), + "name": metadata.get("name", ""), + "path": metadata.get("parentReference", {}).get("path", "") + + "/" + + metadata.get("name", ""), + "size": metadata.get("size", 0), + "mime_type": metadata.get("file", {}).get("mimeType", ""), + "created_datetime": metadata.get("createdDateTime", ""), + "modified_datetime": metadata.get("lastModifiedDateTime", ""), + "lines_extracted": len(lines), + "word_count": word_count, + "note": "DOCX file successfully converted to Markdown using md2docx-python library.", + } + + finally: + # Clean up temporary files + import os + + try: + os.unlink(docx_file_path) + os.unlink(md_file_path) + except OSError: + pass # Files already cleaned up + + except Exception as e: + logger.error(f"Failed to convert DOCX file to Markdown: {item_id}, error: {e}") + raise Exception(f"Failed to convert DOCX file: {e}") from e diff --git a/backend/apps/microsoft_onedrive/functions.json b/backend/apps/microsoft_onedrive/functions.json index c2cb85fce..c82084dcf 100644 --- a/backend/apps/microsoft_onedrive/functions.json +++ b/backend/apps/microsoft_onedrive/functions.json @@ -1127,5 +1127,55 @@ "visible": ["csv_data", "parent_folder_id", "filename"], "additionalProperties": false } + }, + { + "name": "MICROSOFT_ONEDRIVE__CREATE_DOCX_FROM_MARKDOWN", + "description": "Convert Markdown text to a formatted DOCX document and save it to OneDrive. Takes Markdown data as a string and creates a properly formatted Word document in the specified parent folder on OneDrive. Supports common Markdown elements like headers, paragraphs, lists, bold, italic, and code blocks.", + "tags": ["convert", "markdown", "docx", "word", "create", "onedrive"], + "visibility": "public", + "active": true, + "protocol": "connector", + "protocol_data": {}, + "parameters": { + "type": "object", + "properties": { + "markdown_data": { + "type": "string", + "description": "The Markdown text as a string to convert to DOCX. Should be properly formatted Markdown with headers, paragraphs, lists, etc." + }, + "parent_folder_id": { + "type": "string", + "description": "The identifier of the parent folder where the new DOCX file will be created. Use 'root' for the root directory or a specific folder item ID." + }, + "filename": { + "type": "string", + "description": "The name for the DOCX file (without .docx extension). If not provided, defaults to 'converted_document'." + } + }, + "required": ["markdown_data", "parent_folder_id"], + "visible": ["markdown_data", "parent_folder_id", "filename"], + "additionalProperties": false + } + }, + { + "name": "MICROSOFT_ONEDRIVE__READ_MARKDOWN_FROM_DOCX", + "description": "Read and extract Markdown content from a DOCX file stored in OneDrive. Downloads the DOCX file and extracts its content as properly formatted Markdown text, preserving headings, lists, formatting, and other document structure.", + "tags": ["convert", "docx", "markdown", "read", "extract", "onedrive"], + "visibility": "public", + "active": true, + "protocol": "connector", + "protocol_data": {}, + "parameters": { + "type": "object", + "properties": { + "item_id": { + "type": "string", + "description": "The identifier of the DOCX file in OneDrive to convert. Use the full item ID as returned by other OneDrive functions (e.g., '7006ADAF2D3C1355!s5625f3cefe4c4fe28f99a4b6f05cb944')." + } + }, + "required": ["item_id"], + "visible": ["item_id"], + "additionalProperties": false + } } ] diff --git a/backend/pyproject.toml b/backend/pyproject.toml index ac4252a81..581200600 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "stripe>=12.1.0", "e2b-code-interpreter>=1.5.0", "browser-use>=0.5.0", + "md2docx-python>=0.1.8", ] [dependency-groups] @@ -141,6 +142,10 @@ ignore_missing_imports = true module = "datasets.*" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "md2docx_python.*" +ignore_missing_imports = true + [tool.pytest.ini_options] log_cli = true log_cli_level = "INFO" diff --git a/backend/uv.lock b/backend/uv.lock index ecddbd97a..99a58869a 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -32,6 +32,7 @@ dependencies = [ { name = "jsonschema" }, { name = "limits" }, { name = "logfire", extra = ["fastapi", "sqlalchemy"] }, + { name = "md2docx-python" }, { name = "openai" }, { name = "openapi-spec-validator" }, { name = "pgvector" }, @@ -88,6 +89,7 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.23.0" }, { name = "limits", specifier = ">=5.2.0" }, { name = "logfire", extras = ["fastapi", "sqlalchemy"], specifier = ">=3.16.0" }, + { name = "md2docx-python", specifier = ">=0.1.8" }, { name = "openai", specifier = ">=1.80.0" }, { name = "openapi-spec-validator", specifier = ">=0.7.1" }, { name = "pgvector", specifier = ">=0.4.1" }, @@ -1545,6 +1547,46 @@ sqlalchemy = [ { name = "opentelemetry-instrumentation-sqlalchemy" }, ] +[[package]] +name = "lxml" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" }, + { url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" }, + { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" }, + { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" }, + { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" }, + { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" }, + { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" }, + { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" }, + { url = "https://files.pythonhosted.org/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" }, + { url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" }, + { url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" }, + { url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" }, + { url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" }, + { url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" }, + { url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" }, + { url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" }, + { url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" }, + { url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -1557,6 +1599,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] +[[package]] +name = "markdown" +version = "3.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1655,6 +1706,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/da/c7eaab6a58f1034de115b7902141ad8f81b4f3bbf7dc0cc267594947a4d7/mcp-1.12.0-py3-none-any.whl", hash = "sha256:19a498b2bf273283e463b4dd1ed83f791fbba5c25bfa16b8b34cfd5571673e7f", size = 158470, upload-time = "2025-07-17T19:46:34.166Z" }, ] +[[package]] +name = "md2docx-python" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "markdown" }, + { name = "python-docx" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/04/e9d465ab990fc2a28821b8d01d710066b32c814e24b3e9565644201d0d65/md2docx_python-1.0.0-py3-none-any.whl", hash = "sha256:96bb93905222d333368ceb0403bf4d1dc80a8bd70f6ae2e8ae6912c575e905d8", size = 7094, upload-time = "2025-03-14T15:47:54.958Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -5305,6 +5369,19 @@ 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 = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.0"