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.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio/cli/bin_legacy/rucio.py +2825 -0
- rucio/cli/bin_legacy/rucio_admin.py +2500 -0
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +432 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +983 -0
- rucio/client/client.py +120 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +868 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1783 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +452 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +969 -0
- rucio/common/__init__.py +13 -0
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +111 -0
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +788 -0
- rucio/common/constants.py +217 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +237 -0
- rucio/common/dumper/__init__.py +342 -0
- rucio/common/dumper/consistency.py +497 -0
- rucio/common/dumper/data_models.py +362 -0
- rucio/common/dumper/path_parsing.py +75 -0
- rucio/common/exception.py +1208 -0
- rucio/common/extra.py +31 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1409 -0
- rucio/common/plugins.py +185 -0
- rucio/common/policy.py +93 -0
- rucio/common/schema/__init__.py +200 -0
- rucio/common/schema/generic.py +416 -0
- rucio/common/schema/generic_multi_vo.py +395 -0
- rucio/common/stomp_utils.py +423 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +154 -0
- rucio/common/types.py +483 -0
- rucio/common/utils.py +1688 -0
- rucio/core/__init__.py +13 -0
- rucio/core/account.py +496 -0
- rucio/core/account_counter.py +236 -0
- rucio/core/account_limit.py +425 -0
- rucio/core/authentication.py +620 -0
- rucio/core/config.py +437 -0
- rucio/core/credential.py +224 -0
- rucio/core/did.py +3004 -0
- rucio/core/did_meta_plugins/__init__.py +252 -0
- rucio/core/did_meta_plugins/did_column_meta.py +331 -0
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
- rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
- rucio/core/did_meta_plugins/filter_engine.py +672 -0
- rucio/core/did_meta_plugins/json_meta.py +240 -0
- rucio/core/did_meta_plugins/mongo_meta.py +229 -0
- rucio/core/did_meta_plugins/postgres_meta.py +352 -0
- rucio/core/dirac.py +237 -0
- rucio/core/distance.py +187 -0
- rucio/core/exporter.py +59 -0
- rucio/core/heartbeat.py +363 -0
- rucio/core/identity.py +301 -0
- rucio/core/importer.py +260 -0
- rucio/core/lifetime_exception.py +377 -0
- rucio/core/lock.py +577 -0
- rucio/core/message.py +288 -0
- rucio/core/meta_conventions.py +203 -0
- rucio/core/monitor.py +448 -0
- rucio/core/naming_convention.py +195 -0
- rucio/core/nongrid_trace.py +136 -0
- rucio/core/oidc.py +1463 -0
- rucio/core/permission/__init__.py +161 -0
- rucio/core/permission/generic.py +1124 -0
- rucio/core/permission/generic_multi_vo.py +1144 -0
- rucio/core/quarantined_replica.py +224 -0
- rucio/core/replica.py +4483 -0
- rucio/core/replica_sorter.py +362 -0
- rucio/core/request.py +3091 -0
- rucio/core/rse.py +2079 -0
- rucio/core/rse_counter.py +185 -0
- rucio/core/rse_expression_parser.py +459 -0
- rucio/core/rse_selector.py +304 -0
- rucio/core/rule.py +4484 -0
- rucio/core/rule_grouping.py +1620 -0
- rucio/core/scope.py +181 -0
- rucio/core/subscription.py +362 -0
- rucio/core/topology.py +490 -0
- rucio/core/trace.py +375 -0
- rucio/core/transfer.py +1531 -0
- rucio/core/vo.py +169 -0
- rucio/core/volatile_replica.py +151 -0
- rucio/daemons/__init__.py +13 -0
- rucio/daemons/abacus/__init__.py +13 -0
- rucio/daemons/abacus/account.py +116 -0
- rucio/daemons/abacus/collection_replica.py +124 -0
- rucio/daemons/abacus/rse.py +117 -0
- rucio/daemons/atropos/__init__.py +13 -0
- rucio/daemons/atropos/atropos.py +242 -0
- rucio/daemons/auditor/__init__.py +289 -0
- rucio/daemons/auditor/hdfs.py +97 -0
- rucio/daemons/auditor/srmdumps.py +355 -0
- rucio/daemons/automatix/__init__.py +13 -0
- rucio/daemons/automatix/automatix.py +304 -0
- rucio/daemons/badreplicas/__init__.py +13 -0
- rucio/daemons/badreplicas/minos.py +322 -0
- rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
- rucio/daemons/badreplicas/necromancer.py +196 -0
- rucio/daemons/bb8/__init__.py +13 -0
- rucio/daemons/bb8/bb8.py +353 -0
- rucio/daemons/bb8/common.py +759 -0
- rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
- rucio/daemons/bb8/t2_background_rebalance.py +153 -0
- rucio/daemons/cache/__init__.py +13 -0
- rucio/daemons/cache/consumer.py +133 -0
- rucio/daemons/common.py +405 -0
- rucio/daemons/conveyor/__init__.py +13 -0
- rucio/daemons/conveyor/common.py +562 -0
- rucio/daemons/conveyor/finisher.py +529 -0
- rucio/daemons/conveyor/poller.py +394 -0
- rucio/daemons/conveyor/preparer.py +205 -0
- rucio/daemons/conveyor/receiver.py +179 -0
- rucio/daemons/conveyor/stager.py +133 -0
- rucio/daemons/conveyor/submitter.py +403 -0
- rucio/daemons/conveyor/throttler.py +532 -0
- rucio/daemons/follower/__init__.py +13 -0
- rucio/daemons/follower/follower.py +101 -0
- rucio/daemons/hermes/__init__.py +13 -0
- rucio/daemons/hermes/hermes.py +534 -0
- rucio/daemons/judge/__init__.py +13 -0
- rucio/daemons/judge/cleaner.py +159 -0
- rucio/daemons/judge/evaluator.py +185 -0
- rucio/daemons/judge/injector.py +162 -0
- rucio/daemons/judge/repairer.py +154 -0
- rucio/daemons/oauthmanager/__init__.py +13 -0
- rucio/daemons/oauthmanager/oauthmanager.py +198 -0
- rucio/daemons/reaper/__init__.py +13 -0
- rucio/daemons/reaper/dark_reaper.py +282 -0
- rucio/daemons/reaper/reaper.py +739 -0
- rucio/daemons/replicarecoverer/__init__.py +13 -0
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
- rucio/daemons/rsedecommissioner/__init__.py +13 -0
- rucio/daemons/rsedecommissioner/config.py +81 -0
- rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
- rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
- rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
- rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
- rucio/daemons/storage/__init__.py +13 -0
- rucio/daemons/storage/consistency/__init__.py +13 -0
- rucio/daemons/storage/consistency/actions.py +848 -0
- rucio/daemons/tracer/__init__.py +13 -0
- rucio/daemons/tracer/kronos.py +511 -0
- rucio/daemons/transmogrifier/__init__.py +13 -0
- rucio/daemons/transmogrifier/transmogrifier.py +762 -0
- rucio/daemons/undertaker/__init__.py +13 -0
- rucio/daemons/undertaker/undertaker.py +137 -0
- rucio/db/__init__.py +13 -0
- rucio/db/sqla/__init__.py +52 -0
- rucio/db/sqla/constants.py +206 -0
- rucio/db/sqla/migrate_repo/__init__.py +13 -0
- rucio/db/sqla/migrate_repo/env.py +110 -0
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
- rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
- rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
- rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
- rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
- rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
- rucio/db/sqla/models.py +1743 -0
- rucio/db/sqla/sautils.py +55 -0
- rucio/db/sqla/session.py +529 -0
- rucio/db/sqla/types.py +206 -0
- rucio/db/sqla/util.py +543 -0
- rucio/gateway/__init__.py +13 -0
- rucio/gateway/account.py +345 -0
- rucio/gateway/account_limit.py +363 -0
- rucio/gateway/authentication.py +381 -0
- rucio/gateway/config.py +227 -0
- rucio/gateway/credential.py +70 -0
- rucio/gateway/did.py +987 -0
- rucio/gateway/dirac.py +83 -0
- rucio/gateway/exporter.py +60 -0
- rucio/gateway/heartbeat.py +76 -0
- rucio/gateway/identity.py +189 -0
- rucio/gateway/importer.py +46 -0
- rucio/gateway/lifetime_exception.py +121 -0
- rucio/gateway/lock.py +153 -0
- rucio/gateway/meta_conventions.py +98 -0
- rucio/gateway/permission.py +74 -0
- rucio/gateway/quarantined_replica.py +79 -0
- rucio/gateway/replica.py +538 -0
- rucio/gateway/request.py +330 -0
- rucio/gateway/rse.py +632 -0
- rucio/gateway/rule.py +437 -0
- rucio/gateway/scope.py +100 -0
- rucio/gateway/subscription.py +280 -0
- rucio/gateway/vo.py +126 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +194 -0
- rucio/rse/protocols/cache.py +111 -0
- rucio/rse/protocols/dummy.py +100 -0
- rucio/rse/protocols/gfal.py +708 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +361 -0
- rucio/rse/protocols/rclone.py +365 -0
- rucio/rse/protocols/rfio.py +145 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +414 -0
- rucio/rse/protocols/storm.py +195 -0
- rucio/rse/protocols/webdav.py +594 -0
- rucio/rse/protocols/xrootd.py +302 -0
- rucio/rse/rsemanager.py +881 -0
- rucio/rse/translation.py +260 -0
- rucio/tests/__init__.py +13 -0
- rucio/tests/common.py +280 -0
- rucio/tests/common_server.py +149 -0
- rucio/transfertool/__init__.py +13 -0
- rucio/transfertool/bittorrent.py +200 -0
- rucio/transfertool/bittorrent_driver.py +50 -0
- rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
- rucio/transfertool/fts3.py +1600 -0
- rucio/transfertool/fts3_plugins.py +152 -0
- rucio/transfertool/globus.py +201 -0
- rucio/transfertool/globus_library.py +181 -0
- rucio/transfertool/mock.py +89 -0
- rucio/transfertool/transfertool.py +221 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +45 -0
- rucio/web/__init__.py +13 -0
- rucio/web/rest/__init__.py +13 -0
- rucio/web/rest/flaskapi/__init__.py +13 -0
- rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
- rucio/web/rest/flaskapi/v1/__init__.py +13 -0
- rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
- rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
- rucio/web/rest/flaskapi/v1/archives.py +102 -0
- rucio/web/rest/flaskapi/v1/auth.py +1644 -0
- rucio/web/rest/flaskapi/v1/common.py +426 -0
- rucio/web/rest/flaskapi/v1/config.py +304 -0
- rucio/web/rest/flaskapi/v1/credentials.py +213 -0
- rucio/web/rest/flaskapi/v1/dids.py +2340 -0
- rucio/web/rest/flaskapi/v1/dirac.py +116 -0
- rucio/web/rest/flaskapi/v1/export.py +75 -0
- rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
- rucio/web/rest/flaskapi/v1/identities.py +285 -0
- rucio/web/rest/flaskapi/v1/import.py +132 -0
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
- rucio/web/rest/flaskapi/v1/locks.py +358 -0
- rucio/web/rest/flaskapi/v1/main.py +91 -0
- rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
- rucio/web/rest/flaskapi/v1/metrics.py +36 -0
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
- rucio/web/rest/flaskapi/v1/ping.py +88 -0
- rucio/web/rest/flaskapi/v1/redirect.py +366 -0
- rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
- rucio/web/rest/flaskapi/v1/requests.py +998 -0
- rucio/web/rest/flaskapi/v1/rses.py +2250 -0
- rucio/web/rest/flaskapi/v1/rules.py +854 -0
- rucio/web/rest/flaskapi/v1/scopes.py +159 -0
- rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
- rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
- rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
- rucio/web/rest/flaskapi/v1/traces.py +137 -0
- rucio/web/rest/flaskapi/v1/types.py +20 -0
- rucio/web/rest/flaskapi/v1/vos.py +278 -0
- rucio/web/rest/main.py +18 -0
- rucio/web/rest/metrics.py +27 -0
- rucio/web/rest/ping.py +27 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
- rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
- rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
- rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
- rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
- rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
- rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
- rucio-37.0.0rc1.data/scripts/rucio +133 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
- rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
- rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
- rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
- rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
- rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
- rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
- rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
- rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
- rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
- rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
- rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
- rucio-37.0.0rc1.dist-info/METADATA +92 -0
- rucio-37.0.0rc1.dist-info/RECORD +487 -0
- rucio-37.0.0rc1.dist-info/WHEEL +5 -0
- rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
- rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
- 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)
|