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.

Files changed (345) hide show
  1. Utils/CPMetrics.py +270 -0
  2. Utils/CertTools.py +62 -0
  3. Utils/EmailAlert.py +50 -0
  4. Utils/ExtendedUnitTestCase.py +62 -0
  5. Utils/FileTools.py +182 -0
  6. Utils/IteratorTools.py +80 -0
  7. Utils/MathUtils.py +31 -0
  8. Utils/MemoryCache.py +119 -0
  9. Utils/Patterns.py +24 -0
  10. Utils/Pipeline.py +137 -0
  11. Utils/PortForward.py +97 -0
  12. Utils/ProcessStats.py +103 -0
  13. Utils/PythonVersion.py +17 -0
  14. Utils/Signals.py +36 -0
  15. Utils/TemporaryEnvironment.py +27 -0
  16. Utils/Throttled.py +227 -0
  17. Utils/Timers.py +130 -0
  18. Utils/Timestamps.py +86 -0
  19. Utils/TokenManager.py +143 -0
  20. Utils/Tracing.py +60 -0
  21. Utils/TwPrint.py +98 -0
  22. Utils/Utilities.py +308 -0
  23. Utils/__init__.py +11 -0
  24. WMCore/ACDC/Collection.py +57 -0
  25. WMCore/ACDC/CollectionTypes.py +12 -0
  26. WMCore/ACDC/CouchCollection.py +67 -0
  27. WMCore/ACDC/CouchFileset.py +238 -0
  28. WMCore/ACDC/CouchService.py +73 -0
  29. WMCore/ACDC/DataCollectionService.py +485 -0
  30. WMCore/ACDC/Fileset.py +94 -0
  31. WMCore/ACDC/__init__.py +11 -0
  32. WMCore/Algorithms/Alarm.py +39 -0
  33. WMCore/Algorithms/MathAlgos.py +274 -0
  34. WMCore/Algorithms/MiscAlgos.py +67 -0
  35. WMCore/Algorithms/ParseXMLFile.py +115 -0
  36. WMCore/Algorithms/Permissions.py +27 -0
  37. WMCore/Algorithms/Singleton.py +58 -0
  38. WMCore/Algorithms/SubprocessAlgos.py +129 -0
  39. WMCore/Algorithms/__init__.py +7 -0
  40. WMCore/Cache/GenericDataCache.py +98 -0
  41. WMCore/Cache/WMConfigCache.py +572 -0
  42. WMCore/Cache/__init__.py +0 -0
  43. WMCore/Configuration.py +651 -0
  44. WMCore/DAOFactory.py +47 -0
  45. WMCore/DataStructs/File.py +177 -0
  46. WMCore/DataStructs/Fileset.py +140 -0
  47. WMCore/DataStructs/Job.py +182 -0
  48. WMCore/DataStructs/JobGroup.py +142 -0
  49. WMCore/DataStructs/JobPackage.py +49 -0
  50. WMCore/DataStructs/LumiList.py +734 -0
  51. WMCore/DataStructs/Mask.py +219 -0
  52. WMCore/DataStructs/MathStructs/ContinuousSummaryHistogram.py +197 -0
  53. WMCore/DataStructs/MathStructs/DiscreteSummaryHistogram.py +92 -0
  54. WMCore/DataStructs/MathStructs/SummaryHistogram.py +117 -0
  55. WMCore/DataStructs/MathStructs/__init__.py +0 -0
  56. WMCore/DataStructs/Pickleable.py +24 -0
  57. WMCore/DataStructs/Run.py +256 -0
  58. WMCore/DataStructs/Subscription.py +175 -0
  59. WMCore/DataStructs/WMObject.py +47 -0
  60. WMCore/DataStructs/WorkUnit.py +112 -0
  61. WMCore/DataStructs/Workflow.py +60 -0
  62. WMCore/DataStructs/__init__.py +8 -0
  63. WMCore/Database/CMSCouch.py +1349 -0
  64. WMCore/Database/ConfigDBMap.py +29 -0
  65. WMCore/Database/CouchUtils.py +118 -0
  66. WMCore/Database/DBCore.py +198 -0
  67. WMCore/Database/DBCreator.py +113 -0
  68. WMCore/Database/DBExceptionHandler.py +57 -0
  69. WMCore/Database/DBFactory.py +110 -0
  70. WMCore/Database/DBFormatter.py +177 -0
  71. WMCore/Database/Dialects.py +13 -0
  72. WMCore/Database/ExecuteDAO.py +327 -0
  73. WMCore/Database/MongoDB.py +241 -0
  74. WMCore/Database/MySQL/Destroy.py +42 -0
  75. WMCore/Database/MySQL/ListUserContent.py +20 -0
  76. WMCore/Database/MySQL/__init__.py +9 -0
  77. WMCore/Database/MySQLCore.py +132 -0
  78. WMCore/Database/Oracle/Destroy.py +56 -0
  79. WMCore/Database/Oracle/ListUserContent.py +19 -0
  80. WMCore/Database/Oracle/__init__.py +9 -0
  81. WMCore/Database/ResultSet.py +44 -0
  82. WMCore/Database/Transaction.py +91 -0
  83. WMCore/Database/__init__.py +9 -0
  84. WMCore/Database/ipy_profile_couch.py +438 -0
  85. WMCore/GlobalWorkQueue/CherryPyThreads/CleanUpTask.py +29 -0
  86. WMCore/GlobalWorkQueue/CherryPyThreads/HeartbeatMonitor.py +105 -0
  87. WMCore/GlobalWorkQueue/CherryPyThreads/LocationUpdateTask.py +28 -0
  88. WMCore/GlobalWorkQueue/CherryPyThreads/ReqMgrInteractionTask.py +35 -0
  89. WMCore/GlobalWorkQueue/CherryPyThreads/__init__.py +0 -0
  90. WMCore/GlobalWorkQueue/__init__.py +0 -0
  91. WMCore/GroupUser/CouchObject.py +127 -0
  92. WMCore/GroupUser/Decorators.py +51 -0
  93. WMCore/GroupUser/Group.py +33 -0
  94. WMCore/GroupUser/Interface.py +73 -0
  95. WMCore/GroupUser/User.py +96 -0
  96. WMCore/GroupUser/__init__.py +11 -0
  97. WMCore/Lexicon.py +836 -0
  98. WMCore/REST/Auth.py +202 -0
  99. WMCore/REST/CherryPyPeriodicTask.py +166 -0
  100. WMCore/REST/Error.py +333 -0
  101. WMCore/REST/Format.py +642 -0
  102. WMCore/REST/HeartbeatMonitorBase.py +90 -0
  103. WMCore/REST/Main.py +623 -0
  104. WMCore/REST/Server.py +2435 -0
  105. WMCore/REST/Services.py +24 -0
  106. WMCore/REST/Test.py +120 -0
  107. WMCore/REST/Tools.py +38 -0
  108. WMCore/REST/Validation.py +250 -0
  109. WMCore/REST/__init__.py +1 -0
  110. WMCore/ReqMgr/DataStructs/RequestStatus.py +209 -0
  111. WMCore/ReqMgr/DataStructs/RequestType.py +13 -0
  112. WMCore/ReqMgr/DataStructs/__init__.py +0 -0
  113. WMCore/ReqMgr/__init__.py +1 -0
  114. WMCore/Services/AlertManager/AlertManagerAPI.py +111 -0
  115. WMCore/Services/AlertManager/__init__.py +0 -0
  116. WMCore/Services/CRIC/CRIC.py +238 -0
  117. WMCore/Services/CRIC/__init__.py +0 -0
  118. WMCore/Services/DBS/DBS3Reader.py +1044 -0
  119. WMCore/Services/DBS/DBSConcurrency.py +44 -0
  120. WMCore/Services/DBS/DBSErrors.py +113 -0
  121. WMCore/Services/DBS/DBSReader.py +23 -0
  122. WMCore/Services/DBS/DBSUtils.py +139 -0
  123. WMCore/Services/DBS/DBSWriterObjects.py +381 -0
  124. WMCore/Services/DBS/ProdException.py +133 -0
  125. WMCore/Services/DBS/__init__.py +8 -0
  126. WMCore/Services/FWJRDB/FWJRDBAPI.py +118 -0
  127. WMCore/Services/FWJRDB/__init__.py +0 -0
  128. WMCore/Services/HTTPS/HTTPSAuthHandler.py +66 -0
  129. WMCore/Services/HTTPS/__init__.py +0 -0
  130. WMCore/Services/LogDB/LogDB.py +201 -0
  131. WMCore/Services/LogDB/LogDBBackend.py +191 -0
  132. WMCore/Services/LogDB/LogDBExceptions.py +11 -0
  133. WMCore/Services/LogDB/LogDBReport.py +85 -0
  134. WMCore/Services/LogDB/__init__.py +0 -0
  135. WMCore/Services/MSPileup/__init__.py +0 -0
  136. WMCore/Services/MSUtils/MSUtils.py +54 -0
  137. WMCore/Services/MSUtils/__init__.py +0 -0
  138. WMCore/Services/McM/McM.py +173 -0
  139. WMCore/Services/McM/__init__.py +8 -0
  140. WMCore/Services/MonIT/Grafana.py +133 -0
  141. WMCore/Services/MonIT/__init__.py +0 -0
  142. WMCore/Services/PyCondor/PyCondorAPI.py +154 -0
  143. WMCore/Services/PyCondor/PyCondorUtils.py +105 -0
  144. WMCore/Services/PyCondor/__init__.py +0 -0
  145. WMCore/Services/ReqMgr/ReqMgr.py +261 -0
  146. WMCore/Services/ReqMgr/__init__.py +0 -0
  147. WMCore/Services/ReqMgrAux/ReqMgrAux.py +419 -0
  148. WMCore/Services/ReqMgrAux/__init__.py +0 -0
  149. WMCore/Services/RequestDB/RequestDBReader.py +267 -0
  150. WMCore/Services/RequestDB/RequestDBWriter.py +39 -0
  151. WMCore/Services/RequestDB/__init__.py +0 -0
  152. WMCore/Services/Requests.py +624 -0
  153. WMCore/Services/Rucio/Rucio.py +1287 -0
  154. WMCore/Services/Rucio/RucioUtils.py +74 -0
  155. WMCore/Services/Rucio/__init__.py +0 -0
  156. WMCore/Services/RucioConMon/RucioConMon.py +128 -0
  157. WMCore/Services/RucioConMon/__init__.py +0 -0
  158. WMCore/Services/Service.py +400 -0
  159. WMCore/Services/StompAMQ/__init__.py +0 -0
  160. WMCore/Services/TagCollector/TagCollector.py +155 -0
  161. WMCore/Services/TagCollector/XMLUtils.py +98 -0
  162. WMCore/Services/TagCollector/__init__.py +0 -0
  163. WMCore/Services/UUIDLib.py +13 -0
  164. WMCore/Services/UserFileCache/UserFileCache.py +160 -0
  165. WMCore/Services/UserFileCache/__init__.py +8 -0
  166. WMCore/Services/WMAgent/WMAgent.py +63 -0
  167. WMCore/Services/WMAgent/__init__.py +0 -0
  168. WMCore/Services/WMArchive/CMSSWMetrics.py +526 -0
  169. WMCore/Services/WMArchive/DataMap.py +463 -0
  170. WMCore/Services/WMArchive/WMArchive.py +33 -0
  171. WMCore/Services/WMArchive/__init__.py +0 -0
  172. WMCore/Services/WMBS/WMBS.py +97 -0
  173. WMCore/Services/WMBS/__init__.py +0 -0
  174. WMCore/Services/WMStats/DataStruct/RequestInfoCollection.py +300 -0
  175. WMCore/Services/WMStats/DataStruct/__init__.py +0 -0
  176. WMCore/Services/WMStats/WMStatsPycurl.py +145 -0
  177. WMCore/Services/WMStats/WMStatsReader.py +445 -0
  178. WMCore/Services/WMStats/WMStatsWriter.py +273 -0
  179. WMCore/Services/WMStats/__init__.py +0 -0
  180. WMCore/Services/WMStatsServer/WMStatsServer.py +134 -0
  181. WMCore/Services/WMStatsServer/__init__.py +0 -0
  182. WMCore/Services/WorkQueue/WorkQueue.py +492 -0
  183. WMCore/Services/WorkQueue/__init__.py +0 -0
  184. WMCore/Services/__init__.py +8 -0
  185. WMCore/Services/pycurl_manager.py +574 -0
  186. WMCore/WMBase.py +50 -0
  187. WMCore/WMConnectionBase.py +164 -0
  188. WMCore/WMException.py +183 -0
  189. WMCore/WMExceptions.py +269 -0
  190. WMCore/WMFactory.py +76 -0
  191. WMCore/WMInit.py +228 -0
  192. WMCore/WMLogging.py +108 -0
  193. WMCore/WMSpec/ConfigSectionTree.py +442 -0
  194. WMCore/WMSpec/Persistency.py +135 -0
  195. WMCore/WMSpec/Steps/BuildMaster.py +87 -0
  196. WMCore/WMSpec/Steps/BuildTools.py +201 -0
  197. WMCore/WMSpec/Steps/Builder.py +97 -0
  198. WMCore/WMSpec/Steps/Diagnostic.py +89 -0
  199. WMCore/WMSpec/Steps/Emulator.py +62 -0
  200. WMCore/WMSpec/Steps/ExecuteMaster.py +208 -0
  201. WMCore/WMSpec/Steps/Executor.py +210 -0
  202. WMCore/WMSpec/Steps/StepFactory.py +213 -0
  203. WMCore/WMSpec/Steps/TaskEmulator.py +75 -0
  204. WMCore/WMSpec/Steps/Template.py +204 -0
  205. WMCore/WMSpec/Steps/Templates/AlcaHarvest.py +76 -0
  206. WMCore/WMSpec/Steps/Templates/CMSSW.py +613 -0
  207. WMCore/WMSpec/Steps/Templates/DQMUpload.py +59 -0
  208. WMCore/WMSpec/Steps/Templates/DeleteFiles.py +70 -0
  209. WMCore/WMSpec/Steps/Templates/LogArchive.py +84 -0
  210. WMCore/WMSpec/Steps/Templates/LogCollect.py +105 -0
  211. WMCore/WMSpec/Steps/Templates/StageOut.py +105 -0
  212. WMCore/WMSpec/Steps/Templates/__init__.py +10 -0
  213. WMCore/WMSpec/Steps/WMExecutionFailure.py +21 -0
  214. WMCore/WMSpec/Steps/__init__.py +8 -0
  215. WMCore/WMSpec/Utilities.py +63 -0
  216. WMCore/WMSpec/WMSpecErrors.py +12 -0
  217. WMCore/WMSpec/WMStep.py +347 -0
  218. WMCore/WMSpec/WMTask.py +1980 -0
  219. WMCore/WMSpec/WMWorkload.py +2288 -0
  220. WMCore/WMSpec/WMWorkloadTools.py +370 -0
  221. WMCore/WMSpec/__init__.py +9 -0
  222. WMCore/WorkQueue/DataLocationMapper.py +273 -0
  223. WMCore/WorkQueue/DataStructs/ACDCBlock.py +47 -0
  224. WMCore/WorkQueue/DataStructs/Block.py +48 -0
  225. WMCore/WorkQueue/DataStructs/CouchWorkQueueElement.py +148 -0
  226. WMCore/WorkQueue/DataStructs/WorkQueueElement.py +274 -0
  227. WMCore/WorkQueue/DataStructs/WorkQueueElementResult.py +152 -0
  228. WMCore/WorkQueue/DataStructs/WorkQueueElementsSummary.py +185 -0
  229. WMCore/WorkQueue/DataStructs/__init__.py +0 -0
  230. WMCore/WorkQueue/Policy/End/EndPolicyInterface.py +44 -0
  231. WMCore/WorkQueue/Policy/End/SingleShot.py +22 -0
  232. WMCore/WorkQueue/Policy/End/__init__.py +32 -0
  233. WMCore/WorkQueue/Policy/PolicyInterface.py +17 -0
  234. WMCore/WorkQueue/Policy/Start/Block.py +258 -0
  235. WMCore/WorkQueue/Policy/Start/Dataset.py +180 -0
  236. WMCore/WorkQueue/Policy/Start/MonteCarlo.py +131 -0
  237. WMCore/WorkQueue/Policy/Start/ResubmitBlock.py +171 -0
  238. WMCore/WorkQueue/Policy/Start/StartPolicyInterface.py +316 -0
  239. WMCore/WorkQueue/Policy/Start/__init__.py +34 -0
  240. WMCore/WorkQueue/Policy/__init__.py +57 -0
  241. WMCore/WorkQueue/WMBSHelper.py +772 -0
  242. WMCore/WorkQueue/WorkQueue.py +1237 -0
  243. WMCore/WorkQueue/WorkQueueBackend.py +750 -0
  244. WMCore/WorkQueue/WorkQueueBase.py +39 -0
  245. WMCore/WorkQueue/WorkQueueExceptions.py +44 -0
  246. WMCore/WorkQueue/WorkQueueReqMgrInterface.py +278 -0
  247. WMCore/WorkQueue/WorkQueueUtils.py +130 -0
  248. WMCore/WorkQueue/__init__.py +13 -0
  249. WMCore/Wrappers/JsonWrapper/JSONThunker.py +342 -0
  250. WMCore/Wrappers/JsonWrapper/__init__.py +7 -0
  251. WMCore/Wrappers/__init__.py +6 -0
  252. WMCore/__init__.py +10 -0
  253. wmglobalqueue-2.3.10.data/data/bin/wmc-dist-patch +15 -0
  254. wmglobalqueue-2.3.10.data/data/bin/wmc-dist-unpatch +8 -0
  255. wmglobalqueue-2.3.10.data/data/bin/wmc-httpd +3 -0
  256. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/.couchapprc +1 -0
  257. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/README.md +40 -0
  258. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/index.html +264 -0
  259. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/ElementInfoByWorkflow.js +96 -0
  260. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/StuckElementInfo.js +57 -0
  261. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/WorkloadInfoTable.js +80 -0
  262. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/dataTable.js +70 -0
  263. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/namespace.js +23 -0
  264. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/style/main.css +75 -0
  265. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/couchapp.json +4 -0
  266. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/childQueueFilter.js +13 -0
  267. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/filterDeletedDocs.js +3 -0
  268. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/queueFilter.js +11 -0
  269. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/language +1 -0
  270. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/mustache.js +333 -0
  271. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/validate.js +27 -0
  272. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/workqueue_utils.js +61 -0
  273. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/elementsDetail.js +28 -0
  274. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/filter.js +86 -0
  275. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/stuckElements.js +38 -0
  276. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workRestrictions.js +153 -0
  277. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workflowSummary.js +28 -0
  278. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/rewrites.json +73 -0
  279. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/redirect.js +23 -0
  280. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/status.js +40 -0
  281. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/ElementSummaryByWorkflow.html +27 -0
  282. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/StuckElementSummary.html +26 -0
  283. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/TaskStatus.html +23 -0
  284. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/WorkflowSummary.html +27 -0
  285. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/workqueue-common-lib.html +2 -0
  286. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib-remote.html +16 -0
  287. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib.html +18 -0
  288. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/updates/in-place.js +50 -0
  289. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/validate_doc_update.js +8 -0
  290. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.couch.app.js +235 -0
  291. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.pathbinder.js +173 -0
  292. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/map.js +8 -0
  293. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/reduce.js +2 -0
  294. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/map.js +8 -0
  295. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/reduce.js +2 -0
  296. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/map.js +8 -0
  297. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/reduce.js +2 -0
  298. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/map.js +11 -0
  299. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/reduce.js +1 -0
  300. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/availableByPriority/map.js +6 -0
  301. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/conflicts/map.js +5 -0
  302. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elements/map.js +5 -0
  303. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByData/map.js +8 -0
  304. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParent/map.js +8 -0
  305. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParentData/map.js +8 -0
  306. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByPileupData/map.js +8 -0
  307. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByStatus/map.js +8 -0
  308. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsBySubscription/map.js +6 -0
  309. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/map.js +8 -0
  310. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/reduce.js +3 -0
  311. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsDetailByWorkflowAndStatus/map.js +26 -0
  312. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/map.js +10 -0
  313. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/reduce.js +1 -0
  314. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/map.js +6 -0
  315. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/reduce.js +1 -0
  316. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/map.js +6 -0
  317. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/reduce.js +1 -0
  318. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/map.js +6 -0
  319. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/reduce.js +1 -0
  320. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/map.js +6 -0
  321. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/reduce.js +1 -0
  322. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/map.js +6 -0
  323. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/reduce.js +1 -0
  324. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/map.js +6 -0
  325. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/reduce.js +1 -0
  326. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/openRequests/map.js +6 -0
  327. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/recent-items/map.js +5 -0
  328. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/map.js +6 -0
  329. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/reduce.js +1 -0
  330. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/specsByWorkflow/map.js +5 -0
  331. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/stuckElements/map.js +38 -0
  332. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/map.js +12 -0
  333. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/reduce.js +3 -0
  334. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/map.js +6 -0
  335. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/reduce.js +2 -0
  336. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/map.js +6 -0
  337. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/reduce.js +2 -0
  338. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/map.js +9 -0
  339. wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/reduce.js +10 -0
  340. wmglobalqueue-2.3.10.dist-info/LICENSE +202 -0
  341. wmglobalqueue-2.3.10.dist-info/METADATA +24 -0
  342. wmglobalqueue-2.3.10.dist-info/NOTICE +16 -0
  343. wmglobalqueue-2.3.10.dist-info/RECORD +345 -0
  344. wmglobalqueue-2.3.10.dist-info/WHEEL +5 -0
  345. 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