xfc-client 1.1.2__tar.gz

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.
@@ -0,0 +1,26 @@
1
+ Copyright 2018 United Kingdom Research and Innovation
2
+
3
+ Authors: Neil Massey
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ 3. Neither the name of the copyright holder nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: xfc-client
3
+ Version: 1.1.2
4
+ Summary: Client library and command line tool for the Transfer Cache (XFC).
5
+ Home-page: http://www.ceda.ac.uk/
6
+ Author: Neil Massey
7
+ Author-email: Neil Massey <neil.massey@stfc.ac.uk>
8
+ License-Expression: BSD-2-Clause
9
+ Project-URL: Homepage, https://github.com/cedadev/xfc_client
10
+ Project-URL: Issues, https://github.com/cedadev/xfc_client/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.14
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: certifi>=2026.10.19
17
+ Requires-Dist: charset-normalizer>=3.4.2
18
+ Requires-Dist: idna>=3.15
19
+ Requires-Dist: python-dateutil>=2.9.0.post0
20
+ Requires-Dist: pip>=26.2.2
21
+ Requires-Dist: requests>=2.33.0
22
+ Requires-Dist: six>=1.17.0
23
+ Requires-Dist: urllib3>=2.8.0
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: license-file
@@ -0,0 +1,5 @@
1
+ ===========
2
+ xfc_client
3
+ ===========
4
+
5
+ A command line client to control temporary file caching on groupworkspaces on JASMIN.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xfc-client"
7
+ version = "1.1.2"
8
+ authors = [
9
+ { name="Neil Massey", email="neil.massey@stfc.ac.uk" },
10
+ ]
11
+ description = "Client library and command line tool for the Transfer Cache (XFC)."
12
+ readme = "README.md"
13
+ requires-python = ">=3.14"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ license = "BSD-2-Clause"
19
+ license-files = ["LICEN[CS]E*"]
20
+ dependencies = [
21
+ "certifi>=2026.10.19",
22
+ "charset-normalizer>=3.4.2",
23
+ "idna>=3.15",
24
+ "python-dateutil>=2.9.0.post0",
25
+ "pip>=26.2.2",
26
+ "requests>=2.33.0",
27
+ "six>=1.17.0",
28
+ "urllib3>=2.8.0",
29
+ ]
30
+
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/cedadev/xfc_client"
34
+ Issues = "https://github.com/cedadev/xfc_client/issues"
35
+
36
+ [project.scripts]
37
+ xfc = "xfc_client.xfc:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ import os
2
+ from setuptools import setup, find_packages
3
+
4
+ VERSION = "1.1.2"
5
+
6
+ with open(os.path.join(os.path.dirname(__file__), "README.rst")) as readme:
7
+ README = readme.read()
8
+
9
+ # allow setup.py to be run from any path
10
+ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
11
+
12
+ setup(
13
+ name="xfc_client",
14
+ version=VERSION,
15
+ packages=find_packages(),
16
+ install_requires=[
17
+ "requests",
18
+ "python_dateutil",
19
+ "certifi",
20
+ "idna",
21
+ "six",
22
+ "urllib3",
23
+ ],
24
+ include_package_data=True,
25
+ license="BSD License", # example license
26
+ description="A command line client to control temporary file caching on groupworkspaces on JASMIN.",
27
+ long_description=README,
28
+ url="http://www.ceda.ac.uk/",
29
+ author="Neil Massey",
30
+ author_email="support@ceda.ac.uk",
31
+ classifiers=[
32
+ "Environment :: Web Environment",
33
+ "Framework :: HTTP API",
34
+ "Intended Audience :: Developers",
35
+ "License :: OSI Approved :: BSD License", # example license
36
+ "Operating System :: OS Independent",
37
+ "Programming Language :: Python",
38
+ "Programming Language :: Python :: 3",
39
+ "Programming Language :: Python :: 3.6",
40
+ "Programming Language :: Python :: 3.7",
41
+ "Topic :: Internet :: WWW/HTTP",
42
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
43
+ ],
44
+ entry_points={
45
+ "console_scripts": ["xfc=xfc_client.xfc:main"],
46
+ },
47
+ )
@@ -0,0 +1 @@
1
+ VERSION = "1.1.2"
@@ -0,0 +1,420 @@
1
+ #! /usr/bin/env python
2
+ """Command line tool for interacting with the JASMIN transfer cache (XFC) for users who are
3
+ logged into JASMIN and have full JASMIN accounts."""
4
+
5
+ # Author : Neil R Massey
6
+ # Date : 12/05/2017
7
+
8
+ import sys, os
9
+ import argparse
10
+ import requests
11
+ import json
12
+
13
+ from math import log
14
+ from xfc_client import VERSION
15
+
16
+ # switch off warnings
17
+ from requests.packages.urllib3.exceptions import InsecureRequestWarning
18
+
19
+ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
20
+
21
+
22
+ class settings:
23
+ """Settings for the xfc command line tool."""
24
+
25
+ # location of the xfc_control server / app
26
+ XFC_SERVER_URL = "https://xfc.jasmin.ac.uk/xfc_control"
27
+ XFC_API_URL = XFC_SERVER_URL + "/api/v1.1/"
28
+ try:
29
+ USER = os.environ["XFC_USER"]
30
+ except KeyError:
31
+ USER = os.environ["USER"]
32
+ VERSION = VERSION # version of this software
33
+ VERIFY = False
34
+
35
+
36
+ unit_list = list(
37
+ zip(["bytes", "kB", "MB", "GB", "TB", "PB", "EB"], [0, 0, 1, 1, 1, 1, 1])
38
+ )
39
+
40
+
41
+ def sizeof_fmt(num):
42
+ """Human friendly file size"""
43
+ if num > 1:
44
+ exponent = min(int(log(num, 1024)), len(unit_list) - 1)
45
+ quotient = float(num) / 1024**exponent
46
+ unit, num_decimals = unit_list[exponent]
47
+ format_string = "{:>5.%sf} {}" % (num_decimals)
48
+ return format_string.format(quotient, unit)
49
+ elif num == 1:
50
+ return " 1 byte"
51
+ else:
52
+ return " 0 bytes"
53
+
54
+
55
+ class bcolors:
56
+ MAGENTA = "\033[95m"
57
+ BLUE = "\033[94m"
58
+ GREEN = "\033[92m"
59
+ YELLOW = "\033[93m"
60
+ RED = "\033[91m"
61
+ BOLD = "\033[1m"
62
+ UNDERLINE = "\033[4m"
63
+ INVERT = "\033[7m"
64
+ ENDC = "\033[0m"
65
+
66
+
67
+ def user_not_initialized_message():
68
+ sys.stdout.write(
69
+ bcolors.RED
70
+ + "** ERROR ** - User "
71
+ + settings.USER
72
+ + " not initialised yet."
73
+ + bcolors.ENDC
74
+ + " Run "
75
+ + bcolors.YELLOW
76
+ + "xfc init"
77
+ + bcolors.ENDC
78
+ + " first.\n"
79
+ )
80
+
81
+
82
+ def print_response_error(response):
83
+ """Print a concise summary of the error, rather than a whole output of html"""
84
+ for il in response.content.split("\n"):
85
+ if "Exception" in il:
86
+ print(il)
87
+
88
+
89
+ def error_from_response(response):
90
+ try:
91
+ data = response.json()
92
+ if "error" in data:
93
+ sys.stdout.write(
94
+ bcolors.RED + "** ERROR ** - " + data["error"] + bcolors.ENDC + "\n"
95
+ )
96
+ except:
97
+ sys.stdout.write(
98
+ bcolors.RED
99
+ + "** ERROR ** "
100
+ + str(response.status_code)
101
+ + bcolors.ENDC
102
+ + "\n"
103
+ )
104
+
105
+
106
+ def do_init(email=""):
107
+ """Send the HTTP request (POST) to initialize a user's cache space."""
108
+ url = settings.XFC_API_URL + "user"
109
+ data = {"name": settings.USER}
110
+ if email != "":
111
+ data["email"] = email
112
+
113
+ response = requests.post(url, data=json.dumps(data), verify=settings.VERIFY)
114
+ # check the response code
115
+ if response.status_code == 200:
116
+ data = response.json()
117
+ sys.stdout.write(
118
+ bcolors.GREEN
119
+ + "** SUCCESS ** - user initialised with:\n"
120
+ + bcolors.ENDC
121
+ + " Username : "
122
+ + data["name"]
123
+ + "\n"
124
+ + " Email : "
125
+ + data["email"]
126
+ + "\n"
127
+ + " Temporal Quota (TQ) : "
128
+ + sizeof_fmt(data["quota_size"])
129
+ + " days\n"
130
+ + " Hard Quota (HQ) : "
131
+ + sizeof_fmt(data["hard_limit_size"])
132
+ + "\n"
133
+ + " Path : "
134
+ + data["cache_path"]
135
+ + "\n"
136
+ )
137
+ else:
138
+ sys.stdout.write(
139
+ bcolors.RED
140
+ + "** ERROR ** - cannot initialise user "
141
+ + settings.USER
142
+ + bcolors.ENDC
143
+ + "\n"
144
+ )
145
+ error_from_response(response)
146
+
147
+
148
+ def do_email(email=""):
149
+ """Update the email address of the user by sending a PUT request if an email is
150
+ given, or return the email if no email supplied."""
151
+ url = settings.XFC_API_URL + "user?name=" + settings.USER
152
+ if email != "":
153
+ data = {"name": settings.USER, "email": email}
154
+ response = requests.put(url, data=json.dumps(data), verify=settings.VERIFY)
155
+ if response.status_code == 200:
156
+ data = response.json()
157
+ sys.stdout.write(
158
+ bcolors.GREEN
159
+ + "** SUCCESS ** - user email updated to: "
160
+ + data["email"]
161
+ + bcolors.ENDC
162
+ + "\n"
163
+ )
164
+ elif response.status_code == 404:
165
+ user_not_initialized_message()
166
+ else:
167
+ error_from_response(response)
168
+ else:
169
+ data = {"name": settings.USER}
170
+ response = requests.get(url, data=json.dumps(data), verify=settings.VERIFY)
171
+ if response.status_code == 200:
172
+ data = response.json()
173
+ sys.stdout.write(data["email"] + "\n")
174
+ elif response.status_code == 404:
175
+ user_not_initialized_message()
176
+ else:
177
+ error_from_response(response)
178
+
179
+
180
+ def do_info():
181
+ """(re)print the info you get on initialising a user"""
182
+ url = settings.XFC_API_URL + "user?name=" + settings.USER
183
+ response = requests.get(url, verify=settings.VERIFY)
184
+ if response.status_code == 200:
185
+ data = response.json()
186
+ sys.stdout.write(
187
+ bcolors.GREEN
188
+ + "** SUCCESS ** - user info:\n"
189
+ + bcolors.ENDC
190
+ + " Username : "
191
+ + data["name"]
192
+ + "\n"
193
+ + " Email : "
194
+ + data["email"]
195
+ + "\n"
196
+ + " Temporal Quota (TQ) : "
197
+ + sizeof_fmt(data["quota_size"])
198
+ + " days\n"
199
+ + " Hard Quota (HQ) : "
200
+ + sizeof_fmt(data["hard_limit_size"])
201
+ + "\n"
202
+ + " Path : "
203
+ + data["cache_path"]
204
+ + "\n"
205
+ )
206
+ elif response.status_code == 404:
207
+ user_not_initialized_message()
208
+ else:
209
+ error_from_response(response)
210
+
211
+
212
+ def do_path():
213
+ """Send the HTTP request (GET) and process to get the path to the user space on the
214
+ cache."""
215
+ url = settings.XFC_API_URL + "user?name=" + settings.USER
216
+ response = requests.get(url, verify=settings.VERIFY)
217
+ if response.status_code == 200:
218
+ data = response.json()
219
+ sys.stdout.write(data["cache_path"] + "\n")
220
+ elif response.status_code == 404:
221
+ user_not_initialized_message()
222
+ else:
223
+ error_from_response(response)
224
+
225
+
226
+ def do_quota():
227
+ """Send the HTTP request (GET) and process to get the remaining quota and total
228
+ quota for the user."""
229
+ url = settings.XFC_API_URL + "user?name=" + settings.USER
230
+ response = requests.get(url, verify=settings.VERIFY)
231
+ if response.status_code == 200:
232
+ data = response.json()
233
+ used = data["quota_used"]
234
+ allocated = data["quota_size"]
235
+ total = data["total_used"]
236
+ hard_limit = data["hard_limit_size"]
237
+ sys.stdout.write(
238
+ bcolors.MAGENTA
239
+ + "-----------------------------\n"
240
+ + "Quota for user: "
241
+ + settings.USER
242
+ + "\n"
243
+ + "-----------------------------\n"
244
+ + bcolors.ENDC
245
+ + " Temporal Quota (TQ)\n"
246
+ " Used : "
247
+ + sizeof_fmt(used)
248
+ + " days\n"
249
+ + " Allocated : "
250
+ + sizeof_fmt(allocated)
251
+ + " days\n"
252
+ )
253
+ if allocated - used < 0:
254
+ sys.stdout.write(bcolors.RED)
255
+ else:
256
+ sys.stdout.write(bcolors.GREEN)
257
+ sys.stdout.write(
258
+ " Remaining : " + sizeof_fmt(allocated - used) + " days\n" + bcolors.ENDC
259
+ )
260
+
261
+ sys.stdout.write("-----------------------------\n")
262
+ sys.stdout.write(" Hard Quota (HQ)\n")
263
+ sys.stdout.write(" Used : " + sizeof_fmt(total) + "\n")
264
+ sys.stdout.write(" Allocated : " + sizeof_fmt(hard_limit) + "\n")
265
+ if hard_limit - total < 0:
266
+ sys.stdout.write(bcolors.RED)
267
+ else:
268
+ sys.stdout.write(bcolors.GREEN)
269
+ sys.stdout.write(
270
+ " Remaining : " + sizeof_fmt(hard_limit - total) + bcolors.ENDC + "\n"
271
+ )
272
+
273
+ elif response.status_code == 404:
274
+ user_not_initialized_message()
275
+ else:
276
+ error_from_response(response)
277
+
278
+
279
+ def do_notify():
280
+ """Send the HTTP request (PUT) to switch on / off notifications for the user."""
281
+ # first get the status of notifications
282
+ url = settings.XFC_API_URL + "user?name=" + settings.USER
283
+ response = requests.get(url, verify=settings.VERIFY)
284
+ if response.status_code == 200:
285
+ data = response.json()
286
+ notify = data["notify"]
287
+ # update to inverse
288
+ put_data = {"name": settings.USER, "notify": not notify}
289
+ response = requests.put(url, data=json.dumps(put_data), verify=settings.VERIFY)
290
+ if response.status_code == 200:
291
+ data = response.json()
292
+ sys.stdout.write(
293
+ bcolors.GREEN
294
+ + "** SUCCESS ** - user notifications updated to: "
295
+ + ["off", "on"][put_data["notify"]]
296
+ + bcolors.ENDC
297
+ + "\n"
298
+ )
299
+ else:
300
+ error_from_response(response)
301
+
302
+ elif response.status_code == 404:
303
+ user_not_initialized_message()
304
+ else:
305
+ error_from_response(response)
306
+
307
+
308
+ def do_predict():
309
+ """Send the HTTP request to the service which predicts when the user will exceed
310
+ their quota"""
311
+ url = settings.XFC_API_URL + "predict?name=" + settings.USER
312
+ response = requests.get(url, verify=settings.VERIFY)
313
+ if response.status_code == 200:
314
+ data = response.json()
315
+ name = data["name"]
316
+ over_quota = data["over_quota"]
317
+ path = data["cache_disk"]
318
+ time_predict = data["time_predict"]
319
+ pad = "".join(["-" for x in name])
320
+
321
+ sys.stdout.write(
322
+ bcolors.MAGENTA
323
+ + "------------------------------------"
324
+ + pad
325
+ + "\n"
326
+ + "Quota exceeded prediction for user: "
327
+ + name
328
+ + "\n"
329
+ + "------------------------------------"
330
+ + pad
331
+ + "\n"
332
+ + bcolors.ENDC
333
+ )
334
+ if over_quota > 0:
335
+ sys.stdout.write(bcolors.RED)
336
+ sys.stdout.write(
337
+ " Over quota by : " + sizeof_fmt(over_quota) + " days\n"
338
+ )
339
+ else:
340
+ sys.stdout.write(bcolors.GREEN)
341
+ sys.stdout.write(" Quota will not be exceeded\n")
342
+ if time_predict is not None:
343
+ sys.stdout.write(" On date : " + time_predict + "\n")
344
+ sys.stdout.write(bcolors.ENDC)
345
+
346
+ elif response.status_code == 404:
347
+ user_not_initialized_message()
348
+ else:
349
+ error_from_response(response)
350
+
351
+
352
+ def main():
353
+ # help string for the command parsing
354
+ command_help = (
355
+ "Available commands are : \n"
356
+ + "init : Initialize the transfer cache for your JASMIN login\n"
357
+ + "email : view, set or update email address\n"
358
+ + "info : Get the user info\n"
359
+ + "notify : Switch on / off email notifications of scheduled deletions (default is off)\n"
360
+ + "path : Get the path to your storage area in the transfer cache\n"
361
+ + "quota : Get the remaining free space in your quota\n"
362
+ + "predict : Predict when the quota will be exceeded based on the current files\n"
363
+ )
364
+
365
+ parser = argparse.ArgumentParser(
366
+ prog="XFC",
367
+ formatter_class=argparse.RawTextHelpFormatter,
368
+ description="JASMIN transfer cache (XFC) command line tool",
369
+ )
370
+ parser.add_argument(
371
+ "--version", action="version", version="%(prog)s " + settings.VERSION
372
+ )
373
+ parser.add_argument(
374
+ "cmd",
375
+ choices=[
376
+ "init",
377
+ "path",
378
+ "email",
379
+ "info",
380
+ "quota",
381
+ "notify",
382
+ "predict",
383
+ "version",
384
+ ],
385
+ help=command_help,
386
+ metavar="command",
387
+ )
388
+ parser.add_argument(
389
+ "--email",
390
+ action="store",
391
+ default="",
392
+ help="Email address for user in the init and email commands.",
393
+ )
394
+
395
+ args = parser.parse_args()
396
+
397
+ if args.email:
398
+ email = args.email
399
+ else:
400
+ email = ""
401
+
402
+ # switch on the commands
403
+ if args.cmd == "init":
404
+ do_init(email)
405
+ elif args.cmd == "email":
406
+ do_email(email)
407
+ elif args.cmd == "info":
408
+ do_info()
409
+ elif args.cmd == "path":
410
+ do_path()
411
+ elif args.cmd == "quota":
412
+ do_quota()
413
+ elif args.cmd == "notify":
414
+ do_notify()
415
+ elif args.cmd == "predict":
416
+ do_predict()
417
+
418
+
419
+ if __name__ == "__main__":
420
+ main()
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: xfc-client
3
+ Version: 1.1.2
4
+ Summary: Client library and command line tool for the Transfer Cache (XFC).
5
+ Home-page: http://www.ceda.ac.uk/
6
+ Author: Neil Massey
7
+ Author-email: Neil Massey <neil.massey@stfc.ac.uk>
8
+ License-Expression: BSD-2-Clause
9
+ Project-URL: Homepage, https://github.com/cedadev/xfc_client
10
+ Project-URL: Issues, https://github.com/cedadev/xfc_client/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.14
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: certifi>=2026.10.19
17
+ Requires-Dist: charset-normalizer>=3.4.2
18
+ Requires-Dist: idna>=3.15
19
+ Requires-Dist: python-dateutil>=2.9.0.post0
20
+ Requires-Dist: pip>=26.2.2
21
+ Requires-Dist: requests>=2.33.0
22
+ Requires-Dist: six>=1.17.0
23
+ Requires-Dist: urllib3>=2.8.0
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: license-file
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.rst
3
+ pyproject.toml
4
+ setup.py
5
+ xfc_client/__init__.py
6
+ xfc_client/xfc.py
7
+ xfc_client.egg-info/PKG-INFO
8
+ xfc_client.egg-info/SOURCES.txt
9
+ xfc_client.egg-info/dependency_links.txt
10
+ xfc_client.egg-info/entry_points.txt
11
+ xfc_client.egg-info/requires.txt
12
+ xfc_client.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ xfc = xfc_client.xfc:main
@@ -0,0 +1,8 @@
1
+ certifi>=2026.10.19
2
+ charset-normalizer>=3.4.2
3
+ idna>=3.15
4
+ python-dateutil>=2.9.0.post0
5
+ pip>=26.2.2
6
+ requests>=2.33.0
7
+ six>=1.17.0
8
+ urllib3>=2.8.0
@@ -0,0 +1 @@
1
+ xfc_client