shareddata 6.85.3__tar.gz → 6.85.5__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 (187) hide show
  1. {shareddata-6.85.3/src/shareddata.egg-info → shareddata-6.85.5}/PKG-INFO +1 -1
  2. {shareddata-6.85.3 → shareddata-6.85.5}/setup.py +1 -1
  3. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Table.py +11 -0
  4. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableDisk.py +193 -52
  5. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableRewrite.py +26 -0
  6. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TimeSeriesDisk.py +18 -2
  7. {shareddata-6.85.3 → shareddata-6.85.5/src/shareddata.egg-info}/PKG-INFO +1 -1
  8. {shareddata-6.85.3 → shareddata-6.85.5}/LICENSE +0 -0
  9. {shareddata-6.85.3 → shareddata-6.85.5}/MANIFEST.in +0 -0
  10. {shareddata-6.85.3 → shareddata-6.85.5}/README.md +0 -0
  11. {shareddata-6.85.3 → shareddata-6.85.5}/pyproject.toml +0 -0
  12. {shareddata-6.85.3 → shareddata-6.85.5}/setup.cfg +0 -0
  13. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/ServerGunicorn.py +0 -0
  14. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/ServerWaitress.py +0 -0
  15. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/__init__.py +0 -0
  16. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/apidocs-dark.css +0 -0
  17. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/apidocs.yml +0 -0
  18. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/auth.py +0 -0
  19. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/constants.py +0 -0
  20. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/download_paths.py +0 -0
  21. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/download_slots.py +0 -0
  22. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/install/worker.ps1 +0 -0
  23. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/install/worker.sh +0 -0
  24. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/listcache.py +0 -0
  25. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/__init__.py +0 -0
  26. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/cache.py +0 -0
  27. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/collections.py +0 -0
  28. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/data.py +0 -0
  29. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/downloads.py +0 -0
  30. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/metadata.py +0 -0
  31. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/operations.py +0 -0
  32. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/schedules.py +0 -0
  33. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/sqlite.py +0 -0
  34. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/system.py +0 -0
  35. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/tables.py +0 -0
  36. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/timeseries.py +0 -0
  37. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/web_routes.py +0 -0
  38. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/routes/workers.py +0 -0
  39. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/API/utils.py +0 -0
  40. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Auth/__init__.py +0 -0
  41. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Auth/bootstrap.py +0 -0
  42. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Auth/migrate.py +0 -0
  43. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Auth/store.py +0 -0
  44. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Auth/workers.py +0 -0
  45. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/CacheRedis.py +0 -0
  46. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/CollectionMongoDB.py +0 -0
  47. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/__init__.py +0 -0
  48. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/catalog.py +0 -0
  49. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/config.py +0 -0
  50. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/errors.py +0 -0
  51. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/indexes.py +0 -0
  52. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/readers.py +0 -0
  53. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/resolve.py +0 -0
  54. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/DataAPI/service.py +0 -0
  55. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Database.py +0 -0
  56. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Defaults.py +0 -0
  57. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/AWSEC2.py +0 -0
  58. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/AWSS3.py +0 -0
  59. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/AutoDocstrings.py +0 -0
  60. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/ClientAPI.py +0 -0
  61. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/ClientSocket.py +0 -0
  62. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/ClientWebSocket.py +0 -0
  63. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/LogHandlerAPI.py +0 -0
  64. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/LogReader.py +0 -0
  65. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/MongoDBClient.py +0 -0
  66. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/SaveTables.py +0 -0
  67. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/ServerSocket.py +0 -0
  68. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/ServerWebSocket.py +0 -0
  69. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/StreamsCache.py +0 -0
  70. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/StreamsPersist.py +0 -0
  71. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/SyncTable.py +0 -0
  72. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/TunnelWebSocket.py +0 -0
  73. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/IO/__init__.py +0 -0
  74. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Listing.py +0 -0
  75. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/LockWaiters.py +0 -0
  76. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Logger.py +0 -0
  77. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/MCP/__init__.py +0 -0
  78. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/MCP/__main__.py +0 -0
  79. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/MCP/client.py +0 -0
  80. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/MCP/server.py +0 -0
  81. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Metadata.py +0 -0
  82. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/MultiProc.py +0 -0
  83. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/OpenFIGI.py +0 -0
  84. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Redaction.py +0 -0
  85. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/ArchiveOldCollections.py +0 -0
  86. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/BatchJob.py +0 -0
  87. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/ContainerHealth.py +0 -0
  88. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/JobStore.py +0 -0
  89. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/RoutineCommand.py +0 -0
  90. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/RoutineOrigin.py +0 -0
  91. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/Schedule.py +0 -0
  92. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/ScheduleMirror.py +0 -0
  93. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/ScheduleMonitor.py +0 -0
  94. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/ScheduleStore.py +0 -0
  95. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/Scheduler.py +0 -0
  96. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/UpdateActiveJobs.py +0 -0
  97. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/Worker.py +0 -0
  98. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/WorkerBootstrap.py +0 -0
  99. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/WorkerLib.py +0 -0
  100. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/WorkerPool.py +0 -0
  101. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Routines/__init__.py +0 -0
  102. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/SQLite.py +0 -0
  103. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/SharedData.py +0 -0
  104. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/SharedNumpy.py +0 -0
  105. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/StreamKafka.py +0 -0
  106. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Symbol.py +0 -0
  107. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndex.py +0 -0
  108. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndexJitFunctions.py +0 -0
  109. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndexJitFunctionsManual.py +0 -0
  110. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndexJitGenerate.py +0 -0
  111. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndexJitHash.py +0 -0
  112. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableIndexJitLoc.py +0 -0
  113. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableProtocol.py +0 -0
  114. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TableRead.py +0 -0
  115. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/TimeseriesContainer.py +0 -0
  116. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Users.py +0 -0
  117. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Utils.py +0 -0
  118. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/ADDING_A_PAGE.md +0 -0
  119. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/__init__.py +0 -0
  120. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/activity_logger.py +0 -0
  121. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/app.py +0 -0
  122. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/dashAgGridComponentFunctions.js +0 -0
  123. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/dashAgGridFunctions.js +0 -0
  124. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/google_logo.png +0 -0
  125. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/kafka_keys.js +0 -0
  126. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/logs.css +0 -0
  127. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/sched.css +0 -0
  128. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/assets/styles.css +0 -0
  129. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/auth.py +0 -0
  130. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/backends.py +0 -0
  131. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/config.py +0 -0
  132. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/containers.py +0 -0
  133. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/identity.py +0 -0
  134. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/kafka_view.py +0 -0
  135. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/mongo_view.py +0 -0
  136. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/proc.py +0 -0
  137. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/redis_view.py +0 -0
  138. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/route_health.py +0 -0
  139. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/route_registry.py +0 -0
  140. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/sched.py +0 -0
  141. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/schedule_editor.py +0 -0
  142. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/server.py +0 -0
  143. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/services.py +0 -0
  144. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/shell.py +0 -0
  145. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/standalone.py +0 -0
  146. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/account.js +0 -0
  147. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/custom.css +0 -0
  148. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/favicon.ico +0 -0
  149. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/favicon.png +0 -0
  150. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/logo.png +0 -0
  151. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/logo_96x96.png +0 -0
  152. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/shareddata-mark.svg +0 -0
  153. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/shell.css +0 -0
  154. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/shell.js +0 -0
  155. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/standalone.css +0 -0
  156. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/static/standalone.js +0 -0
  157. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/suggest.py +0 -0
  158. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/account.html +0 -0
  159. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/base.html +0 -0
  160. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/claim.html +0 -0
  161. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/claim_done.html +0 -0
  162. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/dash.html +0 -0
  163. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/index.html +0 -0
  164. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/login.html +0 -0
  165. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/login_2fa.html +0 -0
  166. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/partials/navbar.html +0 -0
  167. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/partials/scripts.html +0 -0
  168. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/recovery_codes.html +0 -0
  169. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/shell.html +0 -0
  170. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/templates/standalone.html +0 -0
  171. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/twofactor.py +0 -0
  172. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/utils.py +0 -0
  173. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/Web/workerpool.py +0 -0
  174. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/__init__.py +0 -0
  175. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/build_info.py +0 -0
  176. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/fleet.py +0 -0
  177. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/preflight.py +0 -0
  178. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/private_files.py +0 -0
  179. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/release_backup.py +0 -0
  180. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/server.py +0 -0
  181. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/services.py +0 -0
  182. {shareddata-6.85.3 → shareddata-6.85.5}/src/SharedData/sharedmutexwin.pyd +0 -0
  183. {shareddata-6.85.3 → shareddata-6.85.5}/src/shareddata.egg-info/SOURCES.txt +0 -0
  184. {shareddata-6.85.3 → shareddata-6.85.5}/src/shareddata.egg-info/dependency_links.txt +0 -0
  185. {shareddata-6.85.3 → shareddata-6.85.5}/src/shareddata.egg-info/entry_points.txt +0 -0
  186. {shareddata-6.85.3 → shareddata-6.85.5}/src/shareddata.egg-info/requires.txt +0 -0
  187. {shareddata-6.85.3 → shareddata-6.85.5}/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.85.3
3
+ Version: 6.85.5
4
4
  Summary: Memory Mapped / Shared Memory Database with S3 repository
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.85.3',
69
+ version='6.85.5',
70
70
  description='Memory Mapped / Shared Memory Database with S3 repository',
71
71
  author='shareddata',
72
72
  author_email='admin@shareddata.net',
@@ -1229,6 +1229,17 @@ class Table:
1229
1229
  io_obj.close()
1230
1230
  self.copy_file()
1231
1231
 
1232
+ def _parse_tail_header(self, tail_io):
1233
+ """The tail header record at the start of a tail stream (its first row starts right after it).
1234
+ Unlike read_tailheader, nothing of this table is changed."""
1235
+ tail_io.seek(0)
1236
+ size = int.from_bytes(tail_io.read(8), byteorder='little')
1237
+ description = tail_io.read(size).decode(encoding='UTF-8', errors='ignore').replace('\x00', '')
1238
+ names, formats = description.split(';')[0].split(','), description.split(';')[1].split(',')
1239
+ dtype = np.dtype({'names': names, 'formats': formats})
1240
+ tail_io.seek(0)
1241
+ return np.ndarray((1,), dtype=dtype, buffer=tail_io.read(dtype.itemsize))[0].copy()
1242
+
1232
1243
  def read_tailheader(self, tail_io):
1233
1244
  """
1234
1245
  Reads and parses the tail header from a binary stream.
@@ -23,7 +23,51 @@ from SharedData.Table import Table
23
23
  from SharedData.LockWaiters import TableBusy
24
24
 
25
25
  from SharedData.Utils import cpp
26
- from SharedData.TableProtocol import structural
26
+ from SharedData.TableProtocol import structural, structure
27
+
28
+
29
+ FICLONE = 0x40049409 # linux/fs.h: make the file a reflink clone of another
30
+ CHUNK = 64 << 20
31
+
32
+
33
+ def _clone_prefix(source, stage, prefix):
34
+ """Create *stage* as a copy of the first *prefix* bytes of *source*, which is only read.
35
+
36
+ On a filesystem with reflinks (XFS, btrfs) the whole file is cloned: no data is read or
37
+ written and no space is used until one of them changes. Anywhere else just the prefix (the
38
+ header and the head) is copied by the kernel. Returns 'reflink' or 'copy'.
39
+ """
40
+ import errno
41
+ import fcntl
42
+ from SharedData.TableRewrite import RewriteError
43
+ unsupported = (errno.EOPNOTSUPP, errno.ENOTTY, errno.EXDEV, errno.EINVAL, errno.ENOSYS)
44
+ src, dst = os.open(source, os.O_RDONLY), None
45
+ try:
46
+ dst = os.open(stage, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o600)
47
+ try:
48
+ fcntl.ioctl(dst, FICLONE, src)
49
+ return 'reflink'
50
+ except OSError as error:
51
+ if error.errno not in unsupported:
52
+ raise
53
+ copied = 0
54
+ while copied < prefix:
55
+ want = min(1 << 30, prefix - copied)
56
+ try:
57
+ done = os.copy_file_range(src, dst, want, copied, copied)
58
+ except OSError as error:
59
+ if error.errno not in unsupported:
60
+ raise
61
+ chunk = os.pread(src, min(want, CHUNK), copied)
62
+ done = os.pwrite(dst, chunk, copied) if chunk else 0
63
+ if done <= 0:
64
+ raise RewriteError('the live image ended before its head was copied')
65
+ copied += done
66
+ return 'copy'
67
+ finally:
68
+ os.close(src)
69
+ if dst is not None:
70
+ os.close(dst)
27
71
 
28
72
 
29
73
  class TableDisk(Table):
@@ -260,7 +304,6 @@ class TableDisk(Table):
260
304
  return publish_rewrite(self, None, 'trim', capacity=max(2, int(self.hdr['count'])))
261
305
 
262
306
  ############### RELOAD ###############
263
- @structural
264
307
  def reload(self):
265
308
  """
266
309
  Replace the local image with the remote copy when that is newer.
@@ -269,14 +312,28 @@ class TableDisk(Table):
269
312
  rows not uploaded yet are never discarded. When only the remote tail is newer (the usual
270
313
  case: the head holds the years before this one and the tail this year's rows) only the tail
271
314
  is fetched and applied, see _reload_tail. When the remote head is newer, the whole remote
272
- image is built beside data.bin under a stage name and installed by one os.replace, inside
273
- the caller's STRUCT section: a failure before the replace leaves the old image untouched, and the epoch
274
- published on leaving the section makes other processes reopen the new file. The old index
275
- files describe the old rows, so they are removed and rebuilt.
315
+ image is fetched, see _reload_image.
276
316
 
277
317
  Returns:
278
318
  The records of the reloaded table.
279
319
  """
320
+ change = self._remote_change()
321
+ if change is None:
322
+ return self.records
323
+ if change == 'tail' and not self.is_schemaless:
324
+ done = self._reload_tail()
325
+ if done is not None:
326
+ return done
327
+ return self._reload_image()
328
+
329
+ @structural
330
+ def _reload_image(self):
331
+ """
332
+ The whole remote image is built beside data.bin under a stage name and installed by one
333
+ os.replace, inside a STRUCT section: a failure before the replace leaves the old image
334
+ untouched, and the epoch published on leaving the section makes other processes reopen the
335
+ new file. The old index files describe the old rows, so they are removed and rebuilt.
336
+ """
280
337
  import stat
281
338
  import uuid
282
339
  from SharedData.TableRewrite import fsync_directory, read_layout, require_rewrite_support, sync_file
@@ -339,62 +396,146 @@ class TableDisk(Table):
339
396
  raise Exception(errmsg)
340
397
  return self.records
341
398
 
399
+ def _attach_replacement(self, final, rebuild_files):
400
+ """After os.replace: make the directory entry durable, then map the new image and rebuild its
401
+ indexes. Whatever fails, this object ends mapped to the image that is now installed (flagged
402
+ for index repair by the replacement's own header), never to two images."""
403
+ from SharedData.TableRewrite import fsync_directory
404
+ try:
405
+ fsync_directory(final.parent) # the new name is durable before any old index file is touched
406
+ self.free(acquire=False) # the old mappings and index arrays, flushed first
407
+ if rebuild_files:
408
+ for name in ('dateidx.bin', 'pkey.bin', 'symbolidx.bin', 'portidx.bin'):
409
+ try:
410
+ (final.parent / name).unlink()
411
+ except FileNotFoundError:
412
+ pass
413
+ self.malloc()
414
+ if self.hasindex:
415
+ self.hdr['isidxcreated'] = 0
416
+ self.index.initialize()
417
+ self.shareddata.data[self.relpath] = self
418
+ self.hdr['isloaded'] = 1
419
+ self.mutex['isloaded'] = 1
420
+ except BaseException as error:
421
+ self._mapping_epoch = -1 # the next owner re-attaches by path
422
+ try:
423
+ self.attach()
424
+ except Exception:
425
+ pass
426
+ if isinstance(error, Exception) and not isinstance(error, OSError):
427
+ Logger.log.error(f"TableDisk.reload() {self.relpath}: {error}")
428
+ raise
429
+ return self.records
430
+
342
431
  def _reload_tail(self):
343
432
  """
344
- Apply the remote tail alone: its cost is the tail, not the table (that is what the head/tail
345
- split is for). Runs inside reload's STRUCT section, with the writer lock held.
433
+ Apply the remote tail alone: only the tail object is downloaded (that is what the head/tail
434
+ split is for) and no live file is written (see TableRewrite).
346
435
 
347
- The tail rows are written into the live file right after the head, the file is synced, and
348
- the header (count, tail size and time) is committed last. A process that dies before the
349
- commit leaves the old header: the old tail time is still older than the remote one, so the
350
- next reload applies the tail again, and the index is rebuilt from the committed rows (the
351
- dead holder leaves the table flagged DIRTY). No reader looks at the file meanwhile: STRUCT
352
- keeps lock-free readers waiting and the other readers take the same lock.
436
+ Under the writer lock only (readers go on reading the live image meanwhile) a stage is built
437
+ as a clone of the live image, see _clone_prefix: the head, which did not change, is shared or
438
+ copied by the kernel. Its unused capacity is allocated again, since a clone shares only what
439
+ was written. The tail is streamed into the stage with a bounded buffer, its length and checksum
440
+ checked against its header, then the new header (indexes marked not created) is written, the
441
+ stage synced. A failure up to here leaves the live image and header untouched and the table
442
+ not flagged: nothing was changed. Then, in a STRUCT section, one os.replace installs the stage,
443
+ the directory is synced before any index file is touched, the indexes are rebuilt and the epoch
444
+ published. A process that dies at any point leaves the complete old image or the complete new
445
+ one, and nothing is needed from S3 to recover. A remote head that changed meanwhile means the
446
+ tail belongs to another head: the whole image is replaced instead (returns None).
353
447
  """
354
- from SharedData.TableRewrite import sync_file
355
- tail_gz, _, remote_mtime = self._download_remote_tail()
356
- if tail_gz is None: # nothing newer after all
357
- return self.records
358
- live, size = self.hdr, self.size
359
- self.hdr = live.copy() # the mapped header stays as it was until the commit
360
- self._bootstrap_record_download = True # reload holds the lock: the tail may be written into the live file
448
+ import uuid
449
+ from SharedData.TableRewrite import (RewriteError, copy_metadata, pwrite_all, require_rewrite_support,
450
+ same_digest, sync_header)
451
+ import hashlib
452
+ require_rewrite_support()
453
+ acquired = not self._owns_lock()
454
+ if acquired:
455
+ self.acquire()
456
+ stage = None
361
457
  try:
458
+ self._sync_held_mapping()
459
+ tail_gz, _, remote_mtime = self._download_remote_tail()
460
+ if tail_gz is None: # nothing newer after all
461
+ return self.records
462
+ # The tail belongs to the head it was uploaded with, and the uploader sends the head first:
463
+ # a head that changed since the check in reload() means this tail is not for the head held here.
464
+ if self._remote_change() == 'head':
465
+ return None
466
+ final = self.filepath
467
+ header = self.hdr.copy() # detached: this object's header is not touched
362
468
  tail_gz.seek(0) # S3Download leaves the buffer at its end
363
469
  tail_io = gzip.GzipFile(fileobj=tail_gz, mode='rb')
364
- self.read_tailheader(tail_io) # count, tail size and tail time of the new header
365
- self.hdr['mtimetail'] = remote_mtime
366
- if int(self.hdr['count']) > int(self.hdr['recordssize']):
367
- self.hdr['recordssize'] = self.hdr['count']
368
- if self.size is None or self.size < int(self.hdr['recordssize']):
369
- self.size = int(self.hdr['recordssize'])
370
- self.create_file() # a table that grew gets a longer file: never shortened or rewritten
371
- self.read_tail(tail_io)
372
- sync_file(self.filepath)
373
- self.hdr['isloaded'] = 0
374
- self.hdr['isidxcreated'] = 0
375
- committed = self.hdr
470
+ tailhdr = self._parse_tail_header(tail_io)
471
+ itemsize, tailsize = int(header['itemsize']), int(tailhdr['tailsize'])
472
+ header['md5hashtail'] = tailhdr['md5hash']
473
+ header['tailsize'] = tailsize
474
+ header['count'] = int(header['headsize']) + tailsize
475
+ header['mtimetail'] = remote_mtime
476
+ if int(header['count']) > int(header['recordssize']):
477
+ header['recordssize'] = header['count']
478
+ capacity = int(header['recordssize'])
479
+ hdrsize = header.dtype.itemsize
480
+ tail_at = hdrsize + int(header['headsize']) * itemsize
481
+ total = hdrsize + capacity * itemsize
482
+ header['isidxcreated'] = 0 # durable in the stage: no client may trust the old indexes
483
+ header['isloaded'] = 0
484
+ header['isidxsorted'] = 1 # the uploader sorts before it writes (SharedNumpy.write)
485
+ pending = int(header['minchgid']) # a head row edited here and not uploaded yet stays marked for upload;
486
+ header['minchgid'] = pending if pending <= int(header['headsize']) else int(header['count']) + 1 # otherwise as a write leaves it
487
+ for leftover in final.parent.glob('data.bin.reload.*'): # the lock is held: a stage left by a dead reload
488
+ leftover.unlink()
489
+ stage = final.parent / ('data.bin.reload.' + uuid.uuid4().hex)
490
+ self.shf_data.flush()
491
+ sync_header(self) # the old image is complete on disk before it is cloned
492
+ _clone_prefix(final, stage, tail_at)
493
+ fd = os.open(stage, os.O_RDWR)
494
+ try:
495
+ copy_metadata(final, fd)
496
+ os.posix_fallocate(fd, 0, total) # a clone shares written extents only: the capacity is reserved again
497
+ tail_io.seek(tailhdr.dtype.itemsize)
498
+ digest, written = hashlib.md5(), 0
499
+ while True:
500
+ chunk = tail_io.read(CHUNK)
501
+ if not chunk:
502
+ break
503
+ if written + len(chunk) > tailsize * itemsize:
504
+ raise RewriteError('the remote tail of %s is longer than its header says' % self.relpath)
505
+ digest.update(chunk)
506
+ pwrite_all(fd, chunk, tail_at + written)
507
+ written += len(chunk)
508
+ if written != tailsize * itemsize:
509
+ raise RewriteError('the remote tail of %s holds %d bytes, its header says %d' % (self.relpath, written, tailsize * itemsize))
510
+ if not same_digest(digest.digest(), tailhdr['md5hash']):
511
+ raise RewriteError('the remote tail of %s does not match its checksum' % self.relpath)
512
+ pwrite_all(fd, header.tobytes(), 0)
513
+ os.fsync(fd)
514
+ finally:
515
+ os.close(fd)
516
+ refused = None
517
+ with structure(self): # only the swap and the index rebuild block readers
518
+ try:
519
+ os.replace(stage, final)
520
+ except OSError as error: # nothing changed: leave STRUCT without flagging the live header
521
+ refused = error
522
+ else:
523
+ stage = None
524
+ self.size = capacity
525
+ self._attach_replacement(final, rebuild_files=True)
526
+ if refused is not None:
527
+ raise refused
528
+ return self.records
376
529
  except BaseException:
377
- self.hdr, self.size = live, size
530
+ if stage is not None:
531
+ try:
532
+ stage.unlink()
533
+ except FileNotFoundError:
534
+ pass
378
535
  raise
379
536
  finally:
380
- self._bootstrap_record_download = False
381
- self.shf_hdr[0] = committed # the commit
382
- self.shf_hdr.flush()
383
- sync_file(self.filepath)
384
- try:
385
- self.free(acquire=False)
386
- self.malloc()
387
- if self.hasindex:
388
- self.hdr['isidxcreated'] = 0
389
- self.index.initialize()
390
- self.shareddata.data[self.relpath] = self
391
- self.hdr['isloaded'] = 1
392
- self.mutex['isloaded'] = 1
393
- except Exception as e:
394
- errmsg = f"TableDisk.reload() {self.relpath}: {e}"
395
- Logger.log.error(errmsg)
396
- raise Exception(errmsg)
397
- return self.records
537
+ if acquired:
538
+ self.release()
398
539
 
399
540
  ############### DELETE ###############
400
541
  def delete_local(self):
@@ -61,6 +61,32 @@ def write_all(fd, data):
61
61
  data = data[written:]
62
62
 
63
63
 
64
+ def pwrite_all(fd, data, offset):
65
+ """os.pwrite until every byte is written; a write that makes no progress is an error."""
66
+ view = memoryview(data).cast('B')
67
+ done = 0
68
+ while done < len(view):
69
+ written = os.pwrite(fd, view[done:], offset + done)
70
+ if written <= 0:
71
+ raise RewriteError('no progress writing the replacement image')
72
+ done += written
73
+
74
+
75
+ def copy_metadata(source, fd):
76
+ """Mode, owner, ACLs and extended attributes of *source* onto the open replacement *fd*
77
+ (fchmod is not filtered by the umask). A failure aborts the replacement, as in publish_rewrite."""
78
+ info = os.stat(source)
79
+ os.fchown(fd, info.st_uid, info.st_gid)
80
+ os.fchmod(fd, stat.S_IMODE(info.st_mode))
81
+ for attribute in os.listxattr(source):
82
+ os.setxattr(fd, attribute, os.getxattr(source, attribute))
83
+
84
+
85
+ def same_digest(digest, stored):
86
+ """A header keeps an MD5 in a bytes field, which NumPy returns without its trailing NULs."""
87
+ return bytes(stored) == digest.rstrip(b'\0')
88
+
89
+
64
90
  def read_layout(stream):
65
91
  """Strict, read-only self-described header parser, also used before open."""
66
92
  stream.seek(0)
@@ -124,8 +124,7 @@ class TimeSeriesDisk:
124
124
 
125
125
  if value is not None:
126
126
  # mutex() acquired the non-reentrant tag lock for initialization.
127
- self._write_values_locked(value.index, value.columns, value.to_numpy(),
128
- add_columns=True, flush=True)
127
+ self._write_constructor_value_locked(value)
129
128
  finally:
130
129
  self.release()
131
130
 
@@ -188,6 +187,23 @@ class TimeSeriesDisk:
188
187
  finally:
189
188
  self.release()
190
189
 
190
+ def _write_constructor_value_locked(self, value):
191
+ """
192
+ The `value=` of the constructor, with the meaning it always had: rows whose timestamp is not
193
+ a slot of the series (a weekend, a date after its end) are skipped, not an error. Routines
194
+ build the frame from other tables and hand it over as is. The exact-timestamp check belongs to
195
+ write_values(), the explicit bulk API.
196
+ """
197
+ self.ensure_columns(value.columns, _locked=True)
198
+ tidx = self.get_loc_timestamp(value.index.values.astype(np.int64) / 10**9)
199
+ skipped = int(np.isnan(tidx).sum())
200
+ if skipped:
201
+ Logger.log.warning('%s: %d of %d rows of value are not on the series time index and were skipped'
202
+ % (self.relpath, skipped, len(tidx)))
203
+ sidx = self.columns.get_indexer(value.columns).astype(np.float64)
204
+ self.setValuesJit(self.shf, tidx, sidx, np.asarray(value.to_numpy(), dtype=np.float64))
205
+ self.shf.flush()
206
+
191
207
  def _write_values_locked(self, index, columns, values, *, add_columns, flush):
192
208
  index = pd.DatetimeIndex(index)
193
209
  columns = pd.Index(columns)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shareddata
3
- Version: 6.85.3
3
+ Version: 6.85.5
4
4
  Summary: Memory Mapped / Shared Memory Database with S3 repository
5
5
  Home-page: https://shareddata.net
6
6
  Author: shareddata
File without changes
File without changes
File without changes
File without changes
File without changes