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.
Files changed (288) hide show
  1. {mdengine-0.5.0 → mdengine-0.6.0}/PKG-INFO +26 -8
  2. {mdengine-0.5.0 → mdengine-0.6.0}/README.md +18 -5
  3. {mdengine-0.5.0 → mdengine-0.6.0}/pyproject.toml +14 -3
  4. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/__init__.py +1 -1
  5. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/factory.py +6 -0
  6. mdengine-0.6.0/src/md_generator/db/adapters/sqlite_adapter.py +107 -0
  7. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/main.py +89 -1
  8. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/schemas.py +1 -1
  9. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/settings.py +1 -0
  10. mdengine-0.6.0/src/md_generator/db/api/sqlite_upload.py +87 -0
  11. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/cli/main.py +1 -1
  12. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/job_manager.py +44 -0
  13. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/run_config.py +5 -0
  14. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/util.py +12 -0
  15. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/engine_cli.py +6 -2
  16. mdengine-0.6.0/src/md_generator/openapi/__init__.py +5 -0
  17. mdengine-0.6.0/src/md_generator/openapi/api/__init__.py +1 -0
  18. mdengine-0.6.0/src/md_generator/openapi/api/main.py +84 -0
  19. mdengine-0.6.0/src/md_generator/openapi/api/mcp_server.py +29 -0
  20. mdengine-0.6.0/src/md_generator/openapi/api/run.py +15 -0
  21. mdengine-0.6.0/src/md_generator/openapi/api/schemas.py +33 -0
  22. mdengine-0.6.0/src/md_generator/openapi/api/settings.py +21 -0
  23. mdengine-0.6.0/src/md_generator/openapi/cli/__init__.py +1 -0
  24. mdengine-0.6.0/src/md_generator/openapi/cli/main.py +75 -0
  25. mdengine-0.6.0/src/md_generator/openapi/config/__init__.py +1 -0
  26. mdengine-0.6.0/src/md_generator/openapi/config/default.yaml +14 -0
  27. mdengine-0.6.0/src/md_generator/openapi/converters/__init__.py +5 -0
  28. mdengine-0.6.0/src/md_generator/openapi/converters/swagger2_to_openapi3.py +453 -0
  29. mdengine-0.6.0/src/md_generator/openapi/core/__init__.py +1 -0
  30. mdengine-0.6.0/src/md_generator/openapi/core/extractor.py +207 -0
  31. mdengine-0.6.0/src/md_generator/openapi/core/run_config.py +100 -0
  32. mdengine-0.6.0/src/md_generator/openapi/core/zip_export.py +24 -0
  33. mdengine-0.6.0/src/md_generator/openapi/enrichers/__init__.py +5 -0
  34. mdengine-0.6.0/src/md_generator/openapi/enrichers/rules.py +133 -0
  35. mdengine-0.6.0/src/md_generator/openapi/enrichers/sample_body.py +73 -0
  36. mdengine-0.6.0/src/md_generator/openapi/generators/__init__.py +9 -0
  37. mdengine-0.6.0/src/md_generator/openapi/generators/dependency_graph.py +54 -0
  38. mdengine-0.6.0/src/md_generator/openapi/loaders/__init__.py +5 -0
  39. mdengine-0.6.0/src/md_generator/openapi/loaders/spec_loader.py +115 -0
  40. mdengine-0.6.0/src/md_generator/openapi/mcp/__init__.py +1 -0
  41. mdengine-0.6.0/src/md_generator/openapi/mcp/server.py +71 -0
  42. mdengine-0.6.0/src/md_generator/openapi/models/__init__.py +25 -0
  43. mdengine-0.6.0/src/md_generator/openapi/models/domain.py +106 -0
  44. mdengine-0.6.0/src/md_generator/openapi/normalizers/__init__.py +6 -0
  45. mdengine-0.6.0/src/md_generator/openapi/normalizers/operations.py +287 -0
  46. mdengine-0.6.0/src/md_generator/openapi/normalizers/schema_flatten.py +104 -0
  47. mdengine-0.6.0/src/md_generator/openapi/parsers/__init__.py +5 -0
  48. mdengine-0.6.0/src/md_generator/openapi/parsers/openapi_parser.py +18 -0
  49. mdengine-0.6.0/src/md_generator/openapi/resolvers/__init__.py +5 -0
  50. mdengine-0.6.0/src/md_generator/openapi/resolvers/ref_resolver.py +16 -0
  51. mdengine-0.6.0/src/md_generator/openapi/writers/__init__.py +5 -0
  52. mdengine-0.6.0/src/md_generator/openapi/writers/markdown_writer.py +253 -0
  53. {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/PKG-INFO +26 -8
  54. {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/SOURCES.txt +39 -0
  55. {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/entry_points.txt +3 -0
  56. {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/requires.txt +6 -0
  57. {mdengine-0.5.0 → mdengine-0.6.0}/LICENSE +0 -0
  58. {mdengine-0.5.0 → mdengine-0.6.0}/setup.cfg +0 -0
  59. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/__init__.py +0 -0
  60. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/__init__.py +0 -0
  61. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/convert_runner.py +0 -0
  62. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/jobs.py +0 -0
  63. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/main.py +0 -0
  64. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/mcp_server.py +0 -0
  65. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/mcp_setup.py +0 -0
  66. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/query_options.py +0 -0
  67. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/api/settings.py +0 -0
  68. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/convert_impl.py +0 -0
  69. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/converter.py +0 -0
  70. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/archive/options.py +0 -0
  71. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/__init__.py +0 -0
  72. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/__init__.py +0 -0
  73. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/mongo_adapter.py +0 -0
  74. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/mysql_adapter.py +0 -0
  75. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/oracle_adapter.py +0 -0
  76. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/postgres_adapter.py +0 -0
  77. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/adapters/sql_common.py +0 -0
  78. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/__init__.py +0 -0
  79. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/mcp_server.py +0 -0
  80. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/api/run.py +0 -0
  81. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/cli/__init__.py +0 -0
  82. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/config/__init__.py +0 -0
  83. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/config/default.yaml +0 -0
  84. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/__init__.py +0 -0
  85. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/base_adapter.py +0 -0
  86. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/__init__.py +0 -0
  87. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/dot_emitter.py +0 -0
  88. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/filter.py +0 -0
  89. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/mermaid_emitter.py +0 -0
  90. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/mermaid_render.py +0 -0
  91. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/model.py +0 -0
  92. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/pipeline.py +0 -0
  93. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/erd/render.py +0 -0
  94. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/extractor.py +0 -0
  95. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/markdown_writer.py +0 -0
  96. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/models.py +0 -0
  97. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/core/zip_export.py +0 -0
  98. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/__init__.py +0 -0
  99. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/server.py +0 -0
  100. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/sse.py +0 -0
  101. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/db/mcp/stream.py +0 -0
  102. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/__init__.py +0 -0
  103. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/__init__.py +0 -0
  104. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/factory.py +0 -0
  105. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/neo4j_adapter.py +0 -0
  106. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/adapters/networkx_adapter.py +0 -0
  107. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/__init__.py +0 -0
  108. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/main.py +0 -0
  109. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/mcp_server.py +0 -0
  110. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/run.py +0 -0
  111. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/schemas.py +0 -0
  112. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/api/settings.py +0 -0
  113. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/cli/__init__.py +0 -0
  114. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/cli/main.py +0 -0
  115. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/config/__init__.py +0 -0
  116. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/config/default.yaml +0 -0
  117. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/__init__.py +0 -0
  118. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/base_adapter.py +0 -0
  119. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/extractor.py +0 -0
  120. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/graph_builder.py +0 -0
  121. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/job_manager.py +0 -0
  122. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/markdown_writer.py +0 -0
  123. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/models.py +0 -0
  124. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/run_config.py +0 -0
  125. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/viz.py +0 -0
  126. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/core/zip_export.py +0 -0
  127. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/__init__.py +0 -0
  128. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/server.py +0 -0
  129. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/sse.py +0 -0
  130. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/graph/mcp/stream.py +0 -0
  131. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/__init__.py +0 -0
  132. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/__init__.py +0 -0
  133. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/main.py +0 -0
  134. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/mcp_server.py +0 -0
  135. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/query_options.py +0 -0
  136. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/settings.py +0 -0
  137. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/staging.py +0 -0
  138. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/api/zip_bundle.py +0 -0
  139. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/__init__.py +0 -0
  140. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/base.py +0 -0
  141. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/easy.py +0 -0
  142. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/paddle.py +0 -0
  143. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/backends/tesseract.py +0 -0
  144. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/convert_impl.py +0 -0
  145. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/converter.py +0 -0
  146. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/emit.py +0 -0
  147. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/io_util.py +0 -0
  148. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/image/utils.py +0 -0
  149. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/__init__.py +0 -0
  150. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/__init__.py +0 -0
  151. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/__init__.py +0 -0
  152. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/main.py +0 -0
  153. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/mcp_server.py +0 -0
  154. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/mcp_setup.py +0 -0
  155. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/run.py +0 -0
  156. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/api/settings.py +0 -0
  157. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/converter.py +0 -0
  158. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/formatter.py +0 -0
  159. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/audio/service.py +0 -0
  160. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/document_converter.py +0 -0
  161. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/job_store.py +0 -0
  162. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/__init__.py +0 -0
  163. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/__init__.py +0 -0
  164. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/main.py +0 -0
  165. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/mcp_server.py +0 -0
  166. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/mcp_setup.py +0 -0
  167. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/run.py +0 -0
  168. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/api/settings.py +0 -0
  169. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/converter.py +0 -0
  170. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/formatter.py +0 -0
  171. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/video/service.py +0 -0
  172. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/whisper_language.py +0 -0
  173. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/__init__.py +0 -0
  174. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/__init__.py +0 -0
  175. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/main.py +0 -0
  176. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/mcp_server.py +0 -0
  177. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/mcp_setup.py +0 -0
  178. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/run.py +0 -0
  179. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/api/settings.py +0 -0
  180. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/converter.py +0 -0
  181. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/formatter.py +0 -0
  182. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/metadata.py +0 -0
  183. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/service.py +0 -0
  184. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/media/youtube/transcript.py +0 -0
  185. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/__init__.py +0 -0
  186. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/__init__.py +0 -0
  187. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/main.py +0 -0
  188. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/mcp_server.py +0 -0
  189. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/settings.py +0 -0
  190. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/api/zip_bundle.py +0 -0
  191. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/converter.py +0 -0
  192. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/md_emit.py +0 -0
  193. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/pdf_extract.py +0 -0
  194. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/pdf/utils.py +0 -0
  195. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/__init__.py +0 -0
  196. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/__init__.py +0 -0
  197. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/convert_runner.py +0 -0
  198. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/jobs.py +0 -0
  199. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/main.py +0 -0
  200. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/mcp_server.py +0 -0
  201. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/mcp_setup.py +0 -0
  202. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/run.py +0 -0
  203. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/api/settings.py +0 -0
  204. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/assets.py +0 -0
  205. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/chunker.py +0 -0
  206. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/cli.py +0 -0
  207. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/extractor.py +0 -0
  208. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/html_to_md.py +0 -0
  209. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/options.py +0 -0
  210. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/pipeline.py +0 -0
  211. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/playwright/playwright_fetcher.py +0 -0
  212. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/__init__.py +0 -0
  213. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/__init__.py +0 -0
  214. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/convert_runner.py +0 -0
  215. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/jobs.py +0 -0
  216. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/main.py +0 -0
  217. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/mcp_server.py +0 -0
  218. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/mcp_setup.py +0 -0
  219. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/query_options.py +0 -0
  220. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/api/settings.py +0 -0
  221. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/convert_impl.py +0 -0
  222. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/converter.py +0 -0
  223. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/embedded_extract.py +0 -0
  224. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/ooxml_media.py +0 -0
  225. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/options.py +0 -0
  226. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/post_assets.py +0 -0
  227. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/text_formatting.py +0 -0
  228. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_pdf_md/__init__.py +0 -0
  229. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_pdf_md/convert.py +0 -0
  230. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_word_md/__init__.py +0 -0
  231. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/vendor_word_md/convert.py +0 -0
  232. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/ppt/zip_deep.py +0 -0
  233. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/__init__.py +0 -0
  234. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/__init__.py +0 -0
  235. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/convert_runner.py +0 -0
  236. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/jobs.py +0 -0
  237. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/main.py +0 -0
  238. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/mcp_server.py +0 -0
  239. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/mcp_setup.py +0 -0
  240. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/query_options.py +0 -0
  241. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/api/settings.py +0 -0
  242. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/convert_impl.py +0 -0
  243. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/converter.py +0 -0
  244. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/format_detect.py +0 -0
  245. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_json.py +0 -0
  246. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_txt.py +0 -0
  247. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/md_emit_xml.py +0 -0
  248. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/text/options.py +0 -0
  249. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/__init__.py +0 -0
  250. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/__init__.py +0 -0
  251. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/convert_runner.py +0 -0
  252. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/jobs.py +0 -0
  253. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/main.py +0 -0
  254. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/mcp_server.py +0 -0
  255. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/mcp_setup.py +0 -0
  256. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/query_options.py +0 -0
  257. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/api/settings.py +0 -0
  258. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/assets.py +0 -0
  259. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/convert_impl.py +0 -0
  260. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/converter.py +0 -0
  261. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/crawl.py +0 -0
  262. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/extract.py +0 -0
  263. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/fetch.py +0 -0
  264. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/html_to_md.py +0 -0
  265. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/options.py +0 -0
  266. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/page_convert.py +0 -0
  267. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/post_convert_assets.py +0 -0
  268. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/url/robots_util.py +0 -0
  269. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/__init__.py +0 -0
  270. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/__init__.py +0 -0
  271. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/convert_util.py +0 -0
  272. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/jobs.py +0 -0
  273. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/main.py +0 -0
  274. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/api/mcp_server.py +0 -0
  275. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/artifact.py +0 -0
  276. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/converter.py +0 -0
  277. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/word/settings.py +0 -0
  278. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/__init__.py +0 -0
  279. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/api/__init__.py +0 -0
  280. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/api/app.py +0 -0
  281. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/convert_config.py +0 -0
  282. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/converter.py +0 -0
  283. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/converter_core.py +0 -0
  284. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/excel_reader.py +0 -0
  285. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/markdown_emitter.py +0 -0
  286. {mdengine-0.5.0 → mdengine-0.6.0}/src/md_generator/xlsx/mcp_server.py +0 -0
  287. {mdengine-0.5.0 → mdengine-0.6.0}/src/mdengine.egg-info/dependency_links.txt +0 -0
  288. {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.5.0
4
- Summary: Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown.
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`, `/db-to-md/job`, SSE `/db-to-md/job/{id}/events` |
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
- | `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` (delegates to `md-db` / `md-graph`) |
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 **`graph-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
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`, `/db-to-md/job`, SSE `/db-to-md/job/{id}/events` |
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
- | `mdengine` | `md_generator.engine_cli:main` | `mdengine db-to-md …` / `mdengine graph-to-md …` (delegates to `md-db` / `md-graph`) |
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 **`graph-to-md/tests/`**; `pyproject.toml` configures `pythonpath = ["src"]` so `md_generator` resolves without a separate `PYTHONPATH`.
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.5.0"
8
- description = "Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown."
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"]
@@ -1,5 +1,5 @@
1
1
  """md_generator: PDF, Office, image, text, and ZIP to Markdown conversion."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -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
@@ -16,6 +16,7 @@ class DbApiSettings(BaseSettings):
16
16
  job_workspace_root: str | None = None
17
17
  cors_origins: str = "*"
18
18
  max_sync_zip_mb: int = 80
19
+ max_sqlite_upload_mb: int = 256
19
20
 
20
21
 
21
22
  def cors_list(settings: DbApiSettings) -> list[str]:
@@ -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)