mdengine 0.5.0__tar.gz → 0.6.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.5.0 → mdengine-0.6.0}/PKG-INFO +26 -8
- {mdengine-0.5.0 → mdengine-0.6.0}/README.md +18 -5
- {mdengine-0.5.0 → mdengine-0.6.0}/pyproject.toml +14 -3
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/__init__.py +1 -1
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/factory.py +6 -0
- mdengine-0.6.0/src/md_generator/db/adapters/sqlite_adapter.py +107 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/main.py +89 -1
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/schemas.py +1 -1
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/settings.py +1 -0
- mdengine-0.6.0/src/md_generator/db/api/sqlite_upload.py +87 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/cli/main.py +1 -1
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/job_manager.py +44 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/run_config.py +5 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/util.py +12 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/engine_cli.py +6 -2
- mdengine-0.6.0/src/md_generator/openapi/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/api/__init__.py +1 -0
- mdengine-0.6.0/src/md_generator/openapi/api/main.py +84 -0
- mdengine-0.6.0/src/md_generator/openapi/api/mcp_server.py +29 -0
- mdengine-0.6.0/src/md_generator/openapi/api/run.py +15 -0
- mdengine-0.6.0/src/md_generator/openapi/api/schemas.py +33 -0
- mdengine-0.6.0/src/md_generator/openapi/api/settings.py +21 -0
- mdengine-0.6.0/src/md_generator/openapi/cli/__init__.py +1 -0
- mdengine-0.6.0/src/md_generator/openapi/cli/main.py +75 -0
- mdengine-0.6.0/src/md_generator/openapi/config/__init__.py +1 -0
- mdengine-0.6.0/src/md_generator/openapi/config/default.yaml +14 -0
- mdengine-0.6.0/src/md_generator/openapi/converters/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/converters/swagger2_to_openapi3.py +453 -0
- mdengine-0.6.0/src/md_generator/openapi/core/__init__.py +1 -0
- mdengine-0.6.0/src/md_generator/openapi/core/extractor.py +207 -0
- mdengine-0.6.0/src/md_generator/openapi/core/run_config.py +100 -0
- mdengine-0.6.0/src/md_generator/openapi/core/zip_export.py +24 -0
- mdengine-0.6.0/src/md_generator/openapi/enrichers/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/enrichers/rules.py +133 -0
- mdengine-0.6.0/src/md_generator/openapi/enrichers/sample_body.py +73 -0
- mdengine-0.6.0/src/md_generator/openapi/generators/__init__.py +9 -0
- mdengine-0.6.0/src/md_generator/openapi/generators/dependency_graph.py +54 -0
- mdengine-0.6.0/src/md_generator/openapi/loaders/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/loaders/spec_loader.py +115 -0
- mdengine-0.6.0/src/md_generator/openapi/mcp/__init__.py +1 -0
- mdengine-0.6.0/src/md_generator/openapi/mcp/server.py +71 -0
- mdengine-0.6.0/src/md_generator/openapi/models/__init__.py +25 -0
- mdengine-0.6.0/src/md_generator/openapi/models/domain.py +106 -0
- mdengine-0.6.0/src/md_generator/openapi/normalizers/__init__.py +6 -0
- mdengine-0.6.0/src/md_generator/openapi/normalizers/operations.py +287 -0
- mdengine-0.6.0/src/md_generator/openapi/normalizers/schema_flatten.py +104 -0
- mdengine-0.6.0/src/md_generator/openapi/parsers/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/parsers/openapi_parser.py +18 -0
- mdengine-0.6.0/src/md_generator/openapi/resolvers/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/resolvers/ref_resolver.py +16 -0
- mdengine-0.6.0/src/md_generator/openapi/writers/__init__.py +5 -0
- mdengine-0.6.0/src/md_generator/openapi/writers/markdown_writer.py +253 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/PKG-INFO +26 -8
- {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/SOURCES.txt +39 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/entry_points.txt +3 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/requires.txt +6 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/LICENSE +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/setup.cfg +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/convert_runner.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/query_options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/convert_impl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/mongo_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/mysql_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/oracle_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/postgres_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/sql_common.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/cli/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/config/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/config/default.yaml +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/base_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/dot_emitter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/filter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/mermaid_emitter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/mermaid_render.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/model.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/pipeline.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/render.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/extractor.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/markdown_writer.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/models.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/zip_export.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/sse.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/stream.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/factory.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/neo4j_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/networkx_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/schemas.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/cli/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/cli/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/config/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/config/default.yaml +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/base_adapter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/extractor.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/graph_builder.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/job_manager.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/markdown_writer.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/models.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/run_config.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/viz.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/zip_export.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/sse.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/stream.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/query_options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/staging.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/zip_bundle.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/base.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/easy.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/paddle.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/tesseract.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/convert_impl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/emit.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/io_util.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/utils.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/formatter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/service.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/document_converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/job_store.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/formatter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/service.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/whisper_language.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/formatter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/metadata.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/service.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/transcript.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/zip_bundle.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/md_emit.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/pdf_extract.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/utils.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/convert_runner.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/run.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/assets.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/chunker.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/cli.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/extractor.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/html_to_md.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/pipeline.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/playwright_fetcher.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/convert_runner.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/query_options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/convert_impl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/embedded_extract.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/ooxml_media.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/post_assets.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/text_formatting.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_pdf_md/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_pdf_md/convert.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_word_md/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_word_md/convert.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/zip_deep.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/convert_runner.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/query_options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/convert_impl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/format_detect.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_json.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_txt.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_xml.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/convert_runner.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/mcp_setup.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/query_options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/assets.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/convert_impl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/crawl.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/extract.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/fetch.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/html_to_md.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/options.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/page_convert.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/post_convert_assets.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/robots_util.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/convert_util.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/jobs.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/main.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/artifact.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/settings.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/api/__init__.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/api/app.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/convert_config.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/converter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/converter_core.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/excel_reader.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/markdown_emitter.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/mcp_server.py +0 -0
- {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/dependency_links.txt +0 -0
- {mdengine-0.5.0 → mdengine-0.6.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.
|
|
4
|
-
Summary: Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives,
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, web URLs, databases, graphs, and OpenAPI specs 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,database
|
|
10
|
+
Keywords: markdown,pdf,docx,pptx,xlsx,ocr,zip,url,html,youtube,playwright,database,openapi
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
13
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
@@ -99,6 +99,10 @@ Provides-Extra: graph
|
|
|
99
99
|
Requires-Dist: networkx>=3.2.0; extra == "graph"
|
|
100
100
|
Requires-Dist: neo4j>=5.14.0; extra == "graph"
|
|
101
101
|
Requires-Dist: pyyaml>=6.0.0; extra == "graph"
|
|
102
|
+
Provides-Extra: openapi
|
|
103
|
+
Requires-Dist: pyyaml>=6.0.0; extra == "openapi"
|
|
104
|
+
Requires-Dist: prance>=23.6.0; extra == "openapi"
|
|
105
|
+
Requires-Dist: openapi-spec-validator>=0.7.0; extra == "openapi"
|
|
102
106
|
Provides-Extra: api
|
|
103
107
|
Requires-Dist: fastapi>=0.115.0; extra == "api"
|
|
104
108
|
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "api"
|
|
@@ -120,6 +124,7 @@ Requires-Dist: mdengine[url]; extra == "dev"
|
|
|
120
124
|
Requires-Dist: mdengine[playwright]; extra == "dev"
|
|
121
125
|
Requires-Dist: mdengine[db]; extra == "dev"
|
|
122
126
|
Requires-Dist: mdengine[graph]; extra == "dev"
|
|
127
|
+
Requires-Dist: mdengine[openapi]; extra == "dev"
|
|
123
128
|
Provides-Extra: all
|
|
124
129
|
Requires-Dist: pymupdf>=1.24.0; extra == "all"
|
|
125
130
|
Requires-Dist: pdfplumber>=0.11.0; extra == "all"
|
|
@@ -279,17 +284,26 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
279
284
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
280
285
|
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
281
286
|
| `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`,
|
|
287
|
+
| `md-db-api` | `md_generator.db.api.run:main` | FastAPI on port **8010** (`DB_TO_MD_PORT`): `POST /db-to-md/run`, `POST /db-to-md/run/sqlite` (upload + ZIP), `POST /db-to-md/job`, `POST /db-to-md/job/sqlite` (upload + async job), SSE `/db-to-md/job/{id}/events` |
|
|
283
288
|
| `md-db-mcp` | `md_generator.db.api.mcp_server:main` | Standalone MCP for metadata export tools |
|
|
284
289
|
| `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
290
|
| `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
291
|
| `md-graph-mcp` | `md_generator.graph.api.mcp_server:main` | Standalone MCP for graph export tools |
|
|
287
|
-
| `
|
|
292
|
+
| `md-openapi` | `md_generator.openapi.cli.main:main` | `pip install "mdengine[openapi]"` then `md-openapi generate --file openapi.yaml --output ./docs` (or `mdengine openapi-to-md generate …`) |
|
|
293
|
+
| `md-openapi-api` | `md_generator.openapi.api.run:main` | FastAPI on port **8015** (`OPENAPI_TO_MD_PORT`): `POST /openapi-to-md/generate` (OpenAPI upload → ZIP), `/health`, MCP at **`/mcp`** |
|
|
294
|
+
| `md-openapi-mcp` | `md_generator.openapi.api.mcp_server:main` | Standalone MCP: `api_validate_openapi_yaml`, `api_generate_readme_markdown`, `api_run_sync_zip_base64` |
|
|
295
|
+
| `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` / `mdengine openapi-to-md generate …` |
|
|
296
|
+
|
|
297
|
+
**openapi-to-md (`md-openapi`):** OpenAPI **3.x** is parsed directly; **Swagger 2.0** (`swagger: "2.0"`) documents are **converted in-process** to OpenAPI 3.0.3 (deterministic, in-repo converter) before `$ref` resolution. Edge-heavy specs (unusual OAuth2 flows, vendor extensions) may still need fixes after conversion.
|
|
288
298
|
|
|
289
299
|
**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
300
|
|
|
291
301
|
**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
302
|
|
|
303
|
+
**db-to-md SQLite:** set **`database.type: sqlite`** and **`database.uri`** to a SQLAlchemy URL such as **`sqlite:///my.db`** or **`sqlite:////C:/data/my.db`**. The default SQLite catalog is **`main`**; packaged YAML that still has **`schema: public`** is normalized to **`main`** when the type is SQLite. CLI: **`--type sqlite`**. Stored routines, sequences, and partitions are not used by SQLite and stay empty; tables, indexes, views, triggers, and ERD (via FK introspection) follow the same export path as other SQL engines.
|
|
304
|
+
|
|
305
|
+
**db-to-md API SQLite upload:** **`POST /db-to-md/run/sqlite`** — `multipart/form-data` with field **`file`** (the `.sqlite` / `.db` bytes; must start with the standard `SQLite format 3` header) and optional **`config`** (JSON string with the same shape as a normal run body minus `database`: `schema`, `output`, `features`, `execution`, `limits`, `erd`). Returns the metadata ZIP immediately. **`POST /db-to-md/job/sqlite`** — same form fields; saves the file under the job workspace and runs the existing job pipeline (`GET /db-to-md/job/{id}/download` when complete). Upload size cap: env **`DB_TO_MD_MAX_SQLITE_UPLOAD_MB`** (default **256**). Large ZIPs may still require the async job path if they exceed **`DB_TO_MD_MAX_SYNC_ZIP_MB`** on the sync upload route.
|
|
306
|
+
|
|
293
307
|
**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).
|
|
294
308
|
|
|
295
309
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
@@ -606,7 +620,7 @@ Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python
|
|
|
606
620
|
|
|
607
621
|
### Thin shims (repo clone)
|
|
608
622
|
|
|
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/).
|
|
623
|
+
[`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/). [`openapi-to-md/converter.py`](openapi-to-md/converter.py) delegates to `md-openapi`; tests live under [`openapi-to-md/tests/`](openapi-to-md/tests/); example OpenAPI and output notes: [`openapi-to-md/examples/`](openapi-to-md/examples/).
|
|
610
624
|
|
|
611
625
|
---
|
|
612
626
|
|
|
@@ -638,6 +652,7 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
638
652
|
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
639
653
|
| Database metadata | `md_generator.db.api.main:app` | `db`, `api`, `mcp` |
|
|
640
654
|
| Graph metadata (Neo4j / NetworkX) | `md_generator.graph.api.main:app` | `graph`, `api`, `mcp` |
|
|
655
|
+
| OpenAPI → Markdown | `md_generator.openapi.api.main:app` | `openapi`, `api`, `mcp` |
|
|
641
656
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
642
657
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
643
658
|
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
@@ -651,12 +666,13 @@ uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
|
651
666
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
652
667
|
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
653
668
|
uvicorn md_generator.graph.api.main:app --host 127.0.0.1 --port 8012
|
|
669
|
+
uvicorn md_generator.openapi.api.main:app --host 127.0.0.1 --port 8015
|
|
654
670
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
655
671
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
656
672
|
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
657
673
|
```
|
|
658
674
|
|
|
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.
|
|
675
|
+
**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. **`md-openapi-api`** defaults to **8015** (`OPENAPI_TO_MD_PORT`) so it does not collide with **`md-youtube-api`** (**8013**) or **`md-playwright-api`** (**8014**).
|
|
660
676
|
|
|
661
677
|
### MCP over HTTP on the same server
|
|
662
678
|
|
|
@@ -678,6 +694,7 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
678
694
|
| 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
695
|
| 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
696
|
| 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**) |
|
|
697
|
+
| OpenAPI → Markdown | `OPENAPI_TO_MD_` | `OPENAPI_TO_MD_CORS_ORIGINS`, `OPENAPI_TO_MD_MAX_SYNC_ZIP_MB`, `OPENAPI_TO_MD_HOST`, `OPENAPI_TO_MD_PORT` (default **8015**) |
|
|
681
698
|
| 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` |
|
|
682
699
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
683
700
|
| 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 |
|
|
@@ -710,6 +727,7 @@ Two usage patterns:
|
|
|
710
727
|
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
711
728
|
| Database metadata | `md-db-mcp` or `python -m md_generator.db.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
712
729
|
| Graph (Neo4j / NetworkX) | `md-graph-mcp` or `python -m md_generator.graph.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
730
|
+
| OpenAPI → Markdown | `md-openapi-mcp` or `python -m md_generator.openapi.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
713
731
|
|
|
714
732
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
715
733
|
|
|
@@ -734,7 +752,7 @@ pip install -e ".[dev,all]" # or a smaller subset of extras
|
|
|
734
752
|
python -m pytest
|
|
735
753
|
```
|
|
736
754
|
|
|
737
|
-
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`) and **`
|
|
755
|
+
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`), **`graph-to-md/tests/`**, and **`openapi-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
|
|
738
756
|
|
|
739
757
|
---
|
|
740
758
|
|
|
@@ -129,17 +129,26 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
129
129
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
130
130
|
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
131
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`,
|
|
132
|
+
| `md-db-api` | `md_generator.db.api.run:main` | FastAPI on port **8010** (`DB_TO_MD_PORT`): `POST /db-to-md/run`, `POST /db-to-md/run/sqlite` (upload + ZIP), `POST /db-to-md/job`, `POST /db-to-md/job/sqlite` (upload + async job), SSE `/db-to-md/job/{id}/events` |
|
|
133
133
|
| `md-db-mcp` | `md_generator.db.api.mcp_server:main` | Standalone MCP for metadata export tools |
|
|
134
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
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
136
|
| `md-graph-mcp` | `md_generator.graph.api.mcp_server:main` | Standalone MCP for graph export tools |
|
|
137
|
-
| `
|
|
137
|
+
| `md-openapi` | `md_generator.openapi.cli.main:main` | `pip install "mdengine[openapi]"` then `md-openapi generate --file openapi.yaml --output ./docs` (or `mdengine openapi-to-md generate …`) |
|
|
138
|
+
| `md-openapi-api` | `md_generator.openapi.api.run:main` | FastAPI on port **8015** (`OPENAPI_TO_MD_PORT`): `POST /openapi-to-md/generate` (OpenAPI upload → ZIP), `/health`, MCP at **`/mcp`** |
|
|
139
|
+
| `md-openapi-mcp` | `md_generator.openapi.api.mcp_server:main` | Standalone MCP: `api_validate_openapi_yaml`, `api_generate_readme_markdown`, `api_run_sync_zip_base64` |
|
|
140
|
+
| `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` / `mdengine openapi-to-md generate …` |
|
|
141
|
+
|
|
142
|
+
**openapi-to-md (`md-openapi`):** OpenAPI **3.x** is parsed directly; **Swagger 2.0** (`swagger: "2.0"`) documents are **converted in-process** to OpenAPI 3.0.3 (deterministic, in-repo converter) before `$ref` resolution. Edge-heavy specs (unusual OAuth2 flows, vendor extensions) may still need fixes after conversion.
|
|
138
143
|
|
|
139
144
|
**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
145
|
|
|
141
146
|
**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
147
|
|
|
148
|
+
**db-to-md SQLite:** set **`database.type: sqlite`** and **`database.uri`** to a SQLAlchemy URL such as **`sqlite:///my.db`** or **`sqlite:////C:/data/my.db`**. The default SQLite catalog is **`main`**; packaged YAML that still has **`schema: public`** is normalized to **`main`** when the type is SQLite. CLI: **`--type sqlite`**. Stored routines, sequences, and partitions are not used by SQLite and stay empty; tables, indexes, views, triggers, and ERD (via FK introspection) follow the same export path as other SQL engines.
|
|
149
|
+
|
|
150
|
+
**db-to-md API SQLite upload:** **`POST /db-to-md/run/sqlite`** — `multipart/form-data` with field **`file`** (the `.sqlite` / `.db` bytes; must start with the standard `SQLite format 3` header) and optional **`config`** (JSON string with the same shape as a normal run body minus `database`: `schema`, `output`, `features`, `execution`, `limits`, `erd`). Returns the metadata ZIP immediately. **`POST /db-to-md/job/sqlite`** — same form fields; saves the file under the job workspace and runs the existing job pipeline (`GET /db-to-md/job/{id}/download` when complete). Upload size cap: env **`DB_TO_MD_MAX_SQLITE_UPLOAD_MB`** (default **256**). Large ZIPs may still require the async job path if they exceed **`DB_TO_MD_MAX_SYNC_ZIP_MB`** on the sync upload route.
|
|
151
|
+
|
|
143
152
|
**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).
|
|
144
153
|
|
|
145
154
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
@@ -456,7 +465,7 @@ Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python
|
|
|
456
465
|
|
|
457
466
|
### Thin shims (repo clone)
|
|
458
467
|
|
|
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/).
|
|
468
|
+
[`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/). [`openapi-to-md/converter.py`](openapi-to-md/converter.py) delegates to `md-openapi`; tests live under [`openapi-to-md/tests/`](openapi-to-md/tests/); example OpenAPI and output notes: [`openapi-to-md/examples/`](openapi-to-md/examples/).
|
|
460
469
|
|
|
461
470
|
---
|
|
462
471
|
|
|
@@ -488,6 +497,7 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
488
497
|
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
489
498
|
| Database metadata | `md_generator.db.api.main:app` | `db`, `api`, `mcp` |
|
|
490
499
|
| Graph metadata (Neo4j / NetworkX) | `md_generator.graph.api.main:app` | `graph`, `api`, `mcp` |
|
|
500
|
+
| OpenAPI → Markdown | `md_generator.openapi.api.main:app` | `openapi`, `api`, `mcp` |
|
|
491
501
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
492
502
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
493
503
|
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
@@ -501,12 +511,13 @@ uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
|
501
511
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
502
512
|
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
503
513
|
uvicorn md_generator.graph.api.main:app --host 127.0.0.1 --port 8012
|
|
514
|
+
uvicorn md_generator.openapi.api.main:app --host 127.0.0.1 --port 8015
|
|
504
515
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
505
516
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
506
517
|
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
507
518
|
```
|
|
508
519
|
|
|
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.
|
|
520
|
+
**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. **`md-openapi-api`** defaults to **8015** (`OPENAPI_TO_MD_PORT`) so it does not collide with **`md-youtube-api`** (**8013**) or **`md-playwright-api`** (**8014**).
|
|
510
521
|
|
|
511
522
|
### MCP over HTTP on the same server
|
|
512
523
|
|
|
@@ -528,6 +539,7 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
528
539
|
| 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
540
|
| 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
541
|
| 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**) |
|
|
542
|
+
| OpenAPI → Markdown | `OPENAPI_TO_MD_` | `OPENAPI_TO_MD_CORS_ORIGINS`, `OPENAPI_TO_MD_MAX_SYNC_ZIP_MB`, `OPENAPI_TO_MD_HOST`, `OPENAPI_TO_MD_PORT` (default **8015**) |
|
|
531
543
|
| 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` |
|
|
532
544
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
533
545
|
| 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 |
|
|
@@ -560,6 +572,7 @@ Two usage patterns:
|
|
|
560
572
|
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
561
573
|
| Database metadata | `md-db-mcp` or `python -m md_generator.db.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
562
574
|
| Graph (Neo4j / NetworkX) | `md-graph-mcp` or `python -m md_generator.graph.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
575
|
+
| OpenAPI → Markdown | `md-openapi-mcp` or `python -m md_generator.openapi.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
563
576
|
|
|
564
577
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
565
578
|
|
|
@@ -584,7 +597,7 @@ pip install -e ".[dev,all]" # or a smaller subset of extras
|
|
|
584
597
|
python -m pytest
|
|
585
598
|
```
|
|
586
599
|
|
|
587
|
-
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`) and **`
|
|
600
|
+
Tests live under each legacy folder’s `tests/` directory (e.g. `pdf-to-md/tests/`), **`graph-to-md/tests/`**, and **`openapi-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
|
|
588
601
|
|
|
589
602
|
---
|
|
590
603
|
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mdengine"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives,
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, web URLs, databases, graphs, and OpenAPI specs 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", "database"]
|
|
14
|
+
keywords = ["markdown", "pdf", "docx", "pptx", "xlsx", "ocr", "zip", "url", "html", "youtube", "playwright", "database", "openapi"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -106,6 +106,11 @@ graph = [
|
|
|
106
106
|
"neo4j>=5.14.0",
|
|
107
107
|
"pyyaml>=6.0.0",
|
|
108
108
|
]
|
|
109
|
+
openapi = [
|
|
110
|
+
"pyyaml>=6.0.0",
|
|
111
|
+
"prance>=23.6.0",
|
|
112
|
+
"openapi-spec-validator>=0.7.0",
|
|
113
|
+
]
|
|
109
114
|
api = [
|
|
110
115
|
"fastapi>=0.115.0",
|
|
111
116
|
"uvicorn[standard]>=0.32.0",
|
|
@@ -126,6 +131,7 @@ dev = [
|
|
|
126
131
|
"mdengine[playwright]",
|
|
127
132
|
"mdengine[db]",
|
|
128
133
|
"mdengine[graph]",
|
|
134
|
+
"mdengine[openapi]",
|
|
129
135
|
]
|
|
130
136
|
all = [
|
|
131
137
|
"pymupdf>=1.24.0",
|
|
@@ -187,6 +193,9 @@ md-db-mcp = "md_generator.db.api.mcp_server:main"
|
|
|
187
193
|
md-graph = "md_generator.graph.cli.main:main"
|
|
188
194
|
md-graph-api = "md_generator.graph.api.run:main"
|
|
189
195
|
md-graph-mcp = "md_generator.graph.api.mcp_server:main"
|
|
196
|
+
md-openapi = "md_generator.openapi.cli.main:main"
|
|
197
|
+
md-openapi-api = "md_generator.openapi.api.run:main"
|
|
198
|
+
md-openapi-mcp = "md_generator.openapi.api.mcp_server:main"
|
|
190
199
|
mdengine = "md_generator.engine_cli:main"
|
|
191
200
|
|
|
192
201
|
[tool.setuptools.packages.find]
|
|
@@ -195,6 +204,7 @@ where = ["src"]
|
|
|
195
204
|
[tool.setuptools.package-data]
|
|
196
205
|
"md_generator.db.config" = ["*.yaml"]
|
|
197
206
|
"md_generator.graph.config" = ["*.yaml"]
|
|
207
|
+
"md_generator.openapi.config" = ["*.yaml"]
|
|
198
208
|
|
|
199
209
|
[tool.pytest.ini_options]
|
|
200
210
|
pythonpath = ["src"]
|
|
@@ -213,5 +223,6 @@ testpaths = [
|
|
|
213
223
|
"playwright-to-md/tests",
|
|
214
224
|
"db-to-md/tests",
|
|
215
225
|
"graph-to-md/tests",
|
|
226
|
+
"openapi-to-md/tests",
|
|
216
227
|
]
|
|
217
228
|
markers = ["integration: optional dependency or slower checks"]
|
|
@@ -8,6 +8,7 @@ from md_generator.db.adapters.mongo_adapter import MongoAdapter
|
|
|
8
8
|
from md_generator.db.adapters.mysql_adapter import MysqlAdapter
|
|
9
9
|
from md_generator.db.adapters.oracle_adapter import OracleAdapter
|
|
10
10
|
from md_generator.db.adapters.postgres_adapter import PostgresAdapter
|
|
11
|
+
from md_generator.db.adapters.sqlite_adapter import SqliteAdapter
|
|
11
12
|
from md_generator.db.core.base_adapter import BaseAdapter
|
|
12
13
|
|
|
13
14
|
|
|
@@ -37,4 +38,9 @@ def create_adapter(
|
|
|
37
38
|
if not database:
|
|
38
39
|
raise ValueError("MongoDB requires database name in config or --database")
|
|
39
40
|
return MongoAdapter(uri, database, lim)
|
|
41
|
+
if t in ("sqlite",):
|
|
42
|
+
sch = (schema or "main").strip()
|
|
43
|
+
if sch.lower() == "public":
|
|
44
|
+
sch = "main"
|
|
45
|
+
return SqliteAdapter(uri, sch, lim)
|
|
40
46
|
raise ValueError(f"Unsupported database type: {db_type!r}")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import create_engine, text
|
|
7
|
+
from sqlalchemy.pool import NullPool
|
|
8
|
+
|
|
9
|
+
from md_generator.db.adapters.sql_common import SqlAlchemyAdapter, indexes_from_inspector, table_detail_from_inspector
|
|
10
|
+
from md_generator.db.core.models import IndexInfo, TableDetail, TableInfo, TriggerInfo, ViewInfo
|
|
11
|
+
|
|
12
|
+
_ATTACH_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _normalize_sqlite_uri(uri: str) -> str:
|
|
16
|
+
u = uri.strip()
|
|
17
|
+
if u.startswith("sqlite://") and "+pysqlite" not in u and "+aiosqlite" not in u:
|
|
18
|
+
return u.replace("sqlite://", "sqlite+pysqlite://", 1)
|
|
19
|
+
return u
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _sqlite_master_expr(schema: str) -> str:
|
|
23
|
+
"""Return SQL fragment for sqlite_master of the given catalog (``main`` or attach name)."""
|
|
24
|
+
s = (schema or "main").strip()
|
|
25
|
+
if s.lower() == "main":
|
|
26
|
+
return "sqlite_master"
|
|
27
|
+
if not _ATTACH_NAME.fullmatch(s):
|
|
28
|
+
raise ValueError(f"Invalid SQLite catalog/schema name: {schema!r} (use main or an attach alias)")
|
|
29
|
+
return f'"{s}".sqlite_master'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SqliteAdapter(SqlAlchemyAdapter):
|
|
33
|
+
db_type = "sqlite"
|
|
34
|
+
|
|
35
|
+
def __init__(self, uri: str, schema: str, limits: dict[str, Any]) -> None:
|
|
36
|
+
eng = create_engine(
|
|
37
|
+
_normalize_sqlite_uri(uri),
|
|
38
|
+
poolclass=NullPool,
|
|
39
|
+
pool_pre_ping=True,
|
|
40
|
+
connect_args={"check_same_thread": False},
|
|
41
|
+
future=True,
|
|
42
|
+
)
|
|
43
|
+
super().__init__(eng, schema, limits)
|
|
44
|
+
|
|
45
|
+
def validate_connection(self) -> None:
|
|
46
|
+
with self._engine.connect() as conn:
|
|
47
|
+
conn.execute(text("SELECT 1"))
|
|
48
|
+
|
|
49
|
+
def get_tables(self) -> list[TableInfo]:
|
|
50
|
+
insp = self._inspector()
|
|
51
|
+
sch = self._schema or "main"
|
|
52
|
+
names = insp.get_table_names(schema=sch)
|
|
53
|
+
max_t = int(self._limits.get("max_tables", 10_000))
|
|
54
|
+
out = [TableInfo(schema=sch, name=n) for n in sorted(names)[:max_t]]
|
|
55
|
+
return out
|
|
56
|
+
|
|
57
|
+
def get_table_detail(self, table: TableInfo) -> TableDetail:
|
|
58
|
+
return table_detail_from_inspector(self._inspector(), self._schema, table)
|
|
59
|
+
|
|
60
|
+
def get_indexes(self, table: TableInfo) -> list[IndexInfo]:
|
|
61
|
+
return indexes_from_inspector(self._inspector(), self._schema, table)
|
|
62
|
+
|
|
63
|
+
def get_views(self) -> list[ViewInfo]:
|
|
64
|
+
sch = self._schema or "main"
|
|
65
|
+
master = _sqlite_master_expr(sch)
|
|
66
|
+
q = text(f"SELECT name, sql FROM {master} WHERE type = 'view' AND sql IS NOT NULL ORDER BY name")
|
|
67
|
+
out: list[ViewInfo] = []
|
|
68
|
+
try:
|
|
69
|
+
with self._engine.connect() as conn:
|
|
70
|
+
for row in conn.execute(q).mappings():
|
|
71
|
+
out.append(
|
|
72
|
+
ViewInfo(
|
|
73
|
+
schema=sch,
|
|
74
|
+
name=str(row["name"]),
|
|
75
|
+
definition=str(row["sql"]) if row.get("sql") is not None else None,
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
except Exception:
|
|
79
|
+
return []
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
def get_triggers(self) -> list[TriggerInfo]:
|
|
83
|
+
sch = self._schema or "main"
|
|
84
|
+
master = _sqlite_master_expr(sch)
|
|
85
|
+
q = text(
|
|
86
|
+
f"SELECT name, tbl_name, sql FROM {master} "
|
|
87
|
+
"WHERE type = 'trigger' AND sql IS NOT NULL ORDER BY tbl_name, name"
|
|
88
|
+
)
|
|
89
|
+
out: list[TriggerInfo] = []
|
|
90
|
+
try:
|
|
91
|
+
with self._engine.connect() as conn:
|
|
92
|
+
for row in conn.execute(q).mappings():
|
|
93
|
+
sql = row.get("sql")
|
|
94
|
+
out.append(
|
|
95
|
+
TriggerInfo(
|
|
96
|
+
schema=sch,
|
|
97
|
+
name=str(row["name"]),
|
|
98
|
+
table_schema=sch,
|
|
99
|
+
table_name=str(row["tbl_name"]),
|
|
100
|
+
definition=str(sql) if sql is not None else None,
|
|
101
|
+
timing=None,
|
|
102
|
+
events=None,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
except Exception:
|
|
106
|
+
return []
|
|
107
|
+
return out
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
import zipfile
|
|
5
6
|
from contextlib import asynccontextmanager
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
|
-
from fastapi import FastAPI, HTTPException, Request
|
|
9
|
+
from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile
|
|
10
|
+
from pydantic import ValidationError
|
|
9
11
|
from fastapi.middleware.cors import CORSMiddleware
|
|
10
12
|
from fastapi.responses import FileResponse, Response, StreamingResponse
|
|
11
13
|
from starlette.background import BackgroundTask
|
|
12
14
|
|
|
13
15
|
from md_generator.db.api.schemas import DbToMdRunBody
|
|
14
16
|
from md_generator.db.api.settings import DbApiSettings, cors_list, sqlite_path_resolved
|
|
17
|
+
from md_generator.db.api.sqlite_upload import parse_sqlite_upload_config_json
|
|
15
18
|
from md_generator.db.core.job_manager import JobManager
|
|
19
|
+
from md_generator.db.core.util import is_sqlite_database_bytes, sqlite_uri_for_path
|
|
16
20
|
from md_generator.db.core.zip_export import build_markdown_zip_bytes
|
|
17
21
|
from md_generator.db.mcp.server import build_mcp_stack
|
|
18
22
|
from md_generator.db.mcp.sse import format_sse
|
|
@@ -50,6 +54,90 @@ async def health() -> dict[str, str]:
|
|
|
50
54
|
return {"status": "ok"}
|
|
51
55
|
|
|
52
56
|
|
|
57
|
+
@app.post("/db-to-md/run/sqlite")
|
|
58
|
+
async def db_run_sqlite_upload(
|
|
59
|
+
request: Request,
|
|
60
|
+
file: UploadFile = File(..., description="SQLite .db / .sqlite file (SQLite format 3)"),
|
|
61
|
+
config: str | None = Form(None, description="Optional JSON object: schema, output, features, execution, limits, erd"),
|
|
62
|
+
) -> Response:
|
|
63
|
+
"""Sync export: upload a SQLite file, run db-to-md, return a ZIP (same limits as ``/db-to-md/run``)."""
|
|
64
|
+
import tempfile
|
|
65
|
+
|
|
66
|
+
settings: DbApiSettings = request.app.state.settings
|
|
67
|
+
max_upload = max(1, settings.max_sqlite_upload_mb) * 1024 * 1024
|
|
68
|
+
data = await file.read()
|
|
69
|
+
if len(data) > max_upload:
|
|
70
|
+
raise HTTPException(
|
|
71
|
+
status_code=413,
|
|
72
|
+
detail=f"SQLite upload exceeds DB_TO_MD_MAX_SQLITE_UPLOAD_MB ({settings.max_sqlite_upload_mb})",
|
|
73
|
+
)
|
|
74
|
+
if not is_sqlite_database_bytes(data):
|
|
75
|
+
raise HTTPException(
|
|
76
|
+
status_code=400,
|
|
77
|
+
detail="File is not a SQLite database (expected 'SQLite format 3' header)",
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
opts = parse_sqlite_upload_config_json(config)
|
|
81
|
+
except (ValueError, ValidationError) as e:
|
|
82
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
83
|
+
try:
|
|
84
|
+
with tempfile.TemporaryDirectory() as td:
|
|
85
|
+
dbp = Path(td) / "upload.sqlite"
|
|
86
|
+
dbp.write_bytes(data)
|
|
87
|
+
uri = sqlite_uri_for_path(dbp)
|
|
88
|
+
cfg = opts.to_run_config(uri)
|
|
89
|
+
zip_data = build_markdown_zip_bytes(cfg)
|
|
90
|
+
except HTTPException:
|
|
91
|
+
raise
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
94
|
+
max_b = settings.max_sync_zip_mb * 1024 * 1024
|
|
95
|
+
if len(zip_data) > max_b:
|
|
96
|
+
raise HTTPException(
|
|
97
|
+
status_code=413,
|
|
98
|
+
detail=f"ZIP exceeds DB_TO_MD_MAX_SYNC_ZIP_MB ({settings.max_sync_zip_mb}); use POST /db-to-md/job/sqlite",
|
|
99
|
+
)
|
|
100
|
+
return Response(
|
|
101
|
+
content=zip_data,
|
|
102
|
+
media_type="application/zip",
|
|
103
|
+
headers={"Content-Disposition": 'attachment; filename="db-metadata.zip"'},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.post("/db-to-md/job/sqlite")
|
|
108
|
+
async def db_job_sqlite_upload(
|
|
109
|
+
request: Request,
|
|
110
|
+
file: UploadFile = File(..., description="SQLite .db / .sqlite file (SQLite format 3)"),
|
|
111
|
+
config: str | None = Form(None, description="Optional JSON object: schema, output, features, execution, limits, erd"),
|
|
112
|
+
) -> dict[str, str]:
|
|
113
|
+
"""Async job: save upload under the job workspace, run export, then ``GET …/download`` for the ZIP."""
|
|
114
|
+
settings: DbApiSettings = request.app.state.settings
|
|
115
|
+
jobs: JobManager = request.app.state.jobs
|
|
116
|
+
max_upload = max(1, settings.max_sqlite_upload_mb) * 1024 * 1024
|
|
117
|
+
data = await file.read()
|
|
118
|
+
if len(data) > max_upload:
|
|
119
|
+
raise HTTPException(
|
|
120
|
+
status_code=413,
|
|
121
|
+
detail=f"SQLite upload exceeds DB_TO_MD_MAX_SQLITE_UPLOAD_MB ({settings.max_sqlite_upload_mb})",
|
|
122
|
+
)
|
|
123
|
+
if not is_sqlite_database_bytes(data):
|
|
124
|
+
raise HTTPException(
|
|
125
|
+
status_code=400,
|
|
126
|
+
detail="File is not a SQLite database (expected 'SQLite format 3' header)",
|
|
127
|
+
)
|
|
128
|
+
try:
|
|
129
|
+
opts = parse_sqlite_upload_config_json(config)
|
|
130
|
+
except (ValueError, ValidationError) as e:
|
|
131
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
132
|
+
cfg_template = opts.to_run_config("sqlite:///placeholder")
|
|
133
|
+
try:
|
|
134
|
+
rec = jobs.create_sqlite_file_job(data, cfg_template)
|
|
135
|
+
jobs.run_job_thread(rec.job_id)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
138
|
+
return {"job_id": rec.job_id}
|
|
139
|
+
|
|
140
|
+
|
|
53
141
|
@app.post("/db-to-md/run")
|
|
54
142
|
async def db_run_sync(request: Request, body: DbToMdRunBody) -> Response:
|
|
55
143
|
settings: DbApiSettings = request.app.state.settings
|
|
@@ -10,7 +10,7 @@ from md_generator.db.core.run_config import ErdConfig, RunConfig
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class DatabaseSection(BaseModel):
|
|
13
|
-
type: str = Field(..., description="postgres | mysql | oracle | mongo")
|
|
13
|
+
type: str = Field(..., description="postgres | mysql | oracle | mongo | sqlite")
|
|
14
14
|
uri: str
|
|
15
15
|
schema: str | None = None
|
|
16
16
|
database: str | None = None
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, model_validator
|
|
8
|
+
|
|
9
|
+
from md_generator.db.core.models import FEATURES
|
|
10
|
+
from md_generator.db.core.run_config import ErdConfig, RunConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SqliteUploadOutputSection(BaseModel):
|
|
14
|
+
path: str = "./docs"
|
|
15
|
+
split_files: bool = True
|
|
16
|
+
write_combined_feature_markdown: bool = False
|
|
17
|
+
readme_feature_merge: Literal["none", "inline", "toc"] = "none"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SqliteUploadFeaturesSection(BaseModel):
|
|
21
|
+
include: list[str] | None = None
|
|
22
|
+
exclude: list[str] = Field(default_factory=list)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SqliteUploadExecutionSection(BaseModel):
|
|
26
|
+
workers: int = Field(default=4, ge=1, le=32)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SqliteUploadErdSection(BaseModel):
|
|
30
|
+
max_tables: int = Field(default=100, ge=1, le=100_000)
|
|
31
|
+
scope: Literal["full", "per_schema", "per_table"] = Field(default="full")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SqliteUploadJsonBody(BaseModel):
|
|
35
|
+
"""Same export options as ``DbToMdRunBody`` except the DB file comes from the upload."""
|
|
36
|
+
|
|
37
|
+
schema: str | None = Field(default="main", description="SQLite catalog (default main)")
|
|
38
|
+
output: SqliteUploadOutputSection = Field(default_factory=SqliteUploadOutputSection)
|
|
39
|
+
features: SqliteUploadFeaturesSection = Field(default_factory=SqliteUploadFeaturesSection)
|
|
40
|
+
execution: SqliteUploadExecutionSection = Field(default_factory=SqliteUploadExecutionSection)
|
|
41
|
+
limits: dict[str, Any] = Field(default_factory=dict)
|
|
42
|
+
erd: SqliteUploadErdSection = Field(default_factory=SqliteUploadErdSection)
|
|
43
|
+
|
|
44
|
+
@model_validator(mode="after")
|
|
45
|
+
def check_features(self) -> SqliteUploadJsonBody:
|
|
46
|
+
if self.features.include:
|
|
47
|
+
bad = set(self.features.include) - FEATURES
|
|
48
|
+
if bad:
|
|
49
|
+
raise ValueError(f"Unknown features in include: {sorted(bad)}")
|
|
50
|
+
bad_ex = set(self.features.exclude) - FEATURES
|
|
51
|
+
if bad_ex:
|
|
52
|
+
raise ValueError(f"Unknown features in exclude: {sorted(bad_ex)}")
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def to_run_config(self, sqlite_uri: str) -> RunConfig:
|
|
56
|
+
sch = self.schema
|
|
57
|
+
if sch is None or str(sch).strip() == "" or str(sch).lower() == "public":
|
|
58
|
+
sch = "main"
|
|
59
|
+
inc = frozenset(self.features.include) if self.features.include else frozenset(FEATURES)
|
|
60
|
+
merge = self.output.readme_feature_merge
|
|
61
|
+
write_combined = self.output.write_combined_feature_markdown
|
|
62
|
+
if merge != "none" and self.output.split_files:
|
|
63
|
+
write_combined = True
|
|
64
|
+
return RunConfig(
|
|
65
|
+
db_type="sqlite",
|
|
66
|
+
uri=sqlite_uri,
|
|
67
|
+
schema=sch,
|
|
68
|
+
database=None,
|
|
69
|
+
output_path=Path(self.output.path),
|
|
70
|
+
split_files=self.output.split_files,
|
|
71
|
+
write_combined_feature_markdown=write_combined,
|
|
72
|
+
readme_feature_merge=merge,
|
|
73
|
+
include=inc,
|
|
74
|
+
exclude=frozenset(self.features.exclude),
|
|
75
|
+
workers=self.execution.workers,
|
|
76
|
+
limits=dict(self.limits),
|
|
77
|
+
erd=ErdConfig(max_tables=self.erd.max_tables, scope=self.erd.scope).normalized(),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def parse_sqlite_upload_config_json(raw: str | None) -> SqliteUploadJsonBody:
|
|
82
|
+
if raw is None or not str(raw).strip():
|
|
83
|
+
return SqliteUploadJsonBody()
|
|
84
|
+
data = json.loads(raw)
|
|
85
|
+
if not isinstance(data, dict):
|
|
86
|
+
raise ValueError("config must be a JSON object")
|
|
87
|
+
return SqliteUploadJsonBody.model_validate(data)
|