datahike-browser-tests 1.0.0

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 (324) hide show
  1. package/.circleci/config.yml +405 -0
  2. package/.circleci/scripts/gen_ci.clj +194 -0
  3. package/.cirrus.yml +60 -0
  4. package/.clj-kondo/babashka/sci/config.edn +1 -0
  5. package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
  6. package/.clj-kondo/config.edn +95 -0
  7. package/.dir-locals.el +2 -0
  8. package/.github/FUNDING.yml +3 -0
  9. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
  10. package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
  11. package/.github/ISSUE_TEMPLATE/config.yml +6 -0
  12. package/.github/pull_request_template.md +24 -0
  13. package/.github/workflows/native-image.yml +84 -0
  14. package/LICENSE +203 -0
  15. package/README.md +273 -0
  16. package/bb/deps.edn +9 -0
  17. package/bb/resources/github-fingerprints +3 -0
  18. package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
  19. package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
  20. package/bb/resources/native-image-tests/run-native-image-tests +74 -0
  21. package/bb/resources/native-image-tests/run-python-tests +22 -0
  22. package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
  23. package/bb/resources/native-image-tests/testconfig.edn +5 -0
  24. package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  25. package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
  26. package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
  27. package/bb/resources/template/pom.xml +22 -0
  28. package/bb/src/tools/build.clj +132 -0
  29. package/bb/src/tools/clj_kondo.clj +32 -0
  30. package/bb/src/tools/deploy.clj +26 -0
  31. package/bb/src/tools/examples.clj +19 -0
  32. package/bb/src/tools/npm.clj +100 -0
  33. package/bb/src/tools/python.clj +14 -0
  34. package/bb/src/tools/release.clj +94 -0
  35. package/bb/src/tools/test.clj +148 -0
  36. package/bb/src/tools/version.clj +47 -0
  37. package/bb.edn +269 -0
  38. package/benchmark/src/benchmark/cli.clj +195 -0
  39. package/benchmark/src/benchmark/compare.clj +157 -0
  40. package/benchmark/src/benchmark/config.clj +316 -0
  41. package/benchmark/src/benchmark/measure.clj +187 -0
  42. package/benchmark/src/benchmark/store.clj +190 -0
  43. package/benchmark/test/benchmark/measure_test.clj +156 -0
  44. package/build.clj +30 -0
  45. package/config.edn +49 -0
  46. package/deps.edn +138 -0
  47. package/dev/sandbox.clj +82 -0
  48. package/dev/sandbox.cljs +127 -0
  49. package/dev/sandbox_benchmarks.clj +27 -0
  50. package/dev/sandbox_client.clj +87 -0
  51. package/dev/sandbox_transact_bench.clj +109 -0
  52. package/dev/user.clj +79 -0
  53. package/doc/README.md +96 -0
  54. package/doc/adl/README.md +6 -0
  55. package/doc/adl/adr-000-adr.org +28 -0
  56. package/doc/adl/adr-001-attribute-references.org +15 -0
  57. package/doc/adl/adr-002-build-tooling.org +54 -0
  58. package/doc/adl/adr-003-db-meta-data.md +52 -0
  59. package/doc/adl/adr-004-github-flow.md +40 -0
  60. package/doc/adl/adr-XYZ-template.md +30 -0
  61. package/doc/adl/index.org +3 -0
  62. package/doc/assets/datahike-logo.svg +3 -0
  63. package/doc/assets/datahiking-invoice.org +85 -0
  64. package/doc/assets/hhtree2.png +0 -0
  65. package/doc/assets/network_topology.svg +624 -0
  66. package/doc/assets/perf.png +0 -0
  67. package/doc/assets/schema_mindmap.mm +132 -0
  68. package/doc/assets/schema_mindmap.svg +970 -0
  69. package/doc/assets/temporal_index.mm +74 -0
  70. package/doc/backend-development.md +78 -0
  71. package/doc/bb-pod.md +89 -0
  72. package/doc/benchmarking.md +360 -0
  73. package/doc/bindings/edn-conversion.md +383 -0
  74. package/doc/cli.md +162 -0
  75. package/doc/cljdoc.edn +27 -0
  76. package/doc/cljs-support.md +133 -0
  77. package/doc/config.md +406 -0
  78. package/doc/contributing.md +114 -0
  79. package/doc/datalog-vs-sql.md +210 -0
  80. package/doc/datomic_differences.md +109 -0
  81. package/doc/development/pull-api-ns.md +186 -0
  82. package/doc/development/pull-frame-state-diagram.jpg +0 -0
  83. package/doc/distributed.md +566 -0
  84. package/doc/entity_spec.md +92 -0
  85. package/doc/gc.md +273 -0
  86. package/doc/java-api.md +808 -0
  87. package/doc/javascript-api.md +421 -0
  88. package/doc/libdatahike.md +86 -0
  89. package/doc/logging_and_error_handling.md +43 -0
  90. package/doc/norms.md +66 -0
  91. package/doc/schema-migration.md +85 -0
  92. package/doc/schema.md +287 -0
  93. package/doc/storage-backends.md +363 -0
  94. package/doc/store-id-refactoring.md +596 -0
  95. package/doc/time_variance.md +325 -0
  96. package/doc/unstructured.md +167 -0
  97. package/doc/versioning.md +261 -0
  98. package/examples/basic/README.md +19 -0
  99. package/examples/basic/deps.edn +6 -0
  100. package/examples/basic/docker-compose.yml +13 -0
  101. package/examples/basic/src/examples/core.clj +60 -0
  102. package/examples/basic/src/examples/schema.clj +155 -0
  103. package/examples/basic/src/examples/store.clj +60 -0
  104. package/examples/basic/src/examples/time_travel.clj +185 -0
  105. package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
  106. package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  107. package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
  108. package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
  109. package/examples/java/README.md +162 -0
  110. package/examples/java/pom.xml +62 -0
  111. package/examples/java/src/main/java/examples/QuickStart.java +115 -0
  112. package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
  113. package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
  114. package/flake.lock +27 -0
  115. package/flake.nix +27 -0
  116. package/http-server/datahike/http/middleware.clj +75 -0
  117. package/http-server/datahike/http/server.clj +269 -0
  118. package/java/src/datahike/java/Database.java +274 -0
  119. package/java/src/datahike/java/Datahike.java +281 -0
  120. package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
  121. package/java/src/datahike/java/DatahikeTest.java +370 -0
  122. package/java/src/datahike/java/EDN.java +170 -0
  123. package/java/src/datahike/java/IEntity.java +11 -0
  124. package/java/src/datahike/java/Keywords.java +161 -0
  125. package/java/src/datahike/java/SchemaFlexibility.java +52 -0
  126. package/java/src/datahike/java/Util.java +219 -0
  127. package/karma.conf.js +19 -0
  128. package/libdatahike/compile-cpp +7 -0
  129. package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
  130. package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
  131. package/libdatahike/src/test_cpp.cpp +61 -0
  132. package/npm-package/PUBLISHING.md +140 -0
  133. package/npm-package/README.md +226 -0
  134. package/npm-package/package.template.json +34 -0
  135. package/npm-package/test-isomorphic.ts +281 -0
  136. package/npm-package/test.js +557 -0
  137. package/npm-package/typescript-test.ts +70 -0
  138. package/package.json +16 -0
  139. package/pydatahike/README.md +569 -0
  140. package/pydatahike/pyproject.toml +91 -0
  141. package/pydatahike/setup.py +42 -0
  142. package/pydatahike/src/datahike/__init__.py +134 -0
  143. package/pydatahike/src/datahike/_native.py +250 -0
  144. package/pydatahike/src/datahike/_version.py +2 -0
  145. package/pydatahike/src/datahike/database.py +722 -0
  146. package/pydatahike/src/datahike/edn.py +311 -0
  147. package/pydatahike/src/datahike/py.typed +0 -0
  148. package/pydatahike/tests/conftest.py +17 -0
  149. package/pydatahike/tests/test_basic.py +170 -0
  150. package/pydatahike/tests/test_database.py +51 -0
  151. package/pydatahike/tests/test_edn_conversion.py +299 -0
  152. package/pydatahike/tests/test_query.py +99 -0
  153. package/pydatahike/tests/test_schema.py +55 -0
  154. package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
  155. package/resources/example_server.edn +4 -0
  156. package/shadow-cljs.edn +56 -0
  157. package/src/data_readers.clj +7 -0
  158. package/src/datahike/api/impl.cljc +176 -0
  159. package/src/datahike/api/specification.cljc +633 -0
  160. package/src/datahike/api/types.cljc +261 -0
  161. package/src/datahike/api.cljc +41 -0
  162. package/src/datahike/array.cljc +99 -0
  163. package/src/datahike/cli.clj +166 -0
  164. package/src/datahike/cljs.cljs +6 -0
  165. package/src/datahike/codegen/cli.clj +406 -0
  166. package/src/datahike/codegen/clj_kondo.clj +291 -0
  167. package/src/datahike/codegen/java.clj +403 -0
  168. package/src/datahike/codegen/naming.cljc +33 -0
  169. package/src/datahike/codegen/native.clj +559 -0
  170. package/src/datahike/codegen/pod.clj +488 -0
  171. package/src/datahike/codegen/python.clj +838 -0
  172. package/src/datahike/codegen/report.clj +55 -0
  173. package/src/datahike/codegen/typescript.clj +262 -0
  174. package/src/datahike/codegen/validation.clj +145 -0
  175. package/src/datahike/config.cljc +294 -0
  176. package/src/datahike/connections.cljc +16 -0
  177. package/src/datahike/connector.cljc +265 -0
  178. package/src/datahike/constants.cljc +142 -0
  179. package/src/datahike/core.cljc +297 -0
  180. package/src/datahike/datom.cljc +459 -0
  181. package/src/datahike/db/interface.cljc +119 -0
  182. package/src/datahike/db/search.cljc +305 -0
  183. package/src/datahike/db/transaction.cljc +937 -0
  184. package/src/datahike/db/utils.cljc +338 -0
  185. package/src/datahike/db.cljc +956 -0
  186. package/src/datahike/experimental/unstructured.cljc +126 -0
  187. package/src/datahike/experimental/versioning.cljc +172 -0
  188. package/src/datahike/externs.js +31 -0
  189. package/src/datahike/gc.cljc +69 -0
  190. package/src/datahike/http/client.clj +188 -0
  191. package/src/datahike/http/writer.clj +79 -0
  192. package/src/datahike/impl/entity.cljc +218 -0
  193. package/src/datahike/index/interface.cljc +93 -0
  194. package/src/datahike/index/persistent_set.cljc +469 -0
  195. package/src/datahike/index/utils.cljc +44 -0
  196. package/src/datahike/index.cljc +32 -0
  197. package/src/datahike/js/api.cljs +172 -0
  198. package/src/datahike/js/api_macros.clj +22 -0
  199. package/src/datahike/js.cljs +163 -0
  200. package/src/datahike/json.cljc +209 -0
  201. package/src/datahike/lru.cljc +146 -0
  202. package/src/datahike/migrate.clj +39 -0
  203. package/src/datahike/norm/norm.clj +245 -0
  204. package/src/datahike/online_gc.cljc +252 -0
  205. package/src/datahike/pod.clj +155 -0
  206. package/src/datahike/pull_api.cljc +325 -0
  207. package/src/datahike/query.cljc +1945 -0
  208. package/src/datahike/query_stats.cljc +88 -0
  209. package/src/datahike/readers.cljc +62 -0
  210. package/src/datahike/remote.cljc +218 -0
  211. package/src/datahike/schema.cljc +228 -0
  212. package/src/datahike/schema_cache.cljc +42 -0
  213. package/src/datahike/spec.cljc +101 -0
  214. package/src/datahike/store.cljc +80 -0
  215. package/src/datahike/tools.cljc +308 -0
  216. package/src/datahike/transit.cljc +80 -0
  217. package/src/datahike/writer.cljc +239 -0
  218. package/src/datahike/writing.cljc +362 -0
  219. package/src/deps.cljs +1 -0
  220. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
  221. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
  222. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
  223. package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
  224. package/test/datahike/integration_test/config_record_file_test.clj +14 -0
  225. package/test/datahike/integration_test/config_record_test.clj +14 -0
  226. package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
  227. package/test/datahike/integration_test/return_map_test.clj +62 -0
  228. package/test/datahike/integration_test.cljc +67 -0
  229. package/test/datahike/norm/norm_test.clj +124 -0
  230. package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
  231. package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
  232. package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
  233. package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
  234. package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
  235. package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
  236. package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
  237. package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
  238. package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
  239. package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
  240. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
  241. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
  242. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
  243. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
  244. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
  245. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
  246. package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
  247. package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
  248. package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
  249. package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
  250. package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
  251. package/test/datahike/test/api_test.cljc +895 -0
  252. package/test/datahike/test/array_test.cljc +40 -0
  253. package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
  254. package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
  255. package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
  256. package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
  257. package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
  258. package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
  259. package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
  260. package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
  261. package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
  262. package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
  263. package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
  264. package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
  265. package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
  266. package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
  267. package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
  268. package/test/datahike/test/attribute_refs/utils.cljc +128 -0
  269. package/test/datahike/test/cache_test.cljc +38 -0
  270. package/test/datahike/test/components_test.cljc +92 -0
  271. package/test/datahike/test/config_test.cljc +158 -0
  272. package/test/datahike/test/core_test.cljc +105 -0
  273. package/test/datahike/test/datom_test.cljc +44 -0
  274. package/test/datahike/test/db_test.cljc +54 -0
  275. package/test/datahike/test/entity_spec_test.cljc +159 -0
  276. package/test/datahike/test/entity_test.cljc +103 -0
  277. package/test/datahike/test/explode_test.cljc +143 -0
  278. package/test/datahike/test/filter_test.cljc +75 -0
  279. package/test/datahike/test/gc_test.cljc +159 -0
  280. package/test/datahike/test/http/server_test.clj +192 -0
  281. package/test/datahike/test/http/writer_test.clj +86 -0
  282. package/test/datahike/test/ident_test.cljc +32 -0
  283. package/test/datahike/test/index_test.cljc +345 -0
  284. package/test/datahike/test/insert.cljc +125 -0
  285. package/test/datahike/test/java_bindings_test.clj +6 -0
  286. package/test/datahike/test/listen_test.cljc +41 -0
  287. package/test/datahike/test/lookup_refs_test.cljc +266 -0
  288. package/test/datahike/test/lru_test.cljc +27 -0
  289. package/test/datahike/test/migrate_test.clj +297 -0
  290. package/test/datahike/test/model/core.cljc +376 -0
  291. package/test/datahike/test/model/invariant.cljc +142 -0
  292. package/test/datahike/test/model/rng.cljc +82 -0
  293. package/test/datahike/test/model_test.clj +217 -0
  294. package/test/datahike/test/nodejs_test.cljs +262 -0
  295. package/test/datahike/test/online_gc_test.cljc +475 -0
  296. package/test/datahike/test/pod_test.clj +369 -0
  297. package/test/datahike/test/pull_api_test.cljc +474 -0
  298. package/test/datahike/test/purge_test.cljc +144 -0
  299. package/test/datahike/test/query_aggregates_test.cljc +101 -0
  300. package/test/datahike/test/query_find_specs_test.cljc +52 -0
  301. package/test/datahike/test/query_fns_test.cljc +523 -0
  302. package/test/datahike/test/query_interop_test.cljc +47 -0
  303. package/test/datahike/test/query_not_test.cljc +189 -0
  304. package/test/datahike/test/query_or_test.cljc +158 -0
  305. package/test/datahike/test/query_pull_test.cljc +147 -0
  306. package/test/datahike/test/query_rules_test.cljc +248 -0
  307. package/test/datahike/test/query_stats_test.cljc +218 -0
  308. package/test/datahike/test/query_test.cljc +984 -0
  309. package/test/datahike/test/schema_test.cljc +424 -0
  310. package/test/datahike/test/specification_test.cljc +30 -0
  311. package/test/datahike/test/store_test.cljc +78 -0
  312. package/test/datahike/test/stress_test.cljc +57 -0
  313. package/test/datahike/test/time_variance_test.cljc +518 -0
  314. package/test/datahike/test/tools_test.clj +134 -0
  315. package/test/datahike/test/transact_test.cljc +518 -0
  316. package/test/datahike/test/tuples_test.cljc +564 -0
  317. package/test/datahike/test/unstructured_test.cljc +291 -0
  318. package/test/datahike/test/upsert_impl_test.cljc +205 -0
  319. package/test/datahike/test/upsert_test.cljc +363 -0
  320. package/test/datahike/test/utils.cljc +110 -0
  321. package/test/datahike/test/validation_test.cljc +48 -0
  322. package/test/datahike/test/versioning_test.cljc +56 -0
  323. package/test/datahike/test.cljc +66 -0
  324. package/tests.edn +24 -0
@@ -0,0 +1,838 @@
1
+ (ns datahike.codegen.python
2
+ "Generate Python bindings from api.specification.
3
+
4
+ This namespace generates Python code with:
5
+ - Full type annotations (PEP 484)
6
+ - Docstrings from specification
7
+ - ctypes FFI calls to libdatahike"
8
+ (:require [datahike.api.specification :refer [api-specification]]
9
+ [datahike.codegen.naming :as naming]
10
+ [datahike.codegen.validation :as validation]
11
+ [clojure.string :as str]
12
+ [clojure.java.io :as io]))
13
+
14
+ ;; =============================================================================
15
+ ;; Python Operation Configuration
16
+ ;; =============================================================================
17
+
18
+ (def python-operations
19
+ "Operations exposed via Python API.
20
+ Maps operation names to their binding configuration."
21
+ '{;; Database lifecycle
22
+ database-exists?
23
+ {:pattern :config-query
24
+ :return-type "bool"}
25
+
26
+ create-database
27
+ {:pattern :config-mutation
28
+ :return-type "None"}
29
+
30
+ delete-database
31
+ {:pattern :config-mutation
32
+ :return-type "None"}
33
+
34
+ ;; Query
35
+ q
36
+ {:pattern :query
37
+ :return-type "Any"}
38
+
39
+ ;; Transaction
40
+ transact
41
+ {:pattern :transact
42
+ :return-type "Dict[str, Any]"}
43
+
44
+ ;; Pull API
45
+ pull
46
+ {:pattern :db-selector-eid
47
+ :return-type "Optional[Dict[str, Any]]"}
48
+
49
+ pull-many
50
+ {:pattern :db-selector-eids
51
+ :return-type "List[Dict[str, Any]]"}
52
+
53
+ ;; Entity
54
+ entity
55
+ {:pattern :db-eid
56
+ :return-type "Dict[str, Any]"}
57
+
58
+ ;; Index operations
59
+ datoms
60
+ {:pattern :db-index
61
+ :return-type "List[List[Any]]"}
62
+
63
+ seek-datoms
64
+ {:pattern :db-index
65
+ :c-name "seek_datoms"
66
+ :return-type "List[List[Any]]"}
67
+
68
+ index-range
69
+ {:pattern :db-index-range
70
+ :c-name "index_range"
71
+ :return-type "List[List[Any]]"}
72
+
73
+ ;; Schema
74
+ schema
75
+ {:pattern :db-only
76
+ :return-type "Dict[str, Any]"}
77
+
78
+ reverse-schema
79
+ {:pattern :db-only
80
+ :c-name "reverse_schema"
81
+ :return-type "Dict[str, Any]"}
82
+
83
+ ;; Diagnostics
84
+ metrics
85
+ {:pattern :db-only
86
+ :return-type "Dict[str, Any]"}
87
+
88
+ ;; Maintenance
89
+ gc-storage
90
+ {:pattern :config-timestamp
91
+ :c-name "gc_storage"
92
+ :return-type "Any"}})
93
+
94
+ (def python-excluded-operations
95
+ "Operations explicitly excluded from Python FFI bindings with documented reasons.
96
+
97
+ Each entry maps operation symbol to exclusion reason string.
98
+ Same exclusions as Native bindings since both use libdatahike FFI."
99
+ '{connect "Connection lifecycle managed internally per FFI call"
100
+ release "Connections automatically released after each operation"
101
+ db "Database dereferencing handled internally via input_format parameter"
102
+ listen "Requires persistent callbacks across FFI boundary - not supported"
103
+ unlisten "Requires persistent callbacks across FFI boundary - not supported"
104
+ as-of "Returns DB object - use input_format='asof:timestamp_ms' instead"
105
+ since "Returns DB object - use input_format='since:timestamp_ms' instead"
106
+ history "Returns DB object - use input_format='history' instead"
107
+ filter "Returns DB object - cannot be serialized across FFI boundary"
108
+ with "Pure function better implemented client-side"
109
+ db-with "Pure function better implemented client-side"
110
+ tempid "Temporary IDs only useful within transaction context"
111
+ entity-db "Returns DB from entity - limited utility in FFI context"
112
+ is-filtered "Returns boolean about DB state - limited utility in FFI context"
113
+ transact! "Alias for transact - only need one binding"
114
+ load-entities "Batch loading better done via repeated transact calls"
115
+ query-stats "Query statistics not yet exposed in Python FFI"})
116
+
117
+ ;; =============================================================================
118
+ ;; Type Derivation: Malli → Python
119
+ ;; =============================================================================
120
+
121
+ (defn malli->python-type
122
+ "Convert Malli schema to Python type annotation string.
123
+
124
+ Handles common Malli schemas and semantic types from api.specification.
125
+ Falls back to 'Any' for complex or unknown schemas."
126
+ [schema]
127
+ (cond
128
+ ;; Keyword schemas (primitives)
129
+ (keyword? schema)
130
+ (case schema
131
+ :boolean "bool"
132
+ :string "str"
133
+ :int "int"
134
+ :long "int"
135
+ :double "float"
136
+ :number "float"
137
+ :keyword "str"
138
+ :symbol "str"
139
+ :any "Any"
140
+ :nil "None"
141
+ :map "Dict[str, Any]"
142
+ :vector "List[Any]"
143
+ :sequential "List[Any]"
144
+ :set "Set[Any]"
145
+ "Any")
146
+
147
+ ;; Symbol schemas (type references from api.types)
148
+ (symbol? schema)
149
+ (let [schema-name (name schema)]
150
+ (case schema-name
151
+ ;; Semantic Datahike types
152
+ "SConnection" "Any" ; Connection not exposed in FFI
153
+ "SDB" "Any" ; DB objects handled via input_format
154
+ "SEntity" "Dict[str, Any]"
155
+ "STransactionReport" "Dict[str, Any]"
156
+ "SSchema" "Dict[str, Any]"
157
+ "SMetrics" "Dict[str, Any]"
158
+ "SDatoms" "List[List[Any]]"
159
+ "SEId" "int"
160
+ "SPullPattern" "str"
161
+ "SConfig" "str" ; EDN string in Python FFI
162
+ "STransactions" "str" ; EDN/JSON string
163
+ "SQueryArgs" "Any"
164
+ ;; Default
165
+ "Any"))
166
+
167
+ ;; Vector schemas (compound types)
168
+ (vector? schema)
169
+ (let [[op & args] schema]
170
+ (case op
171
+ ;; [:or Type1 Type2] → Union[Type1, Type2] or Any if too complex
172
+ :or
173
+ (let [python-types (map malli->python-type args)]
174
+ (if (every? #(not= % "Any") python-types)
175
+ (str "Union[" (str/join ", " python-types) "]")
176
+ "Any"))
177
+
178
+ ;; [:maybe Type] → Optional[Type]
179
+ :maybe
180
+ (str "Optional[" (malli->python-type (first args)) "]")
181
+
182
+ ;; [:sequential Type] → List[Type]
183
+ :sequential
184
+ (str "List[" (malli->python-type (first args)) "]")
185
+
186
+ ;; [:vector Type] → List[Type]
187
+ :vector
188
+ (str "List[" (malli->python-type (first args)) "]")
189
+
190
+ ;; [:set Type] → Set[Type]
191
+ :set
192
+ (str "Set[" (malli->python-type (first args)) "]")
193
+
194
+ ;; [:map ...] → Dict
195
+ :map "Dict[str, Any]"
196
+
197
+ ;; [:function ...] or [:=> ...] - extract return type
198
+ (:function :=>)
199
+ (if (= op :=>)
200
+ (malli->python-type (nth schema 2)) ; [:=> input output]
201
+ (malli->python-type (second schema))) ; [:function [:=> ...]]
202
+
203
+ ;; Default
204
+ "Any"))
205
+
206
+ ;; Default
207
+ :else "Any"))
208
+
209
+ ;; =============================================================================
210
+ ;; Naming Conventions
211
+ ;; =============================================================================
212
+
213
+ (defn clj-name->python-name
214
+ "Convert Clojure kebab-case to Python snake_case."
215
+ [op-name]
216
+ (-> (name op-name)
217
+ (str/replace #"[!?]$" "")
218
+ (str/replace #"-" "_")))
219
+
220
+ (defn get-c-name
221
+ "Get C function name for FFI call."
222
+ [op-name config]
223
+ (or (:c-name config) (clj-name->python-name op-name)))
224
+
225
+ ;; =============================================================================
226
+ ;; Documentation Formatting
227
+ ;; =============================================================================
228
+
229
+ (defn format-python-docstring
230
+ "Format operation documentation as Python docstring.
231
+
232
+ Includes:
233
+ - Main documentation text
234
+ - Examples from specification (kept as Clojure syntax)
235
+
236
+ Args:
237
+ doc - Documentation string from api-specification
238
+ examples - Example vector from api-specification
239
+
240
+ Returns formatted Python docstring"
241
+ [doc examples]
242
+ (let [main-doc (or doc "No documentation available.")
243
+ example-text (when (seq examples)
244
+ (str "\n\n Examples:\n"
245
+ (str/join "\n"
246
+ (for [{:keys [desc code]} examples]
247
+ (str " " desc ":\n"
248
+ " " code)))))]
249
+ (str main-doc example-text)))
250
+
251
+ ;; =============================================================================
252
+ ;; Code Generation Templates
253
+ ;; =============================================================================
254
+
255
+ (defn generate-config-query
256
+ "Generate Python function for config-based query operations."
257
+ [op-name {:keys [return-type doc] :as config}]
258
+ (let [py-name (clj-name->python-name op-name)
259
+ c-name (get-c-name op-name config)]
260
+ (format "
261
+ def %s(
262
+ config: str,
263
+ output_format: str = 'cbor'
264
+ ) -> %s:
265
+ '''%s
266
+
267
+ Args:
268
+ config: Database configuration as EDN string
269
+ output_format: Output format ('json', 'edn', or 'cbor')
270
+
271
+ Returns:
272
+ %s
273
+ '''
274
+ callback, get_result = make_callback(output_format)
275
+ get_dll().%s(
276
+ get_isolatethread(),
277
+ config.encode('utf8'),
278
+ output_format.encode('utf8'),
279
+ callback
280
+ )
281
+ return get_result()
282
+ "
283
+ py-name return-type doc return-type c-name)))
284
+
285
+ (defn generate-config-mutation
286
+ "Generate Python function for config-based mutation operations."
287
+ [op-name {:keys [return-type doc] :as config}]
288
+ (let [py-name (clj-name->python-name op-name)
289
+ c-name (get-c-name op-name config)]
290
+ (format "
291
+ def %s(
292
+ config: str,
293
+ output_format: str = 'cbor'
294
+ ) -> %s:
295
+ '''%s
296
+
297
+ Args:
298
+ config: Database configuration as EDN string
299
+ output_format: Output format ('json', 'edn', or 'cbor')
300
+ '''
301
+ callback, get_result = make_callback(output_format)
302
+ get_dll().%s(
303
+ get_isolatethread(),
304
+ config.encode('utf8'),
305
+ output_format.encode('utf8'),
306
+ callback
307
+ )
308
+ get_result() # Check for exceptions
309
+ "
310
+ py-name return-type doc c-name)))
311
+
312
+ (defn generate-query
313
+ "Generate Python function for query operation."
314
+ [op-name {:keys [return-type doc] :as config}]
315
+ (let [py-name (clj-name->python-name op-name)
316
+ c-name (get-c-name op-name config)]
317
+ (format "
318
+ def %s(
319
+ query: str,
320
+ inputs: List[Tuple[str, str]],
321
+ output_format: str = 'cbor'
322
+ ) -> %s:
323
+ '''%s
324
+
325
+ Args:
326
+ query: Datalog query as EDN string
327
+ inputs: List of (format, value) tuples. Formats:
328
+ - 'db': Current database (value is config EDN)
329
+ - 'history': Full history database
330
+ - 'since:{timestamp_ms}': Database since timestamp
331
+ - 'asof:{timestamp_ms}': Database as-of timestamp
332
+ - 'json': JSON data
333
+ - 'edn': EDN data
334
+ output_format: Output format ('json', 'edn', or 'cbor')
335
+
336
+ Returns:
337
+ Query result
338
+
339
+ Example:
340
+ >>> query('[:find ?e :where [?e :name \"Alice\"]]', [('db', config)])
341
+ '''
342
+ n, formats, values = prepare_query_inputs(inputs)
343
+ callback, get_result = make_callback(output_format)
344
+ get_dll().%s(
345
+ get_isolatethread(),
346
+ query.encode('utf8'),
347
+ n,
348
+ formats,
349
+ values,
350
+ output_format.encode('utf8'),
351
+ callback
352
+ )
353
+ return get_result()
354
+ "
355
+ py-name return-type doc c-name)))
356
+
357
+ (defn generate-transact
358
+ "Generate Python function for transact operation."
359
+ [op-name {:keys [return-type doc] :as config}]
360
+ (let [py-name (clj-name->python-name op-name)
361
+ c-name (get-c-name op-name config)]
362
+ (format "
363
+ def %s(
364
+ config: str,
365
+ tx_data: str,
366
+ input_format: str = 'json',
367
+ output_format: str = 'cbor'
368
+ ) -> %s:
369
+ '''%s
370
+
371
+ Args:
372
+ config: Database configuration as EDN string
373
+ tx_data: Transaction data (format depends on input_format)
374
+ input_format: Input data format ('json', 'edn', or 'cbor')
375
+ output_format: Output format ('json', 'edn', or 'cbor')
376
+
377
+ Returns:
378
+ Transaction metadata
379
+
380
+ Example:
381
+ >>> transact(config, '[{\"name\": \"Alice\", \"age\": 30}]')
382
+ '''
383
+ callback, get_result = make_callback(output_format)
384
+ get_dll().%s(
385
+ get_isolatethread(),
386
+ config.encode('utf8'),
387
+ input_format.encode('utf8'),
388
+ tx_data.encode('utf8'),
389
+ output_format.encode('utf8'),
390
+ callback
391
+ )
392
+ return get_result()
393
+ "
394
+ py-name return-type doc c-name)))
395
+
396
+ (defn generate-db-only
397
+ "Generate Python function for db-only operations."
398
+ [op-name {:keys [return-type doc] :as config}]
399
+ (let [py-name (clj-name->python-name op-name)
400
+ c-name (get-c-name op-name config)]
401
+ (format "
402
+ def %s(
403
+ config: str,
404
+ input_format: str = 'db',
405
+ output_format: str = 'cbor'
406
+ ) -> %s:
407
+ '''%s
408
+
409
+ Args:
410
+ config: Database configuration as EDN string
411
+ input_format: Database input format ('db', 'history', 'since:ts', 'asof:ts')
412
+ output_format: Output format ('json', 'edn', or 'cbor')
413
+
414
+ Returns:
415
+ %s
416
+ '''
417
+ callback, get_result = make_callback(output_format)
418
+ get_dll().%s(
419
+ get_isolatethread(),
420
+ input_format.encode('utf8'),
421
+ config.encode('utf8'),
422
+ output_format.encode('utf8'),
423
+ callback
424
+ )
425
+ return get_result()
426
+ "
427
+ py-name return-type doc return-type c-name)))
428
+
429
+ (defn generate-db-eid
430
+ "Generate Python function for db + eid operations."
431
+ [op-name {:keys [return-type doc] :as config}]
432
+ (let [py-name (clj-name->python-name op-name)
433
+ c-name (get-c-name op-name config)]
434
+ (format "
435
+ def %s(
436
+ config: str,
437
+ eid: int,
438
+ input_format: str = 'db',
439
+ output_format: str = 'cbor'
440
+ ) -> %s:
441
+ '''%s
442
+
443
+ Args:
444
+ config: Database configuration as EDN string
445
+ eid: Entity ID
446
+ input_format: Database input format ('db', 'history', 'since:ts', 'asof:ts')
447
+ output_format: Output format ('json', 'edn', or 'cbor')
448
+
449
+ Returns:
450
+ %s
451
+ '''
452
+ callback, get_result = make_callback(output_format)
453
+ get_dll().%s(
454
+ get_isolatethread(),
455
+ input_format.encode('utf8'),
456
+ config.encode('utf8'),
457
+ eid,
458
+ output_format.encode('utf8'),
459
+ callback
460
+ )
461
+ return get_result()
462
+ "
463
+ py-name return-type doc return-type c-name)))
464
+
465
+ (defn generate-db-selector-eid
466
+ "Generate Python function for pull operation."
467
+ [op-name {:keys [return-type doc] :as config}]
468
+ (let [py-name (clj-name->python-name op-name)
469
+ c-name (get-c-name op-name config)]
470
+ (format "
471
+ def %s(
472
+ config: str,
473
+ selector: str,
474
+ eid: int,
475
+ input_format: str = 'db',
476
+ output_format: str = 'cbor'
477
+ ) -> %s:
478
+ '''%s
479
+
480
+ Args:
481
+ config: Database configuration as EDN string
482
+ selector: Pull pattern as EDN string (e.g., '[:db/id :name :age]' or '[*]')
483
+ eid: Entity ID
484
+ input_format: Database input format ('db', 'history', 'since:ts', 'asof:ts')
485
+ output_format: Output format ('json', 'edn', or 'cbor')
486
+
487
+ Returns:
488
+ %s
489
+
490
+ Example:
491
+ >>> pull(config, '[*]', 1)
492
+ {':db/id': 1, ':name': 'Alice', ':age': 30}
493
+ '''
494
+ callback, get_result = make_callback(output_format)
495
+ get_dll().%s(
496
+ get_isolatethread(),
497
+ input_format.encode('utf8'),
498
+ config.encode('utf8'),
499
+ selector.encode('utf8'),
500
+ eid,
501
+ output_format.encode('utf8'),
502
+ callback
503
+ )
504
+ return get_result()
505
+ "
506
+ py-name return-type doc return-type c-name)))
507
+
508
+ (defn generate-db-selector-eids
509
+ "Generate Python function for pull-many operation."
510
+ [op-name {:keys [return-type doc] :as config}]
511
+ (let [py-name (clj-name->python-name op-name)
512
+ c-name (get-c-name op-name config)]
513
+ (format "
514
+ def %s(
515
+ config: str,
516
+ selector: str,
517
+ eids: List[int],
518
+ input_format: str = 'db',
519
+ output_format: str = 'cbor'
520
+ ) -> %s:
521
+ '''%s
522
+
523
+ Args:
524
+ config: Database configuration as EDN string
525
+ selector: Pull pattern as EDN string
526
+ eids: List of entity IDs
527
+ input_format: Database input format ('db', 'history', 'since:ts', 'asof:ts')
528
+ output_format: Output format ('json', 'edn', or 'cbor')
529
+
530
+ Returns:
531
+ %s
532
+ '''
533
+ eids_edn = '[' + ' '.join(str(e) for e in eids) + ']'
534
+ callback, get_result = make_callback(output_format)
535
+ get_dll().%s(
536
+ get_isolatethread(),
537
+ input_format.encode('utf8'),
538
+ config.encode('utf8'),
539
+ selector.encode('utf8'),
540
+ eids_edn.encode('utf8'),
541
+ output_format.encode('utf8'),
542
+ callback
543
+ )
544
+ return get_result()
545
+ "
546
+ py-name return-type doc return-type c-name)))
547
+
548
+ (defn generate-db-index
549
+ "Generate Python function for index operations."
550
+ [op-name {:keys [return-type doc] :as config}]
551
+ (let [py-name (clj-name->python-name op-name)
552
+ c-name (get-c-name op-name config)]
553
+ (format "
554
+ def %s(
555
+ config: str,
556
+ index: str,
557
+ input_format: str = 'db',
558
+ output_format: str = 'cbor'
559
+ ) -> %s:
560
+ '''%s
561
+
562
+ Args:
563
+ config: Database configuration as EDN string
564
+ index: Index keyword as EDN string (':eavt', ':aevt', ':avet', ':vaet')
565
+ input_format: Database input format ('db', 'history', 'since:ts', 'asof:ts')
566
+ output_format: Output format ('json', 'edn', or 'cbor')
567
+
568
+ Returns:
569
+ %s (list of [e a v tx added?] vectors)
570
+ '''
571
+ callback, get_result = make_callback(output_format)
572
+ get_dll().%s(
573
+ get_isolatethread(),
574
+ input_format.encode('utf8'),
575
+ config.encode('utf8'),
576
+ index.encode('utf8'),
577
+ output_format.encode('utf8'),
578
+ callback
579
+ )
580
+ return get_result()
581
+ "
582
+ py-name return-type doc return-type c-name)))
583
+
584
+ (defn generate-db-index-range
585
+ "Generate Python function for index-range operation."
586
+ [op-name {:keys [return-type doc] :as config}]
587
+ (let [py-name (clj-name->python-name op-name)
588
+ c-name (get-c-name op-name config)]
589
+ (format "
590
+ def %s(
591
+ config: str,
592
+ attrid: str,
593
+ start: Any,
594
+ end: Any,
595
+ input_format: str = 'db',
596
+ output_format: str = 'cbor'
597
+ ) -> %s:
598
+ '''%s
599
+
600
+ Args:
601
+ config: Database configuration as EDN string
602
+ attrid: Attribute keyword as EDN string (e.g., ':age')
603
+ start: Start value (will be converted to EDN)
604
+ end: End value (will be converted to EDN)
605
+ input_format: Database input format
606
+ output_format: Output format ('json', 'edn', or 'cbor')
607
+
608
+ Returns:
609
+ %s
610
+ '''
611
+ # Convert Python values to EDN representation
612
+ def to_edn(v):
613
+ if isinstance(v, str):
614
+ return f'\"{v}\"'
615
+ elif isinstance(v, bool):
616
+ return 'true' if v else 'false'
617
+ elif v is None:
618
+ return 'nil'
619
+ else:
620
+ return str(v)
621
+
622
+ callback, get_result = make_callback(output_format)
623
+ get_dll().%s(
624
+ get_isolatethread(),
625
+ input_format.encode('utf8'),
626
+ config.encode('utf8'),
627
+ attrid.encode('utf8'),
628
+ to_edn(start).encode('utf8'),
629
+ to_edn(end).encode('utf8'),
630
+ output_format.encode('utf8'),
631
+ callback
632
+ )
633
+ return get_result()
634
+ "
635
+ py-name return-type doc return-type c-name)))
636
+
637
+ (defn generate-config-timestamp
638
+ "Generate Python function for config + timestamp operations."
639
+ [op-name {:keys [return-type doc] :as config}]
640
+ (let [py-name (clj-name->python-name op-name)
641
+ c-name (get-c-name op-name config)]
642
+ (format "
643
+ def %s(
644
+ config: str,
645
+ before_timestamp_ms: Optional[int] = None,
646
+ output_format: str = 'cbor'
647
+ ) -> %s:
648
+ '''%s
649
+
650
+ Args:
651
+ config: Database configuration as EDN string
652
+ before_timestamp_ms: Unix timestamp in milliseconds (optional)
653
+ output_format: Output format ('json', 'edn', or 'cbor')
654
+
655
+ Returns:
656
+ %s
657
+ '''
658
+ import time
659
+ if before_timestamp_ms is None:
660
+ before_timestamp_ms = int(time.time() * 1000)
661
+
662
+ callback, get_result = make_callback(output_format)
663
+ get_dll().%s(
664
+ get_isolatethread(),
665
+ config.encode('utf8'),
666
+ before_timestamp_ms,
667
+ output_format.encode('utf8'),
668
+ callback
669
+ )
670
+ return get_result()
671
+ "
672
+ py-name return-type doc return-type c-name)))
673
+
674
+ ;; =============================================================================
675
+ ;; Function Generation
676
+ ;; =============================================================================
677
+
678
+ (defn generate-function
679
+ "Generate a single Python function using overlay model.
680
+
681
+ Args:
682
+ op-name - Operation symbol from api-specification
683
+ spec - Full specification from api-specification
684
+ overlay - Python-specific overlay configuration
685
+
686
+ Returns generated Python code string"
687
+ [op-name spec overlay]
688
+ (let [;; Derive or use return type
689
+ return-type (or (:return-type overlay)
690
+ (malli->python-type (:ret spec)))
691
+ ;; Format documentation
692
+ docstring (format-python-docstring (:doc spec) (:examples spec))
693
+ ;; Merge into config
694
+ config (assoc overlay
695
+ :return-type return-type
696
+ :doc docstring)
697
+ ;; Select appropriate generator based on pattern
698
+ generator (case (:pattern overlay)
699
+ :config-query generate-config-query
700
+ :config-mutation generate-config-mutation
701
+ :query generate-query
702
+ :transact generate-transact
703
+ :db-only generate-db-only
704
+ :db-eid generate-db-eid
705
+ :db-selector-eid generate-db-selector-eid
706
+ :db-selector-eids generate-db-selector-eids
707
+ :db-index generate-db-index
708
+ :db-index-range generate-db-index-range
709
+ :config-timestamp generate-config-timestamp
710
+ (throw (ex-info (str "Unknown pattern: " (:pattern overlay))
711
+ {:op-name op-name :overlay overlay})))]
712
+ (generator op-name config)))
713
+
714
+ (defn generate-all-functions
715
+ "Generate all Python functions using overlay model.
716
+
717
+ Iterates over api-specification, checking each operation against:
718
+ - python-operations (overlay config)
719
+ - python-excluded-operations (exclusions)
720
+
721
+ Operations must be either implemented or excluded."
722
+ []
723
+ (let [all-ops api-specification
724
+ implemented (keys python-operations)
725
+ excluded (keys python-excluded-operations)]
726
+ (->> all-ops
727
+ (keep (fn [[op-name spec]]
728
+ (cond
729
+ ;; Explicitly excluded - skip
730
+ (contains? (set excluded) op-name)
731
+ nil
732
+
733
+ ;; Has overlay config - generate it
734
+ (contains? python-operations op-name)
735
+ (let [overlay (get python-operations op-name)]
736
+ (generate-function op-name spec overlay))
737
+
738
+ ;; Missing overlay - warn (but continue)
739
+ :else
740
+ (do
741
+ (println (str "⚠️ WARNING: No Python overlay for operation: " op-name))
742
+ nil))))
743
+ (str/join "\n"))))
744
+
745
+ ;; =============================================================================
746
+ ;; File Generation
747
+ ;; =============================================================================
748
+
749
+ (def file-header
750
+ "\"\"\"Generated Datahike Python bindings.
751
+
752
+ This file is auto-generated from datahike.api.specification.
753
+ DO NOT EDIT MANUALLY - changes will be overwritten.
754
+
755
+ To regenerate: bb codegen-python
756
+
757
+ All functions use callback-based FFI to libdatahike with:
758
+ - Multiple output formats (json, edn, cbor)
759
+ - Proper exception handling
760
+ - Type annotations (PEP 484)
761
+
762
+ Temporal query variants via input_format parameter:
763
+ - 'db': Current database state
764
+ - 'history': Full history including retractions
765
+ - 'since:{timestamp_ms}': Database since timestamp
766
+ - 'asof:{timestamp_ms}': Database as-of timestamp
767
+ \"\"\"
768
+ from typing import Any, Dict, List, Tuple, Optional
769
+ from ._native import (
770
+ get_dll,
771
+ get_isolatethread,
772
+ make_callback,
773
+ prepare_query_inputs,
774
+ DatahikeException,
775
+ )
776
+
777
+ __all__ = [
778
+ 'DatahikeException',
779
+ 'database_exists',
780
+ 'create_database',
781
+ 'delete_database',
782
+ 'q',
783
+ 'transact',
784
+ 'pull',
785
+ 'pull_many',
786
+ 'entity',
787
+ 'datoms',
788
+ 'seek_datoms',
789
+ 'index_range',
790
+ 'schema',
791
+ 'reverse_schema',
792
+ 'metrics',
793
+ 'gc_storage',
794
+ ]
795
+ ")
796
+
797
+ (def file-footer
798
+ "
799
+
800
+ # Re-export exception
801
+ DatahikeException = DatahikeException
802
+ ")
803
+
804
+ (defn generate-file-content
805
+ "Generate complete generated.py content."
806
+ []
807
+ (str file-header
808
+ (generate-all-functions)
809
+ file-footer))
810
+
811
+ (defn write-python-bindings!
812
+ "Write generated Python bindings to output directory."
813
+ [output-dir]
814
+ (let [output-path (str output-dir "/generated.py")
815
+ content (generate-file-content)]
816
+ (io/make-parents output-path)
817
+ (spit output-path content)
818
+ (println "Generated:" output-path)))
819
+
820
+ ;; =============================================================================
821
+ ;; Main Entry Point
822
+ ;; =============================================================================
823
+
824
+ (defn -main
825
+ "Generate Python bindings with coverage validation.
826
+ Usage: clojure -M -m datahike.codegen.python <output-dir>"
827
+ [& args]
828
+ (let [output-dir (or (first args) "pydatahike/src/datahike")]
829
+ (println "Generating Python bindings from specification...")
830
+
831
+ ;; Validate coverage
832
+ (validation/validate-coverage "Python" python-operations python-excluded-operations)
833
+ (validation/validate-exclusion-reasons "Python" python-excluded-operations)
834
+ (validation/validate-overlay-completeness "Python" python-operations [:pattern])
835
+
836
+ ;; Generate bindings
837
+ (write-python-bindings! output-dir)
838
+ (println "Done.")))