queryapigate 0.34.0__tar.gz → 0.34.2__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 (217) hide show
  1. {queryapigate-0.34.0/queryapigate.egg-info → queryapigate-0.34.2}/PKG-INFO +1 -1
  2. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/__init__.py +1 -1
  3. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/app.py +4 -2
  4. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/databases.py +2 -1
  5. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/definitions.py +1 -0
  6. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/engine.py +8 -1
  7. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/mongotools.py +19 -1
  8. {queryapigate-0.34.0 → queryapigate-0.34.2/queryapigate.egg-info}/PKG-INFO +1 -1
  9. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_api_keys.py +26 -0
  10. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_events_server.py +8 -6
  11. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_jwt.py +6 -3
  12. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_mongotools.py +11 -0
  13. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_queryapigate.py +29 -0
  14. {queryapigate-0.34.0 → queryapigate-0.34.2}/LICENSE +0 -0
  15. {queryapigate-0.34.0 → queryapigate-0.34.2}/README.md +0 -0
  16. {queryapigate-0.34.0 → queryapigate-0.34.2}/pyproject.toml +0 -0
  17. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/__main__.py +0 -0
  18. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/accesslog.py +0 -0
  19. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/adminroles.py +0 -0
  20. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/admins.py +0 -0
  21. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/agentwrites.py +0 -0
  22. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/alerts.py +0 -0
  23. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/apikeys.py +0 -0
  24. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/auditchain.py +0 -0
  25. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/auditstream.py +0 -0
  26. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/aws.py +0 -0
  27. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/broadcast.py +0 -0
  28. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/bundle.py +0 -0
  29. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/cache.py +0 -0
  30. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/cli.py +0 -0
  31. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/collection_admin.py +0 -0
  32. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/config.py +0 -0
  33. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/configcode.py +0 -0
  34. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console.py +0 -0
  35. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/AccessMapPage-BVQ6wRwp.js +0 -0
  36. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/AdministratorsPage-BiUGhISO.js +0 -0
  37. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/AgentsPage-CSA-qCUv.js +0 -0
  38. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/AlertsPage-MYZoziNZ.js +0 -0
  39. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/ApiKeysPage-BZISE5oJ.js +0 -0
  40. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/ApprovalsPage-CUiEkHq3.js +0 -0
  41. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/AuditLogPage-D9vrSHU7.js +0 -0
  42. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/CachingPage-2hjur6KQ.js +0 -0
  43. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/CodeBox-BZY9RBv5.js +0 -0
  44. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/ConnectionsPage-D26k-98S.js +0 -0
  45. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/DesignerPage-B_os91xI.js +0 -0
  46. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/DestinationsPage--bjoNE0y.js +0 -0
  47. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/ExportsPage-5ai6Ojbl.js +0 -0
  48. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/HelpPage-DNaGeh-8.js +0 -0
  49. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/HomePage-DINN_eBD.js +0 -0
  50. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/Managed-CVE0layM.js +0 -0
  51. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/MetricsPage-D6UAGhYP.js +0 -0
  52. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/RepositoryPage-BvsZyKvp.js +0 -0
  53. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/Results-D8fDmPdY.js +0 -0
  54. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/RolesPage-BdEQdLFD.js +0 -0
  55. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/SchemaBrowser-Bu0NvoUy.js +0 -0
  56. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/SettingsPage-BQhT4xlX.js +0 -0
  57. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/StatTile-DEmSFOkX.js +0 -0
  58. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/Time-MA05YtZP.js +0 -0
  59. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/forms-BSqwVHNJ.js +0 -0
  60. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/forms-C6RLe1F3.js +0 -0
  61. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/icons-DQ8_pv9M.js +0 -0
  62. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/index-BLd0t6vZ.css +0 -0
  63. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/index-sn5NUET1.js +0 -0
  64. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/maskRules-BGQ7mS0j.js +0 -0
  65. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/reach-2gDU11-l.js +0 -0
  66. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/shared-D5ownoD-.js +0 -0
  67. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
  68. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/assets/useMutation-BF7AWw3p.js +0 -0
  69. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/favicon.svg +0 -0
  70. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/console_dist/index.html +0 -0
  71. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/cors.py +0 -0
  72. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/db.py +0 -0
  73. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/deprecations.py +0 -0
  74. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/descriptions.py +0 -0
  75. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/destinations.py +0 -0
  76. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/duckfiles.py +0 -0
  77. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/errors.py +0 -0
  78. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/events.py +0 -0
  79. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/examples.py +0 -0
  80. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/experimental.py +0 -0
  81. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/exporting.py +0 -0
  82. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/exports.py +0 -0
  83. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/formats.py +0 -0
  84. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/governance.py +0 -0
  85. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/gunicorn_conf.py +0 -0
  86. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/history.py +0 -0
  87. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/instances.py +0 -0
  88. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/jwtauth.py +0 -0
  89. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/lib/h2-2.2.224.jar +0 -0
  90. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/limits.py +0 -0
  91. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/logging_setup.py +0 -0
  92. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/masking.py +0 -0
  93. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/mcp_server.py +0 -0
  94. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/metrics.py +0 -0
  95. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/oidc.py +0 -0
  96. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/openapi.py +0 -0
  97. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/params.py +0 -0
  98. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/parquet.py +0 -0
  99. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/pool.py +0 -0
  100. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/postman.py +0 -0
  101. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/quotas.py +0 -0
  102. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/ratelimit.py +0 -0
  103. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/rediscache.py +0 -0
  104. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/rekey.py +0 -0
  105. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/runners.py +0 -0
  106. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/schema.py +0 -0
  107. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/secheaders.py +0 -0
  108. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/secretrefs.py +0 -0
  109. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/__init__.py +0 -0
  110. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/access.py +0 -0
  111. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/administrators.py +0 -0
  112. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/audit.py +0 -0
  113. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/collections.py +0 -0
  114. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/connections.py +0 -0
  115. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/descriptions.py +0 -0
  116. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/destinations.py +0 -0
  117. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/exports.py +0 -0
  118. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/mcp.py +0 -0
  119. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/services/queries.py +0 -0
  120. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/sqlflow.py +0 -0
  121. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/sqltools.py +0 -0
  122. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/sso.py +0 -0
  123. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/store.py +0 -0
  124. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/tableguard.py +0 -0
  125. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/tracing.py +0 -0
  126. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/usage.py +0 -0
  127. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/v1.py +0 -0
  128. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/v1_spec.py +0 -0
  129. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate/vault.py +0 -0
  130. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate.egg-info/SOURCES.txt +0 -0
  131. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate.egg-info/dependency_links.txt +0 -0
  132. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate.egg-info/entry_points.txt +0 -0
  133. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate.egg-info/requires.txt +0 -0
  134. {queryapigate-0.34.0 → queryapigate-0.34.2}/queryapigate.egg-info/top_level.txt +0 -0
  135. {queryapigate-0.34.0 → queryapigate-0.34.2}/setup.cfg +0 -0
  136. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_adhoc_history.py +0 -0
  137. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_adminroles.py +0 -0
  138. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_admins.py +0 -0
  139. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_agent_writes.py +0 -0
  140. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_alerts.py +0 -0
  141. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_api_administrators.py +0 -0
  142. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_api_v1.py +0 -0
  143. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_audit_chain.py +0 -0
  144. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_audit_log.py +0 -0
  145. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_audit_siem.py +0 -0
  146. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_audit_stream.py +0 -0
  147. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_aws.py +0 -0
  148. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_backup_restore.py +0 -0
  149. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_broadcast.py +0 -0
  150. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_cli.py +0 -0
  151. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_collection_bundle.py +0 -0
  152. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_collection_postman.py +0 -0
  153. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_collections.py +0 -0
  154. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_configcode.py +0 -0
  155. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_connection_encryption.py +0 -0
  156. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_console.py +0 -0
  157. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_cors_and_rate_limit.py +0 -0
  158. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_data_protection.py +0 -0
  159. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_databases.py +0 -0
  160. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_db.py +0 -0
  161. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_dependency_floors.py +0 -0
  162. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_deprecations.py +0 -0
  163. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_descriptions.py +0 -0
  164. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_destinations.py +0 -0
  165. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_docs_site.py +0 -0
  166. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_duckdb.py +0 -0
  167. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_duckdb_files.py +0 -0
  168. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_errors.py +0 -0
  169. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_events.py +0 -0
  170. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_examples.py +0 -0
  171. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_experimental.py +0 -0
  172. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_export_security.py +0 -0
  173. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_exporting.py +0 -0
  174. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_exports.py +0 -0
  175. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_fips.py +0 -0
  176. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_helm.py +0 -0
  177. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_history.py +0 -0
  178. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_howto.py +0 -0
  179. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_instances.py +0 -0
  180. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_integration.py +0 -0
  181. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_jdbc.py +0 -0
  182. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_key_rotation.py +0 -0
  183. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_limits.py +0 -0
  184. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_masking.py +0 -0
  185. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_masking_requests.py +0 -0
  186. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_mcp.py +0 -0
  187. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_mcp_keycloak.py +0 -0
  188. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_mcp_oauth.py +0 -0
  189. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_multinode.py +0 -0
  190. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_observability.py +0 -0
  191. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_oidc.py +0 -0
  192. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_params.py +0 -0
  193. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_parquet.py +0 -0
  194. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_pool.py +0 -0
  195. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_publishing.py +0 -0
  196. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_query_window.py +0 -0
  197. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_quotas.py +0 -0
  198. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_rekey.py +0 -0
  199. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_release_check.py +0 -0
  200. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_response_cache.py +0 -0
  201. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_roles.py +0 -0
  202. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_rolling_upgrade.py +0 -0
  203. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_secretrefs.py +0 -0
  204. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_security_headers.py +0 -0
  205. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_settings.py +0 -0
  206. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_shared_rate_limit.py +0 -0
  207. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_sql_guard_fuzz.py +0 -0
  208. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_sql_guard_writes.py +0 -0
  209. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_sqlflow.py +0 -0
  210. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_sso.py +0 -0
  211. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_sso_keycloak.py +0 -0
  212. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_supply_chain.py +0 -0
  213. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_tableguard.py +0 -0
  214. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_tracing.py +0 -0
  215. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_upgrades.py +0 -0
  216. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_usage.py +0 -0
  217. {queryapigate-0.34.0 → queryapigate-0.34.2}/tests/test_vault.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queryapigate
3
- Version: 0.34.0
3
+ Version: 0.34.2
4
4
  Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
5
5
  Author-email: Anantha Raju C <arcswdev@gmail.com>
6
6
  License-Expression: FSL-1.1-MIT
@@ -1,5 +1,5 @@
1
1
  """QueryAPIGate - expose SQL databases as a REST API."""
2
- __version__ = "0.34.0"
2
+ __version__ = "0.34.2"
3
3
 
4
4
  from .app import create_app # noqa: E402 (app imports __version__)
5
5
 
@@ -718,7 +718,8 @@ def execute_mongo_endpoint():
718
718
  result, elapsed_ms = engine.timed(engine.execute_mongo, data['collection'], filter_doc,
719
719
  data['connection_name'], limit, offset, params, timeout,
720
720
  projection=data.get('projection'), sort=data.get('sort'),
721
- key_name=metrics_key_name(), mask=g.permission.mask)
721
+ key_name=metrics_key_name(), mask=g.permission.mask,
722
+ allowed_tables=g.permission.allowed_tables)
722
723
  except ApiError as error:
723
724
  _record_adhoc_and_broadcast(statement, params,
724
725
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
@@ -1018,7 +1019,8 @@ def run_saved_mongo(saved, path, number, ref, connection_name, raw, output_forma
1018
1019
  result, elapsed_ms = engine.timed(engine.execute_mongo, saved['mongo_collection'], filter_doc,
1019
1020
  connection_name, limit, offset, timeout=timeout,
1020
1021
  projection=saved.get('mongo_projection'), sort=saved.get('mongo_sort'),
1021
- key_name=metrics_key_name(), mask=g.permission.mask)
1022
+ key_name=metrics_key_name(), mask=g.permission.mask,
1023
+ allowed_tables=g.permission.allowed_tables)
1022
1024
  except ApiError as error:
1023
1025
  _record_and_broadcast(path, number,
1024
1026
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
@@ -44,7 +44,8 @@ def matrix():
44
44
  ('Writes, when allowed', {t: YES if _sql(t) else NO for t in TYPES}),
45
45
  ('Query time limit', {t: ('SELECT only' if t == 'mysql' else YES) if t in TIMEOUT_ENFORCED else NO
46
46
  for t in TYPES}), # MySQL's max_execution_time covers SELECT only (MariaDB: all)
47
- ('`allowed_tables` (refused where not supported)', {t: YES if t in tableguard._DIALECT_MAP else NO
47
+ ('`allowed_tables` (refused where not supported)', {t: YES if t in tableguard._DIALECT_MAP else
48
+ 'collections' if t == 'mongo' else NO # see execute_mongo()
48
49
  for t in TYPES}),
49
50
  ('Schema browser, MCP `list_tables`', {t: YES if t in schema._QUERIES or t == 'mongo' else NO for t in TYPES}),
50
51
  ('Primary and foreign keys in the schema', {t: YES if t in schema._KEY_QUERIES or t == 'sqlite' else
@@ -150,6 +150,7 @@ def validate_definition(data):
150
150
  for extra in ('mongo_projection', 'mongo_sort'):
151
151
  if data.get(extra) is not None:
152
152
  fields[extra] = as_object(data.get(extra), extra)
153
+ mongotools.validate_options(fields.get('mongo_projection'), fields.get('mongo_sort'))
153
154
  else:
154
155
  fields['sql_query'] = data['sql_query']
155
156
  return fields, collection
@@ -126,7 +126,7 @@ def execute_sql(sql, connection_name, limit, offset, params=None, timeout=None,
126
126
 
127
127
 
128
128
  def execute_mongo(collection, filter_doc, connection_name, limit, offset, params=None, timeout=None,
129
- projection=None, sort=None, key_name='-', mask=None):
129
+ projection=None, sort=None, key_name='-', mask=None, allowed_tables=None):
130
130
  """Run a Mongo find() on a named connection and return the requested page as a ResultSetDTO - the
131
131
  non-SQL sibling of execute_sql(). Always read-only: MongoDB support is find-only in this version (see
132
132
  mongotools.py and BACKLOG #36), so unlike execute_sql there is no allow_writes/allowed_write_ops
@@ -139,12 +139,19 @@ def execute_mongo(collection, filter_doc, connection_name, limit, offset, params
139
139
  page, in first-seen order, becomes ``columns``, and each document becomes a positionally-aligned row -
140
140
  lossy for wildly different document shapes, but keeps every existing exporter (json/ndjson/csv/xlsx/
141
141
  xml/yaml) working unmodified.
142
+
143
+ ``allowed_tables`` (a key's grant, lower-case names) is, on MongoDB, the collections it may read - checked
144
+ before anything is asked of the server, as validate_sql() checks a statement's tables.
142
145
  """
146
+ if allowed_tables is not None and str(collection).lower() not in allowed_tables:
147
+ raise ApiError(f"This API key may only query these tables: {', '.join(sorted(allowed_tables))}. "
148
+ f'Forbidden: {collection}.', 403, code='table_not_allowed')
143
149
  masking.prepare(mask, '', connection_name, 'mongo') # refused for a caller with rules, for now
144
150
  details = store.get_connection(connection_name)
145
151
  if details['db'] != 'mongo':
146
152
  raise ApiError(f"'{connection_name}' is not a mongo connection", code='wrong_connection_type')
147
153
  filter_doc = mongotools.validate_filter(filter_doc or {})
154
+ mongotools.validate_options(projection, sort)
148
155
  timeout, limit = limits.timeout(timeout), limits.rows(limit)
149
156
  if params:
150
157
  filter_doc = mongotools.fill_placeholders(filter_doc, params)
@@ -55,10 +55,20 @@ def fill_placeholders(filter_doc, values):
55
55
 
56
56
  Unlike sqltools.fill_placeholders, this never produces text to re-parse - the substituted value becomes
57
57
  a real value in the returned document, so it can be a number, bool or null, not just safe text.
58
+
59
+ A value is a scalar, or a list of them - never a document. An object there is an operator the query's author
60
+ didn't write: `{"name": ":u"}` given `{"$ne": null}` matches every document, and `{"$function": ...}` runs
61
+ JavaScript on the server, past validate_filter(), which saw only the placeholder.
58
62
  """
59
- missing = sorted(set(placeholder_names(filter_doc)) - set(values))
63
+ used = set(placeholder_names(filter_doc))
64
+ missing = sorted(used - set(values))
60
65
  if missing:
61
66
  raise ApiError(f"No value provided for placeholder(s): {', '.join(missing)}", code='param_required')
67
+ for name in sorted(used):
68
+ value = values[name]
69
+ if isinstance(value, dict) or (isinstance(value, list) and any(isinstance(v, (dict, list)) for v in value)):
70
+ raise ApiError(f"'{name}' must be a value or a list of values, not a document - it would be read as "
71
+ 'query operators', code='param_invalid')
62
72
  return _substitute(filter_doc, values)
63
73
 
64
74
 
@@ -81,3 +91,11 @@ def validate_filter(filter_doc):
81
91
  raise ApiError('filter must be a JSON object', code='invalid_filter')
82
92
  _check_forbidden(filter_doc)
83
93
  return filter_doc
94
+
95
+
96
+ def validate_options(projection, sort):
97
+ """The same check over a find()'s projection and sort: MongoDB 4.4+ evaluates aggregation expressions in a
98
+ projection, so `{"x": {"$function": ...}}` there runs JavaScript on the server as surely as in the filter."""
99
+ for document in (projection, sort):
100
+ if document is not None:
101
+ _check_forbidden(document)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queryapigate
3
- Version: 0.34.0
3
+ Version: 0.34.2
4
4
  Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
5
5
  Author-email: Anantha Raju C <arcswdev@gmail.com>
6
6
  License-Expression: FSL-1.1-MIT
@@ -698,6 +698,32 @@ class AllowedTablesTests(AppTestCase):
698
698
  headers=self.admin_headers)
699
699
  self.assertEqual(res.status_code, 400, bad)
700
700
 
701
+ def test_on_mongo_it_is_the_collections_a_key_may_read(self):
702
+ # Until 0.34.1 neither /execute_mongo nor a saved mongo query looked at allowed_tables: a key allowed only
703
+ # `orders` read every collection of the connection.
704
+ write_connections({'mg': {'db': 'mongo', 'host': 'h', 'database': 'shop', 'active': True}})
705
+ key = self.create_key('reader', connections=['mg'], allowed_tables=['Orders'])
706
+ from tests.test_queryapigate import _mongo_client
707
+ client = _mongo_client(docs=[{'_id': 1}])
708
+
709
+ def find(collection):
710
+ return self.client.post('/execute_mongo', headers={'X-API-Key': key},
711
+ json={'connection_name': 'mg', 'collection': collection})
712
+ with mock.patch('pymongo.MongoClient', return_value=client):
713
+ refused, allowed = find('secrets'), find('orders')
714
+ self.assertEqual((refused.status_code, refused.get_json()['code']), (403, 'table_not_allowed'))
715
+ self.assertIn('secrets', refused.get_json()['error'])
716
+ self.assertEqual(allowed.status_code, 200, allowed.get_data(as_text=True))
717
+
718
+ self.assertEqual(save_query(self.client, headers=self.admin_headers, body={
719
+ 'filename': 'leak', 'author': 'a', 'description': 'd', 'query_type': 'mongo',
720
+ 'mongo_collection': 'secrets', 'mongo_filter': {}, 'connection_name': 'mg'}).status_code, 201)
721
+ self.client.patch('/api/v1/api-keys/reader', headers=self.admin_headers, json={'queries': ['leak']})
722
+ with mock.patch('pymongo.MongoClient', return_value=client) as connect:
723
+ saved = self.client.get('/q/leak', headers={'X-API-Key': key})
724
+ self.assertEqual((saved.status_code, saved.get_json()['code']), (403, 'table_not_allowed'))
725
+ self.assertFalse(connect.called) # refused before the database was asked anything
726
+
701
727
  def test_allowed_tables_appears_in_the_listing(self):
702
728
  self.create_key('reader', connections=['a'], allowed_tables=['b_table', 'a_table'])
703
729
  listed = self.keys()['reader']
@@ -119,7 +119,9 @@ class EventServerTestCase(unittest.TestCase):
119
119
  asyncio.run_coroutine_threadsafe(server.stop(), loop).result(10)
120
120
  # Wait for the loop's worker threads too (asyncio.to_thread - checking a key opens a store connection):
121
121
  # one still connecting when the test's patched environment is restored reads os.environ while it is
122
- # being changed, which libpq does in C - and that crashes the interpreter, not just the test.
122
+ # being changed, which libpq does in C - and that crashes the interpreter, not just the test. For the
123
+ # same reason a test never changes os.environ while the server runs: it patches the config function that
124
+ # reads the setting instead (CI on a PostgreSQL store, 2026-10-14: a segfault in the metrics test).
123
125
  asyncio.run_coroutine_threadsafe(loop.shutdown_default_executor(), loop).result(10)
124
126
  loop.call_soon_threadsafe(loop.stop)
125
127
  thread.join(5)
@@ -312,7 +314,7 @@ class EventLogTests(EventServerTestCase):
312
314
 
313
315
  def test_a_run_sampled_out_of_history_is_still_an_event(self):
314
316
  stream = self.open('admin')
315
- with mock.patch.dict(os.environ, {'QUERYAPIGATE_HISTORY_SAMPLE_RATE': '0.000001'}):
317
+ with mock.patch.object(config, 'history_sample_rate', return_value=0.000001): # see start_server()
316
318
  self.run_query(self.alice)
317
319
  (_, payload), = stream.events(1)
318
320
  self.assertEqual(payload['query'], 'q')
@@ -332,7 +334,7 @@ class EventLogTests(EventServerTestCase):
332
334
  with db.transaction() as conn:
333
335
  for occurred_at in ('2026-01-01 00:00:00', '2026-01-02 11:00:00', '2026-01-02 13:00:00'):
334
336
  conn.execute(events.INSERT, events.run_columns('q', 1, {'executed_at': occurred_at}))
335
- with mock.patch.dict(os.environ, {'QUERYAPIGATE_EVENTS_RETENTION_HOURS': '12'}):
337
+ with mock.patch.object(config, 'events_retention_hours', return_value=12):
336
338
  self.assertEqual(events.sweep(now=datetime(2026, 1, 3, 0, 0)), 2)
337
339
  self.assertEqual([row[0] for row in db.connection().execute('SELECT occurred_at FROM events')],
338
340
  ['2026-01-02 13:00:00'])
@@ -415,7 +417,7 @@ class MetricsTests(EventServerTestCase):
415
417
  self.assertIn('queryapigate_events_lag_seconds_count 1\n', text)
416
418
 
417
419
  def test_the_metrics_token_applies(self):
418
- with mock.patch.dict(os.environ, {'QUERYAPIGATE_METRICS_TOKEN': 'sesame'}):
420
+ with mock.patch.object(config, 'metrics_token', return_value='sesame'):
419
421
  self.assertEqual(self.metrics().status, 401)
420
422
  self.assertEqual(self.metrics(Authorization='Bearer sesame').status, 200)
421
423
 
@@ -447,7 +449,7 @@ class LimitTests(EventServerTestCase):
447
449
  self.assertEqual((refused.status, refused.headers['retry-after']), (503, '5'))
448
450
 
449
451
  def test_the_server_wide_rate_limit_applies_to_connection_attempts(self):
450
- with mock.patch.dict(os.environ, {'QUERYAPIGATE_RATE_LIMIT': '2/minute'}):
452
+ with mock.patch.object(config, 'rate_limit', return_value=config.parse_rate_limit('2/minute')):
451
453
  statuses = [self.open('admin').status for _ in range(3)]
452
454
  self.assertEqual(statuses, [200, 200, 429])
453
455
 
@@ -470,7 +472,7 @@ class LimitTests(EventServerTestCase):
470
472
 
471
473
  class CorsTests(EventServerTestCase):
472
474
  def test_an_allowed_origin_gets_cors_headers_and_a_preflight(self):
473
- with mock.patch.dict(os.environ, {'QUERYAPIGATE_CORS_ORIGINS': 'https://app.example.com'}):
475
+ with mock.patch.object(config, 'cors_origins', return_value=frozenset({'https://app.example.com'})):
474
476
  preflight = Stream(self.server.port, method='OPTIONS', headers={
475
477
  'Origin': 'https://app.example.com', 'Access-Control-Request-Method': 'GET'})
476
478
  self.assertEqual(preflight.status, 204)
@@ -371,6 +371,11 @@ class EventsServerTests(JwtTestCase):
371
371
  from queryapigate import events
372
372
  from tests.test_events_server import Stream
373
373
  self.events, self.Stream = events, Stream
374
+ # Before the server starts, and undone after it stops: its worker threads open store connections, and
375
+ # libpq reads os.environ from C - changing it meanwhile crashes the interpreter (see test_events_server).
376
+ patcher = mock.patch.dict(os.environ, {'QUERYAPIGATE_HISTORY_FLUSH_INTERVAL': '0'})
377
+ patcher.start()
378
+ self.addCleanup(patcher.stop)
374
379
  loop = asyncio.new_event_loop()
375
380
  thread = threading.Thread(target=loop.run_forever, daemon=True)
376
381
  thread.start()
@@ -380,13 +385,11 @@ class EventsServerTests(JwtTestCase):
380
385
 
381
386
  def stop():
382
387
  asyncio.run_coroutine_threadsafe(server.stop(), loop).result(10)
388
+ asyncio.run_coroutine_threadsafe(loop.shutdown_default_executor(), loop).result(10)
383
389
  loop.call_soon_threadsafe(loop.stop)
384
390
  thread.join(5)
385
391
  loop.close()
386
392
  self.addCleanup(stop)
387
- patcher = mock.patch.dict(os.environ, {'QUERYAPIGATE_HISTORY_FLUSH_INTERVAL': '0'})
388
- patcher.start()
389
- self.addCleanup(patcher.stop)
390
393
 
391
394
  def open(self, value):
392
395
  stream = self.Stream(self.server.port, headers=bearer(value))
@@ -35,6 +35,17 @@ class FillPlaceholdersTests(unittest.TestCase):
35
35
  self.assertEqual(result['age']['$gte'], 21)
36
36
  self.assertIsInstance(result['age']['$gte'], int)
37
37
 
38
+ def test_a_document_is_never_a_value(self):
39
+ # Until 0.34.2 an undeclared placeholder took whatever JSON was sent: {"$ne": null} matched every document,
40
+ # and {"$function": ...} ran server-side JavaScript past validate_filter(), which saw only ':u'.
41
+ for value in ({'$ne': None}, {'$function': {'body': 'function() {}', 'args': [], 'lang': 'js'}},
42
+ [{'$gt': ''}], [['nested']]):
43
+ with self.subTest(value=value), self.assertRaises(ApiError) as caught:
44
+ mongotools.fill_placeholders({'name': ':u'}, {'u': value})
45
+ self.assertEqual(caught.exception.code, 'param_invalid')
46
+ self.assertEqual(mongotools.fill_placeholders({'name': {'$in': ':u'}}, {'u': ['a', 2, None]}),
47
+ {'name': {'$in': ['a', 2, None]}}) # a list of values is still a value
48
+
38
49
  def test_substitutes_inside_a_list(self):
39
50
  result = mongotools.fill_placeholders({'status': {'$in': [':a', ':b']}}, {'a': 'x', 'b': 'y'})
40
51
  self.assertEqual(result['status']['$in'], ['x', 'y'])
@@ -1780,6 +1780,35 @@ class MongoQueryTests(ApiTestCase):
1780
1780
  'collection': 'users', 'filter': {'$where': 'true'}, 'connection_name': 'mg'})
1781
1781
  self.assertEqual(res.status_code, 403)
1782
1782
 
1783
+ def test_server_side_javascript_is_refused_in_a_projection_and_a_sort_too(self):
1784
+ # Until 0.34.2 only the filter was checked: MongoDB 4.4+ evaluates aggregation expressions in a find()
1785
+ # projection, so {"x": {"$function": ...}} there ran JavaScript on the database server.
1786
+ js = {'$function': {'body': 'function() { return 1; }', 'args': [], 'lang': 'js'}}
1787
+ for field in ('projection', 'sort'):
1788
+ with self.subTest(field=field), mock.patch('pymongo.MongoClient') as connect:
1789
+ res = self.client.post('/execute_mongo', json={
1790
+ 'collection': 'users', 'connection_name': 'mg', field: {'x': js}})
1791
+ self.assertEqual((res.status_code, res.get_json()['code']), (403, 'mongo_operator_forbidden'))
1792
+ self.assertFalse(connect.called)
1793
+ saved = save_query(self.client, {
1794
+ 'filename': 'js', 'author': 'a', 'description': 'd', 'query_type': 'mongo', 'mongo_collection': 'users',
1795
+ 'mongo_filter': {}, 'mongo_projection': {'x': {'$accumulator': {}}}, 'connection_name': 'mg'})
1796
+ self.assertEqual((saved.status_code, saved.get_json()['code']), (403, 'mongo_operator_forbidden'))
1797
+
1798
+ def test_a_caller_cant_turn_a_parameter_into_operators(self):
1799
+ # Until 0.34.2: a saved query's undeclared placeholder, given {"$ne": null}, matched every document.
1800
+ self.assertEqual(save_query(self.client, {
1801
+ 'filename': 'by_name', 'author': 'a', 'description': 'd', 'query_type': 'mongo',
1802
+ 'mongo_collection': 'users', 'mongo_filter': {'name': ':u'}, 'connection_name': 'mg'}).status_code, 201)
1803
+ with mock.patch('pymongo.MongoClient') as connect:
1804
+ saved = self.client.post('/q/by_name', json={'params': {'u': {'$ne': None}}})
1805
+ adhoc = self.client.post('/execute_mongo', json={
1806
+ 'collection': 'users', 'connection_name': 'mg', 'filter': {'$expr': ':p'},
1807
+ 'params': {'p': {'$function': {'body': 'function() { return true; }', 'args': [], 'lang': 'js'}}}})
1808
+ for res in (saved, adhoc):
1809
+ self.assertEqual((res.status_code, res.get_json()['code']), (400, 'param_invalid'))
1810
+ self.assertFalse(connect.called)
1811
+
1783
1812
  def test_collection_is_required(self):
1784
1813
  res = self.client.post('/execute_mongo', json={'filter': {}, 'connection_name': 'mg'})
1785
1814
  self.assertEqual(res.status_code, 400)
File without changes
File without changes
File without changes