rucio 37.0.0rc1__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 rucio might be problematic. Click here for more details.

Files changed (487) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/cli/__init__.py +14 -0
  4. rucio/cli/account.py +216 -0
  5. rucio/cli/bin_legacy/__init__.py +13 -0
  6. rucio/cli/bin_legacy/rucio.py +2825 -0
  7. rucio/cli/bin_legacy/rucio_admin.py +2500 -0
  8. rucio/cli/command.py +272 -0
  9. rucio/cli/config.py +72 -0
  10. rucio/cli/did.py +191 -0
  11. rucio/cli/download.py +128 -0
  12. rucio/cli/lifetime_exception.py +33 -0
  13. rucio/cli/replica.py +162 -0
  14. rucio/cli/rse.py +293 -0
  15. rucio/cli/rule.py +158 -0
  16. rucio/cli/scope.py +40 -0
  17. rucio/cli/subscription.py +73 -0
  18. rucio/cli/upload.py +60 -0
  19. rucio/cli/utils.py +226 -0
  20. rucio/client/__init__.py +15 -0
  21. rucio/client/accountclient.py +432 -0
  22. rucio/client/accountlimitclient.py +183 -0
  23. rucio/client/baseclient.py +983 -0
  24. rucio/client/client.py +120 -0
  25. rucio/client/configclient.py +126 -0
  26. rucio/client/credentialclient.py +59 -0
  27. rucio/client/didclient.py +868 -0
  28. rucio/client/diracclient.py +56 -0
  29. rucio/client/downloadclient.py +1783 -0
  30. rucio/client/exportclient.py +44 -0
  31. rucio/client/fileclient.py +50 -0
  32. rucio/client/importclient.py +42 -0
  33. rucio/client/lifetimeclient.py +90 -0
  34. rucio/client/lockclient.py +109 -0
  35. rucio/client/metaconventionsclient.py +140 -0
  36. rucio/client/pingclient.py +44 -0
  37. rucio/client/replicaclient.py +452 -0
  38. rucio/client/requestclient.py +125 -0
  39. rucio/client/richclient.py +317 -0
  40. rucio/client/rseclient.py +746 -0
  41. rucio/client/ruleclient.py +294 -0
  42. rucio/client/scopeclient.py +90 -0
  43. rucio/client/subscriptionclient.py +173 -0
  44. rucio/client/touchclient.py +82 -0
  45. rucio/client/uploadclient.py +969 -0
  46. rucio/common/__init__.py +13 -0
  47. rucio/common/bittorrent.py +234 -0
  48. rucio/common/cache.py +111 -0
  49. rucio/common/checksum.py +168 -0
  50. rucio/common/client.py +122 -0
  51. rucio/common/config.py +788 -0
  52. rucio/common/constants.py +217 -0
  53. rucio/common/constraints.py +17 -0
  54. rucio/common/didtype.py +237 -0
  55. rucio/common/dumper/__init__.py +342 -0
  56. rucio/common/dumper/consistency.py +497 -0
  57. rucio/common/dumper/data_models.py +362 -0
  58. rucio/common/dumper/path_parsing.py +75 -0
  59. rucio/common/exception.py +1208 -0
  60. rucio/common/extra.py +31 -0
  61. rucio/common/logging.py +420 -0
  62. rucio/common/pcache.py +1409 -0
  63. rucio/common/plugins.py +185 -0
  64. rucio/common/policy.py +93 -0
  65. rucio/common/schema/__init__.py +200 -0
  66. rucio/common/schema/generic.py +416 -0
  67. rucio/common/schema/generic_multi_vo.py +395 -0
  68. rucio/common/stomp_utils.py +423 -0
  69. rucio/common/stopwatch.py +55 -0
  70. rucio/common/test_rucio_server.py +154 -0
  71. rucio/common/types.py +483 -0
  72. rucio/common/utils.py +1688 -0
  73. rucio/core/__init__.py +13 -0
  74. rucio/core/account.py +496 -0
  75. rucio/core/account_counter.py +236 -0
  76. rucio/core/account_limit.py +425 -0
  77. rucio/core/authentication.py +620 -0
  78. rucio/core/config.py +437 -0
  79. rucio/core/credential.py +224 -0
  80. rucio/core/did.py +3004 -0
  81. rucio/core/did_meta_plugins/__init__.py +252 -0
  82. rucio/core/did_meta_plugins/did_column_meta.py +331 -0
  83. rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
  84. rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
  85. rucio/core/did_meta_plugins/filter_engine.py +672 -0
  86. rucio/core/did_meta_plugins/json_meta.py +240 -0
  87. rucio/core/did_meta_plugins/mongo_meta.py +229 -0
  88. rucio/core/did_meta_plugins/postgres_meta.py +352 -0
  89. rucio/core/dirac.py +237 -0
  90. rucio/core/distance.py +187 -0
  91. rucio/core/exporter.py +59 -0
  92. rucio/core/heartbeat.py +363 -0
  93. rucio/core/identity.py +301 -0
  94. rucio/core/importer.py +260 -0
  95. rucio/core/lifetime_exception.py +377 -0
  96. rucio/core/lock.py +577 -0
  97. rucio/core/message.py +288 -0
  98. rucio/core/meta_conventions.py +203 -0
  99. rucio/core/monitor.py +448 -0
  100. rucio/core/naming_convention.py +195 -0
  101. rucio/core/nongrid_trace.py +136 -0
  102. rucio/core/oidc.py +1463 -0
  103. rucio/core/permission/__init__.py +161 -0
  104. rucio/core/permission/generic.py +1124 -0
  105. rucio/core/permission/generic_multi_vo.py +1144 -0
  106. rucio/core/quarantined_replica.py +224 -0
  107. rucio/core/replica.py +4483 -0
  108. rucio/core/replica_sorter.py +362 -0
  109. rucio/core/request.py +3091 -0
  110. rucio/core/rse.py +2079 -0
  111. rucio/core/rse_counter.py +185 -0
  112. rucio/core/rse_expression_parser.py +459 -0
  113. rucio/core/rse_selector.py +304 -0
  114. rucio/core/rule.py +4484 -0
  115. rucio/core/rule_grouping.py +1620 -0
  116. rucio/core/scope.py +181 -0
  117. rucio/core/subscription.py +362 -0
  118. rucio/core/topology.py +490 -0
  119. rucio/core/trace.py +375 -0
  120. rucio/core/transfer.py +1531 -0
  121. rucio/core/vo.py +169 -0
  122. rucio/core/volatile_replica.py +151 -0
  123. rucio/daemons/__init__.py +13 -0
  124. rucio/daemons/abacus/__init__.py +13 -0
  125. rucio/daemons/abacus/account.py +116 -0
  126. rucio/daemons/abacus/collection_replica.py +124 -0
  127. rucio/daemons/abacus/rse.py +117 -0
  128. rucio/daemons/atropos/__init__.py +13 -0
  129. rucio/daemons/atropos/atropos.py +242 -0
  130. rucio/daemons/auditor/__init__.py +289 -0
  131. rucio/daemons/auditor/hdfs.py +97 -0
  132. rucio/daemons/auditor/srmdumps.py +355 -0
  133. rucio/daemons/automatix/__init__.py +13 -0
  134. rucio/daemons/automatix/automatix.py +304 -0
  135. rucio/daemons/badreplicas/__init__.py +13 -0
  136. rucio/daemons/badreplicas/minos.py +322 -0
  137. rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
  138. rucio/daemons/badreplicas/necromancer.py +196 -0
  139. rucio/daemons/bb8/__init__.py +13 -0
  140. rucio/daemons/bb8/bb8.py +353 -0
  141. rucio/daemons/bb8/common.py +759 -0
  142. rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
  143. rucio/daemons/bb8/t2_background_rebalance.py +153 -0
  144. rucio/daemons/cache/__init__.py +13 -0
  145. rucio/daemons/cache/consumer.py +133 -0
  146. rucio/daemons/common.py +405 -0
  147. rucio/daemons/conveyor/__init__.py +13 -0
  148. rucio/daemons/conveyor/common.py +562 -0
  149. rucio/daemons/conveyor/finisher.py +529 -0
  150. rucio/daemons/conveyor/poller.py +394 -0
  151. rucio/daemons/conveyor/preparer.py +205 -0
  152. rucio/daemons/conveyor/receiver.py +179 -0
  153. rucio/daemons/conveyor/stager.py +133 -0
  154. rucio/daemons/conveyor/submitter.py +403 -0
  155. rucio/daemons/conveyor/throttler.py +532 -0
  156. rucio/daemons/follower/__init__.py +13 -0
  157. rucio/daemons/follower/follower.py +101 -0
  158. rucio/daemons/hermes/__init__.py +13 -0
  159. rucio/daemons/hermes/hermes.py +534 -0
  160. rucio/daemons/judge/__init__.py +13 -0
  161. rucio/daemons/judge/cleaner.py +159 -0
  162. rucio/daemons/judge/evaluator.py +185 -0
  163. rucio/daemons/judge/injector.py +162 -0
  164. rucio/daemons/judge/repairer.py +154 -0
  165. rucio/daemons/oauthmanager/__init__.py +13 -0
  166. rucio/daemons/oauthmanager/oauthmanager.py +198 -0
  167. rucio/daemons/reaper/__init__.py +13 -0
  168. rucio/daemons/reaper/dark_reaper.py +282 -0
  169. rucio/daemons/reaper/reaper.py +739 -0
  170. rucio/daemons/replicarecoverer/__init__.py +13 -0
  171. rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
  172. rucio/daemons/rsedecommissioner/__init__.py +13 -0
  173. rucio/daemons/rsedecommissioner/config.py +81 -0
  174. rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
  175. rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
  176. rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
  177. rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
  178. rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
  179. rucio/daemons/storage/__init__.py +13 -0
  180. rucio/daemons/storage/consistency/__init__.py +13 -0
  181. rucio/daemons/storage/consistency/actions.py +848 -0
  182. rucio/daemons/tracer/__init__.py +13 -0
  183. rucio/daemons/tracer/kronos.py +511 -0
  184. rucio/daemons/transmogrifier/__init__.py +13 -0
  185. rucio/daemons/transmogrifier/transmogrifier.py +762 -0
  186. rucio/daemons/undertaker/__init__.py +13 -0
  187. rucio/daemons/undertaker/undertaker.py +137 -0
  188. rucio/db/__init__.py +13 -0
  189. rucio/db/sqla/__init__.py +52 -0
  190. rucio/db/sqla/constants.py +206 -0
  191. rucio/db/sqla/migrate_repo/__init__.py +13 -0
  192. rucio/db/sqla/migrate_repo/env.py +110 -0
  193. rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
  194. rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
  195. rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
  196. rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
  197. rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
  198. rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
  199. rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
  200. rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
  201. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
  202. rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
  203. rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
  204. rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
  205. rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
  206. rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
  207. rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
  208. rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
  209. rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
  210. rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
  211. rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
  212. rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
  213. rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
  214. rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
  215. rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
  216. rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
  217. rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
  218. rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
  219. rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
  220. rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
  221. rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
  222. rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
  223. rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
  224. rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
  225. rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
  226. rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
  227. rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
  228. rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
  229. rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
  230. rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
  231. rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
  232. rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
  233. rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
  234. rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
  235. rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
  236. rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
  237. rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
  238. rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
  239. rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
  240. rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
  241. rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
  242. rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
  243. rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
  244. rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
  245. rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
  246. rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
  247. rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
  248. rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
  249. rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
  250. rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
  251. rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
  252. rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
  253. rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
  254. rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
  255. rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
  256. rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
  257. rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
  258. rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
  259. rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
  260. rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
  261. rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
  262. rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
  263. rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
  264. rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
  265. rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
  266. rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
  267. rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
  268. rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
  269. rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
  270. rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
  271. rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
  272. rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
  273. rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
  274. rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
  275. rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
  276. rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
  277. rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
  278. rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
  279. rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
  280. rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
  281. rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
  282. rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
  283. rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
  284. rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
  285. rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
  286. rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
  287. rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
  288. rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
  289. rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
  290. rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
  291. rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
  292. rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
  293. rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
  294. rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
  295. rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
  296. rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
  297. rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
  298. rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
  299. rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
  300. rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
  301. rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
  302. rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
  303. rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
  304. rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
  305. rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
  306. rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
  307. rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
  308. rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
  309. rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
  310. rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
  311. rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
  312. rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
  313. rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
  314. rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
  315. rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
  316. rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
  317. rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
  318. rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
  319. rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
  320. rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
  321. rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
  322. rucio/db/sqla/models.py +1743 -0
  323. rucio/db/sqla/sautils.py +55 -0
  324. rucio/db/sqla/session.py +529 -0
  325. rucio/db/sqla/types.py +206 -0
  326. rucio/db/sqla/util.py +543 -0
  327. rucio/gateway/__init__.py +13 -0
  328. rucio/gateway/account.py +345 -0
  329. rucio/gateway/account_limit.py +363 -0
  330. rucio/gateway/authentication.py +381 -0
  331. rucio/gateway/config.py +227 -0
  332. rucio/gateway/credential.py +70 -0
  333. rucio/gateway/did.py +987 -0
  334. rucio/gateway/dirac.py +83 -0
  335. rucio/gateway/exporter.py +60 -0
  336. rucio/gateway/heartbeat.py +76 -0
  337. rucio/gateway/identity.py +189 -0
  338. rucio/gateway/importer.py +46 -0
  339. rucio/gateway/lifetime_exception.py +121 -0
  340. rucio/gateway/lock.py +153 -0
  341. rucio/gateway/meta_conventions.py +98 -0
  342. rucio/gateway/permission.py +74 -0
  343. rucio/gateway/quarantined_replica.py +79 -0
  344. rucio/gateway/replica.py +538 -0
  345. rucio/gateway/request.py +330 -0
  346. rucio/gateway/rse.py +632 -0
  347. rucio/gateway/rule.py +437 -0
  348. rucio/gateway/scope.py +100 -0
  349. rucio/gateway/subscription.py +280 -0
  350. rucio/gateway/vo.py +126 -0
  351. rucio/rse/__init__.py +96 -0
  352. rucio/rse/protocols/__init__.py +13 -0
  353. rucio/rse/protocols/bittorrent.py +194 -0
  354. rucio/rse/protocols/cache.py +111 -0
  355. rucio/rse/protocols/dummy.py +100 -0
  356. rucio/rse/protocols/gfal.py +708 -0
  357. rucio/rse/protocols/globus.py +243 -0
  358. rucio/rse/protocols/http_cache.py +82 -0
  359. rucio/rse/protocols/mock.py +123 -0
  360. rucio/rse/protocols/ngarc.py +209 -0
  361. rucio/rse/protocols/posix.py +250 -0
  362. rucio/rse/protocols/protocol.py +361 -0
  363. rucio/rse/protocols/rclone.py +365 -0
  364. rucio/rse/protocols/rfio.py +145 -0
  365. rucio/rse/protocols/srm.py +338 -0
  366. rucio/rse/protocols/ssh.py +414 -0
  367. rucio/rse/protocols/storm.py +195 -0
  368. rucio/rse/protocols/webdav.py +594 -0
  369. rucio/rse/protocols/xrootd.py +302 -0
  370. rucio/rse/rsemanager.py +881 -0
  371. rucio/rse/translation.py +260 -0
  372. rucio/tests/__init__.py +13 -0
  373. rucio/tests/common.py +280 -0
  374. rucio/tests/common_server.py +149 -0
  375. rucio/transfertool/__init__.py +13 -0
  376. rucio/transfertool/bittorrent.py +200 -0
  377. rucio/transfertool/bittorrent_driver.py +50 -0
  378. rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
  379. rucio/transfertool/fts3.py +1600 -0
  380. rucio/transfertool/fts3_plugins.py +152 -0
  381. rucio/transfertool/globus.py +201 -0
  382. rucio/transfertool/globus_library.py +181 -0
  383. rucio/transfertool/mock.py +89 -0
  384. rucio/transfertool/transfertool.py +221 -0
  385. rucio/vcsversion.py +11 -0
  386. rucio/version.py +45 -0
  387. rucio/web/__init__.py +13 -0
  388. rucio/web/rest/__init__.py +13 -0
  389. rucio/web/rest/flaskapi/__init__.py +13 -0
  390. rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
  391. rucio/web/rest/flaskapi/v1/__init__.py +13 -0
  392. rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
  393. rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
  394. rucio/web/rest/flaskapi/v1/archives.py +102 -0
  395. rucio/web/rest/flaskapi/v1/auth.py +1644 -0
  396. rucio/web/rest/flaskapi/v1/common.py +426 -0
  397. rucio/web/rest/flaskapi/v1/config.py +304 -0
  398. rucio/web/rest/flaskapi/v1/credentials.py +213 -0
  399. rucio/web/rest/flaskapi/v1/dids.py +2340 -0
  400. rucio/web/rest/flaskapi/v1/dirac.py +116 -0
  401. rucio/web/rest/flaskapi/v1/export.py +75 -0
  402. rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
  403. rucio/web/rest/flaskapi/v1/identities.py +285 -0
  404. rucio/web/rest/flaskapi/v1/import.py +132 -0
  405. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
  406. rucio/web/rest/flaskapi/v1/locks.py +358 -0
  407. rucio/web/rest/flaskapi/v1/main.py +91 -0
  408. rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
  409. rucio/web/rest/flaskapi/v1/metrics.py +36 -0
  410. rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
  411. rucio/web/rest/flaskapi/v1/ping.py +88 -0
  412. rucio/web/rest/flaskapi/v1/redirect.py +366 -0
  413. rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
  414. rucio/web/rest/flaskapi/v1/requests.py +998 -0
  415. rucio/web/rest/flaskapi/v1/rses.py +2250 -0
  416. rucio/web/rest/flaskapi/v1/rules.py +854 -0
  417. rucio/web/rest/flaskapi/v1/scopes.py +159 -0
  418. rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
  419. rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
  420. rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
  421. rucio/web/rest/flaskapi/v1/traces.py +137 -0
  422. rucio/web/rest/flaskapi/v1/types.py +20 -0
  423. rucio/web/rest/flaskapi/v1/vos.py +278 -0
  424. rucio/web/rest/main.py +18 -0
  425. rucio/web/rest/metrics.py +27 -0
  426. rucio/web/rest/ping.py +27 -0
  427. rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
  428. rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
  429. rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
  430. rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
  431. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
  432. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
  433. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
  434. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
  435. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
  436. rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
  437. rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
  438. rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
  439. rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
  440. rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
  441. rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
  442. rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
  443. rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
  444. rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
  445. rucio-37.0.0rc1.data/scripts/rucio +133 -0
  446. rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
  447. rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
  448. rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
  449. rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
  450. rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
  451. rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
  452. rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
  453. rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
  454. rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
  455. rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
  456. rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
  457. rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
  458. rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
  459. rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
  460. rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
  461. rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
  462. rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
  463. rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
  464. rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
  465. rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
  466. rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
  467. rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
  468. rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
  469. rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
  470. rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
  471. rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
  472. rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
  473. rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
  474. rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
  475. rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
  476. rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
  477. rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
  478. rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
  479. rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
  480. rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
  481. rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
  482. rucio-37.0.0rc1.dist-info/METADATA +92 -0
  483. rucio-37.0.0rc1.dist-info/RECORD +487 -0
  484. rucio-37.0.0rc1.dist-info/WHEEL +5 -0
  485. rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
  486. rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
  487. rucio-37.0.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,672 @@
1
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import ast
16
+ import fnmatch
17
+ import operator
18
+ from datetime import date, datetime, timedelta
19
+ from importlib import import_module
20
+ from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
21
+
22
+ import sqlalchemy
23
+ from sqlalchemy import Select, and_, cast, or_, select
24
+ from sqlalchemy.orm import InstrumentedAttribute
25
+ from sqlalchemy.sql.expression import text
26
+
27
+ from rucio.common import exception
28
+ from rucio.common.utils import parse_did_filter_from_string_fe
29
+ from rucio.db.sqla.constants import DIDType
30
+ from rucio.db.sqla.session import read_session
31
+
32
+ if TYPE_CHECKING:
33
+ from collections.abc import Callable, Iterable
34
+
35
+ from sqlalchemy.orm import Session
36
+
37
+ from rucio.db.sqla.models import ModelBase
38
+
39
+ KeyType = TypeVar("KeyType", str, InstrumentedAttribute)
40
+ FilterTuple = tuple[KeyType, Callable[[object, object], Any], Union[bool, datetime, float, str]]
41
+
42
+ # lookup table converting keyword suffixes to pythonic operators.
43
+ OPERATORS_CONVERSION_LUT = {
44
+ "gte": operator.ge,
45
+ "lte": operator.le,
46
+ "lt": operator.lt,
47
+ "gt": operator.gt,
48
+ "ne": operator.ne,
49
+ "": operator.eq
50
+ }
51
+
52
+ ELASTIC_OP_MAP = {
53
+ operator.eq: "=",
54
+ operator.ne: "!",
55
+ operator.gt: "gt",
56
+ operator.lt: "lt",
57
+ operator.ge: "gte",
58
+ operator.le: "lte"
59
+ }
60
+
61
+ # lookup table converting pythonic operators to oracle operators
62
+ ORACLE_OP_MAP = {
63
+ operator.eq: "==",
64
+ operator.ne: "<>",
65
+ operator.gt: ">",
66
+ operator.lt: "<",
67
+ operator.ge: ">=",
68
+ operator.le: "<="
69
+ }
70
+
71
+ # lookup table converting pythonic operators to postgres operators
72
+ POSTGRES_OP_MAP = {
73
+ operator.eq: "=",
74
+ operator.ne: "!=",
75
+ operator.gt: ">",
76
+ operator.lt: "<",
77
+ operator.ge: ">=",
78
+ operator.le: "<="
79
+ }
80
+
81
+ # understood date formats.
82
+ VALID_DATE_FORMATS = (
83
+ '%Y-%m-%d %H:%M:%S',
84
+ '%Y-%m-%dT%H:%M:%S',
85
+ '%Y-%m-%d %H:%M:%S.%fZ',
86
+ '%Y-%m-%dT%H:%M:%S.%fZ',
87
+ '%a, %d %b %Y %H:%M:%S UTC'
88
+ )
89
+
90
+
91
+ class FilterEngine:
92
+ """
93
+ An engine to provide advanced filtering functionality to DID listing requests.
94
+ """
95
+ def __init__(
96
+ self,
97
+ filters: Union[str, dict[str, Any], list[dict[str, Any]]],
98
+ model_class: Optional[type["ModelBase"]] = None,
99
+ strict_coerce: bool = True
100
+ ):
101
+ if isinstance(filters, str):
102
+ filters, _ = parse_did_filter_from_string_fe(filters, omit_name=True)
103
+ elif isinstance(filters, dict):
104
+ filters = [filters]
105
+ elif isinstance(filters, list):
106
+ filters = filters
107
+ else:
108
+ raise exception.DIDFilterSyntaxError("Input filters are of an unrecognised type.")
109
+
110
+ filters = self._make_input_backwards_compatible(filters=filters)
111
+ self._filters, self.mandatory_model_attributes = self._translate_filters(filters=filters, model_class=model_class, strict_coerce=strict_coerce)
112
+ self._sanity_check_translated_filters()
113
+
114
+ @property
115
+ def filters(self) -> list[list["FilterTuple"]]:
116
+ return self._filters
117
+
118
+ def _coerce_filter_word_to_model_attribute(self, word: Any, model_class: Optional[type["ModelBase"]], strict: bool = True) -> Any:
119
+ """
120
+ Attempts to coerce a filter word to an attribute of a <model_class>.
121
+
122
+ :param model_class: The word.
123
+ :param model_class: The SQL model class.
124
+ :params: strict: Enforce that keywords must be coercible to a model attribute.
125
+ :returns: The coerced attribute if successful or (if strict is False) the word if not.
126
+ :raises: KeyNotFound
127
+ """
128
+ if isinstance(word, str):
129
+ if hasattr(model_class, word):
130
+ return getattr(model_class, word)
131
+ else:
132
+ if strict:
133
+ raise exception.KeyNotFound("'{}' keyword could not be coerced to model class attribute. Attribute not found.".format(word))
134
+ return word
135
+
136
+ def _make_input_backwards_compatible(self, filters: list[dict[str, Any]]) -> list[dict[str, Any]]:
137
+ """
138
+ Backwards compatibility for previous versions of filtering.
139
+
140
+ Does the following:
141
+ - converts "created_after" key to "created_at.gte"
142
+ - converts "created_before" key to "created_at.lte"
143
+ """
144
+ for or_group in filters:
145
+ if 'created_after' in or_group:
146
+ or_group['created_at.gte'] = or_group.pop('created_after')
147
+ elif 'created_before' in or_group:
148
+ or_group['created_at.lte'] = or_group.pop('created_before')
149
+ return filters
150
+
151
+ def _sanity_check_translated_filters(self) -> None:
152
+ """
153
+ Perform a few sanity checks on translated filters.
154
+
155
+ Checks the following are all true:
156
+ 1. 'did_type' filters use an equals operator,
157
+ 2. 'name' filters use an equality operator,
158
+ 3. 'length' filters are parsable as an int type,
159
+ 4. wildcard expressions use an equality operator,
160
+ 5. 'created_at' value adheres to one of the date formats <VALID_DATE_FORMATS>,
161
+ 6. there are no duplicate key+operator criteria.
162
+
163
+ :raises: ValueError, DIDFilterSyntaxError, DuplicateCriteriaInDIDFilter
164
+ """
165
+ for or_group in self._filters:
166
+ or_group_test_duplicates = []
167
+ for and_group in or_group:
168
+ key, oper, value = and_group
169
+ if key == 'did_type': # (1)
170
+ if oper != operator.eq:
171
+ raise ValueError("Type operator must be equals.")
172
+ if key == 'name': # (2)
173
+ if oper not in (operator.eq, operator.ne):
174
+ raise ValueError("Name operator must be an equality operator.")
175
+ if key == 'length': # (3)
176
+ try:
177
+ int(value) # type: ignore
178
+ except ValueError:
179
+ raise ValueError('Length has to be an integer value.')
180
+
181
+ if isinstance(value, str): # (4)
182
+ if any([char in value for char in ['*', '%']]):
183
+ if oper not in [operator.eq, operator.ne]:
184
+ raise exception.DIDFilterSyntaxError("Wildcards can only be used with equality operators")
185
+
186
+ if key == 'created_at': # (5)
187
+ if not isinstance(value, datetime):
188
+ raise exception.DIDFilterSyntaxError("Couldn't parse date '{}'. Valid formats are: {}".format(value, VALID_DATE_FORMATS))
189
+
190
+ or_group_test_duplicates.append((key, oper))
191
+ if len(set(or_group_test_duplicates)) != len(or_group_test_duplicates): # (6)
192
+ raise exception.DuplicateCriteriaInDIDFilter()
193
+
194
+ def _translate_filters(
195
+ self,
196
+ filters: "Iterable[dict[str, Any]]",
197
+ model_class: Optional[type["ModelBase"]],
198
+ strict_coerce: bool = True
199
+ ) -> tuple[list[list["FilterTuple"]], list[InstrumentedAttribute[Any]]]:
200
+ """
201
+ Reformats filters from:
202
+
203
+ [{or_group_1->key_1.or_group_1->operator_1: or_group_1->value_1,
204
+ {or_group_1->key_m.or_group_1->operator_m: or_group_1->value_m}
205
+ ...
206
+ {or_group_n->key_1.or_group_n->operator_1: or_group_n->value_1,
207
+ {or_group_n->key_m.or_group_n->operator_m: or_group_n->value_m}
208
+ ]
209
+
210
+ to the format used by the engine:
211
+
212
+ [[[or_group_1->key_1, or_group_1->operator_1, or_group_1->value_1],
213
+ ...
214
+ [or_group_1->key_m, or_group_1->operator_m, or_group_1->value_m]
215
+ ],
216
+ ...
217
+ [[or_group_n->key_1, or_group_n->operator_1, or_group_n->value_1],
218
+ ...
219
+ [or_group_n->key_m, or_group_n->operator_m, or_group_n->value_m]
220
+ ]
221
+ ]
222
+
223
+ replacing all filter operator suffixes with python equivalents using the LUT, <OPERATORS_CONVERSION_LUT>, and
224
+ coercing all filter words to their corresponding <model_class> attribute.
225
+
226
+ Typecasting of values is also attempted.
227
+
228
+ :param filters: The filters to translate.
229
+ :param model_class: The SQL model class.
230
+ :param strict_coerce: Enforce that keywords must be coercible to a model attribute.
231
+ :returns: The list of translated filters, and the set of mandatory model attributes to be used in the filter query.
232
+ :raises: MissingModuleException, DIDFilterSyntaxError
233
+ """
234
+ if model_class:
235
+ try:
236
+ import_module(model_class.__module__)
237
+ except ModuleNotFoundError:
238
+ raise exception.MissingModuleException("Model class module not found.")
239
+
240
+ mandatory_model_attributes = set()
241
+ filters_translated = []
242
+ for or_group in filters:
243
+ and_group_parsed = []
244
+ for key, value in or_group.items():
245
+ # KEY
246
+ # Separate key for key name and possible operator.
247
+ key_tokenised = key.split('.')
248
+ if len(key_tokenised) == 1: # no operator suffix found, assume eq
249
+ try:
250
+ key_no_suffix = ast.literal_eval(key)
251
+ except ValueError:
252
+ key_no_suffix = key
253
+ oper = ''
254
+ elif len(key_tokenised) == 2: # operator suffix found
255
+ try:
256
+ key_no_suffix = ast.literal_eval(key_tokenised[0])
257
+ except ValueError:
258
+ key_no_suffix = key_tokenised[0]
259
+ oper = key_tokenised[1]
260
+ else:
261
+ raise exception.DIDFilterSyntaxError
262
+ key_no_suffix = self._coerce_filter_word_to_model_attribute(key_no_suffix, model_class, strict=strict_coerce)
263
+ if not isinstance(key_no_suffix, str):
264
+ mandatory_model_attributes.add(key_no_suffix)
265
+
266
+ # VALUE
267
+ # Typecasting is required when the entry point is the CLI as values will always be string.
268
+ if isinstance(value, str):
269
+ value = self._try_typecast_string(value)
270
+
271
+ # Convert string operator to pythonic operator.
272
+ and_group_parsed.append(
273
+ (key_no_suffix, OPERATORS_CONVERSION_LUT.get(oper), value))
274
+ filters_translated.append(and_group_parsed)
275
+ return filters_translated, list(mandatory_model_attributes)
276
+
277
+ def _try_typecast_string(self, value: str) -> Union[bool, datetime, float, str]:
278
+ """
279
+ Check if string can be typecasted to bool, datetime or float.
280
+
281
+ :param value: The value to be typecasted.
282
+ :returns: The typecasted value.
283
+ """
284
+ value = value.replace('true', 'True').replace('TRUE', 'True')
285
+ value = value.replace('false', 'False').replace('FALSE', 'False')
286
+ for format in VALID_DATE_FORMATS: # try parsing multiple date formats.
287
+ try:
288
+ typecasted_value = datetime.strptime(value, format)
289
+ except ValueError:
290
+ continue
291
+ else:
292
+ return typecasted_value
293
+ try:
294
+ operators = ('+', '-', '*', '/')
295
+ if not any(operator in value for operator in operators): # fix for lax ast literal_eval in earlier python versions
296
+ value = ast.literal_eval(value) # will catch float, int and bool
297
+ except (ValueError, SyntaxError):
298
+ pass
299
+ return value
300
+
301
+ def create_mongo_query(
302
+ self,
303
+ additional_filters: Optional["Iterable[FilterTuple]"] = None
304
+ ) -> dict[str, Any]:
305
+ """
306
+ Returns a single mongo query describing the filters expression.
307
+
308
+ :param additional_filters: additional filters to be applied to all clauses.
309
+ :returns: a mongo query string describing the filters expression.
310
+ """
311
+ additional_filters = additional_filters or []
312
+ # Add additional filters, applied as AND clauses to each OR group.
313
+ for or_group in self._filters:
314
+ for filter in additional_filters:
315
+ or_group.append(list(filter)) # type: ignore
316
+
317
+ or_expressions: list[dict[str, Any]] = []
318
+ for or_group in self._filters:
319
+ and_expressions: list[dict[str, dict[str, Any]]] = []
320
+ for and_group in or_group:
321
+ key, oper, value = and_group
322
+ if isinstance(value, str) and any([char in value for char in ['*', '%']]): # wildcards
323
+ if value in ('*', '%', '*', '%'): # match wildcard exactly == no filtering on key
324
+ continue
325
+ else: # partial match with wildcard == like || notlike
326
+ if oper == operator.eq:
327
+ expression = {
328
+ key: {
329
+ '$regex': fnmatch.translate(value) # translate partial wildcard expression to regex
330
+ }
331
+ }
332
+ elif oper == operator.ne:
333
+ expression = {
334
+ key: {
335
+ '$not': {
336
+ '$regex': fnmatch.translate(value) # translate partial wildcard expression to regex
337
+ }
338
+ }
339
+ }
340
+ else:
341
+ # mongodb operator keywords follow the same function names as operator package but prefixed with $
342
+ expression = {
343
+ key: {
344
+ '${}'.format(oper.__name__): value
345
+ }
346
+ }
347
+
348
+ and_expressions.append(expression)
349
+ if len(and_expressions) > 1: # $and key must have array as value...
350
+ or_expressions.append({'$and': and_expressions})
351
+ else:
352
+ or_expressions.append(and_expressions[0]) # ...otherwise just use the first, and only, entry.
353
+ if len(or_expressions) > 1:
354
+ query_str = {'$or': or_expressions} # $or key must have array as value...
355
+ else:
356
+ query_str = or_expressions[0] # ...otherwise just use the first, and only, entry.
357
+
358
+ return query_str
359
+
360
+ def create_elastic_query(
361
+ self,
362
+ additional_filters: Optional["Iterable[FilterTuple]"] = None
363
+ ) -> dict[str, Any]:
364
+ """
365
+ Returns a single elastic query dictionary describing the filters expression.
366
+
367
+ :param additional_filters: additional filters to be applied to all clauses.
368
+ :returns: an elastic query dictionary describing the filters expression.
369
+ """
370
+
371
+ additional_filters = additional_filters or []
372
+ for or_group in self._filters:
373
+ for _filter in additional_filters:
374
+ or_group.append(list(_filter)) # type: ignore
375
+
376
+ should_clauses = []
377
+ for or_group in self._filters:
378
+ bool_query = {
379
+ "must": [],
380
+ "must_not": []
381
+ }
382
+
383
+ for and_group in or_group:
384
+ key, oper, value = and_group
385
+ key = str(key)
386
+
387
+ if isinstance(value, str) and any(char in value for char in ['*', '%']):
388
+ if value in ('*', '%', '*', '%'):
389
+ bool_query["must"].append({"wildcard": {key: value}})
390
+ else:
391
+ wildcard_query = {"wildcard": {key: value}}
392
+ if oper == operator.eq:
393
+ bool_query["must"].append(wildcard_query)
394
+ elif oper == operator.ne:
395
+ bool_query["must_not"].append(wildcard_query)
396
+ else:
397
+ if oper in [operator.lt, operator.gt, operator.ge, operator.le]:
398
+ elsop = ELASTIC_OP_MAP[oper]
399
+ bool_query["must"].append({"range": {key: {elsop: value}}})
400
+ elif oper == operator.eq:
401
+ bool_query["must"].append({"term": {key: value}})
402
+ elif oper == operator.ne:
403
+ bool_query["must_not"].append({"term": {key: value}})
404
+
405
+ should_clauses.append({"bool": bool_query})
406
+
407
+ query_expression = {"bool": {"should": should_clauses}}
408
+ return query_expression
409
+
410
+ def create_postgres_query(
411
+ self,
412
+ additional_filters: Optional["Iterable[FilterTuple]"] = None,
413
+ fixed_table_columns: Union[tuple[str, ...], dict[str, str]] = ('scope', 'name', 'vo'),
414
+ jsonb_column: str = 'data'
415
+ ) -> str:
416
+ """
417
+ Returns a single postgres query describing the filters expression.
418
+
419
+ :param additional_filters: additional filters to be applied to all clauses.
420
+ :param fixed_table_columns: the table columns
421
+ :returns: a postgres query string describing the filters expression.
422
+ """
423
+ additional_filters = additional_filters or []
424
+ # Add additional filters, applied as AND clauses to each OR group.
425
+ for or_group in self._filters:
426
+ for _filter in additional_filters:
427
+ or_group.append(list(_filter)) # type: ignore
428
+
429
+ or_expressions: list[str] = []
430
+ for or_group in self._filters:
431
+ and_expressions: list[str] = []
432
+ for and_group in or_group:
433
+ key, oper, value = and_group
434
+ if key in fixed_table_columns: # is this key filtering on a column or in the jsonb?
435
+ is_in_json_column = False
436
+ else:
437
+ is_in_json_column = True
438
+ if isinstance(value, str) and any([char in value for char in ['*', '%']]): # wildcards
439
+ if value in ('*', '%', '*', '%'): # match wildcard exactly == no filtering on key
440
+ continue
441
+ else: # partial match with wildcard == like || notlike
442
+ if oper == operator.eq:
443
+ if is_in_json_column:
444
+ expression = "{}->>'{}' LIKE '{}' ".format(jsonb_column, key, value.replace('*', '%').replace('_', '\_')) # NOQA: W605
445
+ else:
446
+ expression = "{} LIKE '{}' ".format(key, value.replace('*', '%').replace('_', '\_')) # NOQA: W605
447
+ elif oper == operator.ne:
448
+ if is_in_json_column:
449
+ expression = "{}->>'{}' NOT LIKE '{}' ".format(jsonb_column, key, value.replace('*', '%').replace('_', '\_')) # NOQA: W605
450
+ else:
451
+ expression = "{} NOT LIKE '{}' ".format(key, value.replace('*', '%').replace('_', '\_')) # NOQA: W605
452
+ else:
453
+ # Infer what type key should be cast to from typecasting the value in the expression.
454
+ try:
455
+ if isinstance(value, int): # this could be bool or int (as bool subclass of int)
456
+ if isinstance(value, bool):
457
+ if is_in_json_column:
458
+ expression = "({}->>'{}')::boolean {} {}".format(jsonb_column, key, POSTGRES_OP_MAP[oper], str(value).lower())
459
+ else:
460
+ expression = "{}::boolean {} {}".format(key, POSTGRES_OP_MAP[oper], str(value).lower())
461
+ else:
462
+ # cast as float, not integer, to avoid potentially losing precision in key
463
+ if is_in_json_column:
464
+ expression = "({}->>'{}')::float {} {}".format(jsonb_column, key, POSTGRES_OP_MAP[oper], value)
465
+ else:
466
+ expression = "{}::float {} {}".format(key, POSTGRES_OP_MAP[oper], value)
467
+ elif isinstance(value, float):
468
+ if is_in_json_column:
469
+ expression = "({}->>'{}')::float {} {}".format(jsonb_column, key, POSTGRES_OP_MAP[oper], value)
470
+ else:
471
+ expression = "{}::float {} {}".format(key, POSTGRES_OP_MAP[oper], value)
472
+ elif isinstance(value, datetime):
473
+ if is_in_json_column:
474
+ expression = "({}->>'{}')::timestamp {} '{}'".format(jsonb_column, key, POSTGRES_OP_MAP[oper], value)
475
+ else:
476
+ expression = "{}::timestamp {} '{}'".format(key, POSTGRES_OP_MAP[oper], value)
477
+ else:
478
+ if is_in_json_column:
479
+ expression = "{}->>'{}' {} '{}'".format(jsonb_column, key, POSTGRES_OP_MAP[oper], value)
480
+ else:
481
+ expression = "{} {} '{}'".format(key, POSTGRES_OP_MAP[oper], value)
482
+ except Exception as e:
483
+ raise exception.FilterEngineGenericError(e)
484
+ and_expressions.append(expression)
485
+ or_expressions.append(' AND '.join(and_expressions))
486
+ return ' OR '.join(or_expressions)
487
+
488
+ @read_session
489
+ def create_sqla_query(
490
+ self,
491
+ *,
492
+ session: "Session",
493
+ additional_model_attributes: Optional[list[InstrumentedAttribute[Any]]] = None,
494
+ additional_filters: Optional["Iterable[FilterTuple]"] = None,
495
+ json_column: Optional[InstrumentedAttribute] = None
496
+ ) -> Select:
497
+ """
498
+ Returns a database query that fully describes the filters.
499
+
500
+ The logic for construction of syntax describing a filter for key is dependent on whether the key has been previously coerced to a model attribute (i.e. key
501
+ is a table column).
502
+
503
+ :param session: The database session.
504
+ :param additional_model_attributes: Additional model attributes to retrieve.
505
+ :param additional_filters: Additional filters to be applied to all clauses.
506
+ :param json_column: Column to be checked if filter key has not been coerced to a model attribute. Only valid if engine instantiated with strict_coerce=False.
507
+ :returns: A SQLAlchemy Select object.
508
+ :raises: FilterEngineGenericError
509
+ """
510
+ additional_model_attributes = additional_model_attributes or []
511
+ additional_filters = additional_filters or []
512
+ all_model_attributes = set(self.mandatory_model_attributes + additional_model_attributes)
513
+
514
+ # Add additional filters, applied as AND clauses to each OR group.
515
+ for or_group in self._filters:
516
+ for _filter in additional_filters:
517
+ or_group.append(list(_filter)) # type: ignore
518
+
519
+ or_expressions: list = []
520
+ for or_group in self._filters:
521
+ and_expressions = []
522
+ for and_group in or_group:
523
+ key, oper, value = and_group
524
+ if isinstance(key, InstrumentedAttribute): # -> this key filters on a table column.
525
+ if isinstance(value, str) and any([char in value for char in ['*', '%']]): # wildcards
526
+ if value in ('*', '%', '*', '%'): # match wildcard exactly == no filtering on key
527
+ continue
528
+ else: # partial match with wildcard == like || notlike
529
+ if oper == operator.eq:
530
+ expression = key.like(value.replace('*', '%').replace('_', '\_'), escape='\\') # NOQA: W605
531
+ elif oper == operator.ne:
532
+ expression = key.notlike(value.replace('*', '%').replace('_', '\_'), escape='\\') # NOQA: W605
533
+ else:
534
+ expression = oper(key, value)
535
+ if oper == operator.ne: # set .ne operator to include NULLs.
536
+ expression = or_(expression, key.is_(None))
537
+ elif json_column: # -> this key filters on the content of a json column
538
+ if session.bind.dialect.name == 'oracle':
539
+ if isinstance(value, str) and any([char in value for char in ['*', '%']]): # wildcards
540
+ if value in ('*', '%', '*', '%'): # match wildcard exactly == no filtering on key
541
+ continue
542
+ else: # partial match with wildcard == like || notlike
543
+ if oper == operator.eq:
544
+ expression = text("json_exists({},'$?(@.{} like \"{}\")')".format(json_column.key, key, value.replace('*', '%')))
545
+ elif oper == operator.ne:
546
+ raise exception.FilterEngineGenericError("Oracle implementation does not support this operator.")
547
+ else:
548
+ try:
549
+ if isinstance(value, (bool)): # bool must be checked first (as bool subclass of int)
550
+ expression = text("json_exists({},'$?(@.{}.boolean() {} \"{}\")')".format(json_column.key, key, ORACLE_OP_MAP[oper], value))
551
+ elif isinstance(value, (int, float)):
552
+ expression = text("json_exists({},'$?(@.{} {} {})')".format(json_column.key, key, ORACLE_OP_MAP[oper], value))
553
+ else:
554
+ expression = text("json_exists({},'$?(@.{} {} \"{}\")')".format(json_column.key, key, ORACLE_OP_MAP[oper], value))
555
+ except Exception as e:
556
+ raise exception.FilterEngineGenericError(e)
557
+ else:
558
+ if isinstance(value, str) and any([char in value for char in ['*', '%']]): # wildcards
559
+ if value in ('*', '%', '*', '%'): # match wildcard exactly == no filtering on key
560
+ continue
561
+ else: # partial match with wildcard == like || notlike
562
+ if oper == operator.eq:
563
+ expression = json_column[key].as_string().like(value.replace('*', '%').replace('_', '\_'), escape='\\') # NOQA: W605
564
+ elif oper == operator.ne:
565
+ expression = json_column[key].as_string().notlike(value.replace('*', '%').replace('_', '\_'), escape='\\') # NOQA: W605
566
+ else:
567
+ # Infer what type key should be cast to from typecasting the value in the expression.
568
+ try:
569
+ if isinstance(value, int): # this could be bool or int (as bool subclass of int)
570
+ if isinstance(value, bool):
571
+ expression = oper(json_column[key].as_boolean(), value)
572
+ else:
573
+ expression = oper(json_column[key].as_float(), value) # cast as float, not integer, to avoid potentially losing precision in key
574
+ elif isinstance(value, float):
575
+ expression = oper(json_column[key].as_float(), value)
576
+ elif isinstance(value, datetime):
577
+ expression = oper(cast(cast(json_column[key], sqlalchemy.types.Text), sqlalchemy.types.DateTime), value)
578
+ else:
579
+ expression = oper(json_column[key].as_string(), value)
580
+ except Exception as e:
581
+ raise exception.FilterEngineGenericError(e)
582
+ else:
583
+ raise exception.FilterEngineGenericError("Requested filter on key without model attribute, but [json_column] not set.")
584
+
585
+ and_expressions.append(expression)
586
+ or_expressions.append(and_(*and_expressions))
587
+ stmt = select(
588
+ *all_model_attributes
589
+ ).where(
590
+ or_(*or_expressions)
591
+ )
592
+ return stmt
593
+
594
+ def evaluate(self) -> bool:
595
+ """
596
+ Evaluates an expression and returns a boolean result.
597
+
598
+ :returns: boolean output
599
+ """
600
+ or_group_evaluations = []
601
+ for or_group in self._filters:
602
+ and_group_evaluations = []
603
+ for and_group in or_group:
604
+ key, oper, value = and_group
605
+ and_group_evaluations.append(oper(key, value))
606
+ or_group_evaluations.append(all(and_group_evaluations))
607
+ return any(or_group_evaluations)
608
+
609
+ def print_filters(self) -> str:
610
+ """
611
+ A (more) human readable format of <filters>.
612
+ """
613
+ operators_conversion_lut_inv = {op2: op1 for op1, op2 in OPERATORS_CONVERSION_LUT.items()}
614
+
615
+ filters = '\n'
616
+ for or_group in self._filters:
617
+ for and_group in or_group:
618
+ key, oper, value = and_group
619
+ if isinstance(key, InstrumentedAttribute):
620
+ key = and_group[0].key
621
+ if operators_conversion_lut_inv[oper] == "":
622
+ oper = "eq"
623
+ else:
624
+ oper = operators_conversion_lut_inv[oper]
625
+ if isinstance(value, InstrumentedAttribute):
626
+ value = and_group[2].key # type: ignore
627
+ elif isinstance(value, DIDType):
628
+ value = and_group[2].name # type: ignore
629
+ filters = "{}{} {} {}".format(filters, key, oper, value)
630
+ if and_group != or_group[-1]:
631
+ filters += ' AND '
632
+ if or_group != self._filters[-1]:
633
+ filters += ' OR\n'
634
+ return filters
635
+
636
+ @staticmethod
637
+ def print_query(statement, dialect=sqlalchemy.dialects.postgresql.dialect()):
638
+ """
639
+ Generates SQL expression from SQLA expression with parameters rendered inline.
640
+
641
+ For debugging ONLY.
642
+
643
+ :param dialect: the sql dialect.
644
+ :returns: The query statement in the chosen dialect.
645
+ """
646
+ if isinstance(statement, sqlalchemy.orm.Query):
647
+ if dialect is None:
648
+ dialect = statement.session.bind.dialect
649
+ statement = statement.statement
650
+ elif dialect is None:
651
+ dialect = statement.bind.dialect
652
+
653
+ class LiteralCompiler(dialect.statement_compiler):
654
+ def visit_bindparam(self, bindparam, within_columns_clause=False,
655
+ literal_binds=False, **kwargs):
656
+ return self.render_literal_value(bindparam.value, bindparam.type)
657
+
658
+ def render_array_value(self, val, item_type):
659
+ if isinstance(val, list):
660
+ return "{%s}" % ",".join([self.render_array_value(x, item_type) for x in val])
661
+ return self.render_literal_value(val, item_type)
662
+
663
+ def render_literal_value(self, value, type_):
664
+ if isinstance(value, int):
665
+ return str(value)
666
+ elif isinstance(value, (str, date, datetime, timedelta)):
667
+ return "'%s'" % str(value).replace("'", "''")
668
+ elif isinstance(value, list):
669
+ return "'{%s}'" % (",".join([self.render_array_value(x, type_.item_type) for x in value]))
670
+ return super(LiteralCompiler, self).render_literal_value(value, type_)
671
+
672
+ return LiteralCompiler(dialect, statement).process(statement)