mdengine 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mdengine-0.4.0 → mdengine-0.5.0}/PKG-INFO +47 -7
- {mdengine-0.4.0 → mdengine-0.5.0}/README.md +31 -5
- {mdengine-0.4.0 → mdengine-0.5.0}/pyproject.toml +31 -2
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/__init__.py +1 -1
- mdengine-0.5.0/src/md_generator/db/__init__.py +5 -0
- mdengine-0.5.0/src/md_generator/db/adapters/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/db/adapters/factory.py +40 -0
- mdengine-0.5.0/src/md_generator/db/adapters/mongo_adapter.py +142 -0
- mdengine-0.5.0/src/md_generator/db/adapters/mysql_adapter.py +310 -0
- mdengine-0.5.0/src/md_generator/db/adapters/oracle_adapter.py +330 -0
- mdengine-0.5.0/src/md_generator/db/adapters/postgres_adapter.py +290 -0
- mdengine-0.5.0/src/md_generator/db/adapters/sql_common.py +102 -0
- mdengine-0.5.0/src/md_generator/db/api/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/db/api/main.py +178 -0
- mdengine-0.5.0/src/md_generator/db/api/mcp_server.py +29 -0
- mdengine-0.5.0/src/md_generator/db/api/run.py +15 -0
- mdengine-0.5.0/src/md_generator/db/api/schemas.py +82 -0
- mdengine-0.5.0/src/md_generator/db/api/settings.py +31 -0
- mdengine-0.5.0/src/md_generator/db/cli/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/db/cli/main.py +162 -0
- mdengine-0.5.0/src/md_generator/db/config/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/db/config/default.yaml +37 -0
- mdengine-0.5.0/src/md_generator/db/core/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/db/core/base_adapter.py +73 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/dot_emitter.py +50 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/filter.py +65 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/mermaid_emitter.py +39 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/mermaid_render.py +27 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/model.py +65 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/pipeline.py +199 -0
- mdengine-0.5.0/src/md_generator/db/core/erd/render.py +35 -0
- mdengine-0.5.0/src/md_generator/db/core/extractor.py +537 -0
- mdengine-0.5.0/src/md_generator/db/core/job_manager.py +287 -0
- mdengine-0.5.0/src/md_generator/db/core/markdown_writer.py +429 -0
- mdengine-0.5.0/src/md_generator/db/core/models.py +163 -0
- mdengine-0.5.0/src/md_generator/db/core/run_config.py +140 -0
- mdengine-0.5.0/src/md_generator/db/core/util.py +22 -0
- mdengine-0.5.0/src/md_generator/db/core/zip_export.py +25 -0
- mdengine-0.5.0/src/md_generator/db/mcp/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/db/mcp/server.py +62 -0
- mdengine-0.5.0/src/md_generator/db/mcp/sse.py +9 -0
- mdengine-0.5.0/src/md_generator/db/mcp/stream.py +18 -0
- mdengine-0.5.0/src/md_generator/engine_cli.py +30 -0
- mdengine-0.5.0/src/md_generator/graph/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/graph/adapters/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/graph/adapters/factory.py +16 -0
- mdengine-0.5.0/src/md_generator/graph/adapters/neo4j_adapter.py +310 -0
- mdengine-0.5.0/src/md_generator/graph/adapters/networkx_adapter.py +105 -0
- mdengine-0.5.0/src/md_generator/graph/api/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/graph/api/main.py +181 -0
- mdengine-0.5.0/src/md_generator/graph/api/mcp_server.py +29 -0
- mdengine-0.5.0/src/md_generator/graph/api/run.py +15 -0
- mdengine-0.5.0/src/md_generator/graph/api/schemas.py +70 -0
- mdengine-0.5.0/src/md_generator/graph/api/settings.py +31 -0
- mdengine-0.5.0/src/md_generator/graph/cli/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/graph/cli/main.py +138 -0
- mdengine-0.5.0/src/md_generator/graph/config/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/graph/config/default.yaml +30 -0
- mdengine-0.5.0/src/md_generator/graph/core/__init__.py +3 -0
- mdengine-0.5.0/src/md_generator/graph/core/base_adapter.py +35 -0
- mdengine-0.5.0/src/md_generator/graph/core/extractor.py +93 -0
- mdengine-0.5.0/src/md_generator/graph/core/graph_builder.py +72 -0
- mdengine-0.5.0/src/md_generator/graph/core/job_manager.py +280 -0
- mdengine-0.5.0/src/md_generator/graph/core/markdown_writer.py +280 -0
- mdengine-0.5.0/src/md_generator/graph/core/models.py +37 -0
- mdengine-0.5.0/src/md_generator/graph/core/run_config.py +174 -0
- mdengine-0.5.0/src/md_generator/graph/core/viz.py +119 -0
- mdengine-0.5.0/src/md_generator/graph/core/zip_export.py +24 -0
- mdengine-0.5.0/src/md_generator/graph/mcp/__init__.py +1 -0
- mdengine-0.5.0/src/md_generator/graph/mcp/server.py +118 -0
- mdengine-0.5.0/src/md_generator/graph/mcp/sse.py +8 -0
- mdengine-0.5.0/src/md_generator/graph/mcp/stream.py +5 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/PKG-INFO +47 -7
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/SOURCES.txt +69 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/entry_points.txt +7 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/requires.txt +16 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/LICENSE +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/setup.cfg +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/convert_runner.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/query_options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/convert_impl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/archive/options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/query_options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/staging.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/api/zip_bundle.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/backends/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/backends/base.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/backends/easy.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/backends/paddle.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/backends/tesseract.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/convert_impl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/emit.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/io_util.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/image/utils.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/run.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/formatter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/audio/service.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/document_converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/job_store.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/run.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/formatter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/video/service.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/whisper_language.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/run.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/formatter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/metadata.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/service.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/media/youtube/transcript.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/api/zip_bundle.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/md_emit.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/pdf_extract.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/pdf/utils.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/convert_runner.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/run.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/assets.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/chunker.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/cli.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/extractor.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/html_to_md.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/pipeline.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/playwright/playwright_fetcher.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/convert_runner.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/query_options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/convert_impl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/embedded_extract.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/ooxml_media.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/post_assets.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/text_formatting.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/vendor_pdf_md/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/vendor_pdf_md/convert.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/vendor_word_md/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/vendor_word_md/convert.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/ppt/zip_deep.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/convert_runner.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/query_options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/convert_impl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/format_detect.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/md_emit_json.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/md_emit_txt.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/md_emit_xml.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/text/options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/convert_runner.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/mcp_setup.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/query_options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/api/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/assets.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/convert_impl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/crawl.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/extract.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/fetch.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/html_to_md.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/options.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/page_convert.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/post_convert_assets.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/url/robots_util.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/api/convert_util.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/api/jobs.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/api/main.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/api/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/artifact.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/word/settings.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/api/__init__.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/api/app.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/convert_config.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/converter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/converter_core.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/excel_reader.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/markdown_emitter.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/md_generator/xlsx/mcp_server.py +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/dependency_links.txt +0 -0
- {mdengine-0.4.0 → mdengine-0.5.0}/src/mdengine.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdengine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown.
|
|
5
5
|
Author: mdengine contributors
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/vishal7090/md-generator
|
|
8
8
|
Project-URL: Repository, https://github.com/vishal7090/md-generator
|
|
9
9
|
Project-URL: Issues, https://github.com/vishal7090/md-generator/issues
|
|
10
|
-
Keywords: markdown,pdf,docx,pptx,xlsx,ocr,zip,url,html,youtube,playwright
|
|
10
|
+
Keywords: markdown,pdf,docx,pptx,xlsx,ocr,zip,url,html,youtube,playwright,database
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
13
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
@@ -87,6 +87,18 @@ Requires-Dist: lxml>=5.0.0; extra == "playwright"
|
|
|
87
87
|
Requires-Dist: markdownify>=0.14.0; extra == "playwright"
|
|
88
88
|
Requires-Dist: readability-lxml>=0.8.1; extra == "playwright"
|
|
89
89
|
Requires-Dist: Pillow>=10.0.0; extra == "playwright"
|
|
90
|
+
Provides-Extra: db
|
|
91
|
+
Requires-Dist: sqlalchemy>=2.0.0; extra == "db"
|
|
92
|
+
Requires-Dist: pyyaml>=6.0.0; extra == "db"
|
|
93
|
+
Requires-Dist: psycopg2-binary>=2.9.9; extra == "db"
|
|
94
|
+
Requires-Dist: pymysql>=1.1.0; extra == "db"
|
|
95
|
+
Requires-Dist: oracledb>=2.0.0; extra == "db"
|
|
96
|
+
Requires-Dist: pymongo>=4.6.0; extra == "db"
|
|
97
|
+
Requires-Dist: mermaid-py>=0.8.0; extra == "db"
|
|
98
|
+
Provides-Extra: graph
|
|
99
|
+
Requires-Dist: networkx>=3.2.0; extra == "graph"
|
|
100
|
+
Requires-Dist: neo4j>=5.14.0; extra == "graph"
|
|
101
|
+
Requires-Dist: pyyaml>=6.0.0; extra == "graph"
|
|
90
102
|
Provides-Extra: api
|
|
91
103
|
Requires-Dist: fastapi>=0.115.0; extra == "api"
|
|
92
104
|
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "api"
|
|
@@ -106,6 +118,8 @@ Requires-Dist: mdengine[api]; extra == "dev"
|
|
|
106
118
|
Requires-Dist: mdengine[mcp]; extra == "dev"
|
|
107
119
|
Requires-Dist: mdengine[url]; extra == "dev"
|
|
108
120
|
Requires-Dist: mdengine[playwright]; extra == "dev"
|
|
121
|
+
Requires-Dist: mdengine[db]; extra == "dev"
|
|
122
|
+
Requires-Dist: mdengine[graph]; extra == "dev"
|
|
109
123
|
Provides-Extra: all
|
|
110
124
|
Requires-Dist: pymupdf>=1.24.0; extra == "all"
|
|
111
125
|
Requires-Dist: pdfplumber>=0.11.0; extra == "all"
|
|
@@ -136,7 +150,7 @@ Dynamic: license-file
|
|
|
136
150
|
|
|
137
151
|
# mdengine
|
|
138
152
|
|
|
139
|
-
Single Python distribution for converting **PDF**, **Word (.docx)**, **PowerPoint (.pptx)**, **Excel (.xlsx/.xlsm)**, **images** (OCR), **plain text / JSON / XML**, **ZIP archives**,
|
|
153
|
+
Single Python distribution for converting **PDF**, **Word (.docx)**, **PowerPoint (.pptx)**, **Excel (.xlsx/.xlsm)**, **images** (OCR), **plain text / JSON / XML**, **ZIP archives**, **audio / video** (Whisper transcription → Markdown), **database metadata** (SQL + Mongo), and **graphs** (Neo4j / NetworkX → Markdown) into **Markdown** (and related assets). Install only the extras you need; everything imports under the **`md_generator`** package.
|
|
140
154
|
|
|
141
155
|
- **PyPI name:** `mdengine` (import package: `md_generator`)
|
|
142
156
|
- **Source:** [github.com/vishal7090/md-generator](https://github.com/vishal7090/md-generator)
|
|
@@ -214,6 +228,7 @@ pip install "mdengine[all]"
|
|
|
214
228
|
| `video` | **Video → Markdown** (ffmpeg extracts mono 16 kHz WAV, then same Whisper stack as `audio`) |
|
|
215
229
|
| `api` | FastAPI, uvicorn, httpx, pydantic-settings |
|
|
216
230
|
| `mcp` | MCP servers (`mcp`, `fastmcp` where used) |
|
|
231
|
+
| `graph` | **Graph → Markdown** (Neo4j Bolt + NetworkX GraphML/GML): `networkx`, `neo4j`, `pyyaml` |
|
|
217
232
|
| `dev` | pytest + API/MCP test helpers |
|
|
218
233
|
| `all` | Large superset of dependencies (use only if you need everything) |
|
|
219
234
|
|
|
@@ -228,7 +243,7 @@ All converters can be run from a terminal after you install the package (with th
|
|
|
228
243
|
### 1. Install (once)
|
|
229
244
|
|
|
230
245
|
```bash
|
|
231
|
-
pip install "mdengine[pdf,word]" # adjust extras: ppt, xlsx, image, archive, text, …
|
|
246
|
+
pip install "mdengine[pdf,word]" # adjust extras: ppt, xlsx, image, archive, text, db, graph, …
|
|
232
247
|
# or from a clone:
|
|
233
248
|
pip install -e ".[pdf,word,archive]"
|
|
234
249
|
```
|
|
@@ -263,6 +278,19 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
263
278
|
| `md-audio-mcp` | `md_generator.media.audio.api.mcp_server:main` | Standalone MCP (`--transport stdio` \| `sse` \| `streamable-http`) |
|
|
264
279
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
265
280
|
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
281
|
+
| `md-db` | `md_generator.db.cli.main:main` | `pip install "mdengine[db]"` then `md-db --config db.yaml` (or `mdengine db-to-md …`) |
|
|
282
|
+
| `md-db-api` | `md_generator.db.api.run:main` | FastAPI on port **8010** (`DB_TO_MD_PORT`): `POST /db-to-md/run`, `/db-to-md/job`, SSE `/db-to-md/job/{id}/events` |
|
|
283
|
+
| `md-db-mcp` | `md_generator.db.api.mcp_server:main` | Standalone MCP for metadata export tools |
|
|
284
|
+
| `md-graph` | `md_generator.graph.cli.main:main` | `pip install "mdengine[graph]"` then `md-graph --source neo4j --uri bolt://…` (or `mdengine graph-to-md …`) |
|
|
285
|
+
| `md-graph-api` | `md_generator.graph.api.run:main` | FastAPI on port **8012** (`GRAPH_TO_MD_PORT`): `POST /graph-to-md/run`, `/graph-to-md/job`, SSE `/graph-to-md/job/{id}/events` |
|
|
286
|
+
| `md-graph-mcp` | `md_generator.graph.api.mcp_server:main` | Standalone MCP for graph export tools |
|
|
287
|
+
| `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` (delegates to `md-db` / `md-graph`) |
|
|
288
|
+
|
|
289
|
+
**db-to-md ER diagrams:** add **`erd`** to the export feature list (YAML `features.include`, API body, or CLI `--include …,erd`). **Preferred:** **Graphviz** (`dot` on `PATH`, or **`GRAPHVIZ_DOT`**) produces `erd/*.dot`, `erd/*.png`, and `erd/*.svg`. **If Graphviz is missing,** the exporter falls back to **Mermaid** (`erDiagram`): it writes `erd/*.mermaid` plus a fenced **`erd/*.md`** for GitHub-style preview. With **`mermaid-py`** (included in `mdengine[db]`), it also requests **PNG/SVG** via mermaid.ink (requires network unless you self-host mermaid.ink and set **`MERMAID_INK_SERVER`** per [mermaid-py](https://pypi.org/project/mermaid-py/)). Tune **`erd.max_tables`** (default 100) and **`erd.scope`** (`full` \| `per_schema` \| `per_table`) under `erd:` in YAML; CLI: `--erd-max-tables`, `--erd-scope`. Async job SSE uses `progress_update` with `current` starting with `erd:`.
|
|
290
|
+
|
|
291
|
+
**db-to-md split exports and README merge:** with **`output.split_files: true`**, set **`output.write_combined_feature_markdown: true`** to also write root-level combined Markdown (for example `tables.md`, `functions.md`, `indexes.md`, and feature-specific paths such as `oracle/packages.md` or `mongodb/collections.md` when those features run). Set **`output.readme_feature_merge`** to **`inline`** (append full bundle bodies into `README.md`) or **`toc`** (append a linked list to those files). If merge is not **`none`** and split files are on, combined bundle writes are turned on automatically when loading config. CLI: `--write-combined-feature-markdown`, `--readme-feature-merge none|inline|toc`.
|
|
292
|
+
|
|
293
|
+
**graph-to-md (Neo4j + NetworkX):** library lives under [`md_generator/graph/`](src/md_generator/graph/). **Sources:** **`networkx`** (GraphML/GML via `graph.graph_file` or `--graph-file`) or **`neo4j`** (`graph.uri`, `graph.user`, `graph.password`, optional `graph.database` for `session(database=…)`). **Output layout:** by default **`output.combine_markdown: true`** writes **`nodes.md`**, **`relationship.md`**, and **`graph_summary.md`** (summary plus embedded nodes and relationships). Set **`combine_markdown: false`** or CLI **`--individual` / `--markdown-layout individual`** for per-entity files under **`nodes/`** and **`relationships/`**. **Diagrams:** **`viz.mermaid: true`** (default) writes **`graph/graph.mmd`** and embeds a fenced Mermaid block in the export **`README.md`** (no Graphviz required). **`viz.enabled: true`** or CLI **`--viz`** also writes **`graph/graph.dot`** and runs **`dot`** for PNG/SVG/PDF when Graphviz is on `PATH` (or **`GRAPHVIZ_DOT`**). CLI **`--no-mermaid`** disables Mermaid; **`--depth`**, **`--start-node`**, **`--max-nodes`**, **`--max-edges`** bound traversal. Packaged defaults: [`src/md_generator/graph/config/default.yaml`](src/md_generator/graph/config/default.yaml). Tests: **`graph-to-md/tests/`**; API image: [`graph-to-md/Dockerfile.api`](graph-to-md/Dockerfile.api).
|
|
266
294
|
|
|
267
295
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
268
296
|
|
|
@@ -282,6 +310,7 @@ md-zip archive.zip ./unzipped-md
|
|
|
282
310
|
md-url https://example.com/page ./page-bundle --artifact-layout
|
|
283
311
|
md-audio ./voice.mp3 ./voice.md --model tiny
|
|
284
312
|
md-video ./screen.mp4 ./screen.md --model base
|
|
313
|
+
pip install "mdengine[graph]" && md-graph --source neo4j --uri neo4j://localhost:7687 --user neo4j --password secret --database neo4j --output ./graph-out --viz
|
|
285
314
|
```
|
|
286
315
|
|
|
287
316
|
**Windows PowerShell** (same commands; use backslashes for paths if you prefer)
|
|
@@ -292,6 +321,8 @@ md-zip .\archive.zip .\zip-out
|
|
|
292
321
|
md-url https://example.com/page .\page-bundle --artifact-layout
|
|
293
322
|
md-audio .\voice.mp3 .\voice.md --model tiny
|
|
294
323
|
md-video .\screen.mp4 .\screen.md --model base
|
|
324
|
+
pip install "mdengine[graph]"
|
|
325
|
+
md-graph --source neo4j --uri neo4j://localhost:7687 --user neo4j --password secret --database neo4j --output .\graph-out --viz
|
|
295
326
|
```
|
|
296
327
|
|
|
297
328
|
**Windows CMD**
|
|
@@ -575,7 +606,7 @@ Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python
|
|
|
575
606
|
|
|
576
607
|
### Thin shims (repo clone)
|
|
577
608
|
|
|
578
|
-
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`.
|
|
609
|
+
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`. [`db-to-md/converter.py`](db-to-md/converter.py) delegates to `md-db`; tests live under `db-to-md/tests/`. **graph-to-md** uses `md-graph` / `mdengine graph-to-md` directly (no thin `converter.py` shim); tests live under [`graph-to-md/tests/`](graph-to-md/tests/).
|
|
579
610
|
|
|
580
611
|
---
|
|
581
612
|
|
|
@@ -605,6 +636,8 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
605
636
|
| ZIP | `md_generator.archive.api.main:app` | `archive`, `api`, `mcp` (+ extras for nested office/PDF) |
|
|
606
637
|
| URL / HTML | `md_generator.url.api.main:app` | `url`, `api`, `mcp` |
|
|
607
638
|
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
639
|
+
| Database metadata | `md_generator.db.api.main:app` | `db`, `api`, `mcp` |
|
|
640
|
+
| Graph metadata (Neo4j / NetworkX) | `md_generator.graph.api.main:app` | `graph`, `api`, `mcp` |
|
|
608
641
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
609
642
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
610
643
|
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
@@ -617,11 +650,14 @@ uvicorn md_generator.word.api.main:app --host 127.0.0.1 --port 8002
|
|
|
617
650
|
uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
618
651
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
619
652
|
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
653
|
+
uvicorn md_generator.graph.api.main:app --host 127.0.0.1 --port 8012
|
|
620
654
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
621
655
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
622
656
|
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
623
657
|
```
|
|
624
658
|
|
|
659
|
+
**Port note:** **`md-graph-api`** and **`md-video-api`** both default to **8012**; set **`GRAPH_TO_MD_PORT`** or **`MD_VIDEO_API_PORT`** when you need both on one machine.
|
|
660
|
+
|
|
625
661
|
### MCP over HTTP on the same server
|
|
626
662
|
|
|
627
663
|
These apps mount an MCP HTTP app at **`/mcp`** (Streamable HTTP / framework-specific). Start the API as above, then point an MCP client at `http://<host>:<port>/mcp` where supported.
|
|
@@ -640,6 +676,8 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
640
676
|
| XLSX | `XLSX_TO_MD_` | `XLSX_TO_MD_TEMP_DIR`, `XLSX_TO_MD_CORS_ORIGINS`, etc. (see `md_generator.xlsx.api.app`) |
|
|
641
677
|
| URL | `URL_TO_MD_` | `URL_TO_MD_MAX_SYNC_URLS`, `URL_TO_MD_MAX_SYNC_CRAWL_PAGES`, `URL_TO_MD_MAX_JOB_URLS`, `URL_TO_MD_JOB_TTL_SECONDS`, `URL_TO_MD_TEMP_DIR`, `URL_TO_MD_CORS_ORIGINS` |
|
|
642
678
|
| Playwright / SPA | `PLAYWRIGHT_TO_MD_` | `PLAYWRIGHT_TO_MD_MAX_SYNC_URLS`, `PLAYWRIGHT_TO_MD_MAX_JOB_URLS`, `PLAYWRIGHT_TO_MD_JOB_TTL_SECONDS`, `PLAYWRIGHT_TO_MD_TEMP_DIR`, `PLAYWRIGHT_TO_MD_CORS_ORIGINS`, `PLAYWRIGHT_TO_MD_API_HOST`, `PLAYWRIGHT_TO_MD_API_PORT` (default **8014**) |
|
|
679
|
+
| Database metadata | `DB_TO_MD_` | `DB_TO_MD_JOB_SQLITE_PATH`, `DB_TO_MD_JOB_WORKSPACE_ROOT`, `DB_TO_MD_CORS_ORIGINS`, `DB_TO_MD_MAX_SYNC_ZIP_MB`, `DB_TO_MD_HOST`, `DB_TO_MD_PORT` (default **8010**) |
|
|
680
|
+
| Graph metadata | `GRAPH_TO_MD_` | `GRAPH_TO_MD_JOB_SQLITE_PATH`, `GRAPH_TO_MD_JOB_WORKSPACE_ROOT`, `GRAPH_TO_MD_CORS_ORIGINS`, `GRAPH_TO_MD_MAX_SYNC_ZIP_MB`, `GRAPH_TO_MD_HOST`, `GRAPH_TO_MD_PORT` (default **8012**) |
|
|
643
681
|
| Audio API | `MD_AUDIO_` | `MD_AUDIO_MAX_UPLOAD_MB`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB`, `MD_AUDIO_JOB_TTL_SECONDS`, `MD_AUDIO_TEMP_DIR`, `MD_AUDIO_CORS_ORIGINS`, `MD_AUDIO_API_HOST`, `MD_AUDIO_API_PORT` |
|
|
644
682
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
645
683
|
| YouTube API | `MD_YOUTUBE_` | `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_TEMP_DIR`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_API_HOST`, `MD_YOUTUBE_API_PORT` (default **8013**); optional `MD_YOUTUBE_YTDLP` path for audio fallback |
|
|
@@ -670,6 +708,8 @@ Two usage patterns:
|
|
|
670
708
|
| Audio | `md-audio-mcp` or `python -m md_generator.media.audio.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
671
709
|
| Video | `md-video-mcp` or `python -m md_generator.media.video.api.mcp_server` — same transports |
|
|
672
710
|
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
711
|
+
| Database metadata | `md-db-mcp` or `python -m md_generator.db.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
712
|
+
| Graph (Neo4j / NetworkX) | `md-graph-mcp` or `python -m md_generator.graph.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
673
713
|
|
|
674
714
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
675
715
|
|
|
@@ -694,7 +734,7 @@ pip install -e ".[dev,all]" # or a smaller subset of extras
|
|
|
694
734
|
python -m pytest
|
|
695
735
|
```
|
|
696
736
|
|
|
697
|
-
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`)
|
|
737
|
+
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`) and **`graph-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
|
|
698
738
|
|
|
699
739
|
---
|
|
700
740
|
|
|
@@ -704,7 +744,7 @@ Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/test
|
|
|
704
744
|
|------|------|
|
|
705
745
|
| `LICENSE` | MIT license text |
|
|
706
746
|
| `CODE_OF_CONDUCT.md` | [Contributor Covenant](https://www.contributor-covenant.org/) 2.1 |
|
|
707
|
-
| `src/md_generator/` | **Library source** (all formats + `api` subpackages); **audio/video** under [`media/audio/`](src/md_generator/media/audio/) and [`media/video/`](src/md_generator/media/video/) |
|
|
747
|
+
| `src/md_generator/` | **Library source** (all formats + `api` subpackages); **audio/video** under [`media/audio/`](src/md_generator/media/audio/) and [`media/video/`](src/md_generator/media/video/); **graph-to-md** under [`graph/`](src/md_generator/graph/) |
|
|
708
748
|
| `pyproject.toml` | Packaging, extras, CLI entry points, pytest |
|
|
709
749
|
| `*-to-md/` | **Docs, tests, fixtures**, thin `converter.py` shims, some `run.py` helpers |
|
|
710
750
|
| `README.md` | This document |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mdengine
|
|
2
2
|
|
|
3
|
-
Single Python distribution for converting **PDF**, **Word (.docx)**, **PowerPoint (.pptx)**, **Excel (.xlsx/.xlsm)**, **images** (OCR), **plain text / JSON / XML**, **ZIP archives**,
|
|
3
|
+
Single Python distribution for converting **PDF**, **Word (.docx)**, **PowerPoint (.pptx)**, **Excel (.xlsx/.xlsm)**, **images** (OCR), **plain text / JSON / XML**, **ZIP archives**, **audio / video** (Whisper transcription → Markdown), **database metadata** (SQL + Mongo), and **graphs** (Neo4j / NetworkX → Markdown) into **Markdown** (and related assets). Install only the extras you need; everything imports under the **`md_generator`** package.
|
|
4
4
|
|
|
5
5
|
- **PyPI name:** `mdengine` (import package: `md_generator`)
|
|
6
6
|
- **Source:** [github.com/vishal7090/md-generator](https://github.com/vishal7090/md-generator)
|
|
@@ -78,6 +78,7 @@ pip install "mdengine[all]"
|
|
|
78
78
|
| `video` | **Video → Markdown** (ffmpeg extracts mono 16 kHz WAV, then same Whisper stack as `audio`) |
|
|
79
79
|
| `api` | FastAPI, uvicorn, httpx, pydantic-settings |
|
|
80
80
|
| `mcp` | MCP servers (`mcp`, `fastmcp` where used) |
|
|
81
|
+
| `graph` | **Graph → Markdown** (Neo4j Bolt + NetworkX GraphML/GML): `networkx`, `neo4j`, `pyyaml` |
|
|
81
82
|
| `dev` | pytest + API/MCP test helpers |
|
|
82
83
|
| `all` | Large superset of dependencies (use only if you need everything) |
|
|
83
84
|
|
|
@@ -92,7 +93,7 @@ All converters can be run from a terminal after you install the package (with th
|
|
|
92
93
|
### 1. Install (once)
|
|
93
94
|
|
|
94
95
|
```bash
|
|
95
|
-
pip install "mdengine[pdf,word]" # adjust extras: ppt, xlsx, image, archive, text, …
|
|
96
|
+
pip install "mdengine[pdf,word]" # adjust extras: ppt, xlsx, image, archive, text, db, graph, …
|
|
96
97
|
# or from a clone:
|
|
97
98
|
pip install -e ".[pdf,word,archive]"
|
|
98
99
|
```
|
|
@@ -127,6 +128,19 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
127
128
|
| `md-audio-mcp` | `md_generator.media.audio.api.mcp_server:main` | Standalone MCP (`--transport stdio` \| `sse` \| `streamable-http`) |
|
|
128
129
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
129
130
|
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
131
|
+
| `md-db` | `md_generator.db.cli.main:main` | `pip install "mdengine[db]"` then `md-db --config db.yaml` (or `mdengine db-to-md …`) |
|
|
132
|
+
| `md-db-api` | `md_generator.db.api.run:main` | FastAPI on port **8010** (`DB_TO_MD_PORT`): `POST /db-to-md/run`, `/db-to-md/job`, SSE `/db-to-md/job/{id}/events` |
|
|
133
|
+
| `md-db-mcp` | `md_generator.db.api.mcp_server:main` | Standalone MCP for metadata export tools |
|
|
134
|
+
| `md-graph` | `md_generator.graph.cli.main:main` | `pip install "mdengine[graph]"` then `md-graph --source neo4j --uri bolt://…` (or `mdengine graph-to-md …`) |
|
|
135
|
+
| `md-graph-api` | `md_generator.graph.api.run:main` | FastAPI on port **8012** (`GRAPH_TO_MD_PORT`): `POST /graph-to-md/run`, `/graph-to-md/job`, SSE `/graph-to-md/job/{id}/events` |
|
|
136
|
+
| `md-graph-mcp` | `md_generator.graph.api.mcp_server:main` | Standalone MCP for graph export tools |
|
|
137
|
+
| `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` (delegates to `md-db` / `md-graph`) |
|
|
138
|
+
|
|
139
|
+
**db-to-md ER diagrams:** add **`erd`** to the export feature list (YAML `features.include`, API body, or CLI `--include …,erd`). **Preferred:** **Graphviz** (`dot` on `PATH`, or **`GRAPHVIZ_DOT`**) produces `erd/*.dot`, `erd/*.png`, and `erd/*.svg`. **If Graphviz is missing,** the exporter falls back to **Mermaid** (`erDiagram`): it writes `erd/*.mermaid` plus a fenced **`erd/*.md`** for GitHub-style preview. With **`mermaid-py`** (included in `mdengine[db]`), it also requests **PNG/SVG** via mermaid.ink (requires network unless you self-host mermaid.ink and set **`MERMAID_INK_SERVER`** per [mermaid-py](https://pypi.org/project/mermaid-py/)). Tune **`erd.max_tables`** (default 100) and **`erd.scope`** (`full` \| `per_schema` \| `per_table`) under `erd:` in YAML; CLI: `--erd-max-tables`, `--erd-scope`. Async job SSE uses `progress_update` with `current` starting with `erd:`.
|
|
140
|
+
|
|
141
|
+
**db-to-md split exports and README merge:** with **`output.split_files: true`**, set **`output.write_combined_feature_markdown: true`** to also write root-level combined Markdown (for example `tables.md`, `functions.md`, `indexes.md`, and feature-specific paths such as `oracle/packages.md` or `mongodb/collections.md` when those features run). Set **`output.readme_feature_merge`** to **`inline`** (append full bundle bodies into `README.md`) or **`toc`** (append a linked list to those files). If merge is not **`none`** and split files are on, combined bundle writes are turned on automatically when loading config. CLI: `--write-combined-feature-markdown`, `--readme-feature-merge none|inline|toc`.
|
|
142
|
+
|
|
143
|
+
**graph-to-md (Neo4j + NetworkX):** library lives under [`md_generator/graph/`](src/md_generator/graph/). **Sources:** **`networkx`** (GraphML/GML via `graph.graph_file` or `--graph-file`) or **`neo4j`** (`graph.uri`, `graph.user`, `graph.password`, optional `graph.database` for `session(database=…)`). **Output layout:** by default **`output.combine_markdown: true`** writes **`nodes.md`**, **`relationship.md`**, and **`graph_summary.md`** (summary plus embedded nodes and relationships). Set **`combine_markdown: false`** or CLI **`--individual` / `--markdown-layout individual`** for per-entity files under **`nodes/`** and **`relationships/`**. **Diagrams:** **`viz.mermaid: true`** (default) writes **`graph/graph.mmd`** and embeds a fenced Mermaid block in the export **`README.md`** (no Graphviz required). **`viz.enabled: true`** or CLI **`--viz`** also writes **`graph/graph.dot`** and runs **`dot`** for PNG/SVG/PDF when Graphviz is on `PATH` (or **`GRAPHVIZ_DOT`**). CLI **`--no-mermaid`** disables Mermaid; **`--depth`**, **`--start-node`**, **`--max-nodes`**, **`--max-edges`** bound traversal. Packaged defaults: [`src/md_generator/graph/config/default.yaml`](src/md_generator/graph/config/default.yaml). Tests: **`graph-to-md/tests/`**; API image: [`graph-to-md/Dockerfile.api`](graph-to-md/Dockerfile.api).
|
|
130
144
|
|
|
131
145
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
132
146
|
|
|
@@ -146,6 +160,7 @@ md-zip archive.zip ./unzipped-md
|
|
|
146
160
|
md-url https://example.com/page ./page-bundle --artifact-layout
|
|
147
161
|
md-audio ./voice.mp3 ./voice.md --model tiny
|
|
148
162
|
md-video ./screen.mp4 ./screen.md --model base
|
|
163
|
+
pip install "mdengine[graph]" && md-graph --source neo4j --uri neo4j://localhost:7687 --user neo4j --password secret --database neo4j --output ./graph-out --viz
|
|
149
164
|
```
|
|
150
165
|
|
|
151
166
|
**Windows PowerShell** (same commands; use backslashes for paths if you prefer)
|
|
@@ -156,6 +171,8 @@ md-zip .\archive.zip .\zip-out
|
|
|
156
171
|
md-url https://example.com/page .\page-bundle --artifact-layout
|
|
157
172
|
md-audio .\voice.mp3 .\voice.md --model tiny
|
|
158
173
|
md-video .\screen.mp4 .\screen.md --model base
|
|
174
|
+
pip install "mdengine[graph]"
|
|
175
|
+
md-graph --source neo4j --uri neo4j://localhost:7687 --user neo4j --password secret --database neo4j --output .\graph-out --viz
|
|
159
176
|
```
|
|
160
177
|
|
|
161
178
|
**Windows CMD**
|
|
@@ -439,7 +456,7 @@ Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python
|
|
|
439
456
|
|
|
440
457
|
### Thin shims (repo clone)
|
|
441
458
|
|
|
442
|
-
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`.
|
|
459
|
+
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`. [`db-to-md/converter.py`](db-to-md/converter.py) delegates to `md-db`; tests live under `db-to-md/tests/`. **graph-to-md** uses `md-graph` / `mdengine graph-to-md` directly (no thin `converter.py` shim); tests live under [`graph-to-md/tests/`](graph-to-md/tests/).
|
|
443
460
|
|
|
444
461
|
---
|
|
445
462
|
|
|
@@ -469,6 +486,8 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
469
486
|
| ZIP | `md_generator.archive.api.main:app` | `archive`, `api`, `mcp` (+ extras for nested office/PDF) |
|
|
470
487
|
| URL / HTML | `md_generator.url.api.main:app` | `url`, `api`, `mcp` |
|
|
471
488
|
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
489
|
+
| Database metadata | `md_generator.db.api.main:app` | `db`, `api`, `mcp` |
|
|
490
|
+
| Graph metadata (Neo4j / NetworkX) | `md_generator.graph.api.main:app` | `graph`, `api`, `mcp` |
|
|
472
491
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
473
492
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
474
493
|
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
@@ -481,11 +500,14 @@ uvicorn md_generator.word.api.main:app --host 127.0.0.1 --port 8002
|
|
|
481
500
|
uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
482
501
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
483
502
|
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
503
|
+
uvicorn md_generator.graph.api.main:app --host 127.0.0.1 --port 8012
|
|
484
504
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
485
505
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
486
506
|
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
487
507
|
```
|
|
488
508
|
|
|
509
|
+
**Port note:** **`md-graph-api`** and **`md-video-api`** both default to **8012**; set **`GRAPH_TO_MD_PORT`** or **`MD_VIDEO_API_PORT`** when you need both on one machine.
|
|
510
|
+
|
|
489
511
|
### MCP over HTTP on the same server
|
|
490
512
|
|
|
491
513
|
These apps mount an MCP HTTP app at **`/mcp`** (Streamable HTTP / framework-specific). Start the API as above, then point an MCP client at `http://<host>:<port>/mcp` where supported.
|
|
@@ -504,6 +526,8 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
504
526
|
| XLSX | `XLSX_TO_MD_` | `XLSX_TO_MD_TEMP_DIR`, `XLSX_TO_MD_CORS_ORIGINS`, etc. (see `md_generator.xlsx.api.app`) |
|
|
505
527
|
| URL | `URL_TO_MD_` | `URL_TO_MD_MAX_SYNC_URLS`, `URL_TO_MD_MAX_SYNC_CRAWL_PAGES`, `URL_TO_MD_MAX_JOB_URLS`, `URL_TO_MD_JOB_TTL_SECONDS`, `URL_TO_MD_TEMP_DIR`, `URL_TO_MD_CORS_ORIGINS` |
|
|
506
528
|
| Playwright / SPA | `PLAYWRIGHT_TO_MD_` | `PLAYWRIGHT_TO_MD_MAX_SYNC_URLS`, `PLAYWRIGHT_TO_MD_MAX_JOB_URLS`, `PLAYWRIGHT_TO_MD_JOB_TTL_SECONDS`, `PLAYWRIGHT_TO_MD_TEMP_DIR`, `PLAYWRIGHT_TO_MD_CORS_ORIGINS`, `PLAYWRIGHT_TO_MD_API_HOST`, `PLAYWRIGHT_TO_MD_API_PORT` (default **8014**) |
|
|
529
|
+
| Database metadata | `DB_TO_MD_` | `DB_TO_MD_JOB_SQLITE_PATH`, `DB_TO_MD_JOB_WORKSPACE_ROOT`, `DB_TO_MD_CORS_ORIGINS`, `DB_TO_MD_MAX_SYNC_ZIP_MB`, `DB_TO_MD_HOST`, `DB_TO_MD_PORT` (default **8010**) |
|
|
530
|
+
| Graph metadata | `GRAPH_TO_MD_` | `GRAPH_TO_MD_JOB_SQLITE_PATH`, `GRAPH_TO_MD_JOB_WORKSPACE_ROOT`, `GRAPH_TO_MD_CORS_ORIGINS`, `GRAPH_TO_MD_MAX_SYNC_ZIP_MB`, `GRAPH_TO_MD_HOST`, `GRAPH_TO_MD_PORT` (default **8012**) |
|
|
507
531
|
| Audio API | `MD_AUDIO_` | `MD_AUDIO_MAX_UPLOAD_MB`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB`, `MD_AUDIO_JOB_TTL_SECONDS`, `MD_AUDIO_TEMP_DIR`, `MD_AUDIO_CORS_ORIGINS`, `MD_AUDIO_API_HOST`, `MD_AUDIO_API_PORT` |
|
|
508
532
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
509
533
|
| YouTube API | `MD_YOUTUBE_` | `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_TEMP_DIR`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_API_HOST`, `MD_YOUTUBE_API_PORT` (default **8013**); optional `MD_YOUTUBE_YTDLP` path for audio fallback |
|
|
@@ -534,6 +558,8 @@ Two usage patterns:
|
|
|
534
558
|
| Audio | `md-audio-mcp` or `python -m md_generator.media.audio.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
535
559
|
| Video | `md-video-mcp` or `python -m md_generator.media.video.api.mcp_server` — same transports |
|
|
536
560
|
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
561
|
+
| Database metadata | `md-db-mcp` or `python -m md_generator.db.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
562
|
+
| Graph (Neo4j / NetworkX) | `md-graph-mcp` or `python -m md_generator.graph.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
537
563
|
|
|
538
564
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
539
565
|
|
|
@@ -558,7 +584,7 @@ pip install -e ".[dev,all]" # or a smaller subset of extras
|
|
|
558
584
|
python -m pytest
|
|
559
585
|
```
|
|
560
586
|
|
|
561
|
-
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`)
|
|
587
|
+
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`) and **`graph-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
|
|
562
588
|
|
|
563
589
|
---
|
|
564
590
|
|
|
@@ -568,7 +594,7 @@ Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/test
|
|
|
568
594
|
|------|------|
|
|
569
595
|
| `LICENSE` | MIT license text |
|
|
570
596
|
| `CODE_OF_CONDUCT.md` | [Contributor Covenant](https://www.contributor-covenant.org/) 2.1 |
|
|
571
|
-
| `src/md_generator/` | **Library source** (all formats + `api` subpackages); **audio/video** under [`media/audio/`](src/md_generator/media/audio/) and [`media/video/`](src/md_generator/media/video/) |
|
|
597
|
+
| `src/md_generator/` | **Library source** (all formats + `api` subpackages); **audio/video** under [`media/audio/`](src/md_generator/media/audio/) and [`media/video/`](src/md_generator/media/video/); **graph-to-md** under [`graph/`](src/md_generator/graph/) |
|
|
572
598
|
| `pyproject.toml` | Packaging, extras, CLI entry points, pytest |
|
|
573
599
|
| `*-to-md/` | **Docs, tests, fixtures**, thin `converter.py` shims, some `run.py` helpers |
|
|
574
600
|
| `README.md` | This document |
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mdengine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "mdengine contributors" }]
|
|
14
|
-
keywords = ["markdown", "pdf", "docx", "pptx", "xlsx", "ocr", "zip", "url", "html", "youtube", "playwright"]
|
|
14
|
+
keywords = ["markdown", "pdf", "docx", "pptx", "xlsx", "ocr", "zip", "url", "html", "youtube", "playwright", "database"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -92,6 +92,20 @@ playwright = [
|
|
|
92
92
|
"readability-lxml>=0.8.1",
|
|
93
93
|
"Pillow>=10.0.0",
|
|
94
94
|
]
|
|
95
|
+
db = [
|
|
96
|
+
"sqlalchemy>=2.0.0",
|
|
97
|
+
"pyyaml>=6.0.0",
|
|
98
|
+
"psycopg2-binary>=2.9.9",
|
|
99
|
+
"pymysql>=1.1.0",
|
|
100
|
+
"oracledb>=2.0.0",
|
|
101
|
+
"pymongo>=4.6.0",
|
|
102
|
+
"mermaid-py>=0.8.0",
|
|
103
|
+
]
|
|
104
|
+
graph = [
|
|
105
|
+
"networkx>=3.2.0",
|
|
106
|
+
"neo4j>=5.14.0",
|
|
107
|
+
"pyyaml>=6.0.0",
|
|
108
|
+
]
|
|
95
109
|
api = [
|
|
96
110
|
"fastapi>=0.115.0",
|
|
97
111
|
"uvicorn[standard]>=0.32.0",
|
|
@@ -110,6 +124,8 @@ dev = [
|
|
|
110
124
|
"mdengine[mcp]",
|
|
111
125
|
"mdengine[url]",
|
|
112
126
|
"mdengine[playwright]",
|
|
127
|
+
"mdengine[db]",
|
|
128
|
+
"mdengine[graph]",
|
|
113
129
|
]
|
|
114
130
|
all = [
|
|
115
131
|
"pymupdf>=1.24.0",
|
|
@@ -165,10 +181,21 @@ md-youtube-mcp = "md_generator.media.youtube.api.mcp_server:main"
|
|
|
165
181
|
md-playwright = "md_generator.playwright.cli:main"
|
|
166
182
|
md-playwright-api = "md_generator.playwright.api.run:main"
|
|
167
183
|
md-playwright-mcp = "md_generator.playwright.api.mcp_server:main"
|
|
184
|
+
md-db = "md_generator.db.cli.main:main"
|
|
185
|
+
md-db-api = "md_generator.db.api.run:main"
|
|
186
|
+
md-db-mcp = "md_generator.db.api.mcp_server:main"
|
|
187
|
+
md-graph = "md_generator.graph.cli.main:main"
|
|
188
|
+
md-graph-api = "md_generator.graph.api.run:main"
|
|
189
|
+
md-graph-mcp = "md_generator.graph.api.mcp_server:main"
|
|
190
|
+
mdengine = "md_generator.engine_cli:main"
|
|
168
191
|
|
|
169
192
|
[tool.setuptools.packages.find]
|
|
170
193
|
where = ["src"]
|
|
171
194
|
|
|
195
|
+
[tool.setuptools.package-data]
|
|
196
|
+
"md_generator.db.config" = ["*.yaml"]
|
|
197
|
+
"md_generator.graph.config" = ["*.yaml"]
|
|
198
|
+
|
|
172
199
|
[tool.pytest.ini_options]
|
|
173
200
|
pythonpath = ["src"]
|
|
174
201
|
testpaths = [
|
|
@@ -184,5 +211,7 @@ testpaths = [
|
|
|
184
211
|
"video-to-md/tests",
|
|
185
212
|
"youtube-to-md/tests",
|
|
186
213
|
"playwright-to-md/tests",
|
|
214
|
+
"db-to-md/tests",
|
|
215
|
+
"graph-to-md/tests",
|
|
187
216
|
]
|
|
188
217
|
markers = ["integration: optional dependency or slower checks"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from sqlalchemy.engine.url import make_url
|
|
6
|
+
|
|
7
|
+
from md_generator.db.adapters.mongo_adapter import MongoAdapter
|
|
8
|
+
from md_generator.db.adapters.mysql_adapter import MysqlAdapter
|
|
9
|
+
from md_generator.db.adapters.oracle_adapter import OracleAdapter
|
|
10
|
+
from md_generator.db.adapters.postgres_adapter import PostgresAdapter
|
|
11
|
+
from md_generator.db.core.base_adapter import BaseAdapter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_adapter(
|
|
15
|
+
db_type: str,
|
|
16
|
+
uri: str,
|
|
17
|
+
*,
|
|
18
|
+
schema: str | None = None,
|
|
19
|
+
database: str | None = None,
|
|
20
|
+
limits: dict[str, Any] | None = None,
|
|
21
|
+
) -> BaseAdapter:
|
|
22
|
+
lim = dict(limits or {})
|
|
23
|
+
t = db_type.lower().strip()
|
|
24
|
+
if t in ("postgres", "postgresql"):
|
|
25
|
+
sch = schema or "public"
|
|
26
|
+
return PostgresAdapter(uri, sch, lim)
|
|
27
|
+
if t in ("mysql", "mariadb"):
|
|
28
|
+
sch = schema or make_url(uri).database
|
|
29
|
+
if not sch:
|
|
30
|
+
raise ValueError("MySQL requires database in URI path or --schema")
|
|
31
|
+
return MysqlAdapter(uri, sch, lim)
|
|
32
|
+
if t in ("oracle",):
|
|
33
|
+
if not schema:
|
|
34
|
+
raise ValueError("Oracle requires schema (owner) in config or --schema")
|
|
35
|
+
return OracleAdapter(uri, schema, lim)
|
|
36
|
+
if t in ("mongo", "mongodb"):
|
|
37
|
+
if not database:
|
|
38
|
+
raise ValueError("MongoDB requires database name in config or --database")
|
|
39
|
+
return MongoAdapter(uri, database, lim)
|
|
40
|
+
raise ValueError(f"Unsupported database type: {db_type!r}")
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from md_generator.db.core.base_adapter import BaseAdapter
|
|
7
|
+
from md_generator.db.core.models import MongoCollectionInfo, MongoIndexInfo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _bson_type_name(v: Any) -> str:
|
|
11
|
+
if v is None:
|
|
12
|
+
return "null"
|
|
13
|
+
t = type(v).__name__
|
|
14
|
+
if t == "ObjectId":
|
|
15
|
+
return "ObjectId"
|
|
16
|
+
if t in ("int", "float"):
|
|
17
|
+
return "number"
|
|
18
|
+
if t == "bool":
|
|
19
|
+
return "boolean"
|
|
20
|
+
if t == "str":
|
|
21
|
+
return "string"
|
|
22
|
+
if t == "bytes":
|
|
23
|
+
return "binary"
|
|
24
|
+
if t == "datetime":
|
|
25
|
+
return "datetime"
|
|
26
|
+
if t == "list":
|
|
27
|
+
return "array"
|
|
28
|
+
if t == "dict":
|
|
29
|
+
return "object"
|
|
30
|
+
return t
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _merge_schema(a: Any, b: Any) -> Any:
|
|
34
|
+
"""Deterministic merge of inferred type trees."""
|
|
35
|
+
if a is None:
|
|
36
|
+
return b
|
|
37
|
+
if b is None:
|
|
38
|
+
return a
|
|
39
|
+
if isinstance(a, str) and isinstance(b, str):
|
|
40
|
+
if a == b:
|
|
41
|
+
return a
|
|
42
|
+
return "|".join(sorted({a, b}))
|
|
43
|
+
if isinstance(a, dict) and isinstance(b, dict):
|
|
44
|
+
keys = sorted(set(a) | set(b))
|
|
45
|
+
return {k: _merge_schema(a.get(k), b.get(k)) for k in keys}
|
|
46
|
+
if isinstance(a, list) and isinstance(b, list):
|
|
47
|
+
merged: Any = None
|
|
48
|
+
for x in a + b:
|
|
49
|
+
merged = _merge_schema(merged, x) if merged is not None else x
|
|
50
|
+
return [merged] if merged is not None else []
|
|
51
|
+
if isinstance(a, list):
|
|
52
|
+
return _merge_schema(a[0] if a else None, b)
|
|
53
|
+
if isinstance(b, list):
|
|
54
|
+
return _merge_schema(a, b[0] if b else None)
|
|
55
|
+
return "|".join(sorted({_bson_type_name(a), _bson_type_name(b)}))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _infer_from_doc(doc: Mapping[str, Any]) -> Any:
|
|
59
|
+
out: dict[str, Any] = {}
|
|
60
|
+
for k in sorted(doc.keys()):
|
|
61
|
+
v = doc[k]
|
|
62
|
+
if isinstance(v, dict):
|
|
63
|
+
out[k] = _infer_from_doc(v)
|
|
64
|
+
elif isinstance(v, list):
|
|
65
|
+
elem: Any = None
|
|
66
|
+
for item in v:
|
|
67
|
+
if isinstance(item, dict):
|
|
68
|
+
elem = _merge_schema(elem, _infer_from_doc(item))
|
|
69
|
+
else:
|
|
70
|
+
elem = _merge_schema(elem, _bson_type_name(item))
|
|
71
|
+
out[k] = [elem] if elem is not None else ["null"]
|
|
72
|
+
else:
|
|
73
|
+
out[k] = _bson_type_name(v)
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MongoAdapter(BaseAdapter):
|
|
78
|
+
db_type = "mongo"
|
|
79
|
+
|
|
80
|
+
def __init__(self, uri: str, database: str, limits: dict[str, Any]) -> None:
|
|
81
|
+
from pymongo import MongoClient
|
|
82
|
+
|
|
83
|
+
self._client: Any = MongoClient(uri, serverSelectionTimeoutMS=8000)
|
|
84
|
+
self._db = self._client[database]
|
|
85
|
+
self._database_name = database
|
|
86
|
+
self._limits = limits
|
|
87
|
+
|
|
88
|
+
def limits(self) -> dict[str, Any]:
|
|
89
|
+
return dict(self._limits)
|
|
90
|
+
|
|
91
|
+
def validate_connection(self) -> None:
|
|
92
|
+
self._client.admin.command("ping")
|
|
93
|
+
|
|
94
|
+
def close(self) -> None:
|
|
95
|
+
self._client.close()
|
|
96
|
+
|
|
97
|
+
def get_collections(self) -> list[MongoCollectionInfo]:
|
|
98
|
+
max_c = int(self._limits.get("max_collections", 500))
|
|
99
|
+
sample_n = int(self._limits.get("sample_size", 50))
|
|
100
|
+
names = sorted(n for n in self._db.list_collection_names() if not n.startswith("system."))[:max_c]
|
|
101
|
+
out: list[MongoCollectionInfo] = []
|
|
102
|
+
for name in names:
|
|
103
|
+
coll = self._db[name]
|
|
104
|
+
schema: Any = None
|
|
105
|
+
count = 0
|
|
106
|
+
try:
|
|
107
|
+
for doc in coll.aggregate([{"$sample": {"size": sample_n}}]):
|
|
108
|
+
if not isinstance(doc, dict):
|
|
109
|
+
continue
|
|
110
|
+
count += 1
|
|
111
|
+
schema = _merge_schema(schema, _infer_from_doc(doc))
|
|
112
|
+
except Exception:
|
|
113
|
+
try:
|
|
114
|
+
for doc in coll.find().limit(sample_n):
|
|
115
|
+
if isinstance(doc, dict):
|
|
116
|
+
count += 1
|
|
117
|
+
schema = _merge_schema(schema, _infer_from_doc(doc))
|
|
118
|
+
except Exception:
|
|
119
|
+
schema = {}
|
|
120
|
+
if schema is None:
|
|
121
|
+
schema = {}
|
|
122
|
+
idxes: list[MongoIndexInfo] = []
|
|
123
|
+
try:
|
|
124
|
+
for spec in coll.list_indexes():
|
|
125
|
+
raw = dict(spec)
|
|
126
|
+
iname = str(raw.get("name") or "")
|
|
127
|
+
unique = bool(raw.get("unique", False))
|
|
128
|
+
keys_raw = raw.get("key") or {}
|
|
129
|
+
keys = {str(k): v for k, v in dict(keys_raw).items()}
|
|
130
|
+
idxes.append(MongoIndexInfo(name=iname, keys=keys, unique=unique))
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
idxes.sort(key=lambda x: x.name)
|
|
134
|
+
out.append(
|
|
135
|
+
MongoCollectionInfo(
|
|
136
|
+
name=name,
|
|
137
|
+
inferred_schema=schema if isinstance(schema, dict) else {"_inferred": schema},
|
|
138
|
+
indexes=tuple(idxes),
|
|
139
|
+
sample_size=count,
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
return out
|