mdengine 0.4.0__tar.gz → 0.5.0__tar.gz

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