safe-file-example 0.1.1__tar.gz → 0.2.0__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.
@@ -8,7 +8,7 @@ build/*
8
8
  wheels/
9
9
  # Virtual environments
10
10
  .venv/
11
- venv/
11
+ venv/
12
12
  env/
13
13
  bin/
14
14
  #Other
@@ -0,0 +1,28 @@
1
+ # Optimized for performance with parallel execution
2
+ default_stages: [pre-commit]
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v6.0.0
6
+ hooks:
7
+ - id: trailing-whitespace
8
+ - id: end-of-file-fixer
9
+ - id: check-toml # This is not necessary to have here
10
+ - id: check-yaml
11
+ - id: check-added-large-files
12
+ args: ['--maxkb=10000']
13
+ - id: check-merge-conflict
14
+ - id: detect-private-key
15
+ - id: check-symlinks
16
+ - id: pretty-format-json
17
+ args: ["--autofix"]
18
+ - id: fix-byte-order-marker
19
+ - id: check-case-conflict
20
+ - id: requirements-txt-fixer
21
+ - id: mixed-line-ending
22
+
23
+ - repo: https://github.com/astral-sh/ruff-pre-commit
24
+ rev: v0.14.14
25
+ hooks:
26
+ - id: ruff
27
+ args: [--fix, --diff]
28
+ - id: ruff-format
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: safe-file-example
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: A python package for interacting with SAFE File transfer API
5
5
  Project-URL: Homepage, https://git.app.uib.no/itpublic/python/safe-file-example
6
6
  Author-email: Dip Paul <dip.paul@uib.no>, Eirik Halseth <eirik.halseth@uib.no>, Marco Antonio Corrales Cardenas <marco.corrales@uib.no>
@@ -25,24 +25,23 @@ Requires-Dist: urllib3==2.6.3
25
25
  Description-Content-Type: text/markdown
26
26
 
27
27
  # `safe-file-example`
28
-
29
28
  ## Description
30
29
 
31
30
  A python package for interacting with SAFE File transfer API
32
31
 
33
32
  ## Current Functionalities
34
33
 
35
- - Generate a JWT Token
34
+ - Generate a JWT Token
36
35
  - Import a File
37
36
  - Export a File
38
37
 
39
- ## Prerequisite
38
+ ## Prerequisite
40
39
 
41
- - Having minimum Python 3.12 installed in your system and setting up a Python Virtual Environment.
40
+ - Having minimum Python 3.12 installed in your system and setting up a Python Virtual Environment.
42
41
 
43
42
  ## Workflow
44
43
 
45
- - Activate your Python Virtual Environment,
44
+ - Activate your Python Virtual Environment,
46
45
  - Then run the following command.
47
46
 
48
47
  ```bash
@@ -54,6 +53,7 @@ pip install safe-file-example
54
53
  safe-file-example
55
54
  ```
56
55
 
56
+
57
57
  **Usage**:
58
58
 
59
59
  ```console
@@ -66,19 +66,19 @@ $ safe-file-example [OPTIONS] COMMAND [ARGS]...
66
66
 
67
67
  **Commands**:
68
68
 
69
- * `token`: Get and store access token
69
+ * `get-token`: Get and store access token
70
70
  * `import-file`: Import a file
71
71
  * `export-file`: Export a file
72
72
  * `interactive`: Run interactive mode
73
73
 
74
- ## `safe-file-example token`
74
+ ## `safe-file-example get-token`
75
75
 
76
76
  Get and store access token
77
77
 
78
78
  **Usage**:
79
79
 
80
80
  ```console
81
- $ safe-file-example token [OPTIONS]
81
+ $ safe-file-example get-token [OPTIONS]
82
82
  ```
83
83
 
84
84
  **Options**:
@@ -132,4 +132,4 @@ $ safe-file-example interactive [OPTIONS]
132
132
 
133
133
  **Options**:
134
134
 
135
- * `--help`: Show this message and exit.
135
+ * `--help`: Show this message and exit.
@@ -1,22 +1,21 @@
1
1
  # `safe-file-example`
2
-
3
2
  ## Description
4
3
 
5
4
  A python package for interacting with SAFE File transfer API
6
5
 
7
6
  ## Current Functionalities
8
7
 
9
- - Generate a JWT Token
8
+ - Generate a JWT Token
10
9
  - Import a File
11
10
  - Export a File
12
11
 
13
- ## Prerequisite
12
+ ## Prerequisite
14
13
 
15
- - Having minimum Python 3.12 installed in your system and setting up a Python Virtual Environment.
14
+ - Having minimum Python 3.12 installed in your system and setting up a Python Virtual Environment.
16
15
 
17
16
  ## Workflow
18
17
 
19
- - Activate your Python Virtual Environment,
18
+ - Activate your Python Virtual Environment,
20
19
  - Then run the following command.
21
20
 
22
21
  ```bash
@@ -28,6 +27,7 @@ pip install safe-file-example
28
27
  safe-file-example
29
28
  ```
30
29
 
30
+
31
31
  **Usage**:
32
32
 
33
33
  ```console
@@ -40,19 +40,19 @@ $ safe-file-example [OPTIONS] COMMAND [ARGS]...
40
40
 
41
41
  **Commands**:
42
42
 
43
- * `token`: Get and store access token
43
+ * `get-token`: Get and store access token
44
44
  * `import-file`: Import a file
45
45
  * `export-file`: Export a file
46
46
  * `interactive`: Run interactive mode
47
47
 
48
- ## `safe-file-example token`
48
+ ## `safe-file-example get-token`
49
49
 
50
50
  Get and store access token
51
51
 
52
52
  **Usage**:
53
53
 
54
54
  ```console
55
- $ safe-file-example token [OPTIONS]
55
+ $ safe-file-example get-token [OPTIONS]
56
56
  ```
57
57
 
58
58
  **Options**:
@@ -106,4 +106,4 @@ $ safe-file-example interactive [OPTIONS]
106
106
 
107
107
  **Options**:
108
108
 
109
- * `--help`: Show this message and exit.
109
+ * `--help`: Show this message and exit.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "safe-file-example"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  { name="Dip Paul", email="dip.paul@uib.no" },
10
10
  { name="Eirik Halseth", email="eirik.halseth@uib.no" },
@@ -45,4 +45,4 @@ safe-file-example="safe.main:app"
45
45
  packages = ["safe"]
46
46
  include = [
47
47
  "safe/assets/*.png"
48
- ]
48
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.0.0"
@@ -10,8 +10,10 @@ from pathlib import Path
10
10
  import safe.modules.import_module as import_module
11
11
  import safe.modules.export_module as export_module
12
12
  import safe.modules.list_export_module as list_export_module
13
- import safe.modules.select_helper as select_option
14
13
  import importlib.resources
14
+ from safe.modules.helper_functions import HelperFunction
15
+
16
+ helperFunction = HelperFunction()
15
17
  try:
16
18
  import readline
17
19
  except ImportError:
@@ -25,23 +27,26 @@ token_file = os.path.join(config_dir, "token.json")
25
27
  base_url = "https://api.safe.uib.no/v1"
26
28
  logo_path = importlib.resources.files("safe.assets").joinpath("safe_logo.png")
27
29
 
30
+
28
31
  def get_access_token():
29
32
  endpoint = "/auth"
30
33
  auth_url = f"{base_url}{endpoint}"
31
34
  webbrowser.open(auth_url)
32
-
35
+
33
36
  Access_token = input("Paste You're JWT token here: ")
34
-
37
+
35
38
  os.makedirs(config_dir, exist_ok=True)
36
39
  with open(token_file, "w") as f:
37
40
  f.write(Access_token)
38
- if os.name != 'nt':
39
- os.chmod(token_file, 0o600) #Change the ownership on Linux/MacOS
41
+ if os.name != "nt":
42
+ os.chmod(token_file, 0o600) # Change the ownership on Linux/MacOS
40
43
  print("token succesfully stored")
41
44
 
45
+
42
46
  def exit_cli():
43
47
  sys.exit()
44
48
 
49
+
45
50
  def importFile():
46
51
  endpoint = "/files/import"
47
52
  url = f"{base_url}{endpoint}"
@@ -64,11 +69,13 @@ def importFile():
64
69
  root = tk.Tk()
65
70
  app_icon = tk.PhotoImage(file=str(logo_path))
66
71
  root.withdraw()
67
- root.attributes('-topmost', True)
72
+ root.attributes("-topmost", True)
68
73
  root.iconphoto(True, app_icon)
69
74
  root.update()
70
- file_path_str = filedialog.askopenfilename(title="Select a file to import", initialdir=os.path.expanduser("~"))
71
- root.attributes('-topmost', False)
75
+ file_path_str = filedialog.askopenfilename(
76
+ title="Select a file to import", initialdir=os.path.expanduser("~")
77
+ )
78
+ root.attributes("-topmost", False)
72
79
  root.iconphoto(False, app_icon)
73
80
  root.destroy()
74
81
 
@@ -77,7 +84,7 @@ def importFile():
77
84
  continue
78
85
 
79
86
  file_path = Path(file_path_str)
80
-
87
+
81
88
  print("\n--- Import Summary ---")
82
89
  print(f"\nServer: {server}")
83
90
  print(f"File: {file_path.name}\n")
@@ -101,6 +108,7 @@ def importFile():
101
108
  print("Import cancelled.")
102
109
  break
103
110
 
111
+
104
112
  def exportFile():
105
113
  export_endpoint = "/files/export"
106
114
  export_url = f"{base_url}{export_endpoint}"
@@ -131,14 +139,12 @@ def exportFile():
131
139
  return
132
140
 
133
141
  while True:
134
- filename = select_option.select_from_list(
135
- files,
136
- "Select file by number or type filename: "
142
+ filename = helperFunction.select_from_list(
143
+ files, "Select file by number or type filename: "
137
144
  )
138
145
  if filename:
139
146
  break
140
147
 
141
-
142
148
  # ---- Directory picker ----
143
149
  print("Select output directory")
144
150
  root = tk.Tk()
@@ -147,7 +153,9 @@ def exportFile():
147
153
  root.attributes("-topmost", True)
148
154
  root.iconphoto(True, app_icon)
149
155
  root.update()
150
- output_dir = filedialog.askdirectory(title="Select output directory", initialdir=os.path.expanduser("~"))
156
+ output_dir = filedialog.askdirectory(
157
+ title="Select output directory", initialdir=os.path.expanduser("~")
158
+ )
151
159
  root.attributes("-topmost", False)
152
160
  root.iconphoto(False, app_icon)
153
161
  root.destroy()
@@ -185,6 +193,7 @@ def exportFile():
185
193
  print("Export cancelled.")
186
194
  return
187
195
 
196
+
188
197
  def check_token():
189
198
  if os.path.exists(token_file):
190
199
  with open(token_file, "r") as f:
@@ -205,22 +214,42 @@ def check_token():
205
214
  else:
206
215
  print("No JWT token found in the token file.")
207
216
 
217
+
208
218
  @app.callback()
209
- def main(ctx:typer.Context):
219
+ def main(ctx: typer.Context):
220
+ print("Reminder: Please use SAFE-VPN when accessing the SAFE File Examples.")
221
+ print(
222
+ "Note: Do not share the information in the JWT-token with anyone as it is a personal access token"
223
+ )
224
+ version_check()
210
225
  if ctx.invoked_subcommand is None:
211
226
  typer.echo(ctx.get_help())
212
227
  raise typer.Exit()
213
228
 
214
- @app.command()
229
+
230
+ def version_check():
231
+ """prints local version and the newest available version in pip"""
232
+ helperFunction.check_pip_version()
233
+
234
+
215
235
  def token():
216
236
  """Get and store access token"""
217
237
  get_access_token()
218
238
 
219
- @app.command()
239
+
220
240
  def import_file(
221
241
  server: str = typer.Option(None, help="Server name"),
222
- file_path: Path = typer.Option(None, exists=True, file_okay=True, dir_okay=False, readable=True, help="Path to file to import"),
223
- interactive: bool = typer.Option(False, "-i", "--interactive", help="Run import interactively"),
242
+ file_path: Path = typer.Option(
243
+ None,
244
+ exists=True,
245
+ file_okay=True,
246
+ dir_okay=False,
247
+ readable=True,
248
+ help="Path to file to import",
249
+ ),
250
+ interactive: bool = typer.Option(
251
+ False, "-i", "--interactive", help="Run import interactively"
252
+ ),
224
253
  ):
225
254
  """Import a file"""
226
255
  if interactive or not (server and file_path):
@@ -249,12 +278,21 @@ def import_file(
249
278
  )
250
279
  typer.echo(f"Import result: {success}")
251
280
 
252
- @app.command()
281
+
253
282
  def export_file(
254
283
  server: str = typer.Option(None, help="Server name"),
255
284
  filename: str = typer.Option(None, help="Filename to export"),
256
- output_dir: Path = typer.Option(None, exists=True, file_okay=False, dir_okay=True, writable=True, help="Output directory"),
257
- interactive: bool = typer.Option(False, "-i", "--interactive", help="Run export interactively"),
285
+ output_dir: Path = typer.Option(
286
+ None,
287
+ exists=True,
288
+ file_okay=False,
289
+ dir_okay=True,
290
+ writable=True,
291
+ help="Output directory",
292
+ ),
293
+ interactive: bool = typer.Option(
294
+ False, "-i", "--interactive", help="Run export interactively"
295
+ ),
258
296
  ):
259
297
  """Export a file"""
260
298
  if interactive or not (server and filename and output_dir):
@@ -274,7 +312,9 @@ def export_file(
274
312
 
275
313
  output_path = output_dir / filename
276
314
 
277
- typer.echo(f"Exporting file {filename} from server {server} to {output_path}...")
315
+ typer.echo(
316
+ f"Exporting file {filename} from server {server} to {output_path}..."
317
+ )
278
318
 
279
319
  success = export_module.write_file_to_disk(
280
320
  download_path=output_path,
@@ -288,21 +328,30 @@ def export_file(
288
328
 
289
329
  def interactive_menu():
290
330
  check_token()
291
- choices: list = [get_access_token, importFile, exportFile, exit_cli]
331
+ # Map functions to their user-friendly descriptions (matching --help output)
332
+ choices = [
333
+ (get_access_token, "get-token"),
334
+ (importFile, "import-file"),
335
+ (exportFile, "export-file"),
336
+ (exit_cli, "Exit the Prorgam"),
337
+ ]
292
338
  while True:
293
- print("\n--- SAFE CLI ---")
294
- for index, item in enumerate(choices, start=1):
295
- print(f"{index}. {item.__name__}")
296
-
297
- menu_names = [fn.__name__ for fn in choices]
298
- selection = select_option.select_from_list(
299
- menu_names,
300
- "Enter menu number or command name: "
339
+ print("\n--- SAFE File Examples ---")
340
+ for index, (_, description) in enumerate(choices, start=1):
341
+ print(f"{index}. {description}")
342
+
343
+ menu_names = [desc for _, desc in choices]
344
+ selection = helperFunction.select_from_list(
345
+ menu_names, "Enter menu number or command name: "
301
346
  )
302
347
  if not selection:
303
348
  continue
304
349
 
305
- selected_fn = choices[menu_names.index(selection)]
350
+ selected_fn = None
351
+ for fn, desc in choices:
352
+ if desc == selection:
353
+ selected_fn = fn
354
+ break
306
355
 
307
356
  if selected_fn == exit_cli:
308
357
  exit_cli()
@@ -310,15 +359,17 @@ def interactive_menu():
310
359
  else:
311
360
  selected_fn()
312
361
 
362
+
363
+ app.command(name="get-token")(token)
364
+ app.command(name="import-file")(import_file)
365
+ app.command(name="export-file")(export_file)
366
+
367
+
313
368
  @app.command(name="interactive")
314
369
  def interactive_command():
315
370
  """Run interactive mode"""
316
371
  interactive_menu()
317
372
 
318
- app.command()(token)
319
- app.command()(import_file)
320
- app.command()(export_file)
321
373
 
322
374
  if __name__ == "__main__":
323
375
  app()
324
-
@@ -0,0 +1 @@
1
+ __version__ = "0.0.0"
@@ -19,9 +19,7 @@ def write_file_to_disk(
19
19
  temp_path = download_path.with_suffix(download_path.suffix + ".safetmp")
20
20
  download_path.parent.mkdir(parents=True, exist_ok=True)
21
21
 
22
- headers = {
23
- "Authorization": f"Bearer {token}"
24
- }
22
+ headers = {"Authorization": f"Bearer {token}"}
25
23
 
26
24
  params = {
27
25
  "filename": filename,
@@ -55,11 +53,11 @@ def write_file_to_disk(
55
53
  print("download completed:", download_path)
56
54
  return True
57
55
 
58
- except Exception as e:
56
+ except Exception:
59
57
  if temp_path.exists():
60
58
  try:
61
59
  temp_path.unlink()
62
60
  except Exception:
63
61
  pass
64
62
  print("Download failed")
65
- return False
63
+ return False
@@ -0,0 +1,52 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+ import json
3
+ from urllib.request import urlopen
4
+
5
+
6
+ class HelperFunction:
7
+ def select_from_list(self, options: list[str], prompt: str) -> str | None:
8
+ """
9
+ Allow selection by number or exact name.
10
+ Returns the selected string or None if invalid.
11
+ """
12
+ choice = input(prompt).strip()
13
+
14
+ # Number selection
15
+ if choice.isdigit():
16
+ idx = int(choice) - 1
17
+ if 0 <= idx < len(options):
18
+ return options[idx]
19
+ print("Invalid number.")
20
+ return None
21
+
22
+ # Name selection
23
+ if choice in options:
24
+ return choice
25
+
26
+ print("Invalid selection.")
27
+ return None
28
+
29
+ def bytes_to_human_readable(self, size):
30
+ power = 2**10
31
+ n = 0
32
+ power_labels = {0: "", 1: "kilo", 2: "mega", 3: "giga", 4: "tera"}
33
+ while size > power:
34
+ size /= power
35
+ n += 1
36
+ return f"{size} {power_labels[n] + 'bytes'}"
37
+
38
+ def check_pip_version(self):
39
+ package_name = "safe-file-example"
40
+ try:
41
+ installed_version = version(package_name)
42
+ except PackageNotFoundError:
43
+ installed_version = "not installed"
44
+
45
+ with urlopen(
46
+ f"https://pypi.org/pypi/{package_name}/json", timeout=5
47
+ ) as response:
48
+ pypi_version = json.load(response)["info"]["version"]
49
+
50
+ if pypi_version > installed_version:
51
+ print(f"{package_name} - {pypi_version} is available")
52
+ print(f"run pip install --upgrade {package_name} to upgrade")
@@ -1,4 +1,3 @@
1
- import os
2
1
  import mimetypes
3
2
  import requests
4
3
  from pathlib import Path
@@ -68,4 +67,4 @@ def upload_file(
68
67
  return False
69
68
  except Exception as e:
70
69
  print("Unexpected error:", type(e).__name__, e)
71
- return False
70
+ return False
@@ -1,5 +1,9 @@
1
1
  import requests
2
2
  from typing import List
3
+ from .helper_functions import HelperFunction
4
+
5
+ helper_function = HelperFunction()
6
+
3
7
 
4
8
  def list_export_files(
5
9
  base_url: str,
@@ -9,12 +13,9 @@ def list_export_files(
9
13
  pagination: bool = True,
10
14
  timeout: int = 30,
11
15
  ) -> List[str]:
12
-
13
16
  url = f"{base_url}/files/info/list/export"
14
17
 
15
- headers = {
16
- "Authorization": f"Bearer {token}"
17
- }
18
+ headers = {"Authorization": f"Bearer {token}"}
18
19
 
19
20
  params = {
20
21
  "server": server,
@@ -37,18 +38,22 @@ def list_export_files(
37
38
 
38
39
  # Extract filenames
39
40
  content = data.get("content", {})
40
- entries = content.get("Files and Directories", [])
41
- for entry in entries:
42
- if isinstance(entry, dict):
43
- files.extend(entry.keys())
41
+ data = content.get("files_and_directories", [])
42
+ entries = {
43
+ item["name"]: helper_function.bytes_to_human_readable(item["size"])
44
+ for item in data
45
+ if item.get("type") == "file"
46
+ }
47
+
48
+ files = list(entries.keys()) # List containing name of files
49
+ size_of_file = list(entries.values()) # List containing size of files
44
50
 
45
- # Pretty print
46
51
  print("\n--- Available export files ---")
47
52
  if not files:
48
53
  print("(No files found)")
49
54
  else:
50
55
  for i, name in enumerate((files), start=1):
51
- print(f"{i}. {name}")
56
+ print(f"{i}. {name}" + f" -- {size_of_file[i - 1]}")
52
57
  print("------------------------------\n")
53
58
 
54
59
  return files
@@ -1 +0,0 @@
1
- __version__ = "0.0.0"
@@ -1 +0,0 @@
1
- __version__ = "0.0.0"
@@ -1,22 +0,0 @@
1
-
2
- def select_from_list(options: list[str], prompt: str) -> str | None:
3
- """
4
- Allow selection by number or exact name.
5
- Returns the selected string or None if invalid.
6
- """
7
- choice = input(prompt).strip()
8
-
9
- # Number selection
10
- if choice.isdigit():
11
- idx = int(choice) - 1
12
- if 0 <= idx < len(options):
13
- return options[idx]
14
- print("Invalid number.")
15
- return None
16
-
17
- # Name selection
18
- if choice in options:
19
- return choice
20
-
21
- print("Invalid selection.")
22
- return None