shareddata 6.85.18__tar.gz → 6.86.1__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.
- shareddata-6.86.1/PKG-INFO +164 -0
- shareddata-6.86.1/README.md +89 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/setup.py +2 -2
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/apidocs.yml +141 -12
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/collections.py +111 -26
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/tables.py +30 -17
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/timeseries.py +26 -18
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Auth/workers.py +1 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/CacheRedis.py +4 -2
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/ClientAPI.py +38 -15
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/LogHandlerAPI.py +14 -14
- shareddata-6.86.1/src/SharedData/IO/SaveTables.py +326 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/ServerSocket.py +2 -2
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/ServerWebSocket.py +2 -2
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/StreamsCache.py +4 -4
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/StreamsPersist.py +2 -2
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/SyncTable.py +1 -1
- shareddata-6.86.1/src/SharedData/Routines/RoutineCommand.py +90 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/Worker.py +1 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/WorkerLib.py +318 -62
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/SharedData.py +126 -38
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/SharedNumpy.py +75 -15
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Table.py +44 -13
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableDisk.py +28 -45
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndex.py +6 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TimeSeriesDisk.py +30 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/auth.py +20 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/route_health.py +1 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/standalone.py +1 -1
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/build_info.py +3 -1
- shareddata-6.86.1/src/shareddata.egg-info/PKG-INFO +164 -0
- shareddata-6.85.18/PKG-INFO +0 -243
- shareddata-6.85.18/README.md +0 -168
- shareddata-6.85.18/src/SharedData/IO/SaveTables.py +0 -144
- shareddata-6.85.18/src/SharedData/Routines/RoutineCommand.py +0 -36
- shareddata-6.85.18/src/shareddata.egg-info/PKG-INFO +0 -243
- {shareddata-6.85.18 → shareddata-6.86.1}/LICENSE +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/MANIFEST.in +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/pyproject.toml +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/setup.cfg +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/ServerGunicorn.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/ServerWaitress.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/apidocs-dark.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/auth.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/constants.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/download_paths.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/download_slots.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/install/worker.ps1 +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/install/worker.sh +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/listcache.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/cache.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/data.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/downloads.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/metadata.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/operations.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/schedules.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/sqlite.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/system.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/web_routes.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/routes/workers.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/API/utils.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Auth/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Auth/bootstrap.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Auth/migrate.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Auth/store.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/CollectionMongoDB.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/catalog.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/config.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/errors.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/indexes.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/readers.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/resolve.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/DataAPI/service.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Database.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Defaults.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/AWSEC2.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/AWSS3.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/AutoDocstrings.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/ClientSocket.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/ClientWebSocket.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/LogReader.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/MongoDBClient.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/TunnelWebSocket.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/IO/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Listing.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/LockWaiters.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Logger.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/MCP/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/MCP/__main__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/MCP/client.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/MCP/server.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Metadata.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/MultiProc.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/OpenFIGI.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Redaction.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/ArchiveOldCollections.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/BatchJob.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/ContainerHealth.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/JobStore.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/RoutineOrigin.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/Schedule.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/ScheduleMirror.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/ScheduleMonitor.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/ScheduleStore.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/Scheduler.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/UpdateActiveJobs.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/WorkerBootstrap.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/WorkerPool.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Routines/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/SQLite.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/StreamKafka.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Symbol.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndexJitFunctions.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndexJitFunctionsManual.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndexJitGenerate.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndexJitHash.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableIndexJitLoc.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableProtocol.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableRead.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TableRewrite.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/TimeseriesContainer.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Users.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Utils.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/ADDING_A_PAGE.md +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/activity_logger.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/app.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/dashAgGridComponentFunctions.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/dashAgGridFunctions.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/google_logo.png +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/kafka_keys.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/logs.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/sched.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/assets/styles.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/backends.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/config.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/containers.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/identity.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/kafka_view.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/mongo_view.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/proc.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/redis_view.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/route_registry.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/sched.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/schedule_editor.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/server.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/services.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/shell.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/account.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/custom.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/favicon.ico +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/favicon.png +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/logo.png +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/logo_96x96.png +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/shareddata-mark.svg +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/shell.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/shell.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/standalone.css +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/static/standalone.js +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/suggest.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/account.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/base.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/claim.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/claim_done.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/dash.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/index.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/login.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/login_2fa.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/partials/navbar.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/partials/scripts.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/recovery_codes.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/shell.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/templates/standalone.html +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/twofactor.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/utils.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/Web/workerpool.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/__init__.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/fleet.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/preflight.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/private_files.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/release_backup.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/server.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/services.py +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/SharedData/sharedmutexwin.pyd +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/shareddata.egg-info/SOURCES.txt +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/shareddata.egg-info/dependency_links.txt +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/shareddata.egg-info/entry_points.txt +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/shareddata.egg-info/requires.txt +0 -0
- {shareddata-6.85.18 → shareddata-6.86.1}/src/shareddata.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shareddata
|
|
3
|
+
Version: 6.86.1
|
|
4
|
+
Summary: Memory-mapped record and time-series store with an HTTP API and a distributed scheduler
|
|
5
|
+
Home-page: https://shareddata.net
|
|
6
|
+
Author: shareddata
|
|
7
|
+
Author-email: admin@shareddata.net
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://shareddata.net
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.11, <3.13
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: ipykernel<8,>=7.4
|
|
19
|
+
Requires-Dist: python-json-logger<5,>=4.2
|
|
20
|
+
Requires-Dist: python-dotenv<2,>=1.2.4
|
|
21
|
+
Requires-Dist: pandas<3,>=2.2
|
|
22
|
+
Requires-Dist: scipy<1.18,>=1.14
|
|
23
|
+
Requires-Dist: numpy<1.27,>=1.26.4
|
|
24
|
+
Requires-Dist: numba==0.59.1
|
|
25
|
+
Requires-Dist: XlsxWriter<4,>=3.2.9
|
|
26
|
+
Requires-Dist: openpyxl<4,>=3.1.5
|
|
27
|
+
Requires-Dist: tqdm<5,>=4.70.1
|
|
28
|
+
Requires-Dist: cffi<3,>=2.1
|
|
29
|
+
Requires-Dist: tzlocal<6,>=5.4.4
|
|
30
|
+
Requires-Dist: websockets<18,>=17.1
|
|
31
|
+
Requires-Dist: lz4<5,>=4.4.5
|
|
32
|
+
Requires-Dist: flask<4,>=3.1.3
|
|
33
|
+
Requires-Dist: waitress<4,>=3.0.2
|
|
34
|
+
Requires-Dist: requests<3,>=2.34.2
|
|
35
|
+
Requires-Dist: flasgger==0.9.7.1
|
|
36
|
+
Requires-Dist: pymongo<5,>=4.18.2
|
|
37
|
+
Requires-Dist: setuptools>=84
|
|
38
|
+
Requires-Dist: filelock<5,>=4.0.9
|
|
39
|
+
Requires-Dist: gunicorn<27,>=26.2
|
|
40
|
+
Requires-Dist: aiohttp<4,>=3.14.3
|
|
41
|
+
Requires-Dist: boto3<2,>=1.43
|
|
42
|
+
Requires-Dist: confluent-kafka<3,>=2.15.1
|
|
43
|
+
Requires-Dist: aiokafka<1,>=0.14
|
|
44
|
+
Requires-Dist: redis<9,>=8.1
|
|
45
|
+
Requires-Dist: Authlib<2,>=1.8
|
|
46
|
+
Requires-Dist: PyJWT<3,>=2.8
|
|
47
|
+
Requires-Dist: argon2-cffi<26,>=25.1
|
|
48
|
+
Requires-Dist: flask-jwt-extended<5,>=4.6
|
|
49
|
+
Requires-Dist: cryptography<51,>=50.0.2
|
|
50
|
+
Requires-Dist: segno<2,>=1.6
|
|
51
|
+
Requires-Dist: psutil<8,>=5.9
|
|
52
|
+
Requires-Dist: mcp>=1.27
|
|
53
|
+
Requires-Dist: httpx>=0.27
|
|
54
|
+
Provides-Extra: test
|
|
55
|
+
Requires-Dist: pytest<10,>=8.3.4; extra == "test"
|
|
56
|
+
Requires-Dist: pytest-asyncio<2,>=0.24; extra == "test"
|
|
57
|
+
Requires-Dist: playwright<2,>=1.63; extra == "test"
|
|
58
|
+
Provides-Extra: s3
|
|
59
|
+
Provides-Extra: kafka
|
|
60
|
+
Provides-Extra: redis
|
|
61
|
+
Provides-Extra: all
|
|
62
|
+
Dynamic: author
|
|
63
|
+
Dynamic: author-email
|
|
64
|
+
Dynamic: classifier
|
|
65
|
+
Dynamic: description
|
|
66
|
+
Dynamic: description-content-type
|
|
67
|
+
Dynamic: home-page
|
|
68
|
+
Dynamic: license
|
|
69
|
+
Dynamic: license-file
|
|
70
|
+
Dynamic: project-url
|
|
71
|
+
Dynamic: provides-extra
|
|
72
|
+
Dynamic: requires-dist
|
|
73
|
+
Dynamic: requires-python
|
|
74
|
+
Dynamic: summary
|
|
75
|
+
|
|
76
|
+
# SharedData
|
|
77
|
+
|
|
78
|
+
SharedData is an open-source memory-mapped store for records and time series —
|
|
79
|
+
in the workload category of tick stores such as kdb+ — plus the scheduler and
|
|
80
|
+
worker fleet that produce the data. Local processes map the same files through
|
|
81
|
+
the operating system's page cache; indexed writes coordinate through a
|
|
82
|
+
per-table process lock. Remote clients use an authenticated HTTP API; shared
|
|
83
|
+
memory does not span hosts.
|
|
84
|
+
|
|
85
|
+
Website and manual: <https://shareddata.net>
|
|
86
|
+
|
|
87
|
+
## Install
|
|
88
|
+
|
|
89
|
+
Python 3.11 or 3.12:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
python -m pip install shareddata
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The [quick start](https://shareddata.net/docs/QUICK_START/) uses illustrative
|
|
96
|
+
records in isolated storage. See [configuration](https://shareddata.net/docs/CONFIGURATION/)
|
|
97
|
+
for public environment names and [Windows limitations](https://shareddata.net/docs/WINDOWS/)
|
|
98
|
+
for native requirements and rewrite behavior.
|
|
99
|
+
|
|
100
|
+
## A record in one minute
|
|
101
|
+
|
|
102
|
+
Save as `example.py` after setting up isolated storage in the
|
|
103
|
+
[quick start](https://shareddata.net/docs/QUICK_START/), then run `python example.py`:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from SharedData.SharedData import SharedData
|
|
107
|
+
import pandas as pd
|
|
108
|
+
|
|
109
|
+
store = SharedData(__file__, user="example")
|
|
110
|
+
index = pd.MultiIndex.from_tuples(
|
|
111
|
+
[(pd.Timestamp("2026-01-02"), "EXAMPLE")],
|
|
112
|
+
names=["date", "symbol"],
|
|
113
|
+
)
|
|
114
|
+
rows = pd.DataFrame({"close": [100.0]}, index=index)
|
|
115
|
+
records = store.table(
|
|
116
|
+
"MarketData", "D1", "DEMO", "PRICES",
|
|
117
|
+
value=rows,
|
|
118
|
+
)
|
|
119
|
+
print(records.loc["2026-01-02", "EXAMPLE"])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Containers and execution
|
|
123
|
+
|
|
124
|
+
- [Tables](https://shareddata.net/docs/core/table/): structured NumPy records and primary-key lookup.
|
|
125
|
+
- [Time series](https://shareddata.net/docs/core/timeseries/): timestamped float matrices.
|
|
126
|
+
- Collections, Redis caches, Kafka streams and optional S3 integration.
|
|
127
|
+
- [HTTP API](https://shareddata.net/docs/guide/swagger/): authenticated remote access.
|
|
128
|
+
- [Measured latency](https://shareddata.net/docs/performance/): what the containers cost in practice.
|
|
129
|
+
|
|
130
|
+
## A store and the fleet that fills it
|
|
131
|
+
|
|
132
|
+
A routine is any program the Scheduler owns — a feed collector, a parser, a
|
|
133
|
+
consolidation job, an API server. A schedule lists the routines that should
|
|
134
|
+
stay alive or run at their times. Batch routines wait for their batch dependencies
|
|
135
|
+
to complete; realtime routines wait for their realtime dependencies to be running.
|
|
136
|
+
Dependencies between the two kinds are not enforced at runtime. Commands travel
|
|
137
|
+
through a per-host worker pool that validates the requested source revision
|
|
138
|
+
before every launch; routines report heartbeats and lifecycle messages through
|
|
139
|
+
the logging plane, and the Scheduler writes queryable state into the store.
|
|
140
|
+
|
|
141
|
+
- [Orchestration](https://shareddata.net/docs/orchestration/): the routine model, the Scheduler's tick and the worker pool.
|
|
142
|
+
- [Use cases](https://shareddata.net/docs/use-cases/): the workloads a production deployment runs, generalized.
|
|
143
|
+
- [Schedules](https://shareddata.net/docs/scheduler-store/) and [batch jobs](https://shareddata.net/docs/job-store/): where the state lives.
|
|
144
|
+
|
|
145
|
+
## Data by symbol
|
|
146
|
+
|
|
147
|
+
The [semantic data API](https://shareddata.net/docs/data-api/) resolves a
|
|
148
|
+
symbol or entity to a dataset and includes provenance. It is a convenience,
|
|
149
|
+
not the fast path: producers keep using direct container interfaces for
|
|
150
|
+
performance-critical work.
|
|
151
|
+
|
|
152
|
+
## Client integration
|
|
153
|
+
|
|
154
|
+
[Client integration](https://shareddata.net/docs/ecosystem/) covers
|
|
155
|
+
authenticated API access, the MCP server for compatible agent clients, and
|
|
156
|
+
worker access.
|
|
157
|
+
|
|
158
|
+
## Development and license
|
|
159
|
+
|
|
160
|
+
Submit changes through the repository's review process. Read the
|
|
161
|
+
[development guide](https://shareddata.net/docs/DEVELOPMENT/) and run the
|
|
162
|
+
checks relevant to your change; [building and publishing](https://shareddata.net/docs/guide/building/)
|
|
163
|
+
explains how the public site is produced and audited. SharedData is released
|
|
164
|
+
under the MIT license; see `LICENSE` in the source distribution.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# SharedData
|
|
2
|
+
|
|
3
|
+
SharedData is an open-source memory-mapped store for records and time series —
|
|
4
|
+
in the workload category of tick stores such as kdb+ — plus the scheduler and
|
|
5
|
+
worker fleet that produce the data. Local processes map the same files through
|
|
6
|
+
the operating system's page cache; indexed writes coordinate through a
|
|
7
|
+
per-table process lock. Remote clients use an authenticated HTTP API; shared
|
|
8
|
+
memory does not span hosts.
|
|
9
|
+
|
|
10
|
+
Website and manual: <https://shareddata.net>
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Python 3.11 or 3.12:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
python -m pip install shareddata
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The [quick start](https://shareddata.net/docs/QUICK_START/) uses illustrative
|
|
21
|
+
records in isolated storage. See [configuration](https://shareddata.net/docs/CONFIGURATION/)
|
|
22
|
+
for public environment names and [Windows limitations](https://shareddata.net/docs/WINDOWS/)
|
|
23
|
+
for native requirements and rewrite behavior.
|
|
24
|
+
|
|
25
|
+
## A record in one minute
|
|
26
|
+
|
|
27
|
+
Save as `example.py` after setting up isolated storage in the
|
|
28
|
+
[quick start](https://shareddata.net/docs/QUICK_START/), then run `python example.py`:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from SharedData.SharedData import SharedData
|
|
32
|
+
import pandas as pd
|
|
33
|
+
|
|
34
|
+
store = SharedData(__file__, user="example")
|
|
35
|
+
index = pd.MultiIndex.from_tuples(
|
|
36
|
+
[(pd.Timestamp("2026-01-02"), "EXAMPLE")],
|
|
37
|
+
names=["date", "symbol"],
|
|
38
|
+
)
|
|
39
|
+
rows = pd.DataFrame({"close": [100.0]}, index=index)
|
|
40
|
+
records = store.table(
|
|
41
|
+
"MarketData", "D1", "DEMO", "PRICES",
|
|
42
|
+
value=rows,
|
|
43
|
+
)
|
|
44
|
+
print(records.loc["2026-01-02", "EXAMPLE"])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Containers and execution
|
|
48
|
+
|
|
49
|
+
- [Tables](https://shareddata.net/docs/core/table/): structured NumPy records and primary-key lookup.
|
|
50
|
+
- [Time series](https://shareddata.net/docs/core/timeseries/): timestamped float matrices.
|
|
51
|
+
- Collections, Redis caches, Kafka streams and optional S3 integration.
|
|
52
|
+
- [HTTP API](https://shareddata.net/docs/guide/swagger/): authenticated remote access.
|
|
53
|
+
- [Measured latency](https://shareddata.net/docs/performance/): what the containers cost in practice.
|
|
54
|
+
|
|
55
|
+
## A store and the fleet that fills it
|
|
56
|
+
|
|
57
|
+
A routine is any program the Scheduler owns — a feed collector, a parser, a
|
|
58
|
+
consolidation job, an API server. A schedule lists the routines that should
|
|
59
|
+
stay alive or run at their times. Batch routines wait for their batch dependencies
|
|
60
|
+
to complete; realtime routines wait for their realtime dependencies to be running.
|
|
61
|
+
Dependencies between the two kinds are not enforced at runtime. Commands travel
|
|
62
|
+
through a per-host worker pool that validates the requested source revision
|
|
63
|
+
before every launch; routines report heartbeats and lifecycle messages through
|
|
64
|
+
the logging plane, and the Scheduler writes queryable state into the store.
|
|
65
|
+
|
|
66
|
+
- [Orchestration](https://shareddata.net/docs/orchestration/): the routine model, the Scheduler's tick and the worker pool.
|
|
67
|
+
- [Use cases](https://shareddata.net/docs/use-cases/): the workloads a production deployment runs, generalized.
|
|
68
|
+
- [Schedules](https://shareddata.net/docs/scheduler-store/) and [batch jobs](https://shareddata.net/docs/job-store/): where the state lives.
|
|
69
|
+
|
|
70
|
+
## Data by symbol
|
|
71
|
+
|
|
72
|
+
The [semantic data API](https://shareddata.net/docs/data-api/) resolves a
|
|
73
|
+
symbol or entity to a dataset and includes provenance. It is a convenience,
|
|
74
|
+
not the fast path: producers keep using direct container interfaces for
|
|
75
|
+
performance-critical work.
|
|
76
|
+
|
|
77
|
+
## Client integration
|
|
78
|
+
|
|
79
|
+
[Client integration](https://shareddata.net/docs/ecosystem/) covers
|
|
80
|
+
authenticated API access, the MCP server for compatible agent clients, and
|
|
81
|
+
worker access.
|
|
82
|
+
|
|
83
|
+
## Development and license
|
|
84
|
+
|
|
85
|
+
Submit changes through the repository's review process. Read the
|
|
86
|
+
[development guide](https://shareddata.net/docs/DEVELOPMENT/) and run the
|
|
87
|
+
checks relevant to your change; [building and publishing](https://shareddata.net/docs/guide/building/)
|
|
88
|
+
explains how the public site is produced and audited. SharedData is released
|
|
89
|
+
under the MIT license; see `LICENSE` in the source distribution.
|
|
@@ -66,8 +66,8 @@ test_requires = [
|
|
|
66
66
|
|
|
67
67
|
setup(
|
|
68
68
|
name='shareddata',
|
|
69
|
-
version='6.
|
|
70
|
-
description='Memory
|
|
69
|
+
version='6.86.1',
|
|
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',
|
|
73
73
|
url='https://shareddata.net',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
openapi: 3.0.3
|
|
3
3
|
info:
|
|
4
4
|
title: SharedData API
|
|
5
|
-
version: 6.
|
|
5
|
+
version: 6.86.1
|
|
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(...)`).
|
|
@@ -811,6 +811,8 @@ paths:
|
|
|
811
811
|
format: binary
|
|
812
812
|
'204':
|
|
813
813
|
description: No data found
|
|
814
|
+
'404':
|
|
815
|
+
description: JSON TagNotFound with type and message; the tag does not exist.
|
|
814
816
|
'401':
|
|
815
817
|
$ref: '#/components/responses/Unauthorized'
|
|
816
818
|
'500':
|
|
@@ -854,7 +856,8 @@ paths:
|
|
|
854
856
|
format: binary
|
|
855
857
|
responses:
|
|
856
858
|
'201':
|
|
857
|
-
description: Data inserted
|
|
859
|
+
description: 'Data inserted. JSON with status, tag, message and `appended_columns`: the columns added
|
|
860
|
+
to an existing tag (empty for a new tag).'
|
|
858
861
|
'401':
|
|
859
862
|
$ref: '#/components/responses/Unauthorized'
|
|
860
863
|
'500':
|
|
@@ -1111,7 +1114,8 @@ paths:
|
|
|
1111
1114
|
'500':
|
|
1112
1115
|
$ref: '#/components/responses/ServerError'
|
|
1113
1116
|
description: 'Upserts rows sent as BSON+LZ4 by primary key; creates the table on first write when `names`,
|
|
1114
|
-
`formats` and `size` are given.
|
|
1117
|
+
`formats` and `size` are given. A `size` larger than the capacity of an existing table grows it. Python:
|
|
1118
|
+
`ClientAPI.post_table(...)`.'
|
|
1115
1119
|
delete:
|
|
1116
1120
|
tags:
|
|
1117
1121
|
- Tables
|
|
@@ -1290,7 +1294,7 @@ paths:
|
|
|
1290
1294
|
post:
|
|
1291
1295
|
tags:
|
|
1292
1296
|
- Collections
|
|
1293
|
-
summary: Insert documents (lz4+BSON or JSON)
|
|
1297
|
+
summary: Insert or upsert documents (lz4+BSON or JSON)
|
|
1294
1298
|
parameters:
|
|
1295
1299
|
- in: path
|
|
1296
1300
|
name: database
|
|
@@ -1327,20 +1331,66 @@ paths:
|
|
|
1327
1331
|
format: binary
|
|
1328
1332
|
application/json:
|
|
1329
1333
|
schema:
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1334
|
+
oneOf:
|
|
1335
|
+
- type: array
|
|
1336
|
+
nullable: true
|
|
1337
|
+
items:
|
|
1338
|
+
type: object
|
|
1339
|
+
- type: object
|
|
1333
1340
|
responses:
|
|
1334
1341
|
'201':
|
|
1335
|
-
description:
|
|
1342
|
+
description: Batch accepted. An empty batch succeeds with documents equal to zero.
|
|
1343
|
+
content:
|
|
1344
|
+
application/json:
|
|
1345
|
+
schema:
|
|
1346
|
+
type: object
|
|
1347
|
+
required: [status, documents]
|
|
1348
|
+
properties:
|
|
1349
|
+
status:
|
|
1350
|
+
type: string
|
|
1351
|
+
enum: [success]
|
|
1352
|
+
documents:
|
|
1353
|
+
type: integer
|
|
1354
|
+
minimum: 0
|
|
1355
|
+
description: Documents accepted in this request (not the number of newly inserted rows).
|
|
1356
|
+
example: {status: success, documents: 0}
|
|
1336
1357
|
'401':
|
|
1337
1358
|
$ref: '#/components/responses/Unauthorized'
|
|
1338
1359
|
'400':
|
|
1339
|
-
description:
|
|
1360
|
+
description: >-
|
|
1361
|
+
Empty or undecodable body, missing BSON data field, invalid document shape,
|
|
1362
|
+
missing primary-key fields, or an incompatible append/upsert mode.
|
|
1363
|
+
The entire request is rejected; none of its documents are written.
|
|
1364
|
+
headers:
|
|
1365
|
+
Error-Message:
|
|
1366
|
+
description: Reason the request was rejected, also returned in the JSON message.
|
|
1367
|
+
schema:
|
|
1368
|
+
type: string
|
|
1369
|
+
content:
|
|
1370
|
+
application/json:
|
|
1371
|
+
schema:
|
|
1372
|
+
type: object
|
|
1373
|
+
required: [type, message]
|
|
1374
|
+
properties:
|
|
1375
|
+
type:
|
|
1376
|
+
type: string
|
|
1377
|
+
enum: [BadRequest]
|
|
1378
|
+
message:
|
|
1379
|
+
type: string
|
|
1380
|
+
example: {type: BadRequest, message: "data must be a list of documents, not int"}
|
|
1340
1381
|
'500':
|
|
1341
1382
|
$ref: '#/components/responses/ServerError'
|
|
1342
|
-
description:
|
|
1343
|
-
|
|
1383
|
+
description: >-
|
|
1384
|
+
Accepts LZ4-compressed BSON {"data": [documents]} with Content-Encoding lz4,
|
|
1385
|
+
or a plain JSON list. A single document object is also accepted. BSON data
|
|
1386
|
+
equal to null or [] (and JSON null or []) is an empty batch: returns 201 with
|
|
1387
|
+
documents equal to zero without opening or changing the collection.
|
|
1388
|
+
With hasindex=true (default), every document must contain every field of
|
|
1389
|
+
the database's primary key and documents are upserted; with hasindex=false,
|
|
1390
|
+
they are appended and the collection must have no primary-key index.
|
|
1391
|
+
Missing keys reject the whole request, including otherwise valid documents.
|
|
1392
|
+
A real storage/server failure returns 500 and can leave partial writes;
|
|
1393
|
+
only validation errors guarantee that nothing was written.
|
|
1344
1394
|
patch:
|
|
1345
1395
|
tags:
|
|
1346
1396
|
- Collections
|
|
@@ -1835,7 +1885,9 @@ paths:
|
|
|
1835
1885
|
'403':
|
|
1836
1886
|
description: Master token required
|
|
1837
1887
|
description: 'Each route: `id`, `command` (its name on the index), `path`, `target`, `group`, `description`,
|
|
1838
|
-
`enabled`, `revision`.
|
|
1888
|
+
`enabled`, `revision`. The body also has `activation`: `applied_generation`, `desired_generation`,
|
|
1889
|
+
and `state` (`unpublished` before any activation status has been recorded; later states include
|
|
1890
|
+
`pending`, `applied`, `unconfirmed`, and `failed`). Master token.'
|
|
1839
1891
|
post:
|
|
1840
1892
|
tags:
|
|
1841
1893
|
- Routes
|
|
@@ -1880,6 +1932,21 @@ paths:
|
|
|
1880
1932
|
description: 'Adds a service behind nginx on this deploy''s port: requests to `path` are proxied to `target`
|
|
1881
1933
|
(a private or local http(s) URL), behind the SharedData sign-in. nginx is checked and reloaded before the
|
|
1882
1934
|
answer, so the route works when it returns; `reachable` says whether the target answered. Master token.'
|
|
1935
|
+
/api/routes/activation/verify:
|
|
1936
|
+
post:
|
|
1937
|
+
tags:
|
|
1938
|
+
- Routes
|
|
1939
|
+
summary: Check that every configured proxy is serving this generation (master token)
|
|
1940
|
+
responses:
|
|
1941
|
+
'200':
|
|
1942
|
+
description: 'activation: applied_generation, desired_generation, state, and the probe results'
|
|
1943
|
+
'400':
|
|
1944
|
+
description: SHAREDDATA_WEB_PROXY_PROBES is missing or a probe URL is not http(s) without credentials
|
|
1945
|
+
'403':
|
|
1946
|
+
description: Master token required
|
|
1947
|
+
description: 'Reads `SHAREDDATA_WEB_PROXY_PROBES` (a JSON object of proxy name to marker URL) and GETs each
|
|
1948
|
+
URL with a 3 second timeout and no redirects. A probe passes when the status is 200 and the stripped body equals
|
|
1949
|
+
`{generation}:{sha256 of the catalog}`. Master token.'
|
|
1883
1950
|
/api/routes/{route_id}:
|
|
1884
1951
|
parameters:
|
|
1885
1952
|
- name: route_id
|
|
@@ -2952,6 +3019,68 @@ paths:
|
|
|
2952
3019
|
description: Unknown command token
|
|
2953
3020
|
'403':
|
|
2954
3021
|
description: Not a master
|
|
3022
|
+
/api/workers/shared-tokens:
|
|
3023
|
+
get:
|
|
3024
|
+
tags:
|
|
3025
|
+
- Workers
|
|
3026
|
+
summary: List shared worker tokens (master)
|
|
3027
|
+
description: One token shared by a group of interchangeable workers. The list is the label, the name pattern,
|
|
3028
|
+
the creator and the last use. The secret is not included. Tokens that workers already presented are listed
|
|
3029
|
+
after the ones created here.
|
|
3030
|
+
responses:
|
|
3031
|
+
'200':
|
|
3032
|
+
description: Shared worker tokens without their secret
|
|
3033
|
+
'403':
|
|
3034
|
+
description: Not a master
|
|
3035
|
+
post:
|
|
3036
|
+
tags:
|
|
3037
|
+
- Workers
|
|
3038
|
+
summary: Create a shared worker token (master)
|
|
3039
|
+
description: Shown once. `names` is the worker-name pattern, with `*` for the part that changes, for example
|
|
3040
|
+
`*@SCALE-*`. At least three characters must be fixed. The label is letters, digits, dot, dash or underscore,
|
|
3041
|
+
up to 40 characters. Revoke the existing label before creating it again.
|
|
3042
|
+
requestBody:
|
|
3043
|
+
required: true
|
|
3044
|
+
content:
|
|
3045
|
+
application/json:
|
|
3046
|
+
schema:
|
|
3047
|
+
type: object
|
|
3048
|
+
required:
|
|
3049
|
+
- label
|
|
3050
|
+
- names
|
|
3051
|
+
properties:
|
|
3052
|
+
label:
|
|
3053
|
+
type: string
|
|
3054
|
+
example: scale
|
|
3055
|
+
names:
|
|
3056
|
+
type: string
|
|
3057
|
+
example: '*@SCALE-*'
|
|
3058
|
+
responses:
|
|
3059
|
+
'201':
|
|
3060
|
+
description: '`{token, label, names, enabled, created_at, created_by, last_seen_at}` — `token` only on this response'
|
|
3061
|
+
'400':
|
|
3062
|
+
description: Invalid label or names, or a token with that label already exists
|
|
3063
|
+
'403':
|
|
3064
|
+
description: Not a master
|
|
3065
|
+
/api/workers/shared-tokens/{label}:
|
|
3066
|
+
delete:
|
|
3067
|
+
tags:
|
|
3068
|
+
- Workers
|
|
3069
|
+
summary: Revoke a shared worker token (master)
|
|
3070
|
+
parameters:
|
|
3071
|
+
- name: label
|
|
3072
|
+
in: path
|
|
3073
|
+
required: true
|
|
3074
|
+
schema:
|
|
3075
|
+
type: string
|
|
3076
|
+
description: The token stops working (API processes cache tokens up to 5 minutes).
|
|
3077
|
+
responses:
|
|
3078
|
+
'200':
|
|
3079
|
+
description: Revoked
|
|
3080
|
+
'404':
|
|
3081
|
+
description: Unknown shared worker token
|
|
3082
|
+
'403':
|
|
3083
|
+
description: Not a master
|
|
2955
3084
|
/api/data/sources:
|
|
2956
3085
|
get:
|
|
2957
3086
|
tags:
|
|
@@ -6,6 +6,7 @@ import datetime
|
|
|
6
6
|
import gzip
|
|
7
7
|
import json
|
|
8
8
|
import time
|
|
9
|
+
import traceback
|
|
9
10
|
|
|
10
11
|
import bson
|
|
11
12
|
from bson import json_util
|
|
@@ -17,6 +18,7 @@ from flask import Blueprint, Response, jsonify, request
|
|
|
17
18
|
|
|
18
19
|
from SharedData.CollectionMongoDB import CollectionMongoDB
|
|
19
20
|
from SharedData.Database import DATABASE_PKEYS
|
|
21
|
+
from SharedData.Logger import Logger, redact_credentials
|
|
20
22
|
|
|
21
23
|
from SharedData.API.constants import (
|
|
22
24
|
DEFAULT_USER,
|
|
@@ -36,6 +38,73 @@ from SharedData.API.utils import (
|
|
|
36
38
|
# Blueprint definition
|
|
37
39
|
collections_bp = Blueprint('collections', __name__)
|
|
38
40
|
|
|
41
|
+
|
|
42
|
+
class InvalidPayload(ValueError):
|
|
43
|
+
"""The posted body is not something that can be written: answered 400 with this message."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def payload_error(error, status=400):
|
|
47
|
+
"""A 4xx whose reason is in the Error-Message header (what ClientAPI reports) and the JSON body."""
|
|
48
|
+
message = redact_credentials(error).replace('\r', ' ').replace('\n', ' ')
|
|
49
|
+
response = make_error_response(message, status=status, error_type='BadRequest')
|
|
50
|
+
response.headers['Error-Message'] = message
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def decode_posted_documents(request):
|
|
55
|
+
"""The documents of a collection POST, as a list of dicts.
|
|
56
|
+
|
|
57
|
+
None (what ClientAPI sent for an empty list before 6.86, issue #112) is an empty batch; a single
|
|
58
|
+
document is a batch of one. Anything else that is not a list of documents raises InvalidPayload."""
|
|
59
|
+
if not request.data:
|
|
60
|
+
raise InvalidPayload('empty request body: send {"data": [documents]} (lz4 BSON) or a JSON list')
|
|
61
|
+
lz4 = request.headers.get('Content-Encoding', '').lower() == 'lz4'
|
|
62
|
+
try:
|
|
63
|
+
if lz4:
|
|
64
|
+
payload = bson.decode(lz4f.decompress(request.data))
|
|
65
|
+
else:
|
|
66
|
+
payload = json.loads(request.data)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise InvalidPayload(f'request body could not be decoded as {"lz4 BSON" if lz4 else "JSON"}: '
|
|
69
|
+
f'{type(e).__name__}: {e}')
|
|
70
|
+
if lz4:
|
|
71
|
+
if 'data' not in payload:
|
|
72
|
+
raise InvalidPayload("lz4 BSON body has no 'data' field")
|
|
73
|
+
documents = payload['data']
|
|
74
|
+
else:
|
|
75
|
+
documents = payload
|
|
76
|
+
if documents is None:
|
|
77
|
+
return []
|
|
78
|
+
if isinstance(documents, dict):
|
|
79
|
+
return [documents]
|
|
80
|
+
if not isinstance(documents, list):
|
|
81
|
+
raise InvalidPayload(f'data must be a list of documents, not {type(documents).__name__}')
|
|
82
|
+
for i, document in enumerate(documents):
|
|
83
|
+
if not isinstance(document, dict):
|
|
84
|
+
raise InvalidPayload(f'document {i} is {type(document).__name__}, not an object '
|
|
85
|
+
f'(data must be a list of documents)')
|
|
86
|
+
return documents
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def missing_pkey_report(documents, pkey_columns):
|
|
90
|
+
"""Why these documents cannot be upserted, or '' when every one has every primary-key field."""
|
|
91
|
+
missing = {}
|
|
92
|
+
incomplete = 0
|
|
93
|
+
first = None
|
|
94
|
+
for i, document in enumerate(documents):
|
|
95
|
+
absent = [field for field in pkey_columns if field not in document]
|
|
96
|
+
if absent:
|
|
97
|
+
incomplete += 1
|
|
98
|
+
first = i if first is None else first
|
|
99
|
+
for field in absent:
|
|
100
|
+
missing[field] = missing.get(field, 0) + 1
|
|
101
|
+
if not incomplete:
|
|
102
|
+
return ''
|
|
103
|
+
fields = ', '.join(f'{field} ({count})' for field, count in missing.items())
|
|
104
|
+
return (f'{incomplete}/{len(documents)} documents miss primary-key fields: {fields}; '
|
|
105
|
+
f'the primary key is {", ".join(pkey_columns)} (first incomplete document: {first}); '
|
|
106
|
+
'nothing was written')
|
|
107
|
+
|
|
39
108
|
def parse_json_query(query: dict) -> dict:
|
|
40
109
|
"""
|
|
41
110
|
Recursively parses a MongoDB-like query dictionary, converting string representations
|
|
@@ -145,11 +214,15 @@ def init_collections_routes(shdata, authenticate_fn):
|
|
|
145
214
|
return jsonify({'error': 'method not allowed'}), 405
|
|
146
215
|
|
|
147
216
|
except Exception as e:
|
|
217
|
+
# A server-side failure: keep its stack in the server log, the client only gets the message.
|
|
218
|
+
Logger.log.error(redact_credentials(f'collection {request.method} {database}/{period}/{source}/{tablename}'
|
|
219
|
+
f'{"/" + request.args["tablesubfolder"] if request.args.get("tablesubfolder") else ""}: '
|
|
220
|
+
f'{type(e).__name__}: {e}\n{traceback.format_exc()}'))
|
|
148
221
|
time.sleep(ERROR_SLEEP_SECONDS)
|
|
149
|
-
response = Response(status=500)
|
|
150
|
-
response.headers['Error-Message'] =
|
|
222
|
+
response = Response(status=500)
|
|
223
|
+
response.headers['Error-Message'] = redact_credentials(e).replace('\r', ' ').replace('\n', ' ')
|
|
151
224
|
return response
|
|
152
|
-
|
|
225
|
+
|
|
153
226
|
def head_collection(database: str, period: str, source: str, tablename: str, request):
|
|
154
227
|
"""Handle HTTP HEAD requests by returning metadata headers for a specified MongoDB collection."""
|
|
155
228
|
user = request.args.get('user', DEFAULT_USER)
|
|
@@ -302,37 +375,49 @@ def init_collections_routes(shdata, authenticate_fn):
|
|
|
302
375
|
response.headers['Content-Length'] = len(response_json)
|
|
303
376
|
return with_cursor(response)
|
|
304
377
|
|
|
305
|
-
def post_collection(database, period, source, tablename, request):
|
|
306
|
-
"""Handle a POST request to insert or upsert a collection of documents.
|
|
378
|
+
def post_collection(database, period, source, tablename, request):
|
|
379
|
+
"""Handle a POST request to insert or upsert a collection of documents.
|
|
380
|
+
|
|
381
|
+
Body: lz4-compressed BSON {"data": [documents]} (what ClientAPI sends) or a plain JSON list.
|
|
382
|
+
A payload that is not a list of documents, or (upsert) documents missing a primary-key
|
|
383
|
+
field, is a 400 saying why and nothing is written. An empty batch is nothing to write."""
|
|
307
384
|
tablesubfolder = request.args.get('tablesubfolder')
|
|
308
385
|
user = request.args.get('user', DEFAULT_USER)
|
|
309
386
|
hasindex = parse_bool_param(request.args.get('hasindex'), True)
|
|
310
387
|
|
|
311
388
|
tablepath = apply_subfolder(tablename, tablesubfolder)
|
|
312
389
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
390
|
+
try:
|
|
391
|
+
documents = decode_posted_documents(request)
|
|
392
|
+
except InvalidPayload as e:
|
|
393
|
+
return payload_error(e)
|
|
394
|
+
|
|
395
|
+
if documents:
|
|
396
|
+
# Opening a collection can create it or upgrade its indexes. An empty upload
|
|
397
|
+
# must not do either (nor fail building a unique index on append-only data).
|
|
398
|
+
collection = shdata.collection(database, period, source, tablepath, user=user, hasindex=hasindex)
|
|
399
|
+
if hasindex:
|
|
400
|
+
if not collection.hasindex:
|
|
401
|
+
return payload_error(
|
|
402
|
+
f'{collection.relpath} has no unique index on its primary key '
|
|
403
|
+
f'({", ".join(collection.pkey_columns)}), so it cannot be upserted; '
|
|
404
|
+
'post with hasindex=False to append')
|
|
405
|
+
missing = missing_pkey_report(documents, collection.pkey_columns)
|
|
406
|
+
if missing:
|
|
407
|
+
return payload_error(missing)
|
|
408
|
+
collection.upsert(documents)
|
|
409
|
+
else:
|
|
410
|
+
if collection.hasindex:
|
|
411
|
+
return payload_error(
|
|
412
|
+
f'{collection.relpath} has a unique index on its primary key '
|
|
413
|
+
f'({", ".join(collection.pkey_columns)}), so documents cannot be appended; '
|
|
414
|
+
'post with hasindex=True to upsert')
|
|
415
|
+
collection.extend(documents)
|
|
416
|
+
|
|
417
|
+
response_data = json.dumps({'status': 'success', 'documents': len(documents)}).encode('utf-8')
|
|
333
418
|
response = Response(response_data, status=201, mimetype='application/json')
|
|
334
419
|
response.headers['Content-Length'] = str(len(response_data))
|
|
335
|
-
return response
|
|
420
|
+
return response
|
|
336
421
|
|
|
337
422
|
def patch_collection(database, period, source, tablename, request):
|
|
338
423
|
"""Update a single document in a specified collection within the database."""
|