shareddata 6.86.2__tar.gz → 6.99.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 (219) hide show
  1. {shareddata-6.86.2/src/shareddata.egg-info → shareddata-6.99.0}/PKG-INFO +1 -1
  2. {shareddata-6.86.2 → shareddata-6.99.0}/setup.py +3 -1
  3. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/__init__.py +8 -1
  4. shareddata-6.99.0/src/SharedData/API/access_log.py +306 -0
  5. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/apidocs.yml +243 -1
  6. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/auth.py +10 -2
  7. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/schedules.py +74 -6
  8. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/tables.py +25 -17
  9. shareddata-6.99.0/src/SharedData/Charts/Server.py +240 -0
  10. shareddata-6.99.0/src/SharedData/Charts/__init__.py +1 -0
  11. shareddata-6.99.0/src/SharedData/Charts/auth.py +84 -0
  12. shareddata-6.99.0/src/SharedData/Charts/cache.py +66 -0
  13. shareddata-6.99.0/src/SharedData/Charts/errors.py +4 -0
  14. shareddata-6.99.0/src/SharedData/Charts/index.py +190 -0
  15. shareddata-6.99.0/src/SharedData/Charts/lifecycle.py +44 -0
  16. shareddata-6.99.0/src/SharedData/Charts/memory.py +49 -0
  17. shareddata-6.99.0/src/SharedData/Charts/periods.py +61 -0
  18. shareddata-6.99.0/src/SharedData/Charts/schedule.py +82 -0
  19. shareddata-6.99.0/src/SharedData/Charts/series.py +83 -0
  20. shareddata-6.99.0/src/SharedData/Charts/service.py +334 -0
  21. shareddata-6.99.0/src/SharedData/Charts/templates/tradingview.html +16 -0
  22. shareddata-6.99.0/src/SharedData/Charts/tradingview.py +89 -0
  23. shareddata-6.99.0/src/SharedData/ConsensusGraph.py +869 -0
  24. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/resolve.py +45 -0
  25. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Defaults.py +31 -0
  26. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/ClientAPI.py +146 -51
  27. shareddata-6.99.0/src/SharedData/KnowledgeBase.py +1474 -0
  28. shareddata-6.99.0/src/SharedData/KnowledgeContract.py +978 -0
  29. shareddata-6.99.0/src/SharedData/KnowledgeEvaluation.py +232 -0
  30. shareddata-6.99.0/src/SharedData/KnowledgeExperiment.py +335 -0
  31. shareddata-6.99.0/src/SharedData/KnowledgeGraph.py +536 -0
  32. shareddata-6.99.0/src/SharedData/KnowledgeLookup.py +156 -0
  33. shareddata-6.99.0/src/SharedData/KnowledgeMarket.py +417 -0
  34. shareddata-6.99.0/src/SharedData/KnowledgeOntology.py +176 -0
  35. shareddata-6.99.0/src/SharedData/KnowledgeResearch.py +481 -0
  36. shareddata-6.99.0/src/SharedData/KnowledgeScenario.py +200 -0
  37. shareddata-6.99.0/src/SharedData/KnowledgeXBRL.py +273 -0
  38. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Logger.py +4 -0
  39. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/MCP/server.py +3 -7
  40. shareddata-6.99.0/src/SharedData/ResearchMCP.py +432 -0
  41. shareddata-6.99.0/src/SharedData/Routines/HealthProbe.py +100 -0
  42. shareddata-6.99.0/src/SharedData/Routines/PackageRoutine.py +235 -0
  43. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/RoutineCommand.py +8 -1
  44. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/RoutineOrigin.py +6 -1
  45. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/Schedule.py +19 -4
  46. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/ScheduleStore.py +1 -1
  47. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/WorkerLib.py +30 -4
  48. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/SharedData.py +21 -4
  49. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/SharedNumpy.py +135 -8
  50. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Table.py +169 -30
  51. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableDisk.py +5 -3
  52. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndex.py +277 -120
  53. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndexJitFunctions.py +1247 -48
  54. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndexJitGenerate.py +34 -14
  55. shareddata-6.99.0/src/SharedData/TableVector.py +140 -0
  56. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Utils.py +4 -2
  57. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/proc.py +1 -1
  58. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/sched.py +2 -0
  59. shareddata-6.99.0/src/SharedData/Web/schedule_editor.py +365 -0
  60. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/workerpool.py +6 -1
  61. {shareddata-6.86.2 → shareddata-6.99.0/src/shareddata.egg-info}/PKG-INFO +1 -1
  62. {shareddata-6.86.2 → shareddata-6.99.0}/src/shareddata.egg-info/SOURCES.txt +31 -0
  63. {shareddata-6.86.2 → shareddata-6.99.0}/src/shareddata.egg-info/entry_points.txt +1 -0
  64. shareddata-6.86.2/src/SharedData/Web/schedule_editor.py +0 -156
  65. {shareddata-6.86.2 → shareddata-6.99.0}/LICENSE +0 -0
  66. {shareddata-6.86.2 → shareddata-6.99.0}/MANIFEST.in +0 -0
  67. {shareddata-6.86.2 → shareddata-6.99.0}/README.md +0 -0
  68. {shareddata-6.86.2 → shareddata-6.99.0}/pyproject.toml +0 -0
  69. {shareddata-6.86.2 → shareddata-6.99.0}/setup.cfg +0 -0
  70. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/ServerGunicorn.py +0 -0
  71. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/ServerWaitress.py +0 -0
  72. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/apidocs-dark.css +0 -0
  73. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/constants.py +0 -0
  74. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/download_paths.py +0 -0
  75. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/download_slots.py +0 -0
  76. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/install/worker.ps1 +0 -0
  77. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/install/worker.sh +0 -0
  78. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/listcache.py +0 -0
  79. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/__init__.py +0 -0
  80. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/cache.py +0 -0
  81. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/collections.py +0 -0
  82. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/data.py +0 -0
  83. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/downloads.py +0 -0
  84. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/metadata.py +0 -0
  85. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/operations.py +0 -0
  86. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/sqlite.py +0 -0
  87. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/system.py +0 -0
  88. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/timeseries.py +0 -0
  89. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/web_routes.py +0 -0
  90. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/routes/workers.py +0 -0
  91. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/API/utils.py +0 -0
  92. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Auth/__init__.py +0 -0
  93. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Auth/bootstrap.py +0 -0
  94. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Auth/migrate.py +0 -0
  95. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Auth/store.py +0 -0
  96. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Auth/workers.py +0 -0
  97. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/CacheRedis.py +0 -0
  98. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/CollectionMongoDB.py +0 -0
  99. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/__init__.py +0 -0
  100. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/catalog.py +0 -0
  101. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/config.py +0 -0
  102. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/errors.py +0 -0
  103. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/indexes.py +0 -0
  104. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/readers.py +0 -0
  105. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/DataAPI/service.py +0 -0
  106. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Database.py +0 -0
  107. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/AWSEC2.py +0 -0
  108. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/AWSS3.py +0 -0
  109. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/AutoDocstrings.py +0 -0
  110. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/ClientSocket.py +0 -0
  111. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/ClientWebSocket.py +0 -0
  112. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/LogHandlerAPI.py +0 -0
  113. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/LogReader.py +0 -0
  114. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/MongoDBClient.py +0 -0
  115. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/SaveTables.py +0 -0
  116. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/ServerSocket.py +0 -0
  117. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/ServerWebSocket.py +0 -0
  118. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/StreamsCache.py +0 -0
  119. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/StreamsPersist.py +0 -0
  120. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/SyncTable.py +0 -0
  121. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/TunnelWebSocket.py +0 -0
  122. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/IO/__init__.py +0 -0
  123. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Listing.py +0 -0
  124. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/LockWaiters.py +0 -0
  125. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/MCP/__init__.py +0 -0
  126. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/MCP/__main__.py +0 -0
  127. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/MCP/client.py +0 -0
  128. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Metadata.py +0 -0
  129. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/MultiProc.py +0 -0
  130. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/OpenFIGI.py +0 -0
  131. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Redaction.py +0 -0
  132. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/ArchiveOldCollections.py +0 -0
  133. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/BatchJob.py +0 -0
  134. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/ContainerHealth.py +0 -0
  135. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/JobStore.py +0 -0
  136. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/ScheduleMirror.py +0 -0
  137. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/ScheduleMonitor.py +0 -0
  138. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/Scheduler.py +0 -0
  139. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/UpdateActiveJobs.py +0 -0
  140. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/Worker.py +0 -0
  141. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/WorkerBootstrap.py +0 -0
  142. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/WorkerPool.py +0 -0
  143. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Routines/__init__.py +0 -0
  144. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/SQLite.py +0 -0
  145. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/StreamKafka.py +0 -0
  146. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Symbol.py +0 -0
  147. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndexJitFunctionsManual.py +0 -0
  148. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndexJitHash.py +0 -0
  149. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableIndexJitLoc.py +0 -0
  150. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableProtocol.py +0 -0
  151. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableRead.py +0 -0
  152. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TableRewrite.py +0 -0
  153. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TimeSeriesDisk.py +0 -0
  154. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/TimeseriesContainer.py +0 -0
  155. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Users.py +0 -0
  156. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/ADDING_A_PAGE.md +0 -0
  157. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/__init__.py +0 -0
  158. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/activity_logger.py +0 -0
  159. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/app.py +0 -0
  160. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/dashAgGridComponentFunctions.js +0 -0
  161. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/dashAgGridFunctions.js +0 -0
  162. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/google_logo.png +0 -0
  163. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/kafka_keys.js +0 -0
  164. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/logs.css +0 -0
  165. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/sched.css +0 -0
  166. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/assets/styles.css +0 -0
  167. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/auth.py +0 -0
  168. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/backends.py +0 -0
  169. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/config.py +0 -0
  170. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/containers.py +0 -0
  171. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/identity.py +0 -0
  172. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/kafka_view.py +0 -0
  173. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/mongo_view.py +0 -0
  174. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/redis_view.py +0 -0
  175. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/route_health.py +0 -0
  176. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/route_registry.py +0 -0
  177. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/server.py +0 -0
  178. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/services.py +0 -0
  179. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/shell.py +0 -0
  180. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/standalone.py +0 -0
  181. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/account.js +0 -0
  182. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/custom.css +0 -0
  183. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/favicon.ico +0 -0
  184. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/favicon.png +0 -0
  185. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/logo.png +0 -0
  186. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/logo_96x96.png +0 -0
  187. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/shareddata-mark.svg +0 -0
  188. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/shell.css +0 -0
  189. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/shell.js +0 -0
  190. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/standalone.css +0 -0
  191. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/static/standalone.js +0 -0
  192. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/suggest.py +0 -0
  193. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/account.html +0 -0
  194. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/base.html +0 -0
  195. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/claim.html +0 -0
  196. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/claim_done.html +0 -0
  197. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/dash.html +0 -0
  198. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/index.html +0 -0
  199. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/login.html +0 -0
  200. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/login_2fa.html +0 -0
  201. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/partials/navbar.html +0 -0
  202. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/partials/scripts.html +0 -0
  203. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/recovery_codes.html +0 -0
  204. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/shell.html +0 -0
  205. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/templates/standalone.html +0 -0
  206. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/twofactor.py +0 -0
  207. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/Web/utils.py +0 -0
  208. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/__init__.py +0 -0
  209. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/build_info.py +0 -0
  210. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/fleet.py +0 -0
  211. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/preflight.py +0 -0
  212. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/private_files.py +0 -0
  213. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/release_backup.py +0 -0
  214. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/server.py +0 -0
  215. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/services.py +0 -0
  216. {shareddata-6.86.2 → shareddata-6.99.0}/src/SharedData/sharedmutexwin.pyd +0 -0
  217. {shareddata-6.86.2 → shareddata-6.99.0}/src/shareddata.egg-info/dependency_links.txt +0 -0
  218. {shareddata-6.86.2 → shareddata-6.99.0}/src/shareddata.egg-info/requires.txt +0 -0
  219. {shareddata-6.86.2 → shareddata-6.99.0}/src/shareddata.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shareddata
3
- Version: 6.86.2
3
+ Version: 6.99.0
4
4
  Summary: Memory-mapped record and time-series store with an HTTP API and a distributed scheduler
5
5
  Home-page: https://shareddata.net
6
6
  Author: shareddata
@@ -66,7 +66,7 @@ test_requires = [
66
66
 
67
67
  setup(
68
68
  name='shareddata',
69
- version='6.86.2',
69
+ version='6.99.0',
70
70
  description='Memory-mapped record and time-series store with an HTTP API and a distributed scheduler',
71
71
  author='shareddata',
72
72
  author_email='admin@shareddata.net',
@@ -92,6 +92,7 @@ setup(
92
92
  extras_require={'test': test_requires, 's3': [], 'kafka': [], 'redis': [], 'all': []},
93
93
  include_package_data=True,
94
94
  package_data={
95
+ 'SharedData.Charts': ['templates/*'],
95
96
  'SharedData.API': ['apidocs.yml', 'apidocs-dark.css', 'install/*'],
96
97
  'SharedData.Web': [
97
98
  'templates/*', 'templates/**/*',
@@ -105,6 +106,7 @@ setup(
105
106
  'shdata-web=SharedData.Web.server:main',
106
107
  'shdata-server=SharedData.server:main',
107
108
  'shareddata-mcp=SharedData.MCP.server:main',
109
+ 'shareddata-research-mcp=SharedData.ResearchMCP:main',
108
110
  ],
109
111
  },
110
112
  )
@@ -111,12 +111,16 @@ def create_app(shdata=None, quiet=True, app=None):
111
111
 
112
112
  @app.before_request
113
113
  def auth_availability():
114
+ from flask import g
114
115
  from SharedData.Auth.store import StoreUnavailable
115
116
  # Worker install scripts and enrollment take a one-time install code instead of a token.
116
117
  public = request.path in ('/api/health', '/api/heartbeat', '/api/workers/enroll') or \
117
118
  request.path.startswith(('/api/auth/', '/api/web/', '/api/workers/install/'))
118
119
  from SharedData.API.auth import local_log_token_ok
119
- if request.path.startswith('/api/') and not public and not local_log_token_ok(request):
120
+ if local_log_token_ok(request):
121
+ g.client_user = 'local-log'
122
+ return
123
+ if request.path.startswith('/api/') and not public:
120
124
  try:
121
125
  if not authenticate_fn(request, shdata):
122
126
  return jsonify(error='unauthorized'), 401
@@ -126,6 +130,9 @@ def create_app(shdata=None, quiet=True, app=None):
126
130
  # Register request lifecycle hooks
127
131
  register_request_hooks(app)
128
132
 
133
+ from SharedData.API import access_log
134
+ access_log.register(app, shdata)
135
+
129
136
  # Store shdata on app for access in routes
130
137
  app.shdata = shdata
131
138
 
@@ -0,0 +1,306 @@
1
+ """
2
+ access_log.py — per-client API access logging, two-tier, never on the request path.
3
+
4
+ Tier 1 (always): per-minute aggregate counters keyed by
5
+ (user, route, status_class) — requests, bytes_in, bytes_out, total_ms, max_ms.
6
+ Small docs; powers bandwidth / usage / route-preference analytics.
7
+
8
+ Tier 2 (events): one document per request, but only for security-relevant or
9
+ sampled traffic — status >= 300, failed authentication, unmatched paths, plus a
10
+ configurable sample of successful requests.
11
+
12
+ Flush: a daemon thread drains both tiers every FLUSH_INTERVAL_S seconds and
13
+ writes through the in-process SharedData handle — Requests/RT/API/ACCESS_AGG
14
+ and Requests/RT/API/ACCESS_EVT (hasindex=False collections → insert_many).
15
+ No HTTP loopback: a ClientAPI.post_collection call would itself be a request
16
+ and self-log. Each server worker process flushes its own buffer (worker_pid).
17
+
18
+ Overhead: the request path does one dict update under a short lock and, for
19
+ events, a put_nowait — no I/O, no body reads. A full event queue drops the
20
+ record (counted in dropped_events). The request path never blocks on Mongo.
21
+
22
+ Secrets: the token itself is never logged — only token_fp (first 8 chars of the
23
+ server-side token_hash, stashed on flask.g by API/auth.py). The raw query
24
+ string is never logged either (it can carry ?token=); only the allow-listed
25
+ data-path arguments are kept.
26
+
27
+ Client IP: X-Real-IP / X-Forwarded-For are trusted only when the direct peer
28
+ is a known proxy; a LAN client hitting the API port directly cannot spoof its
29
+ address. Built in: loopback only (a same-host proxy). A proxy on another
30
+ address, or one that reaches the API by its LAN address, is listed in env
31
+ SHAREDDATA_ACCESSLOG_TRUSTED (comma-separated, replaces the built-in list).
32
+ """
33
+
34
+ import os
35
+ import queue
36
+ import random
37
+ import socket
38
+ import threading
39
+ import time
40
+ from collections import defaultdict
41
+ from datetime import datetime, timezone
42
+
43
+ from flask import g, request
44
+
45
+ from SharedData.Logger import Logger
46
+
47
+ # ─── Configuration ──────────────────────────────────────────────────────────
48
+
49
+ FLUSH_INTERVAL_S = float(os.environ.get('SHAREDDATA_ACCESSLOG_FLUSH_S', 30))
50
+ MAX_QUEUE_SIZE = int(os.environ.get('SHAREDDATA_ACCESSLOG_QUEUE', 10_000))
51
+ EVENT_SAMPLE = float(os.environ.get('SHAREDDATA_ACCESSLOG_SAMPLE', 0.01))
52
+ DISABLED = os.environ.get('SHAREDDATA_ACCESSLOG', '1') in ('0', 'false', 'off')
53
+
54
+ DATABASE = 'Requests'
55
+ PERIOD = 'RT'
56
+ SOURCE = 'API'
57
+ AGG_TABLE = 'ACCESS_AGG'
58
+ EVT_TABLE = 'ACCESS_EVT'
59
+
60
+ # Peers allowed to supply client addresses in X-Real-IP / X-Forwarded-For:
61
+ # loopback (same-host proxy), unless the deployment lists its own proxies.
62
+ _TRUSTED_PROXIES = frozenset(
63
+ part.strip() for part in (os.environ.get('SHAREDDATA_ACCESSLOG_TRUSTED') or '127.0.0.1,::1').split(',')
64
+ if part.strip()
65
+ )
66
+
67
+ # Public/polled endpoints and noise that is not worth a counter.
68
+ _SKIP_PREFIXES = (
69
+ '/api/health',
70
+ '/api/heartbeat',
71
+ '/flasgger_static',
72
+ '/favicon.ico',
73
+ )
74
+
75
+ # Data-path arguments worth keeping on event records (the object a client
76
+ # read/wrote). Everything else — most of all 'token' — stays out.
77
+ _DATA_ARGS = ('database', 'period', 'source', 'tablename', 'tablesubfolder',
78
+ 'timeseries', 'symbol', 'user')
79
+
80
+ _PATH_MAX = 200
81
+ _UA_MAX = 160
82
+
83
+ # ─── Internal state ─────────────────────────────────────────────────────────
84
+
85
+ _evt_queue: 'queue.Queue[dict]' = queue.Queue(maxsize=MAX_QUEUE_SIZE)
86
+
87
+ # {(minute_epoch, user, route, status_class): [requests, bytes_in, bytes_out, total_ms, max_ms]}
88
+ _agg = defaultdict(lambda: [0, 0, 0, 0.0, 0.0])
89
+ _agg_lock = threading.Lock()
90
+
91
+ _pending_evts: list = [] # evt docs retained across a failed flush
92
+ _pending_lock = threading.Lock()
93
+ dropped_events = 0 # events lost to a full queue / retained-buffer cap
94
+
95
+ _shdata = None
96
+ _sink = None # fn(collection_name, docs); injectable for tests
97
+ _HOSTNAME = socket.gethostname()
98
+
99
+
100
+ def _client_ip() -> str:
101
+ """The real client address: the peer itself, unless the peer is a known
102
+ proxy that supplied X-Real-IP / X-Forwarded-For (original client first)."""
103
+ peer = request.remote_addr or ''
104
+ if peer in _TRUSTED_PROXIES:
105
+ real = request.headers.get('X-Real-IP')
106
+ if real:
107
+ return real.split(',')[0].strip()
108
+ xff = request.headers.get('X-Forwarded-For')
109
+ if xff:
110
+ return xff.split(',')[0].strip()
111
+ return peer or 'unknown'
112
+
113
+
114
+ def _user() -> str:
115
+ """Auth identity resolved by API/auth.authenticate (or 'local-log' set by
116
+ the auth_availability hook). 'invalid-token' when a token was presented
117
+ and rejected, 'anonymous' when none was needed/presented."""
118
+ user = getattr(g, 'client_user', None)
119
+ if user:
120
+ return user
121
+ if getattr(g, 'auth_ok', None) is False:
122
+ return 'invalid-token'
123
+ return 'anonymous'
124
+
125
+
126
+ def _route() -> str:
127
+ rule = request.url_rule
128
+ return rule.rule if rule is not None else '<unmatched>'
129
+
130
+
131
+ def _record_event(response, ms, bytes_in, bytes_out):
132
+ rec = {
133
+ 'kind': 'evt',
134
+ 'ts': datetime.now(timezone.utc).isoformat(),
135
+ 'host': _HOSTNAME,
136
+ 'worker_pid': os.getpid(),
137
+ 'remote_ip': _client_ip(),
138
+ 'user': _user(),
139
+ 'method': request.method,
140
+ 'route': _route(),
141
+ 'status': response.status_code,
142
+ 'ms': round(ms, 3),
143
+ 'bytes_in': bytes_in,
144
+ 'bytes_out': bytes_out,
145
+ }
146
+ token_fp = getattr(g, 'token_fp', None)
147
+ if token_fp:
148
+ rec['token_fp'] = token_fp
149
+ if request.url_rule is None or response.status_code >= 300:
150
+ rec['path'] = request.path[:_PATH_MAX] # raw path: probes/errors only
151
+ ua = request.headers.get('User-Agent')
152
+ if ua:
153
+ rec['user_agent'] = ua[:_UA_MAX]
154
+ args = {k: request.args.get(k) for k in _DATA_ARGS if request.args.get(k) is not None}
155
+ if args:
156
+ rec['args'] = args
157
+ global dropped_events
158
+ try:
159
+ _evt_queue.put_nowait(rec)
160
+ except queue.Full:
161
+ dropped_events += 1
162
+
163
+
164
+ # ─── Flask hooks ────────────────────────────────────────────────────────────
165
+
166
+ def before_request():
167
+ g._accesslog_start = time.perf_counter()
168
+
169
+
170
+ def after_request(response):
171
+ try:
172
+ path = request.path
173
+ if any(path.startswith(p) for p in _SKIP_PREFIXES):
174
+ return response
175
+
176
+ start = getattr(g, '_accesslog_start', None)
177
+ ms = (time.perf_counter() - start) * 1000.0 if start is not None else 0.0
178
+ status = response.status_code
179
+
180
+ content_length = response.calculate_content_length()
181
+ bytes_out = content_length if content_length is not None else 0
182
+ bytes_in = g.get('request_bytes', request.content_length or 0)
183
+
184
+ minute = int(time.time() // 60) * 60
185
+ key = (minute, _user(), _route(), f'{status // 100}xx')
186
+ with _agg_lock:
187
+ bucket = _agg[key]
188
+ bucket[0] += 1
189
+ bucket[1] += bytes_in
190
+ bucket[2] += bytes_out
191
+ bucket[3] += ms
192
+ if ms > bucket[4]:
193
+ bucket[4] = ms
194
+
195
+ if status >= 300 or getattr(g, 'auth_ok', None) is False \
196
+ or random.random() < EVENT_SAMPLE:
197
+ _record_event(response, ms, bytes_in, bytes_out)
198
+
199
+ except Exception as exc: # logging must never break the app
200
+ try:
201
+ Logger.log.warning(f'access_log after_request: {exc}')
202
+ except Exception:
203
+ pass
204
+
205
+ return response
206
+
207
+
208
+ # ─── Flush ──────────────────────────────────────────────────────────────────
209
+
210
+ def _agg_docs(snapshot):
211
+ docs = []
212
+ for (minute, user, route, status_class), (n, bi, bo, total_ms, max_ms) in snapshot.items():
213
+ docs.append({
214
+ 'kind': 'agg',
215
+ 'ts': datetime.fromtimestamp(minute, tz=timezone.utc).isoformat(),
216
+ 'host': _HOSTNAME,
217
+ 'worker_pid': os.getpid(),
218
+ 'user': user,
219
+ 'route': route,
220
+ 'status_class': status_class,
221
+ 'requests': n,
222
+ 'bytes_in': bi,
223
+ 'bytes_out': bo,
224
+ 'total_ms': round(total_ms, 3),
225
+ 'max_ms': round(max_ms, 3),
226
+ })
227
+ return docs
228
+
229
+
230
+ def _flush_once(sink):
231
+ """Drain both tiers and hand them to sink(name, docs). Returns doc counts.
232
+ On sink failure event docs are retained for the next cycle (capped);
233
+ aggregate buckets are minute-resolution and simply lost on failure."""
234
+ global dropped_events, _agg
235
+ with _agg_lock:
236
+ snapshot = _agg
237
+ _agg = defaultdict(lambda: [0, 0, 0, 0.0, 0.0])
238
+ with _pending_lock:
239
+ evts = list(_pending_evts)
240
+ _pending_evts.clear()
241
+ while True:
242
+ try:
243
+ evts.append(_evt_queue.get_nowait())
244
+ except queue.Empty:
245
+ break
246
+
247
+ agg_docs = _agg_docs(snapshot)
248
+ if not agg_docs and not evts:
249
+ return 0, 0
250
+ try:
251
+ if agg_docs:
252
+ sink(AGG_TABLE, agg_docs)
253
+ if evts:
254
+ sink(EVT_TABLE, evts)
255
+ return len(agg_docs), len(evts)
256
+ except Exception as exc:
257
+ Logger.log.error(f'access_log flush error: {exc}')
258
+ with _pending_lock:
259
+ keep = _pending_evts + evts
260
+ if len(keep) > MAX_QUEUE_SIZE:
261
+ dropped_events += len(keep) - MAX_QUEUE_SIZE
262
+ keep = keep[-MAX_QUEUE_SIZE:]
263
+ _pending_evts[:] = keep
264
+ # aggs are minute-buckets; on failure they are simply lost (bounded)
265
+ return 0, 0
266
+
267
+
268
+ def _default_sink(name, docs):
269
+ coll = _shdata.collection(DATABASE, PERIOD, SOURCE, name,
270
+ hasindex=False, user='master')
271
+ coll.extend(docs)
272
+
273
+
274
+ def _flush_worker():
275
+ while True:
276
+ time.sleep(FLUSH_INTERVAL_S)
277
+ _flush_once(_sink or _default_sink)
278
+
279
+
280
+ # ─── Registration ───────────────────────────────────────────────────────────
281
+
282
+ def register(flask_app, shdata_instance, sink=None):
283
+ """Attach access logging to a Flask app; starts the flush thread once.
284
+
285
+ Parameters
286
+ ----------
287
+ flask_app : Flask application instance
288
+ shdata_instance : SharedData handle for the direct collection write
289
+ sink : optional fn(name, docs) override (tests)
290
+ """
291
+ global _shdata, _sink
292
+ if DISABLED:
293
+ Logger.log.info('access_log: disabled (SHAREDDATA_ACCESSLOG=0)')
294
+ return
295
+ if flask_app.extensions.get('shareddata_access_log'):
296
+ return
297
+ _shdata = shdata_instance
298
+ if sink is not None:
299
+ _sink = sink
300
+ flask_app.before_request(before_request)
301
+ flask_app.after_request(after_request)
302
+ flask_app.extensions['shareddata_access_log'] = True
303
+ threading.Thread(target=_flush_worker, daemon=True,
304
+ name='api-access-log-flush').start()
305
+ Logger.log.info('access_log: registered (flush every %ds, event sample %.3f)',
306
+ FLUSH_INTERVAL_S, EVENT_SAMPLE)
@@ -2,7 +2,7 @@
2
2
  openapi: 3.0.3
3
3
  info:
4
4
  title: SharedData API
5
- version: 6.86.2
5
+ version: 6.99.0
6
6
  description: |
7
7
  HTTP API of a SharedData server. The same operations are available from Python
8
8
  through `SharedData.IO.ClientAPI` (for example `ClientAPI.get_table(...)`).
@@ -128,6 +128,95 @@ components:
128
128
  in: query
129
129
  name: token
130
130
  description: API token in the URL (avoid).
131
+ schemas:
132
+ ScheduleRoutine:
133
+ type: object
134
+ properties:
135
+ routine:
136
+ type: string
137
+ description: user@COMPUTER:script, the routine's name
138
+ user:
139
+ type: string
140
+ computer:
141
+ type: string
142
+ script:
143
+ type: string
144
+ description: Repo/folder/code, Repo#branch/folder/code or SharedData.Module
145
+ group:
146
+ type: string
147
+ realtime:
148
+ type: boolean
149
+ external:
150
+ type: boolean
151
+ runtimes:
152
+ type: string
153
+ description: HH:MM,HH:MM (server local time); empty for none
154
+ dependencies:
155
+ type: string
156
+ description: Comma-separated routine names
157
+ args:
158
+ type: string
159
+ description: Masked when a flag's name suggests a secret
160
+ args_masked:
161
+ type: boolean
162
+ health_url:
163
+ type: string
164
+ ScheduleRoutineEdit:
165
+ type: object
166
+ additionalProperties: false
167
+ required:
168
+ - revision
169
+ properties:
170
+ revision:
171
+ type: string
172
+ description: The revision GET /api/schedules/{name}/routines answered (64 hex characters)
173
+ user:
174
+ type: string
175
+ maxLength: 100
176
+ pattern: ^[A-Za-z0-9_.+-]+$
177
+ description: Required for POST and PUT
178
+ computer:
179
+ type: string
180
+ maxLength: 100
181
+ pattern: ^[A-Za-z0-9_.+-]+$
182
+ description: Required for POST and PUT
183
+ script:
184
+ type: string
185
+ maxLength: 600
186
+ description: Required for POST and PUT. Repo/folder/code[.py], Repo#branch/folder/code or SharedData.Module;
187
+ a path inside its repository
188
+ group:
189
+ type: string
190
+ maxLength: 2000
191
+ realtime:
192
+ type: boolean
193
+ external:
194
+ type: boolean
195
+ runtimes:
196
+ type: string
197
+ description: HH:MM, distinct, comma-separated; empty for none
198
+ dependencies:
199
+ oneOf:
200
+ - type: string
201
+ - type: array
202
+ items:
203
+ type: string
204
+ description: Routine names (user@COMPUTER:script, _HHMM for one run time of a routine with several), each
205
+ in the schedule, distinct, not later than this routine, no cycle
206
+ args:
207
+ type: string
208
+ maxLength: 20000
209
+ description: One line, single spaces between arguments (the Worker does not interpret quotes). Left out,
210
+ or sent back masked exactly as read, keeps the stored arguments
211
+ health_url:
212
+ type: string
213
+ description: Realtime routines only; http(s) on localhost or a private IP, no credentials, query or fragment
214
+ routine:
215
+ type: string
216
+ description: Read-only; may be sent back, then it must be the routine edited
217
+ args_masked:
218
+ type: boolean
219
+ description: Read-only; ignored
131
220
  parameters:
132
221
  Token:
133
222
  in: query
@@ -1727,6 +1816,29 @@ paths:
1727
1816
  description: No such schedule here
1728
1817
  description: 'What the SCHED page shows, for a script: the routines in run order, each with its status as the
1729
1818
  Scheduler keeps it, a level, its last message and how many seconds ago it spoke (`age`).'
1819
+ delete:
1820
+ tags:
1821
+ - Workers
1822
+ summary: Remove every routine of a schedule; it stays kept, empty (master token)
1823
+ parameters:
1824
+ - name: revision
1825
+ in: query
1826
+ description: The revision read, when it is not sent as {"revision"} in the body
1827
+ schema:
1828
+ type: string
1829
+ responses:
1830
+ '200':
1831
+ description: '{schedule, revision, kept, routines: []}'
1832
+ '400':
1833
+ description: Revision missing
1834
+ '403':
1835
+ description: Master token required
1836
+ '404':
1837
+ description: No such schedule here
1838
+ '409':
1839
+ description: The schedule changed since its revision was read
1840
+ description: 'The schedule is kept empty, so its old copy in the metadata store is never imported again.
1841
+ Running processes are not stopped.'
1730
1842
  /api/workers:
1731
1843
  get:
1732
1844
  tags:
@@ -1874,6 +1986,136 @@ paths:
1874
1986
  description: 'Takes effect within a second and stays until switched back, also across a restart of the
1875
1987
  Scheduler. While a schedule is on `observe` the Scheduler goes on keeping the status of its routines; a
1876
1988
  routine can still be restarted or stopped by a person. Master token.'
1989
+ /api/schedules/{name}/routines:
1990
+ parameters:
1991
+ - name: name
1992
+ in: path
1993
+ required: true
1994
+ description: Schedule name, e.g. MASTER
1995
+ schema:
1996
+ type: string
1997
+ get:
1998
+ tags:
1999
+ - Workers
2000
+ summary: The definition of a schedule, every routine with the revision an edit sends back (master token)
2001
+ responses:
2002
+ '200':
2003
+ description: '{schedule, revision, kept, routines: [ScheduleRoutine]}. kept=false: no such schedule
2004
+ yet, its first routine creates it.'
2005
+ '400':
2006
+ description: Invalid schedule name
2007
+ '403':
2008
+ description: Master token required
2009
+ post:
2010
+ tags:
2011
+ - Workers
2012
+ summary: Add a routine to a schedule; the first one creates the schedule (master token)
2013
+ requestBody:
2014
+ required: true
2015
+ content:
2016
+ application/json:
2017
+ schema:
2018
+ $ref: '#/components/schemas/ScheduleRoutineEdit'
2019
+ responses:
2020
+ '201':
2021
+ description: '{schedule, revision, routine: ScheduleRoutine}'
2022
+ '400':
2023
+ description: A field is wrong (the error says which), revision missing, or a dependency is missing,
2024
+ later than the routine or makes a cycle. Nothing was written.
2025
+ '403':
2026
+ description: Master token required
2027
+ '409':
2028
+ description: The schedule changed since its revision was read, or the routine already exists
2029
+ description: 'Every field is checked before the store is touched. The Scheduler takes the routine up on its
2030
+ next tick; nothing is started by the edit itself.'
2031
+ /api/schedules/{name}/routines/{routine}:
2032
+ parameters:
2033
+ - name: name
2034
+ in: path
2035
+ required: true
2036
+ description: Schedule name, e.g. MASTER
2037
+ schema:
2038
+ type: string
2039
+ - name: routine
2040
+ in: path
2041
+ required: true
2042
+ description: The routine's name, user@COMPUTER:script, e.g. user@HOST:Repo/folder/code (percent-encode '#')
2043
+ schema:
2044
+ type: string
2045
+ get:
2046
+ tags:
2047
+ - Workers
2048
+ summary: One routine of a schedule (master token)
2049
+ responses:
2050
+ '200':
2051
+ description: '{schedule, revision, routine: ScheduleRoutine}'
2052
+ '403':
2053
+ description: Master token required
2054
+ '404':
2055
+ description: No such routine in the schedule
2056
+ put:
2057
+ tags:
2058
+ - Workers
2059
+ summary: The routine becomes these fields; one left out takes its default, except args (master token)
2060
+ requestBody:
2061
+ required: true
2062
+ content:
2063
+ application/json:
2064
+ schema:
2065
+ $ref: '#/components/schemas/ScheduleRoutineEdit'
2066
+ responses:
2067
+ '200':
2068
+ description: '{schedule, revision, routine: ScheduleRoutine}; a new user, computer or script renames it'
2069
+ '400':
2070
+ description: A field is wrong, revision missing, or the edit breaks a dependency. Nothing was written.
2071
+ '403':
2072
+ description: Master token required
2073
+ '404':
2074
+ description: No such schedule or routine
2075
+ '409':
2076
+ description: The schedule changed since its revision was read, or the new name is taken
2077
+ patch:
2078
+ tags:
2079
+ - Workers
2080
+ summary: Only the fields sent change; the routine is checked whole afterwards (master token)
2081
+ requestBody:
2082
+ required: true
2083
+ content:
2084
+ application/json:
2085
+ schema:
2086
+ $ref: '#/components/schemas/ScheduleRoutineEdit'
2087
+ responses:
2088
+ '200':
2089
+ description: '{schedule, revision, routine: ScheduleRoutine}'
2090
+ '400':
2091
+ description: A field is wrong, nothing to change, revision missing, or the edit breaks a dependency
2092
+ '403':
2093
+ description: Master token required
2094
+ '404':
2095
+ description: No such schedule or routine
2096
+ '409':
2097
+ description: The schedule changed since its revision was read, or the new name is taken
2098
+ delete:
2099
+ tags:
2100
+ - Workers
2101
+ summary: Remove a routine from a schedule; its running process is not stopped (master token)
2102
+ parameters:
2103
+ - name: revision
2104
+ in: query
2105
+ description: The revision read, when it is not sent as {"revision"} in the body
2106
+ schema:
2107
+ type: string
2108
+ responses:
2109
+ '200':
2110
+ description: '{schedule, revision, deleted}'
2111
+ '400':
2112
+ description: Revision missing, or another routine depends on this one
2113
+ '403':
2114
+ description: Master token required
2115
+ '404':
2116
+ description: No such schedule or routine
2117
+ '409':
2118
+ description: The schedule changed since its revision was read
1877
2119
  /api/routes:
1878
2120
  get:
1879
2121
  tags:
@@ -120,17 +120,21 @@ def check_permissions(reqpath: list, permissions: dict, method: str, identity=No
120
120
  def authenticate(request, shdata) -> bool:
121
121
  """
122
122
  Authenticate an incoming HTTP request by validating a token and verifying permissions.
123
-
123
+
124
124
  Parameters:
125
125
  request (flask.Request): The incoming HTTP request object.
126
126
  shdata: SharedData instance for accessing user table.
127
-
127
+
128
128
  Returns:
129
129
  bool: True if authenticated and authorized; False otherwise.
130
130
  """
131
+ from flask import g
131
132
  from SharedData.Auth.store import get_store, StoreUnavailable, token_hash
132
133
  token = get_token_from_request(request)
134
+ if token:
135
+ g.token_fp = token_hash(token)[:8]
133
136
  if not token:
137
+ g.auth_ok = False
134
138
  time.sleep(AUTH_FAILURE_SLEEP_SECONDS)
135
139
  return False
136
140
  try:
@@ -138,8 +142,12 @@ def authenticate(request, shdata) -> bool:
138
142
  except Exception as exc:
139
143
  raise StoreUnavailable('auth store lookup failed') from exc
140
144
  if not userdata:
145
+ g.auth_ok = False
146
+ g.client_user = 'invalid-token'
141
147
  time.sleep(AUTH_FAILURE_SLEEP_SECONDS)
142
148
  return False
149
+ g.auth_ok = True
150
+ g.client_user = userdata.get('email') or userdata.get('symbol') or 'unknown'
143
151
  with user_by_token_lock:
144
152
  if len(user_by_token) >= 4096:
145
153
  user_by_token.clear()