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