shareddata 6.85.2__tar.gz → 6.85.4__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.2/src/shareddata.egg-info → shareddata-6.85.4}/PKG-INFO +1 -1
  2. {shareddata-6.85.2 → shareddata-6.85.4}/setup.py +1 -1
  3. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Table.py +24 -7
  4. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableDisk.py +211 -8
  5. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableRewrite.py +26 -0
  6. {shareddata-6.85.2 → shareddata-6.85.4/src/shareddata.egg-info}/PKG-INFO +1 -1
  7. {shareddata-6.85.2 → shareddata-6.85.4}/LICENSE +0 -0
  8. {shareddata-6.85.2 → shareddata-6.85.4}/MANIFEST.in +0 -0
  9. {shareddata-6.85.2 → shareddata-6.85.4}/README.md +0 -0
  10. {shareddata-6.85.2 → shareddata-6.85.4}/pyproject.toml +0 -0
  11. {shareddata-6.85.2 → shareddata-6.85.4}/setup.cfg +0 -0
  12. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/ServerGunicorn.py +0 -0
  13. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/ServerWaitress.py +0 -0
  14. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/__init__.py +0 -0
  15. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/apidocs-dark.css +0 -0
  16. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/apidocs.yml +0 -0
  17. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/auth.py +0 -0
  18. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/constants.py +0 -0
  19. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/download_paths.py +0 -0
  20. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/download_slots.py +0 -0
  21. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/install/worker.ps1 +0 -0
  22. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/install/worker.sh +0 -0
  23. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/listcache.py +0 -0
  24. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/__init__.py +0 -0
  25. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/cache.py +0 -0
  26. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/collections.py +0 -0
  27. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/data.py +0 -0
  28. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/downloads.py +0 -0
  29. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/metadata.py +0 -0
  30. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/operations.py +0 -0
  31. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/schedules.py +0 -0
  32. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/sqlite.py +0 -0
  33. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/system.py +0 -0
  34. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/tables.py +0 -0
  35. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/timeseries.py +0 -0
  36. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/web_routes.py +0 -0
  37. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/routes/workers.py +0 -0
  38. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/API/utils.py +0 -0
  39. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Auth/__init__.py +0 -0
  40. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Auth/bootstrap.py +0 -0
  41. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Auth/migrate.py +0 -0
  42. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Auth/store.py +0 -0
  43. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Auth/workers.py +0 -0
  44. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/CacheRedis.py +0 -0
  45. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/CollectionMongoDB.py +0 -0
  46. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/__init__.py +0 -0
  47. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/catalog.py +0 -0
  48. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/config.py +0 -0
  49. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/errors.py +0 -0
  50. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/indexes.py +0 -0
  51. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/readers.py +0 -0
  52. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/resolve.py +0 -0
  53. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/DataAPI/service.py +0 -0
  54. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Database.py +0 -0
  55. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Defaults.py +0 -0
  56. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/AWSEC2.py +0 -0
  57. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/AWSS3.py +0 -0
  58. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/AutoDocstrings.py +0 -0
  59. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/ClientAPI.py +0 -0
  60. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/ClientSocket.py +0 -0
  61. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/ClientWebSocket.py +0 -0
  62. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/LogHandlerAPI.py +0 -0
  63. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/LogReader.py +0 -0
  64. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/MongoDBClient.py +0 -0
  65. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/SaveTables.py +0 -0
  66. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/ServerSocket.py +0 -0
  67. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/ServerWebSocket.py +0 -0
  68. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/StreamsCache.py +0 -0
  69. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/StreamsPersist.py +0 -0
  70. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/SyncTable.py +0 -0
  71. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/TunnelWebSocket.py +0 -0
  72. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/IO/__init__.py +0 -0
  73. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Listing.py +0 -0
  74. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/LockWaiters.py +0 -0
  75. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Logger.py +0 -0
  76. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/MCP/__init__.py +0 -0
  77. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/MCP/__main__.py +0 -0
  78. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/MCP/client.py +0 -0
  79. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/MCP/server.py +0 -0
  80. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Metadata.py +0 -0
  81. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/MultiProc.py +0 -0
  82. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/OpenFIGI.py +0 -0
  83. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Redaction.py +0 -0
  84. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/ArchiveOldCollections.py +0 -0
  85. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/BatchJob.py +0 -0
  86. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/ContainerHealth.py +0 -0
  87. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/JobStore.py +0 -0
  88. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/RoutineCommand.py +0 -0
  89. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/RoutineOrigin.py +0 -0
  90. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/Schedule.py +0 -0
  91. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/ScheduleMirror.py +0 -0
  92. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/ScheduleMonitor.py +0 -0
  93. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/ScheduleStore.py +0 -0
  94. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/Scheduler.py +0 -0
  95. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/UpdateActiveJobs.py +0 -0
  96. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/Worker.py +0 -0
  97. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/WorkerBootstrap.py +0 -0
  98. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/WorkerLib.py +0 -0
  99. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/WorkerPool.py +0 -0
  100. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Routines/__init__.py +0 -0
  101. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/SQLite.py +0 -0
  102. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/SharedData.py +0 -0
  103. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/SharedNumpy.py +0 -0
  104. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/StreamKafka.py +0 -0
  105. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Symbol.py +0 -0
  106. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndex.py +0 -0
  107. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndexJitFunctions.py +0 -0
  108. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndexJitFunctionsManual.py +0 -0
  109. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndexJitGenerate.py +0 -0
  110. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndexJitHash.py +0 -0
  111. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableIndexJitLoc.py +0 -0
  112. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableProtocol.py +0 -0
  113. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TableRead.py +0 -0
  114. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TimeSeriesDisk.py +0 -0
  115. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/TimeseriesContainer.py +0 -0
  116. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Users.py +0 -0
  117. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Utils.py +0 -0
  118. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/ADDING_A_PAGE.md +0 -0
  119. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/__init__.py +0 -0
  120. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/activity_logger.py +0 -0
  121. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/app.py +0 -0
  122. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/dashAgGridComponentFunctions.js +0 -0
  123. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/dashAgGridFunctions.js +0 -0
  124. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/google_logo.png +0 -0
  125. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/kafka_keys.js +0 -0
  126. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/logs.css +0 -0
  127. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/sched.css +0 -0
  128. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/assets/styles.css +0 -0
  129. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/auth.py +0 -0
  130. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/backends.py +0 -0
  131. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/config.py +0 -0
  132. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/containers.py +0 -0
  133. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/identity.py +0 -0
  134. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/kafka_view.py +0 -0
  135. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/mongo_view.py +0 -0
  136. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/proc.py +0 -0
  137. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/redis_view.py +0 -0
  138. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/route_health.py +0 -0
  139. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/route_registry.py +0 -0
  140. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/sched.py +0 -0
  141. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/schedule_editor.py +0 -0
  142. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/server.py +0 -0
  143. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/services.py +0 -0
  144. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/shell.py +0 -0
  145. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/standalone.py +0 -0
  146. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/account.js +0 -0
  147. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/custom.css +0 -0
  148. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/favicon.ico +0 -0
  149. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/favicon.png +0 -0
  150. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/logo.png +0 -0
  151. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/logo_96x96.png +0 -0
  152. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/shareddata-mark.svg +0 -0
  153. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/shell.css +0 -0
  154. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/shell.js +0 -0
  155. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/standalone.css +0 -0
  156. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/static/standalone.js +0 -0
  157. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/suggest.py +0 -0
  158. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/account.html +0 -0
  159. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/base.html +0 -0
  160. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/claim.html +0 -0
  161. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/claim_done.html +0 -0
  162. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/dash.html +0 -0
  163. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/index.html +0 -0
  164. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/login.html +0 -0
  165. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/login_2fa.html +0 -0
  166. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/partials/navbar.html +0 -0
  167. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/partials/scripts.html +0 -0
  168. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/recovery_codes.html +0 -0
  169. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/shell.html +0 -0
  170. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/templates/standalone.html +0 -0
  171. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/twofactor.py +0 -0
  172. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/utils.py +0 -0
  173. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/Web/workerpool.py +0 -0
  174. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/__init__.py +0 -0
  175. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/build_info.py +0 -0
  176. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/fleet.py +0 -0
  177. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/preflight.py +0 -0
  178. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/private_files.py +0 -0
  179. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/release_backup.py +0 -0
  180. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/server.py +0 -0
  181. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/services.py +0 -0
  182. {shareddata-6.85.2 → shareddata-6.85.4}/src/SharedData/sharedmutexwin.pyd +0 -0
  183. {shareddata-6.85.2 → shareddata-6.85.4}/src/shareddata.egg-info/SOURCES.txt +0 -0
  184. {shareddata-6.85.2 → shareddata-6.85.4}/src/shareddata.egg-info/dependency_links.txt +0 -0
  185. {shareddata-6.85.2 → shareddata-6.85.4}/src/shareddata.egg-info/entry_points.txt +0 -0
  186. {shareddata-6.85.2 → shareddata-6.85.4}/src/shareddata.egg-info/requires.txt +0 -0
  187. {shareddata-6.85.2 → shareddata-6.85.4}/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.2
3
+ Version: 6.85.4
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.2',
69
+ version='6.85.4',
70
70
  description='Memory Mapped / Shared Memory Database with S3 repository',
71
71
  author='shareddata',
72
72
  author_email='admin@shareddata.net',
@@ -865,20 +865,26 @@ class Table:
865
865
  raise RewriteError('automatic disk header migration requires staged publication')
866
866
 
867
867
  ############### DOWNLOAD ###############
868
- def _remote_is_newer(self):
869
- """True when the remote head or tail is newer than this table's (what S3Download would
870
- fetch). Nothing newer, no remote copy, or S3 switched off: False, and the local image stays."""
868
+ def _remote_change(self):
869
+ """What a reload would have to fetch: 'head' when the remote head is newer than this table's
870
+ (the whole image changed), 'tail' when only the remote tail is (the usual case: the rows of
871
+ the current year), None when nothing is newer, there is no remote copy, or S3 is off."""
871
872
  from SharedData.services import s3_enabled
872
873
  if not s3_enabled() or self.hdr is None:
873
- return False
874
+ return None
874
875
  head = S3GetMtime(str(self.headpath) + '.gzip', self.database_folder)
875
876
  if head is not None and head > float(self.hdr['mtimehead']):
876
- return True
877
+ return 'head'
877
878
  if int(self.hdr['hastail']) == 1:
878
879
  tail = S3GetMtime(str(self.tailpath) + '.gzip', self.database_folder)
879
880
  if tail is not None and tail > float(self.hdr['mtimetail']):
880
- return True
881
- return False
881
+ return 'tail'
882
+ return None
883
+
884
+ def _download_remote_tail(self):
885
+ """[io, local mtime, remote mtime] of the remote tail; io is None unless it is newer than this table's."""
886
+ return S3Download(str(self.tailpath) + '.gzip', local_mtime=float(self.hdr['mtimetail']),
887
+ database_folder=self.database_folder)
882
888
 
883
889
  @structural
884
890
  def download(self):
@@ -1223,6 +1229,17 @@ class Table:
1223
1229
  io_obj.close()
1224
1230
  self.copy_file()
1225
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
+
1226
1243
  def read_tailheader(self, tail_io):
1227
1244
  """
1228
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,26 +304,44 @@ 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.
267
310
 
268
311
  Nothing newer remotely (or no remote copy, or S3 off) leaves the local table as it is, so
269
- rows not uploaded yet are never discarded. Otherwise the whole remote image is built beside
270
- data.bin under a stage name and installed by one os.replace, inside the caller's STRUCT
271
- section: a failure before the replace leaves the old image untouched, and the epoch
272
- published on leaving the section makes other processes reopen the new file. The old index
273
- files describe the old rows, so they are removed and rebuilt.
312
+ rows not uploaded yet are never discarded. When only the remote tail is newer (the usual
313
+ case: the head holds the years before this one and the tail this year's rows) only the tail
314
+ is fetched and applied, see _reload_tail. When the remote head is newer, the whole remote
315
+ image is fetched, see _reload_image.
274
316
 
275
317
  Returns:
276
318
  The records of the reloaded table.
277
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
+ """
278
337
  import stat
279
338
  import uuid
280
339
  from SharedData.TableRewrite import fsync_directory, read_layout, require_rewrite_support, sync_file
281
- if not self._remote_is_newer():
340
+ change = self._remote_change()
341
+ if change is None:
282
342
  return self.records
343
+ if change == 'tail' and not self.is_schemaless:
344
+ return self._reload_tail()
283
345
  require_rewrite_support()
284
346
  final = self.filepath
285
347
  old = os.stat(final)
@@ -334,6 +396,147 @@ class TableDisk(Table):
334
396
  raise Exception(errmsg)
335
397
  return self.records
336
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
+
431
+ def _reload_tail(self):
432
+ """
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).
435
+
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).
447
+ """
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
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
468
+ tail_gz.seek(0) # S3Download leaves the buffer at its end
469
+ tail_io = gzip.GzipFile(fileobj=tail_gz, mode='rb')
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
529
+ except BaseException:
530
+ if stage is not None:
531
+ try:
532
+ stage.unlink()
533
+ except FileNotFoundError:
534
+ pass
535
+ raise
536
+ finally:
537
+ if acquired:
538
+ self.release()
539
+
337
540
  ############### DELETE ###############
338
541
  def delete_local(self):
339
542
  """
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shareddata
3
- Version: 6.85.2
3
+ Version: 6.85.4
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