We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ius repo link is being decommissioned
Motivation
----------
The link currently used for the CentOS repo (https://centos7.iuscommunity.org/ius-release.rpm) is being decommissioned and replaced with a new link (https://repo.ius.io/ius-release-el7.rpm). Trying to use the old link will cause the Travis suites to error/fail.

Modification
------------
Change to the new link.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/rucio/client/ruleclient.py`
Content:
```
1 # Copyright 2013-2018 CERN for the benefit of the ATLAS collaboration.
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 # Authors:
16 # - Martin Barisits <martin.barisits@cern.ch>, 2013-2018
17 # - Vincent Garonne <vgaronne@gmail.com>, 2013-2018
18 # - Cedric Serfon <cedric.serfon@cern.ch>, 2014-2015
19 # - Ralph Vigne <ralph.vigne@cern.ch>, 2015
20 # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2018
21 #
22 # PY3K COMPATIBLE
23 
24 from json import dumps, loads
25 from requests.status_codes import codes
26 
27 from rucio.client.baseclient import BaseClient
28 from rucio.client.baseclient import choice
29 from rucio.common.utils import build_url
30 
31 
32 class RuleClient(BaseClient):
33 
34     """RuleClient class for working with replication rules"""
35 
36     RULE_BASEURL = 'rules'
37 
38     def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None, auth_type=None, creds=None, timeout=600, dq2_wrapper=False):
39         super(RuleClient, self).__init__(rucio_host, auth_host, account, ca_cert, auth_type, creds, timeout, dq2_wrapper)
40 
41     def add_replication_rule(self, dids, copies, rse_expression, weight=None, lifetime=None, grouping='DATASET', account=None,
42                              locked=False, source_replica_expression=None, activity=None, notify='N', purge_replicas=False,
43                              ignore_availability=False, comment=None, ask_approval=False, asynchronous=False, priority=3,
44                              meta=None):
45         """
46         :param dids:                       The data identifier set.
47         :param copies:                     The number of replicas.
48         :param rse_expression:             Boolean string expression to give the list of RSEs.
49         :param weight:                     If the weighting option of the replication rule is used, the choice of RSEs takes their weight into account.
50         :param lifetime:                   The lifetime of the replication rules (in seconds).
51         :param grouping:                   ALL -  All files will be replicated to the same RSE.
52                                            DATASET - All files in the same dataset will be replicated to the same RSE.
53                                            NONE - Files will be completely spread over all allowed RSEs without any grouping considerations at all.
54         :param account:                    The account owning the rule.
55         :param locked:                     If the rule is locked, it cannot be deleted.
56         :param source_replica_expression:  RSE Expression for RSEs to be considered for source replicas.
57         :param activity:                   Transfer Activity to be passed to FTS.
58         :param notify:                     Notification setting for the rule (Y, N, C).
59         :param purge_replicas:             When the rule gets deleted purge the associated replicas immediately.
60         :param ignore_availability:        Option to ignore the availability of RSEs.
61         :param ask_approval:               Ask for approval of this replication rule.
62         :param asynchronous:               Create rule asynchronously by judge-injector.
63         :param priority:                   Priority of the transfers.
64         :param comment:                    Comment about the rule.
65         :param meta:                       Metadata, as dictionary.
66         """
67         path = self.RULE_BASEURL + '/'
68         url = build_url(choice(self.list_hosts), path=path)
69         # TODO remove the subscription_id from the client; It will only be used by the core;
70         data = dumps({'dids': dids, 'copies': copies, 'rse_expression': rse_expression,
71                       'weight': weight, 'lifetime': lifetime, 'grouping': grouping,
72                       'account': account, 'locked': locked, 'source_replica_expression': source_replica_expression,
73                       'activity': activity, 'notify': notify, 'purge_replicas': purge_replicas,
74                       'ignore_availability': ignore_availability, 'comment': comment, 'ask_approval': ask_approval,
75                       'asynchronous': asynchronous, 'priority': priority, 'meta': meta})
76         r = self._send_request(url, type='POST', data=data)
77         if r.status_code == codes.created:
78             return loads(r.text)
79         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
80         raise exc_cls(exc_msg)
81 
82     def delete_replication_rule(self, rule_id, purge_replicas=None):
83         """
84         Deletes a replication rule and all associated locks.
85 
86         :param rule_id:         The id of the rule to be deleted
87         :param purge_replicas:  Immediately delete the replicas.
88         :raises:                RuleNotFound, AccessDenied
89         """
90 
91         path = self.RULE_BASEURL + '/' + rule_id
92         url = build_url(choice(self.list_hosts), path=path)
93 
94         data = dumps({'purge_replicas': purge_replicas})
95 
96         r = self._send_request(url, type='DEL', data=data)
97 
98         if r.status_code == codes.ok:
99             return True
100         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
101         raise exc_cls(exc_msg)
102 
103     def get_replication_rule(self, rule_id, estimate_ttc=False):
104         """
105         Get a replication rule.
106 
107         :param rule_id:  The id of the rule to be retrieved.
108         :param estimate_ttc: bool, if rule_info should return ttc information
109         :raises:         RuleNotFound
110         """
111         path = self.RULE_BASEURL + '/' + rule_id
112         url = build_url(choice(self.list_hosts), path=path)
113         data = dumps({'estimate_ttc': estimate_ttc})
114         r = self._send_request(url, type='GET', data=data)
115         if r.status_code == codes.ok:
116             return next(self._load_json_data(r))
117         else:
118             exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
119             raise exc_cls(exc_msg)
120 
121     def update_replication_rule(self, rule_id, options):
122         """
123         :param rule_id:   The id of the rule to be retrieved.
124         :param options:   Options dictionary.
125         :raises:          RuleNotFound
126         """
127         path = self.RULE_BASEURL + '/' + rule_id
128         url = build_url(choice(self.list_hosts), path=path)
129         data = dumps({'options': options})
130         r = self._send_request(url, type='PUT', data=data)
131         if r.status_code == codes.ok:
132             return True
133         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
134         raise exc_cls(exc_msg)
135 
136     def reduce_replication_rule(self, rule_id, copies, exclude_expression=None):
137         """
138         :param rule_id:             Rule to be reduced.
139         :param copies:              Number of copies of the new rule.
140         :param exclude_expression:  RSE Expression of RSEs to exclude.
141         :raises:                    RuleReplaceFailed, RuleNotFound
142         """
143 
144         path = self.RULE_BASEURL + '/' + rule_id + '/reduce'
145         url = build_url(choice(self.list_hosts), path=path)
146         data = dumps({'copies': copies, 'exclude_expression': exclude_expression})
147         r = self._send_request(url, type='POST', data=data)
148         if r.status_code == codes.ok:
149             return loads(r.text)
150         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
151         raise exc_cls(exc_msg)
152 
153     def move_replication_rule(self, rule_id, rse_expression):
154         """
155         Move a replication rule to another RSE and, once done, delete the original one.
156 
157         :param rule_id:             Rule to be moved.
158         :param rse_expression:      RSE expression of the new rule.
159         :raises:                    RuleNotFound, RuleReplaceFailed
160         """
161 
162         path = self.RULE_BASEURL + '/' + rule_id + '/move'
163         url = build_url(choice(self.list_hosts), path=path)
164         data = dumps({'rule_id': rule_id, 'rse_expression': rse_expression})
165         r = self._send_request(url, type='POST', data=data)
166         if r.status_code == codes.created:
167             return loads(r.text)
168         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
169         raise exc_cls(exc_msg)
170 
171     def approve_replication_rule(self, rule_id):
172         """
173         :param rule_id:             Rule to be approved.
174         :raises:                    RuleNotFound
175         """
176 
177         path = self.RULE_BASEURL + '/' + rule_id
178         url = build_url(choice(self.list_hosts), path=path)
179         data = dumps({'options': {'approve': True}})
180         r = self._send_request(url, type='PUT', data=data)
181         if r.status_code == codes.ok:
182             return True
183         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
184         raise exc_cls(exc_msg)
185 
186     def deny_replication_rule(self, rule_id):
187         """
188         :param rule_id:             Rule to be denied.
189         :raises:                    RuleNotFound
190         """
191 
192         path = self.RULE_BASEURL + '/' + rule_id
193         url = build_url(choice(self.list_hosts), path=path)
194         data = dumps({'options': {'approve': False}})
195         r = self._send_request(url, type='PUT', data=data)
196         if r.status_code == codes.ok:
197             return True
198         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
199         raise exc_cls(exc_msg)
200 
201     def list_replication_rule_full_history(self, scope, name):
202         """
203         List the rule history of a DID.
204 
205         :param scope: The scope of the DID.
206         :param name: The name of the DID.
207         """
208         path = self.RULE_BASEURL + '/' + scope + '/' + name + '/history'
209         url = build_url(choice(self.list_hosts), path=path)
210         r = self._send_request(url, type='GET')
211         if r.status_code == codes.ok:
212             return self._load_json_data(r)
213         exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
214         raise exc_cls(exc_msg)
215 
216     def examine_replication_rule(self, rule_id):
217         """
218         Examine a replication rule for errors during transfer.
219 
220         :param rule_id:             Rule to be denied.
221         :raises:                    RuleNotFound
222         """
223         path = self.RULE_BASEURL + '/' + rule_id + '/analysis'
224         url = build_url(choice(self.list_hosts), path=path)
225         r = self._send_request(url, type='GET')
226         if r.status_code == codes.ok:
227             return next(self._load_json_data(r))
228         exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
229         raise exc_cls(exc_msg)
230 
231     def list_replica_locks(self, rule_id):
232         """
233         List details of all replica locks for a rule.
234 
235         :param rule_id:             Rule to be denied.
236         :raises:                    RuleNotFound
237         """
238         path = self.RULE_BASEURL + '/' + rule_id + '/locks'
239         url = build_url(choice(self.list_hosts), path=path)
240         r = self._send_request(url, type='GET')
241         if r.status_code == codes.ok:
242             return self._load_json_data(r)
243         exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
244         raise exc_cls(exc_msg)
245
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/rucio/client/ruleclient.py b/lib/rucio/client/ruleclient.py
--- a/lib/rucio/client/ruleclient.py
+++ b/lib/rucio/client/ruleclient.py
@@ -21,6 +21,11 @@
 #
 # PY3K COMPATIBLE
 
+try:
+    from urllib import quote_plus
+except ImportError:
+    from urllib.parse import quote_plus
+
 from json import dumps, loads
 from requests.status_codes import codes
 
@@ -205,7 +210,7 @@
         :param scope: The scope of the DID.
         :param name: The name of the DID.
         """
-        path = self.RULE_BASEURL + '/' + scope + '/' + name + '/history'
+        path = '/'.join([self.RULE_BASEURL, quote_plus(scope), quote_plus(name), 'history'])
         url = build_url(choice(self.list_hosts), path=path)
         r = self._send_request(url, type='GET')
         if r.status_code == codes.ok:

{"golden_diff": "diff --git a/lib/rucio/client/ruleclient.py b/lib/rucio/client/ruleclient.py\n--- a/lib/rucio/client/ruleclient.py\n+++ b/lib/rucio/client/ruleclient.py\n@@ -21,6 +21,11 @@\n #\n # PY3K COMPATIBLE\n \n+try:\n+    from urllib import quote_plus\n+except ImportError:\n+    from urllib.parse import quote_plus\n+\n from json import dumps, loads\n from requests.status_codes import codes\n \n@@ -205,7 +210,7 @@\n         :param scope: The scope of the DID.\n         :param name: The name of the DID.\n         \"\"\"\n-        path = self.RULE_BASEURL + '/' + scope + '/' + name + '/history'\n+        path = '/'.join([self.RULE_BASEURL, quote_plus(scope), quote_plus(name), 'history'])\n         url = build_url(choice(self.list_hosts), path=path)\n         r = self._send_request(url, type='GET')\n         if r.status_code == codes.ok:\n", "issue": "ius repo link is being decommissioned\nMotivation\r\n----------\r\nThe link currently used for the CentOS repo (https://centos7.iuscommunity.org/ius-release.rpm) is being decommissioned and replaced with a new link (https://repo.ius.io/ius-release-el7.rpm). Trying to use the old link will cause the Travis suites to error/fail.\r\n\r\nModification\r\n------------\r\nChange to the new link.\r\n\r\n\n", "before_files": [{"content": "# Copyright 2013-2018 CERN for the benefit of the ATLAS collaboration.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# Authors:\n# - Martin Barisits <martin.barisits@cern.ch>, 2013-2018\n# - Vincent Garonne <vgaronne@gmail.com>, 2013-2018\n# - Cedric Serfon <cedric.serfon@cern.ch>, 2014-2015\n# - Ralph Vigne <ralph.vigne@cern.ch>, 2015\n# - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2018\n#\n# PY3K COMPATIBLE\n\nfrom json import dumps, loads\nfrom requests.status_codes import codes\n\nfrom rucio.client.baseclient import BaseClient\nfrom rucio.client.baseclient import choice\nfrom rucio.common.utils import build_url\n\n\nclass RuleClient(BaseClient):\n\n    \"\"\"RuleClient class for working with replication rules\"\"\"\n\n    RULE_BASEURL = 'rules'\n\n    def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None, auth_type=None, creds=None, timeout=600, dq2_wrapper=False):\n        super(RuleClient, self).__init__(rucio_host, auth_host, account, ca_cert, auth_type, creds, timeout, dq2_wrapper)\n\n    def add_replication_rule(self, dids, copies, rse_expression, weight=None, lifetime=None, grouping='DATASET', account=None,\n                             locked=False, source_replica_expression=None, activity=None, notify='N', purge_replicas=False,\n                             ignore_availability=False, comment=None, ask_approval=False, asynchronous=False, priority=3,\n                             meta=None):\n        \"\"\"\n        :param dids:                       The data identifier set.\n        :param copies:                     The number of replicas.\n        :param rse_expression:             Boolean string expression to give the list of RSEs.\n        :param weight:                     If the weighting option of the replication rule is used, the choice of RSEs takes their weight into account.\n        :param lifetime:                   The lifetime of the replication rules (in seconds).\n        :param grouping:                   ALL -  All files will be replicated to the same RSE.\n                                           DATASET - All files in the same dataset will be replicated to the same RSE.\n                                           NONE - Files will be completely spread over all allowed RSEs without any grouping considerations at all.\n        :param account:                    The account owning the rule.\n        :param locked:                     If the rule is locked, it cannot be deleted.\n        :param source_replica_expression:  RSE Expression for RSEs to be considered for source replicas.\n        :param activity:                   Transfer Activity to be passed to FTS.\n        :param notify:                     Notification setting for the rule (Y, N, C).\n        :param purge_replicas:             When the rule gets deleted purge the associated replicas immediately.\n        :param ignore_availability:        Option to ignore the availability of RSEs.\n        :param ask_approval:               Ask for approval of this replication rule.\n        :param asynchronous:               Create rule asynchronously by judge-injector.\n        :param priority:                   Priority of the transfers.\n        :param comment:                    Comment about the rule.\n        :param meta:                       Metadata, as dictionary.\n        \"\"\"\n        path = self.RULE_BASEURL + '/'\n        url = build_url(choice(self.list_hosts), path=path)\n        # TODO remove the subscription_id from the client; It will only be used by the core;\n        data = dumps({'dids': dids, 'copies': copies, 'rse_expression': rse_expression,\n                      'weight': weight, 'lifetime': lifetime, 'grouping': grouping,\n                      'account': account, 'locked': locked, 'source_replica_expression': source_replica_expression,\n                      'activity': activity, 'notify': notify, 'purge_replicas': purge_replicas,\n                      'ignore_availability': ignore_availability, 'comment': comment, 'ask_approval': ask_approval,\n                      'asynchronous': asynchronous, 'priority': priority, 'meta': meta})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.created:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def delete_replication_rule(self, rule_id, purge_replicas=None):\n        \"\"\"\n        Deletes a replication rule and all associated locks.\n\n        :param rule_id:         The id of the rule to be deleted\n        :param purge_replicas:  Immediately delete the replicas.\n        :raises:                RuleNotFound, AccessDenied\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n\n        data = dumps({'purge_replicas': purge_replicas})\n\n        r = self._send_request(url, type='DEL', data=data)\n\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def get_replication_rule(self, rule_id, estimate_ttc=False):\n        \"\"\"\n        Get a replication rule.\n\n        :param rule_id:  The id of the rule to be retrieved.\n        :param estimate_ttc: bool, if rule_info should return ttc information\n        :raises:         RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'estimate_ttc': estimate_ttc})\n        r = self._send_request(url, type='GET', data=data)\n        if r.status_code == codes.ok:\n            return next(self._load_json_data(r))\n        else:\n            exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n            raise exc_cls(exc_msg)\n\n    def update_replication_rule(self, rule_id, options):\n        \"\"\"\n        :param rule_id:   The id of the rule to be retrieved.\n        :param options:   Options dictionary.\n        :raises:          RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': options})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def reduce_replication_rule(self, rule_id, copies, exclude_expression=None):\n        \"\"\"\n        :param rule_id:             Rule to be reduced.\n        :param copies:              Number of copies of the new rule.\n        :param exclude_expression:  RSE Expression of RSEs to exclude.\n        :raises:                    RuleReplaceFailed, RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id + '/reduce'\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'copies': copies, 'exclude_expression': exclude_expression})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.ok:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def move_replication_rule(self, rule_id, rse_expression):\n        \"\"\"\n        Move a replication rule to another RSE and, once done, delete the original one.\n\n        :param rule_id:             Rule to be moved.\n        :param rse_expression:      RSE expression of the new rule.\n        :raises:                    RuleNotFound, RuleReplaceFailed\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id + '/move'\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'rule_id': rule_id, 'rse_expression': rse_expression})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.created:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def approve_replication_rule(self, rule_id):\n        \"\"\"\n        :param rule_id:             Rule to be approved.\n        :raises:                    RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': {'approve': True}})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def deny_replication_rule(self, rule_id):\n        \"\"\"\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': {'approve': False}})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def list_replication_rule_full_history(self, scope, name):\n        \"\"\"\n        List the rule history of a DID.\n\n        :param scope: The scope of the DID.\n        :param name: The name of the DID.\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + scope + '/' + name + '/history'\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return self._load_json_data(r)\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n\n    def examine_replication_rule(self, rule_id):\n        \"\"\"\n        Examine a replication rule for errors during transfer.\n\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id + '/analysis'\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return next(self._load_json_data(r))\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n\n    def list_replica_locks(self, rule_id):\n        \"\"\"\n        List details of all replica locks for a rule.\n\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id + '/locks'\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return self._load_json_data(r)\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n", "path": "lib/rucio/client/ruleclient.py"}], "after_files": [{"content": "# Copyright 2013-2018 CERN for the benefit of the ATLAS collaboration.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# Authors:\n# - Martin Barisits <martin.barisits@cern.ch>, 2013-2018\n# - Vincent Garonne <vgaronne@gmail.com>, 2013-2018\n# - Cedric Serfon <cedric.serfon@cern.ch>, 2014-2015\n# - Ralph Vigne <ralph.vigne@cern.ch>, 2015\n# - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2018\n#\n# PY3K COMPATIBLE\n\ntry:\n    from urllib import quote_plus\nexcept ImportError:\n    from urllib.parse import quote_plus\n\nfrom json import dumps, loads\nfrom requests.status_codes import codes\n\nfrom rucio.client.baseclient import BaseClient\nfrom rucio.client.baseclient import choice\nfrom rucio.common.utils import build_url\n\n\nclass RuleClient(BaseClient):\n\n    \"\"\"RuleClient class for working with replication rules\"\"\"\n\n    RULE_BASEURL = 'rules'\n\n    def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None, auth_type=None, creds=None, timeout=600, dq2_wrapper=False):\n        super(RuleClient, self).__init__(rucio_host, auth_host, account, ca_cert, auth_type, creds, timeout, dq2_wrapper)\n\n    def add_replication_rule(self, dids, copies, rse_expression, weight=None, lifetime=None, grouping='DATASET', account=None,\n                             locked=False, source_replica_expression=None, activity=None, notify='N', purge_replicas=False,\n                             ignore_availability=False, comment=None, ask_approval=False, asynchronous=False, priority=3,\n                             meta=None):\n        \"\"\"\n        :param dids:                       The data identifier set.\n        :param copies:                     The number of replicas.\n        :param rse_expression:             Boolean string expression to give the list of RSEs.\n        :param weight:                     If the weighting option of the replication rule is used, the choice of RSEs takes their weight into account.\n        :param lifetime:                   The lifetime of the replication rules (in seconds).\n        :param grouping:                   ALL -  All files will be replicated to the same RSE.\n                                           DATASET - All files in the same dataset will be replicated to the same RSE.\n                                           NONE - Files will be completely spread over all allowed RSEs without any grouping considerations at all.\n        :param account:                    The account owning the rule.\n        :param locked:                     If the rule is locked, it cannot be deleted.\n        :param source_replica_expression:  RSE Expression for RSEs to be considered for source replicas.\n        :param activity:                   Transfer Activity to be passed to FTS.\n        :param notify:                     Notification setting for the rule (Y, N, C).\n        :param purge_replicas:             When the rule gets deleted purge the associated replicas immediately.\n        :param ignore_availability:        Option to ignore the availability of RSEs.\n        :param ask_approval:               Ask for approval of this replication rule.\n        :param asynchronous:               Create rule asynchronously by judge-injector.\n        :param priority:                   Priority of the transfers.\n        :param comment:                    Comment about the rule.\n        :param meta:                       Metadata, as dictionary.\n        \"\"\"\n        path = self.RULE_BASEURL + '/'\n        url = build_url(choice(self.list_hosts), path=path)\n        # TODO remove the subscription_id from the client; It will only be used by the core;\n        data = dumps({'dids': dids, 'copies': copies, 'rse_expression': rse_expression,\n                      'weight': weight, 'lifetime': lifetime, 'grouping': grouping,\n                      'account': account, 'locked': locked, 'source_replica_expression': source_replica_expression,\n                      'activity': activity, 'notify': notify, 'purge_replicas': purge_replicas,\n                      'ignore_availability': ignore_availability, 'comment': comment, 'ask_approval': ask_approval,\n                      'asynchronous': asynchronous, 'priority': priority, 'meta': meta})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.created:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def delete_replication_rule(self, rule_id, purge_replicas=None):\n        \"\"\"\n        Deletes a replication rule and all associated locks.\n\n        :param rule_id:         The id of the rule to be deleted\n        :param purge_replicas:  Immediately delete the replicas.\n        :raises:                RuleNotFound, AccessDenied\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n\n        data = dumps({'purge_replicas': purge_replicas})\n\n        r = self._send_request(url, type='DEL', data=data)\n\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def get_replication_rule(self, rule_id, estimate_ttc=False):\n        \"\"\"\n        Get a replication rule.\n\n        :param rule_id:  The id of the rule to be retrieved.\n        :param estimate_ttc: bool, if rule_info should return ttc information\n        :raises:         RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'estimate_ttc': estimate_ttc})\n        r = self._send_request(url, type='GET', data=data)\n        if r.status_code == codes.ok:\n            return next(self._load_json_data(r))\n        else:\n            exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n            raise exc_cls(exc_msg)\n\n    def update_replication_rule(self, rule_id, options):\n        \"\"\"\n        :param rule_id:   The id of the rule to be retrieved.\n        :param options:   Options dictionary.\n        :raises:          RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': options})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def reduce_replication_rule(self, rule_id, copies, exclude_expression=None):\n        \"\"\"\n        :param rule_id:             Rule to be reduced.\n        :param copies:              Number of copies of the new rule.\n        :param exclude_expression:  RSE Expression of RSEs to exclude.\n        :raises:                    RuleReplaceFailed, RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id + '/reduce'\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'copies': copies, 'exclude_expression': exclude_expression})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.ok:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def move_replication_rule(self, rule_id, rse_expression):\n        \"\"\"\n        Move a replication rule to another RSE and, once done, delete the original one.\n\n        :param rule_id:             Rule to be moved.\n        :param rse_expression:      RSE expression of the new rule.\n        :raises:                    RuleNotFound, RuleReplaceFailed\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id + '/move'\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'rule_id': rule_id, 'rse_expression': rse_expression})\n        r = self._send_request(url, type='POST', data=data)\n        if r.status_code == codes.created:\n            return loads(r.text)\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def approve_replication_rule(self, rule_id):\n        \"\"\"\n        :param rule_id:             Rule to be approved.\n        :raises:                    RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': {'approve': True}})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def deny_replication_rule(self, rule_id):\n        \"\"\"\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n\n        path = self.RULE_BASEURL + '/' + rule_id\n        url = build_url(choice(self.list_hosts), path=path)\n        data = dumps({'options': {'approve': False}})\n        r = self._send_request(url, type='PUT', data=data)\n        if r.status_code == codes.ok:\n            return True\n        exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)\n        raise exc_cls(exc_msg)\n\n    def list_replication_rule_full_history(self, scope, name):\n        \"\"\"\n        List the rule history of a DID.\n\n        :param scope: The scope of the DID.\n        :param name: The name of the DID.\n        \"\"\"\n        path = '/'.join([self.RULE_BASEURL, quote_plus(scope), quote_plus(name), 'history'])\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return self._load_json_data(r)\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n\n    def examine_replication_rule(self, rule_id):\n        \"\"\"\n        Examine a replication rule for errors during transfer.\n\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id + '/analysis'\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return next(self._load_json_data(r))\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n\n    def list_replica_locks(self, rule_id):\n        \"\"\"\n        List details of all replica locks for a rule.\n\n        :param rule_id:             Rule to be denied.\n        :raises:                    RuleNotFound\n        \"\"\"\n        path = self.RULE_BASEURL + '/' + rule_id + '/locks'\n        url = build_url(choice(self.list_hosts), path=path)\n        r = self._send_request(url, type='GET')\n        if r.status_code == codes.ok:\n            return self._load_json_data(r)\n        exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)\n        raise exc_cls(exc_msg)\n", "path": "lib/rucio/client/ruleclient.py"}]}