wmglobalqueue 2.3.10__py3-none-any.whl
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.
Potentially problematic release.
This version of wmglobalqueue might be problematic. Click here for more details.
- Utils/CPMetrics.py +270 -0
- Utils/CertTools.py +62 -0
- Utils/EmailAlert.py +50 -0
- Utils/ExtendedUnitTestCase.py +62 -0
- Utils/FileTools.py +182 -0
- Utils/IteratorTools.py +80 -0
- Utils/MathUtils.py +31 -0
- Utils/MemoryCache.py +119 -0
- Utils/Patterns.py +24 -0
- Utils/Pipeline.py +137 -0
- Utils/PortForward.py +97 -0
- Utils/ProcessStats.py +103 -0
- Utils/PythonVersion.py +17 -0
- Utils/Signals.py +36 -0
- Utils/TemporaryEnvironment.py +27 -0
- Utils/Throttled.py +227 -0
- Utils/Timers.py +130 -0
- Utils/Timestamps.py +86 -0
- Utils/TokenManager.py +143 -0
- Utils/Tracing.py +60 -0
- Utils/TwPrint.py +98 -0
- Utils/Utilities.py +308 -0
- Utils/__init__.py +11 -0
- WMCore/ACDC/Collection.py +57 -0
- WMCore/ACDC/CollectionTypes.py +12 -0
- WMCore/ACDC/CouchCollection.py +67 -0
- WMCore/ACDC/CouchFileset.py +238 -0
- WMCore/ACDC/CouchService.py +73 -0
- WMCore/ACDC/DataCollectionService.py +485 -0
- WMCore/ACDC/Fileset.py +94 -0
- WMCore/ACDC/__init__.py +11 -0
- WMCore/Algorithms/Alarm.py +39 -0
- WMCore/Algorithms/MathAlgos.py +274 -0
- WMCore/Algorithms/MiscAlgos.py +67 -0
- WMCore/Algorithms/ParseXMLFile.py +115 -0
- WMCore/Algorithms/Permissions.py +27 -0
- WMCore/Algorithms/Singleton.py +58 -0
- WMCore/Algorithms/SubprocessAlgos.py +129 -0
- WMCore/Algorithms/__init__.py +7 -0
- WMCore/Cache/GenericDataCache.py +98 -0
- WMCore/Cache/WMConfigCache.py +572 -0
- WMCore/Cache/__init__.py +0 -0
- WMCore/Configuration.py +651 -0
- WMCore/DAOFactory.py +47 -0
- WMCore/DataStructs/File.py +177 -0
- WMCore/DataStructs/Fileset.py +140 -0
- WMCore/DataStructs/Job.py +182 -0
- WMCore/DataStructs/JobGroup.py +142 -0
- WMCore/DataStructs/JobPackage.py +49 -0
- WMCore/DataStructs/LumiList.py +734 -0
- WMCore/DataStructs/Mask.py +219 -0
- WMCore/DataStructs/MathStructs/ContinuousSummaryHistogram.py +197 -0
- WMCore/DataStructs/MathStructs/DiscreteSummaryHistogram.py +92 -0
- WMCore/DataStructs/MathStructs/SummaryHistogram.py +117 -0
- WMCore/DataStructs/MathStructs/__init__.py +0 -0
- WMCore/DataStructs/Pickleable.py +24 -0
- WMCore/DataStructs/Run.py +256 -0
- WMCore/DataStructs/Subscription.py +175 -0
- WMCore/DataStructs/WMObject.py +47 -0
- WMCore/DataStructs/WorkUnit.py +112 -0
- WMCore/DataStructs/Workflow.py +60 -0
- WMCore/DataStructs/__init__.py +8 -0
- WMCore/Database/CMSCouch.py +1349 -0
- WMCore/Database/ConfigDBMap.py +29 -0
- WMCore/Database/CouchUtils.py +118 -0
- WMCore/Database/DBCore.py +198 -0
- WMCore/Database/DBCreator.py +113 -0
- WMCore/Database/DBExceptionHandler.py +57 -0
- WMCore/Database/DBFactory.py +110 -0
- WMCore/Database/DBFormatter.py +177 -0
- WMCore/Database/Dialects.py +13 -0
- WMCore/Database/ExecuteDAO.py +327 -0
- WMCore/Database/MongoDB.py +241 -0
- WMCore/Database/MySQL/Destroy.py +42 -0
- WMCore/Database/MySQL/ListUserContent.py +20 -0
- WMCore/Database/MySQL/__init__.py +9 -0
- WMCore/Database/MySQLCore.py +132 -0
- WMCore/Database/Oracle/Destroy.py +56 -0
- WMCore/Database/Oracle/ListUserContent.py +19 -0
- WMCore/Database/Oracle/__init__.py +9 -0
- WMCore/Database/ResultSet.py +44 -0
- WMCore/Database/Transaction.py +91 -0
- WMCore/Database/__init__.py +9 -0
- WMCore/Database/ipy_profile_couch.py +438 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/CleanUpTask.py +29 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/HeartbeatMonitor.py +105 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/LocationUpdateTask.py +28 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/ReqMgrInteractionTask.py +35 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/__init__.py +0 -0
- WMCore/GlobalWorkQueue/__init__.py +0 -0
- WMCore/GroupUser/CouchObject.py +127 -0
- WMCore/GroupUser/Decorators.py +51 -0
- WMCore/GroupUser/Group.py +33 -0
- WMCore/GroupUser/Interface.py +73 -0
- WMCore/GroupUser/User.py +96 -0
- WMCore/GroupUser/__init__.py +11 -0
- WMCore/Lexicon.py +836 -0
- WMCore/REST/Auth.py +202 -0
- WMCore/REST/CherryPyPeriodicTask.py +166 -0
- WMCore/REST/Error.py +333 -0
- WMCore/REST/Format.py +642 -0
- WMCore/REST/HeartbeatMonitorBase.py +90 -0
- WMCore/REST/Main.py +623 -0
- WMCore/REST/Server.py +2435 -0
- WMCore/REST/Services.py +24 -0
- WMCore/REST/Test.py +120 -0
- WMCore/REST/Tools.py +38 -0
- WMCore/REST/Validation.py +250 -0
- WMCore/REST/__init__.py +1 -0
- WMCore/ReqMgr/DataStructs/RequestStatus.py +209 -0
- WMCore/ReqMgr/DataStructs/RequestType.py +13 -0
- WMCore/ReqMgr/DataStructs/__init__.py +0 -0
- WMCore/ReqMgr/__init__.py +1 -0
- WMCore/Services/AlertManager/AlertManagerAPI.py +111 -0
- WMCore/Services/AlertManager/__init__.py +0 -0
- WMCore/Services/CRIC/CRIC.py +238 -0
- WMCore/Services/CRIC/__init__.py +0 -0
- WMCore/Services/DBS/DBS3Reader.py +1044 -0
- WMCore/Services/DBS/DBSConcurrency.py +44 -0
- WMCore/Services/DBS/DBSErrors.py +113 -0
- WMCore/Services/DBS/DBSReader.py +23 -0
- WMCore/Services/DBS/DBSUtils.py +139 -0
- WMCore/Services/DBS/DBSWriterObjects.py +381 -0
- WMCore/Services/DBS/ProdException.py +133 -0
- WMCore/Services/DBS/__init__.py +8 -0
- WMCore/Services/FWJRDB/FWJRDBAPI.py +118 -0
- WMCore/Services/FWJRDB/__init__.py +0 -0
- WMCore/Services/HTTPS/HTTPSAuthHandler.py +66 -0
- WMCore/Services/HTTPS/__init__.py +0 -0
- WMCore/Services/LogDB/LogDB.py +201 -0
- WMCore/Services/LogDB/LogDBBackend.py +191 -0
- WMCore/Services/LogDB/LogDBExceptions.py +11 -0
- WMCore/Services/LogDB/LogDBReport.py +85 -0
- WMCore/Services/LogDB/__init__.py +0 -0
- WMCore/Services/MSPileup/__init__.py +0 -0
- WMCore/Services/MSUtils/MSUtils.py +54 -0
- WMCore/Services/MSUtils/__init__.py +0 -0
- WMCore/Services/McM/McM.py +173 -0
- WMCore/Services/McM/__init__.py +8 -0
- WMCore/Services/MonIT/Grafana.py +133 -0
- WMCore/Services/MonIT/__init__.py +0 -0
- WMCore/Services/PyCondor/PyCondorAPI.py +154 -0
- WMCore/Services/PyCondor/PyCondorUtils.py +105 -0
- WMCore/Services/PyCondor/__init__.py +0 -0
- WMCore/Services/ReqMgr/ReqMgr.py +261 -0
- WMCore/Services/ReqMgr/__init__.py +0 -0
- WMCore/Services/ReqMgrAux/ReqMgrAux.py +419 -0
- WMCore/Services/ReqMgrAux/__init__.py +0 -0
- WMCore/Services/RequestDB/RequestDBReader.py +267 -0
- WMCore/Services/RequestDB/RequestDBWriter.py +39 -0
- WMCore/Services/RequestDB/__init__.py +0 -0
- WMCore/Services/Requests.py +624 -0
- WMCore/Services/Rucio/Rucio.py +1287 -0
- WMCore/Services/Rucio/RucioUtils.py +74 -0
- WMCore/Services/Rucio/__init__.py +0 -0
- WMCore/Services/RucioConMon/RucioConMon.py +128 -0
- WMCore/Services/RucioConMon/__init__.py +0 -0
- WMCore/Services/Service.py +400 -0
- WMCore/Services/StompAMQ/__init__.py +0 -0
- WMCore/Services/TagCollector/TagCollector.py +155 -0
- WMCore/Services/TagCollector/XMLUtils.py +98 -0
- WMCore/Services/TagCollector/__init__.py +0 -0
- WMCore/Services/UUIDLib.py +13 -0
- WMCore/Services/UserFileCache/UserFileCache.py +160 -0
- WMCore/Services/UserFileCache/__init__.py +8 -0
- WMCore/Services/WMAgent/WMAgent.py +63 -0
- WMCore/Services/WMAgent/__init__.py +0 -0
- WMCore/Services/WMArchive/CMSSWMetrics.py +526 -0
- WMCore/Services/WMArchive/DataMap.py +463 -0
- WMCore/Services/WMArchive/WMArchive.py +33 -0
- WMCore/Services/WMArchive/__init__.py +0 -0
- WMCore/Services/WMBS/WMBS.py +97 -0
- WMCore/Services/WMBS/__init__.py +0 -0
- WMCore/Services/WMStats/DataStruct/RequestInfoCollection.py +300 -0
- WMCore/Services/WMStats/DataStruct/__init__.py +0 -0
- WMCore/Services/WMStats/WMStatsPycurl.py +145 -0
- WMCore/Services/WMStats/WMStatsReader.py +445 -0
- WMCore/Services/WMStats/WMStatsWriter.py +273 -0
- WMCore/Services/WMStats/__init__.py +0 -0
- WMCore/Services/WMStatsServer/WMStatsServer.py +134 -0
- WMCore/Services/WMStatsServer/__init__.py +0 -0
- WMCore/Services/WorkQueue/WorkQueue.py +492 -0
- WMCore/Services/WorkQueue/__init__.py +0 -0
- WMCore/Services/__init__.py +8 -0
- WMCore/Services/pycurl_manager.py +574 -0
- WMCore/WMBase.py +50 -0
- WMCore/WMConnectionBase.py +164 -0
- WMCore/WMException.py +183 -0
- WMCore/WMExceptions.py +269 -0
- WMCore/WMFactory.py +76 -0
- WMCore/WMInit.py +228 -0
- WMCore/WMLogging.py +108 -0
- WMCore/WMSpec/ConfigSectionTree.py +442 -0
- WMCore/WMSpec/Persistency.py +135 -0
- WMCore/WMSpec/Steps/BuildMaster.py +87 -0
- WMCore/WMSpec/Steps/BuildTools.py +201 -0
- WMCore/WMSpec/Steps/Builder.py +97 -0
- WMCore/WMSpec/Steps/Diagnostic.py +89 -0
- WMCore/WMSpec/Steps/Emulator.py +62 -0
- WMCore/WMSpec/Steps/ExecuteMaster.py +208 -0
- WMCore/WMSpec/Steps/Executor.py +210 -0
- WMCore/WMSpec/Steps/StepFactory.py +213 -0
- WMCore/WMSpec/Steps/TaskEmulator.py +75 -0
- WMCore/WMSpec/Steps/Template.py +204 -0
- WMCore/WMSpec/Steps/Templates/AlcaHarvest.py +76 -0
- WMCore/WMSpec/Steps/Templates/CMSSW.py +613 -0
- WMCore/WMSpec/Steps/Templates/DQMUpload.py +59 -0
- WMCore/WMSpec/Steps/Templates/DeleteFiles.py +70 -0
- WMCore/WMSpec/Steps/Templates/LogArchive.py +84 -0
- WMCore/WMSpec/Steps/Templates/LogCollect.py +105 -0
- WMCore/WMSpec/Steps/Templates/StageOut.py +105 -0
- WMCore/WMSpec/Steps/Templates/__init__.py +10 -0
- WMCore/WMSpec/Steps/WMExecutionFailure.py +21 -0
- WMCore/WMSpec/Steps/__init__.py +8 -0
- WMCore/WMSpec/Utilities.py +63 -0
- WMCore/WMSpec/WMSpecErrors.py +12 -0
- WMCore/WMSpec/WMStep.py +347 -0
- WMCore/WMSpec/WMTask.py +1980 -0
- WMCore/WMSpec/WMWorkload.py +2288 -0
- WMCore/WMSpec/WMWorkloadTools.py +370 -0
- WMCore/WMSpec/__init__.py +9 -0
- WMCore/WorkQueue/DataLocationMapper.py +273 -0
- WMCore/WorkQueue/DataStructs/ACDCBlock.py +47 -0
- WMCore/WorkQueue/DataStructs/Block.py +48 -0
- WMCore/WorkQueue/DataStructs/CouchWorkQueueElement.py +148 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElement.py +274 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElementResult.py +152 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElementsSummary.py +185 -0
- WMCore/WorkQueue/DataStructs/__init__.py +0 -0
- WMCore/WorkQueue/Policy/End/EndPolicyInterface.py +44 -0
- WMCore/WorkQueue/Policy/End/SingleShot.py +22 -0
- WMCore/WorkQueue/Policy/End/__init__.py +32 -0
- WMCore/WorkQueue/Policy/PolicyInterface.py +17 -0
- WMCore/WorkQueue/Policy/Start/Block.py +258 -0
- WMCore/WorkQueue/Policy/Start/Dataset.py +180 -0
- WMCore/WorkQueue/Policy/Start/MonteCarlo.py +131 -0
- WMCore/WorkQueue/Policy/Start/ResubmitBlock.py +171 -0
- WMCore/WorkQueue/Policy/Start/StartPolicyInterface.py +316 -0
- WMCore/WorkQueue/Policy/Start/__init__.py +34 -0
- WMCore/WorkQueue/Policy/__init__.py +57 -0
- WMCore/WorkQueue/WMBSHelper.py +772 -0
- WMCore/WorkQueue/WorkQueue.py +1237 -0
- WMCore/WorkQueue/WorkQueueBackend.py +750 -0
- WMCore/WorkQueue/WorkQueueBase.py +39 -0
- WMCore/WorkQueue/WorkQueueExceptions.py +44 -0
- WMCore/WorkQueue/WorkQueueReqMgrInterface.py +278 -0
- WMCore/WorkQueue/WorkQueueUtils.py +130 -0
- WMCore/WorkQueue/__init__.py +13 -0
- WMCore/Wrappers/JsonWrapper/JSONThunker.py +342 -0
- WMCore/Wrappers/JsonWrapper/__init__.py +7 -0
- WMCore/Wrappers/__init__.py +6 -0
- WMCore/__init__.py +10 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-dist-patch +15 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-dist-unpatch +8 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-httpd +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/.couchapprc +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/README.md +40 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/index.html +264 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/ElementInfoByWorkflow.js +96 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/StuckElementInfo.js +57 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/WorkloadInfoTable.js +80 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/dataTable.js +70 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/namespace.js +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/style/main.css +75 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/couchapp.json +4 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/childQueueFilter.js +13 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/filterDeletedDocs.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/queueFilter.js +11 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/language +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/mustache.js +333 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/validate.js +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/workqueue_utils.js +61 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/elementsDetail.js +28 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/filter.js +86 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/stuckElements.js +38 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workRestrictions.js +153 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workflowSummary.js +28 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/rewrites.json +73 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/redirect.js +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/status.js +40 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/ElementSummaryByWorkflow.html +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/StuckElementSummary.html +26 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/TaskStatus.html +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/WorkflowSummary.html +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/workqueue-common-lib.html +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib-remote.html +16 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib.html +18 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/updates/in-place.js +50 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/validate_doc_update.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.couch.app.js +235 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.pathbinder.js +173 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/map.js +11 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/availableByPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/conflicts/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elements/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParent/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParentData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByPileupData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByStatus/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsBySubscription/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/reduce.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsDetailByWorkflowAndStatus/map.js +26 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/map.js +10 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/openRequests/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/recent-items/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/specsByWorkflow/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/stuckElements/map.js +38 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/map.js +12 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/reduce.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/map.js +9 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/reduce.js +10 -0
- wmglobalqueue-2.3.10.dist-info/LICENSE +202 -0
- wmglobalqueue-2.3.10.dist-info/METADATA +24 -0
- wmglobalqueue-2.3.10.dist-info/NOTICE +16 -0
- wmglobalqueue-2.3.10.dist-info/RECORD +345 -0
- wmglobalqueue-2.3.10.dist-info/WHEEL +5 -0
- wmglobalqueue-2.3.10.dist-info/top_level.txt +2 -0
WMCore/REST/Format.py
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import gzip
|
|
4
|
+
from builtins import str, bytes, object
|
|
5
|
+
|
|
6
|
+
from Utils.PythonVersion import PY3
|
|
7
|
+
from Utils.Utilities import encodeUnicodeToBytes, encodeUnicodeToBytesConditional
|
|
8
|
+
from future.utils import viewitems
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import xml.sax.saxutils
|
|
13
|
+
import zlib
|
|
14
|
+
from traceback import format_exc
|
|
15
|
+
|
|
16
|
+
import cherrypy
|
|
17
|
+
|
|
18
|
+
from WMCore.REST.Error import RESTError, ExecutionError, report_rest_error
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from cherrypy.lib import httputil
|
|
22
|
+
except ImportError:
|
|
23
|
+
from cherrypy.lib import http as httputil
|
|
24
|
+
|
|
25
|
+
def vary_by(header):
|
|
26
|
+
"""Add 'Vary' header for `header`."""
|
|
27
|
+
varies = cherrypy.response.headers.get('Vary', '')
|
|
28
|
+
varies = [x.strip() for x in varies.split(",") if x.strip()]
|
|
29
|
+
if header not in varies:
|
|
30
|
+
varies.append(header)
|
|
31
|
+
cherrypy.response.headers['Vary'] = ", ".join(varies)
|
|
32
|
+
|
|
33
|
+
def is_iterable(obj):
|
|
34
|
+
"""Check if `obj` is iterable."""
|
|
35
|
+
try:
|
|
36
|
+
iter(obj)
|
|
37
|
+
except TypeError:
|
|
38
|
+
return False
|
|
39
|
+
else:
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
class RESTFormat(object):
|
|
43
|
+
def __call__(self, stream, etag):
|
|
44
|
+
"""Main entry point for generating output for `stream` using `etag`
|
|
45
|
+
object to generate ETag header. Returns a generator function for
|
|
46
|
+
producing a verbatim copy of `stream` item, including any premables
|
|
47
|
+
and trailers needed for the selected format. The intention is that
|
|
48
|
+
the caller will use the iterable to generate chunked HTTP transfer
|
|
49
|
+
encoding, or a simple result such as an image."""
|
|
50
|
+
# Make 'stream' iterable. We convert everything to chunks here.
|
|
51
|
+
# The final stream consumer will collapse small responses back
|
|
52
|
+
# to a single string. Convert files to 1MB chunks.
|
|
53
|
+
if stream is None:
|
|
54
|
+
stream = ['']
|
|
55
|
+
elif isinstance(stream, (str, bytes)):
|
|
56
|
+
stream = [stream]
|
|
57
|
+
elif hasattr(stream, "read"):
|
|
58
|
+
# types.FileType is not available anymore in python3,
|
|
59
|
+
# using it raises pylint W1624.
|
|
60
|
+
# Since cherrypy.lib.file_generator only uses the .read() attribute
|
|
61
|
+
# of a file, we simply check if stream.read() is present instead.
|
|
62
|
+
# https://github.com/cherrypy/cherrypy/blob/2a8aaccd649eb1011382c39f5cd93f76f980c0b1/cherrypy/lib/__init__.py#L64
|
|
63
|
+
stream = cherrypy.lib.file_generator(stream, 512 * 1024)
|
|
64
|
+
|
|
65
|
+
return self.stream_chunked(stream, etag, *self.chunk_args(stream))
|
|
66
|
+
|
|
67
|
+
def chunk_args(self, stream):
|
|
68
|
+
"""Return extra arguments needed for `stream_chunked()`. The default
|
|
69
|
+
return an empty tuple, so no extra arguments. Override in the derived
|
|
70
|
+
class if `stream_chunked()` needs preamble or trailer arguments."""
|
|
71
|
+
return tuple()
|
|
72
|
+
|
|
73
|
+
class XMLFormat(RESTFormat):
|
|
74
|
+
"""Format an iterable of objects into XML encoded in UTF-8.
|
|
75
|
+
|
|
76
|
+
Generates normally first a preamble, a stream of XML-rendered objects,
|
|
77
|
+
then the trailer, computing an ETag on the output string in the process.
|
|
78
|
+
This is designed exclusively for use with iterables for chunked transfer
|
|
79
|
+
encoding HTTP responses; it's not a general purpose formatting utility.
|
|
80
|
+
|
|
81
|
+
Outputs first a preamble, then XML encoded output of input stream, and
|
|
82
|
+
finally a trailer. Any exceptions raised by input stream are reported to
|
|
83
|
+
`report_rest_error` and swallowed, as this is normally used to generate
|
|
84
|
+
output for CherryPy responses, which cannot handle exceptions reasonably
|
|
85
|
+
after the output generation begins; later processing may reconvert those
|
|
86
|
+
back to exceptions however (cf. stream_maybe_etag()). Once the preamble
|
|
87
|
+
has been emitted, the trailer is also emitted even if the input stream
|
|
88
|
+
raises an exception, in order to make the output well-formed; the client
|
|
89
|
+
must inspect the X-REST-Status trailer header to find out if it got the
|
|
90
|
+
complete output. No ETag header is generated in case of an exception.
|
|
91
|
+
|
|
92
|
+
The ETag generation is deterministic only if iterating over input is
|
|
93
|
+
deterministic. Beware in particular the key order for a dict is
|
|
94
|
+
arbitrary and may differ for two semantically identical dicts.
|
|
95
|
+
|
|
96
|
+
A X-REST-Status trailer header is added only in case of error. There is
|
|
97
|
+
normally 'X-REST-Status: 100' in normal response headers, and it remains
|
|
98
|
+
valid in case of success.
|
|
99
|
+
|
|
100
|
+
The output is generated as an XML document whose top-level entity name
|
|
101
|
+
is defined by the label given at the formatter construction time. The
|
|
102
|
+
caller must define ``cherrypy.request.rest_generate_data`` to element
|
|
103
|
+
name for wrapping stream contents. Usually the top-level entity is the
|
|
104
|
+
application name and the ``cherrypy.request.rest_generate_data`` is
|
|
105
|
+
``result``.
|
|
106
|
+
|
|
107
|
+
Iterables are output as ``<array><i>ITEM</i><i>ITEM</i></array>``,
|
|
108
|
+
dictionaries as ``<dict><key>KEY</key><value>VALUE</value></dict>``.
|
|
109
|
+
`None` is output as empty contents, and hence there is no way to
|
|
110
|
+
distinguish `None` and an empty string from each other. Scalar types
|
|
111
|
+
are output as rendered by `str()`, but obviously XML encoding unsafe
|
|
112
|
+
characters. This class does not support formatting arbitrary types.
|
|
113
|
+
|
|
114
|
+
The formatter does not insert any spaces into the output. Although the
|
|
115
|
+
output is generated as a preamble, stream of objects, and trailer just
|
|
116
|
+
like by the `JSONFormatter`, each of which is a separate HTTP transfer
|
|
117
|
+
chunk, the output does *not* have guaranteed line-oriented structure
|
|
118
|
+
like the `JSONFormatter` produces. Note in particular that if the data
|
|
119
|
+
stream contains strings with newlines, the output will have arbitrary
|
|
120
|
+
line structure. On the other hand, as the output is well-formed XML,
|
|
121
|
+
virtually all SAX processors can read the stream incrementally even if
|
|
122
|
+
the client isn't able to fully preserve chunked HTTP transfer encoding."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, label):
|
|
125
|
+
self.label = label
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def format_obj(obj):
|
|
129
|
+
"""Render an object `obj` into XML."""
|
|
130
|
+
if isinstance(obj, type(None)):
|
|
131
|
+
result = ""
|
|
132
|
+
elif isinstance(obj, str):
|
|
133
|
+
result = xml.sax.saxutils.escape(obj).encode("utf-8")
|
|
134
|
+
elif isinstance(obj, bytes):
|
|
135
|
+
result = xml.sax.saxutils.escape(obj)
|
|
136
|
+
elif isinstance(obj, (int, float, bool)):
|
|
137
|
+
result = xml.sax.saxutils.escape(str(obj)).encode("utf-8")
|
|
138
|
+
elif isinstance(obj, dict):
|
|
139
|
+
result = "<dict>"
|
|
140
|
+
for k, v in viewitems(obj):
|
|
141
|
+
result += "<key>%s</key><value>%s</value>" % \
|
|
142
|
+
(xml.sax.saxutils.escape(k).encode("utf-8"),
|
|
143
|
+
XMLFormat.format_obj(v))
|
|
144
|
+
result += "</dict>"
|
|
145
|
+
elif is_iterable(obj):
|
|
146
|
+
result = "<array>"
|
|
147
|
+
for v in obj:
|
|
148
|
+
result += "<i>%s</i>" % XMLFormat.format_obj(v)
|
|
149
|
+
result += "</array>"
|
|
150
|
+
else:
|
|
151
|
+
cherrypy.log("cannot represent object of type %s in xml (%s)"
|
|
152
|
+
% (type(obj).__class__.__name__, repr(obj)))
|
|
153
|
+
raise ExecutionError("cannot represent object in xml")
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
def stream_chunked(self, stream, etag, preamble, trailer):
|
|
157
|
+
"""Generator for actually producing the output."""
|
|
158
|
+
try:
|
|
159
|
+
etag.update(preamble)
|
|
160
|
+
yield preamble
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
for obj in stream:
|
|
164
|
+
chunk = XMLFormat.format_obj(obj)
|
|
165
|
+
etag.update(chunk)
|
|
166
|
+
yield chunk
|
|
167
|
+
except GeneratorExit:
|
|
168
|
+
etag.invalidate()
|
|
169
|
+
trailer = None
|
|
170
|
+
raise
|
|
171
|
+
finally:
|
|
172
|
+
if trailer:
|
|
173
|
+
etag.update(trailer)
|
|
174
|
+
yield trailer
|
|
175
|
+
|
|
176
|
+
except RESTError as e:
|
|
177
|
+
etag.invalidate()
|
|
178
|
+
report_rest_error(e, format_exc(), False)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
etag.invalidate()
|
|
181
|
+
report_rest_error(ExecutionError(), format_exc(), False)
|
|
182
|
+
|
|
183
|
+
def chunk_args(self, stream):
|
|
184
|
+
"""Return header and trailer needed to wrap `stream` as XML reply."""
|
|
185
|
+
preamble = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
|
|
186
|
+
preamble += "<%s>" % self.label
|
|
187
|
+
if cherrypy.request.rest_generate_preamble:
|
|
188
|
+
desc = self.format_obj(cherrypy.request.rest_generate_preamble)
|
|
189
|
+
preamble += "<desc>%s</desc>" % desc
|
|
190
|
+
preamble += "<%s>" % cherrypy.request.rest_generate_data
|
|
191
|
+
trailer = "</%s></%s>" % (cherrypy.request.rest_generate_data, self.label)
|
|
192
|
+
return preamble, trailer
|
|
193
|
+
|
|
194
|
+
class JSONFormat(RESTFormat):
|
|
195
|
+
"""Format an iterable of objects into JSON.
|
|
196
|
+
|
|
197
|
+
Generates normally first a preamble, a stream of JSON-rendered objects,
|
|
198
|
+
then the trailer, computing an ETag on the output string in the process.
|
|
199
|
+
This is designed exclusively for use with iterables for chunked transfer
|
|
200
|
+
encoding HTTP responses; it's not a general purpose formatting utility.
|
|
201
|
+
|
|
202
|
+
Outputs first a preamble, then JSON encoded output of input stream, and
|
|
203
|
+
finally a trailer. Any exceptions raised by input stream are reported to
|
|
204
|
+
`report_rest_error` and swallowed, as this is normally used to generate
|
|
205
|
+
output for CherryPy responses, which cannot handle exceptions reasonably
|
|
206
|
+
after the output generation begins; later processing may reconvert those
|
|
207
|
+
back to exceptions however (cf. stream_maybe_etag()). Once the preamble
|
|
208
|
+
has been emitted, the trailer is also emitted even if the input stream
|
|
209
|
+
raises an exception, in order to make the output well-formed; the client
|
|
210
|
+
must inspect the X-REST-Status trailer header to find out if it got the
|
|
211
|
+
complete output. No ETag header is generated in case of an exception.
|
|
212
|
+
|
|
213
|
+
The ETag generation is deterministic only if `cjson.encode()` output is
|
|
214
|
+
deterministic for the input. Beware in particular the key order for a
|
|
215
|
+
dict is arbitrary and may differ for two semantically identical dicts.
|
|
216
|
+
|
|
217
|
+
A X-REST-Status trailer header is added only in case of error. There is
|
|
218
|
+
normally 'X-REST-Status: 100' in normal response headers, and it remains
|
|
219
|
+
valid in case of success.
|
|
220
|
+
|
|
221
|
+
The output is always generated as a JSON dictionary. The caller must
|
|
222
|
+
define ``cherrypy.request.rest_generate_data`` as the key for actual
|
|
223
|
+
contents, usually something like "result". The `stream` value will be
|
|
224
|
+
generated as an array value for that key.
|
|
225
|
+
|
|
226
|
+
If ``cherrypy.request.rest_generate_preamble`` is a non-empty list, it
|
|
227
|
+
is output as the ``desc`` key value in the preamble before outputting
|
|
228
|
+
the `stream` contents. Otherwise the output consists solely of `stream`.
|
|
229
|
+
A common use of ``rest_generate_preamble`` is list of column labels
|
|
230
|
+
with `stream` an iterable of lists of column values.
|
|
231
|
+
|
|
232
|
+
The output is guaranteed to contain one line of preamble which starts a
|
|
233
|
+
dictionary and an array ("``{key: [``"), one line of JSON rendering of
|
|
234
|
+
each object in `stream`, with the first line starting with exactly one
|
|
235
|
+
space and second and subsequent lines starting with a comma, and one
|
|
236
|
+
final trailer line consisting of "``]}``". Each line is generated as a
|
|
237
|
+
HTTP transfer chunk. This format is fixed so readers can be constructed
|
|
238
|
+
to read and parse the stream incrementally one line at a time,
|
|
239
|
+
facilitating maximum throughput processing of the response."""
|
|
240
|
+
|
|
241
|
+
def stream_chunked(self, stream, etag, preamble, trailer):
|
|
242
|
+
"""Generator for actually producing the output."""
|
|
243
|
+
comma = " "
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
if preamble:
|
|
247
|
+
etag.update(preamble)
|
|
248
|
+
yield preamble
|
|
249
|
+
|
|
250
|
+
obj = None
|
|
251
|
+
try:
|
|
252
|
+
for obj in stream:
|
|
253
|
+
chunk = comma + json.dumps(obj) + "\n"
|
|
254
|
+
etag.update(chunk)
|
|
255
|
+
yield chunk
|
|
256
|
+
comma = ","
|
|
257
|
+
except cherrypy.HTTPError:
|
|
258
|
+
raise
|
|
259
|
+
except GeneratorExit:
|
|
260
|
+
etag.invalidate()
|
|
261
|
+
trailer = None
|
|
262
|
+
raise
|
|
263
|
+
except Exception as exp:
|
|
264
|
+
print("ERROR, json.dumps failed to serialize %s, type %s\nException: %s" \
|
|
265
|
+
% (obj, type(obj), str(exp)))
|
|
266
|
+
raise
|
|
267
|
+
finally:
|
|
268
|
+
if trailer:
|
|
269
|
+
etag.update(trailer)
|
|
270
|
+
yield trailer
|
|
271
|
+
|
|
272
|
+
cherrypy.response.headers["X-REST-Status"] = 100
|
|
273
|
+
except cherrypy.HTTPError:
|
|
274
|
+
raise
|
|
275
|
+
except RESTError as e:
|
|
276
|
+
etag.invalidate()
|
|
277
|
+
report_rest_error(e, format_exc(), False)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
etag.invalidate()
|
|
280
|
+
report_rest_error(ExecutionError(), format_exc(), False)
|
|
281
|
+
|
|
282
|
+
def chunk_args(self, stream):
|
|
283
|
+
"""Return header and trailer needed to wrap `stream` as JSON reply."""
|
|
284
|
+
comma = ""
|
|
285
|
+
preamble = "{"
|
|
286
|
+
trailer = "]}\n"
|
|
287
|
+
if cherrypy.request.rest_generate_preamble:
|
|
288
|
+
desc = json.dumps(cherrypy.request.rest_generate_preamble)
|
|
289
|
+
preamble += '"desc": %s' % desc
|
|
290
|
+
comma = ", "
|
|
291
|
+
preamble += '%s"%s": [\n' % (comma, cherrypy.request.rest_generate_data)
|
|
292
|
+
return preamble, trailer
|
|
293
|
+
|
|
294
|
+
class PrettyJSONFormat(JSONFormat):
|
|
295
|
+
""" Format used for human, (web browser)"""
|
|
296
|
+
|
|
297
|
+
def stream_chunked(self, stream, etag, preamble, trailer):
|
|
298
|
+
"""Generator for actually producing the output."""
|
|
299
|
+
comma = " "
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
if preamble:
|
|
303
|
+
etag.update(preamble)
|
|
304
|
+
yield preamble
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
for obj in stream:
|
|
308
|
+
chunk = comma + json.dumps(obj, indent=2)
|
|
309
|
+
etag.update(chunk)
|
|
310
|
+
yield chunk
|
|
311
|
+
comma = ","
|
|
312
|
+
except GeneratorExit:
|
|
313
|
+
etag.invalidate()
|
|
314
|
+
trailer = None
|
|
315
|
+
raise
|
|
316
|
+
finally:
|
|
317
|
+
if trailer:
|
|
318
|
+
etag.update(trailer)
|
|
319
|
+
yield trailer
|
|
320
|
+
|
|
321
|
+
cherrypy.response.headers["X-REST-Status"] = 100
|
|
322
|
+
except RESTError as e:
|
|
323
|
+
etag.invalidate()
|
|
324
|
+
report_rest_error(e, format_exc(), False)
|
|
325
|
+
except Exception as e:
|
|
326
|
+
etag.invalidate()
|
|
327
|
+
report_rest_error(ExecutionError(), format_exc(), False)
|
|
328
|
+
|
|
329
|
+
class PrettyJSONHTMLFormat(PrettyJSONFormat):
|
|
330
|
+
""" Format used for human, (web browser) wrap around html tag on json"""
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def format_obj(obj):
|
|
334
|
+
"""Render an object `obj` into HTML."""
|
|
335
|
+
if isinstance(obj, type(None)):
|
|
336
|
+
result = ""
|
|
337
|
+
elif isinstance(obj, str):
|
|
338
|
+
obj = xml.sax.saxutils.quoteattr(obj)
|
|
339
|
+
result = "<pre>%s</pre>" % obj if '\n' in obj else obj
|
|
340
|
+
elif isinstance(obj, bytes):
|
|
341
|
+
obj = xml.sax.saxutils.quoteattr(str(obj, "utf-8"))
|
|
342
|
+
result = "<pre>%s</pre>" % obj if '\n' in obj else obj
|
|
343
|
+
elif isinstance(obj, (int, float, bool)):
|
|
344
|
+
result = "%s" % obj
|
|
345
|
+
elif isinstance(obj, dict):
|
|
346
|
+
result = "<ul>"
|
|
347
|
+
for k, v in viewitems(obj):
|
|
348
|
+
result += "<li><b>%s</b>: %s</li>" % (k, PrettyJSONHTMLFormat.format_obj(v))
|
|
349
|
+
result += "</ul>"
|
|
350
|
+
elif is_iterable(obj):
|
|
351
|
+
empty = True
|
|
352
|
+
result = "<details open><ul>"
|
|
353
|
+
for v in obj:
|
|
354
|
+
empty = False
|
|
355
|
+
result += "<li>%s</li>" % PrettyJSONHTMLFormat.format_obj(v)
|
|
356
|
+
result += "</ul></details>"
|
|
357
|
+
if empty:
|
|
358
|
+
result = ""
|
|
359
|
+
else:
|
|
360
|
+
cherrypy.log("cannot represent object of type %s in xml (%s)"
|
|
361
|
+
% (type(obj).__class__.__name__, repr(obj)))
|
|
362
|
+
raise ExecutionError("cannot represent object in xml")
|
|
363
|
+
return result
|
|
364
|
+
|
|
365
|
+
def stream_chunked(self, stream, etag, preamble, trailer):
|
|
366
|
+
"""Generator for actually producing the output."""
|
|
367
|
+
try:
|
|
368
|
+
etag.update(preamble)
|
|
369
|
+
yield preamble
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
for obj in stream:
|
|
373
|
+
chunk = PrettyJSONHTMLFormat.format_obj(obj)
|
|
374
|
+
etag.update(chunk)
|
|
375
|
+
yield chunk
|
|
376
|
+
except GeneratorExit:
|
|
377
|
+
etag.invalidate()
|
|
378
|
+
trailer = None
|
|
379
|
+
raise
|
|
380
|
+
finally:
|
|
381
|
+
if trailer:
|
|
382
|
+
etag.update(trailer)
|
|
383
|
+
yield trailer
|
|
384
|
+
|
|
385
|
+
except RESTError as e:
|
|
386
|
+
etag.invalidate()
|
|
387
|
+
report_rest_error(e, format_exc(), False)
|
|
388
|
+
except Exception as e:
|
|
389
|
+
etag.invalidate()
|
|
390
|
+
report_rest_error(ExecutionError(), format_exc(), False)
|
|
391
|
+
|
|
392
|
+
def chunk_args(self, stream):
|
|
393
|
+
"""Return header and trailer needed to wrap `stream` as XML reply."""
|
|
394
|
+
preamble = "<html><body>"
|
|
395
|
+
trailer = "</body></html>"
|
|
396
|
+
return preamble, trailer
|
|
397
|
+
|
|
398
|
+
class RawFormat(RESTFormat):
|
|
399
|
+
"""Format an iterable of objects as raw data.
|
|
400
|
+
|
|
401
|
+
Generates raw data completely unmodified, for example image data or
|
|
402
|
+
streaming arbitrary external data files including even plain text.
|
|
403
|
+
Computes an ETag on the output in the process. The result is always
|
|
404
|
+
chunked, even simple strings on input. Usually small enough responses
|
|
405
|
+
will automatically be converted back to a single string response post
|
|
406
|
+
compression and ETag processing.
|
|
407
|
+
|
|
408
|
+
Any exceptions raised by input stream are reported to `report_rest_error`
|
|
409
|
+
and swallowed, as this is normally used to generate output for CherryPy
|
|
410
|
+
responses, which cannot handle exceptions reasonably after the output
|
|
411
|
+
generation begins; later processing may reconvert those back to exceptions
|
|
412
|
+
however (cf. stream_maybe_etag()). A X-REST-Status trailer header is added
|
|
413
|
+
if (and only if) an exception occurs; the client must inspect that to find
|
|
414
|
+
out if it got the complete output. There is normally 'X-REST-Status: 100'
|
|
415
|
+
in normal response headers, and it remains valid in case of success.
|
|
416
|
+
No ETag header is generated in case of an exception."""
|
|
417
|
+
|
|
418
|
+
def stream_chunked(self, stream, etag):
|
|
419
|
+
"""Generator for actually producing the output."""
|
|
420
|
+
try:
|
|
421
|
+
for chunk in stream:
|
|
422
|
+
etag.update(chunk)
|
|
423
|
+
yield chunk
|
|
424
|
+
|
|
425
|
+
except RESTError as e:
|
|
426
|
+
etag.invalidate()
|
|
427
|
+
report_rest_error(e, format_exc(), False)
|
|
428
|
+
except Exception as e:
|
|
429
|
+
etag.invalidate()
|
|
430
|
+
report_rest_error(ExecutionError(), format_exc(), False)
|
|
431
|
+
except BaseException:
|
|
432
|
+
etag.invalidate()
|
|
433
|
+
raise
|
|
434
|
+
|
|
435
|
+
class DigestETag(object):
|
|
436
|
+
"""Compute hash digest over contents for ETag header."""
|
|
437
|
+
algorithm = None
|
|
438
|
+
|
|
439
|
+
def __init__(self, algorithm=None):
|
|
440
|
+
"""Prepare ETag computer."""
|
|
441
|
+
self.digest = hashlib.new(algorithm or self.algorithm)
|
|
442
|
+
|
|
443
|
+
def update(self, val):
|
|
444
|
+
"""Process response data `val`."""
|
|
445
|
+
if self.digest:
|
|
446
|
+
self.digest.update(encodeUnicodeToBytes(val))
|
|
447
|
+
|
|
448
|
+
def value(self):
|
|
449
|
+
"""Return ETag header value for current input."""
|
|
450
|
+
return self.digest and '"%s"' % self.digest.hexdigest()
|
|
451
|
+
|
|
452
|
+
def invalidate(self):
|
|
453
|
+
"""Invalidate the ETag calculator so value() will return None."""
|
|
454
|
+
self.digest = None
|
|
455
|
+
|
|
456
|
+
class MD5ETag(DigestETag):
|
|
457
|
+
"""Compute MD5 hash over contents for ETag header."""
|
|
458
|
+
algorithm = 'md5'
|
|
459
|
+
|
|
460
|
+
class SHA1ETag(DigestETag):
|
|
461
|
+
"""Compute SHA1 hash over contents for ETag header."""
|
|
462
|
+
algorithm = 'sha1'
|
|
463
|
+
|
|
464
|
+
def _stream_compress_identity(reply, *args):
|
|
465
|
+
"""Streaming compressor which returns original data unchanged."""
|
|
466
|
+
return reply
|
|
467
|
+
|
|
468
|
+
def _stream_compress_deflate(reply, compress_level, max_chunk):
|
|
469
|
+
"""Streaming compressor for the 'deflate' method. Generates output that
|
|
470
|
+
is guaranteed to expand at the exact same chunk boundaries as original
|
|
471
|
+
reply stream."""
|
|
472
|
+
|
|
473
|
+
# Create zlib compression object, with raw data stream (negative window size)
|
|
474
|
+
z = zlib.compressobj(compress_level, zlib.DEFLATED, -zlib.MAX_WBITS,
|
|
475
|
+
zlib.DEF_MEM_LEVEL, 0)
|
|
476
|
+
|
|
477
|
+
# Data pending compression. We only take entire chunks from original
|
|
478
|
+
# reply. Then process reply one chunk at a time. Whenever we have enough
|
|
479
|
+
# data to compress, spit it out flushing the zlib engine entirely, so we
|
|
480
|
+
# respect original chunk boundaries.
|
|
481
|
+
npending = 0
|
|
482
|
+
pending = []
|
|
483
|
+
for chunk in reply:
|
|
484
|
+
pending.append(chunk)
|
|
485
|
+
npending += len(chunk)
|
|
486
|
+
if npending >= max_chunk:
|
|
487
|
+
part = z.compress(encodeUnicodeToBytes("".join(pending))) + z.flush(zlib.Z_FULL_FLUSH)
|
|
488
|
+
pending = []
|
|
489
|
+
npending = 0
|
|
490
|
+
yield part
|
|
491
|
+
|
|
492
|
+
# Crank the compressor one more time for remaining output.
|
|
493
|
+
if npending:
|
|
494
|
+
yield z.compress(encodeUnicodeToBytes("".join(pending))) + z.flush(zlib.Z_FINISH)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _stream_compress_gzip(reply, compress_level, *args):
|
|
498
|
+
"""Streaming compressor for the 'gzip' method. Generates output that
|
|
499
|
+
is guaranteed to expand at the exact same chunk boundaries as original
|
|
500
|
+
reply stream."""
|
|
501
|
+
data = []
|
|
502
|
+
for chunk in reply:
|
|
503
|
+
data.append(chunk)
|
|
504
|
+
if data:
|
|
505
|
+
yield gzip.compress(encodeUnicodeToBytes("".join(data)), compress_level)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
# : Stream compression methods.
|
|
509
|
+
_stream_compressor = {
|
|
510
|
+
'identity': _stream_compress_identity,
|
|
511
|
+
'deflate': _stream_compress_deflate,
|
|
512
|
+
'gzip': _stream_compress_gzip
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
def stream_compress(reply, available, compress_level, max_chunk):
|
|
516
|
+
"""If compression has been requested via Accept-Encoding request header,
|
|
517
|
+
and is granted for this response via `available` compression methods,
|
|
518
|
+
convert the streaming `reply` into another streaming response which is
|
|
519
|
+
compressed at the exact chunk boundaries of the original response,
|
|
520
|
+
except that individual chunks may be coalesced up to `max_chunk` size.
|
|
521
|
+
The `compression_level` tells how hard to compress, zero disables the
|
|
522
|
+
compression entirely."""
|
|
523
|
+
|
|
524
|
+
global _stream_compressor
|
|
525
|
+
for enc in cherrypy.request.headers.elements('Accept-Encoding'):
|
|
526
|
+
if enc.value not in available:
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
elif enc.value in _stream_compressor and compress_level > 0:
|
|
530
|
+
# Add 'Vary' header for 'Accept-Encoding'.
|
|
531
|
+
vary_by('Accept-Encoding')
|
|
532
|
+
|
|
533
|
+
# Compress contents at original chunk boundaries.
|
|
534
|
+
if 'Content-Length' in cherrypy.response.headers:
|
|
535
|
+
del cherrypy.response.headers['Content-Length']
|
|
536
|
+
cherrypy.response.headers['Content-Encoding'] = enc.value
|
|
537
|
+
return _stream_compressor[enc.value](reply, compress_level, max_chunk)
|
|
538
|
+
|
|
539
|
+
return reply
|
|
540
|
+
|
|
541
|
+
def _etag_match(status, etagval, match, nomatch):
|
|
542
|
+
"""Match ETag value against any If-Match / If-None-Match headers."""
|
|
543
|
+
# Execute conditions only for status 2xx. We only handle GET/HEAD
|
|
544
|
+
# requests here, it makes no sense to try to do this for PUT etc.
|
|
545
|
+
# as they need to be handled as request pre-condition, not in the
|
|
546
|
+
# streaming out part here.
|
|
547
|
+
if cherrypy.request.method in ('GET', 'HEAD'):
|
|
548
|
+
status, dummyReason, dummyMsg = httputil.valid_status(status)
|
|
549
|
+
if status >= 200 and status <= 299:
|
|
550
|
+
if match and ("*" in match or etagval in match):
|
|
551
|
+
raise cherrypy.HTTPError(412, "Precondition on ETag %s failed" % etagval)
|
|
552
|
+
if nomatch and ("*" in nomatch or etagval in nomatch):
|
|
553
|
+
raise cherrypy.HTTPRedirect([], 304)
|
|
554
|
+
|
|
555
|
+
def _etag_tail(head, tail, etag):
|
|
556
|
+
"""Generator which first returns anything in `head`, then `tail`.
|
|
557
|
+
Sets ETag header at the end to value of `etag` if it's defined and
|
|
558
|
+
yields a value."""
|
|
559
|
+
for chunk in head:
|
|
560
|
+
yield encodeUnicodeToBytes(chunk)
|
|
561
|
+
|
|
562
|
+
for chunk in tail:
|
|
563
|
+
yield encodeUnicodeToBytes(chunk)
|
|
564
|
+
|
|
565
|
+
etagval = (etag and etag.value())
|
|
566
|
+
if etagval:
|
|
567
|
+
cherrypy.response.headers["ETag"] = etagval
|
|
568
|
+
|
|
569
|
+
def stream_maybe_etag(size_limit, etag, reply):
|
|
570
|
+
"""Maybe generate ETag header for the response, and handle If-Match
|
|
571
|
+
and If-None-Match request headers. Consumes the reply until at most
|
|
572
|
+
`size_limit` bytes. If the response fits into that size, adds the
|
|
573
|
+
ETag header and matches it against any If-Match / If-None-Match
|
|
574
|
+
request headers and replies appropriately.
|
|
575
|
+
|
|
576
|
+
If the response is fully buffered, and the `reply` generator actually
|
|
577
|
+
results in an error and sets X-Error-HTTP / X-Error-Detail headers,
|
|
578
|
+
converts that error back into a real HTTP error response. Otherwise
|
|
579
|
+
responds with the fully buffered body directly, without generator
|
|
580
|
+
and chunking. In other words, responses smaller than `size_limit`
|
|
581
|
+
are always fully buffered and replied immediately without chunking.
|
|
582
|
+
If the response is not fully buffered, it's guaranteed to be output
|
|
583
|
+
at original chunk boundaries.
|
|
584
|
+
|
|
585
|
+
Note that if this function is fed the output from `stream_compress()`
|
|
586
|
+
as it normally would be, the `size_limit` constrains the compressed
|
|
587
|
+
size, and chunk boundaries correspond to compressed chunks."""
|
|
588
|
+
|
|
589
|
+
req = cherrypy.request
|
|
590
|
+
res = cherrypy.response
|
|
591
|
+
match = [str(x) for x in (req.headers.elements('If-Match') or [])]
|
|
592
|
+
nomatch = [str(x) for x in (req.headers.elements('If-None-Match') or [])]
|
|
593
|
+
|
|
594
|
+
# If ETag is already set, match conditions and output without buffering.
|
|
595
|
+
etagval = res.headers.get('ETag', None)
|
|
596
|
+
if etagval:
|
|
597
|
+
_etag_match(res.status or 200, etagval, match, nomatch)
|
|
598
|
+
res.headers['Trailer'] = 'X-REST-Status'
|
|
599
|
+
return _etag_tail([], reply, None)
|
|
600
|
+
|
|
601
|
+
# Buffer up to size_limit bytes internally. This interally builds up the
|
|
602
|
+
# ETag value inside 'etag'. In case of exceptions the ETag invalidates.
|
|
603
|
+
# If we exceed the limit, fall back to streaming without checking ETag
|
|
604
|
+
# against If-Match/If-None-Match. We'll still set the ETag in the trailer
|
|
605
|
+
# headers, so clients which understand trailers will get the value; most
|
|
606
|
+
# clients including browsers will ignore them.
|
|
607
|
+
size = 0
|
|
608
|
+
result = []
|
|
609
|
+
for chunk in reply:
|
|
610
|
+
result.append(chunk)
|
|
611
|
+
size += len(chunk)
|
|
612
|
+
if size > size_limit:
|
|
613
|
+
res.headers['Trailer'] = 'X-REST-Status'
|
|
614
|
+
return _etag_tail(result, reply, etag)
|
|
615
|
+
|
|
616
|
+
# We've buffered the entire response, but it may be an error reply. The
|
|
617
|
+
# generator code does not know if it's allowed to raise exceptions, so
|
|
618
|
+
# it swallows all errors and converts them into X-* headers. We recover
|
|
619
|
+
# the original HTTP response code and message from X-Error-{HTTP,Detail}
|
|
620
|
+
# headers, if any are present.
|
|
621
|
+
err = res.headers.get('X-Error-HTTP', None)
|
|
622
|
+
if err:
|
|
623
|
+
message = res.headers.get('X-Error-Detail', 'Original error lost')
|
|
624
|
+
raise cherrypy.HTTPError(int(err), message)
|
|
625
|
+
|
|
626
|
+
# OK, we buffered the entire reply and it's ok. Check ETag match criteria.
|
|
627
|
+
# The original stream generator must guarantee that if it fails it resets
|
|
628
|
+
# the 'etag' value, even if the error handlers above didn't run.
|
|
629
|
+
etagval = etag.value()
|
|
630
|
+
if etagval:
|
|
631
|
+
res.headers['ETag'] = etagval
|
|
632
|
+
_etag_match(res.status or 200, etagval, match, nomatch)
|
|
633
|
+
|
|
634
|
+
# OK, respond with the buffered reply as a plain string.
|
|
635
|
+
res.headers['Content-Length'] = size
|
|
636
|
+
# TODO investigate why `result` is a list of bytes strings in py3
|
|
637
|
+
# The current solution seems to work in both py2 and py3
|
|
638
|
+
resp = b"" if PY3 else ""
|
|
639
|
+
for item in result:
|
|
640
|
+
resp += encodeUnicodeToBytesConditional(item, condition=PY3)
|
|
641
|
+
assert len(resp) == size
|
|
642
|
+
return resp
|