dfsync 0.4.7__tar.gz → 0.4.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dfsync
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary:
5
5
  Author: Mihai Balint
6
6
  Author-email: balint.mihai@gmail.com
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: black (==23.7.0)
14
+ Requires-Dist: black (==24.3.0)
15
15
  Requires-Dist: click (>=8,<9)
16
16
  Requires-Dist: click-default-group (>=1.2.2,<2.0.0)
17
17
  Requires-Dist: gitpython (>=3.1.14,<4.0.0)
@@ -1,5 +1,10 @@
1
+ import click
1
2
  import json
3
+ import subprocess
4
+ import sys
2
5
  import urllib.request
6
+ from typing import Optional
7
+
3
8
  from dfsync.lib import ControlledThreadedOperation
4
9
 
5
10
 
@@ -8,14 +13,14 @@ def get_package_version(package_name):
8
13
  from importlib.metadata import version
9
14
 
10
15
  return version(package_name)
11
- except:
16
+ except Exception:
12
17
  pass
13
18
 
14
19
  try:
15
20
  import pkg_resources
16
21
 
17
22
  return pkg_resources.get_distribution(package_name).version
18
- except:
23
+ except Exception:
19
24
  pass
20
25
 
21
26
  return None
@@ -41,7 +46,7 @@ def get_latest_version():
41
46
  raise ValueError("No version visible on github")
42
47
 
43
48
  return versions[0]
44
- except Exception as e:
49
+ except Exception:
45
50
  return get_installed_version()
46
51
 
47
52
 
@@ -52,7 +57,7 @@ def parse_version(version_str):
52
57
  for part in version_str.split("."):
53
58
  try:
54
59
  parsed_version.append(int(part))
55
- except:
60
+ except Exception:
56
61
  parsed_version.append(part)
57
62
  return parsed_version
58
63
 
@@ -80,15 +85,52 @@ class AsyncVersionChecker(ControlledThreadedOperation):
80
85
  self.installed = get_installed_version()
81
86
  self.latest = get_latest_version()
82
87
  self.installed_is_older = is_older_version(self.installed, self.latest)
88
+ self._is_install_editable = is_installed_in_editable_mode("dfsync")
83
89
  self._cta_count = 0
84
90
  self.stop()
85
91
 
86
92
  @property
87
- def should_emit_upgrade_warning(self):
93
+ def should_emit_update_warning(self):
88
94
  return self.is_completed and self.installed_is_older and self._cta_count < 1
89
95
 
90
- def set_emitted_upgrade_warning(self):
96
+ def set_emitted_update_warning(self):
91
97
  self._cta_count += 1
92
98
 
93
- def get_upgrade_warning(self):
94
- return f"dfsync ver. {self.latest} is available, please upgrade! ({self.installed} is installed)"
99
+ def get_update_warning(self):
100
+ if self._is_install_editable:
101
+ return f"dfsync ver. {self.latest} is available, please update! ({self.installed} is installed)"
102
+ else:
103
+ return f"dfsync ver. {self.latest} is available ({self.installed} is installed), please update using `dfsync self-update`"
104
+
105
+
106
+ def _read_editable_location_from_pip_show_output(out: str) -> Optional[str]:
107
+ editable_label = "Editable project location: "
108
+ site_packages_label = "site-packages"
109
+ site_packages_lines = []
110
+
111
+ for line in out.split("\n"):
112
+ if line.startswith("Location: "):
113
+ continue
114
+ if line.startswith(editable_label):
115
+ return line[len(editable_label) :].strip()
116
+ if site_packages_label in line:
117
+ site_packages_lines.append(line)
118
+
119
+ return "UNKNOWN-EDITABLE-LOCATION" if len(site_packages_lines) > 0 else None
120
+
121
+
122
+ def is_installed_in_editable_mode(package_name: str) -> bool:
123
+ try:
124
+ out = subprocess.check_output([sys.executable, "-m", "pip", "show", "-f", package_name]).decode()
125
+ editable_location = _read_editable_location_from_pip_show_output(out)
126
+ return editable_location is not None
127
+ except Exception:
128
+ return False
129
+
130
+
131
+ def update_package(package_name: str):
132
+ click.echo(f"Running {sys.executable} -m pip install --upgrade {package_name}")
133
+ try:
134
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", package_name])
135
+ except Exception as e:
136
+ click.echo(f"Failed to update {package_name}: {e}")
@@ -77,14 +77,16 @@ def update_local_kube_config(new_kube_credentials):
77
77
  credentials_yaml = yaml.dump(credentials)
78
78
  with open(f"{LOCAL_CREDENTIALS_FILE}.new", "w") as f:
79
79
  f.write(credentials_yaml)
80
+ os.chmod(f"{LOCAL_CREDENTIALS_FILE}.new", 0o600)
80
81
 
81
82
  if os.path.isfile(LOCAL_CREDENTIALS_FILE):
82
83
  if os.path.isfile(f"{LOCAL_CREDENTIALS_FILE}.old"):
83
84
  os.remove(f"{LOCAL_CREDENTIALS_FILE}.old")
84
85
  os.rename(f"{LOCAL_CREDENTIALS_FILE}", f"{LOCAL_CREDENTIALS_FILE}.old")
86
+ os.chmod(f"{LOCAL_CREDENTIALS_FILE}.old", 0o600)
85
87
 
86
88
  os.rename(f"{LOCAL_CREDENTIALS_FILE}.new", f"{LOCAL_CREDENTIALS_FILE}")
87
-
89
+ os.chmod(LOCAL_CREDENTIALS_FILE, 0o600)
88
90
  return credentials_yaml
89
91
 
90
92
 
@@ -13,7 +13,14 @@ from watchdog.observers import Observer
13
13
  from watchdog.events import FileSystemEventHandler
14
14
 
15
15
  from dfsync.backends import rsync_backend, kube_backend
16
- from dfsync.distribution import get_installed_version, get_latest_version, is_older_version, AsyncVersionChecker
16
+ from dfsync.distribution import (
17
+ get_installed_version,
18
+ get_latest_version,
19
+ is_older_version,
20
+ AsyncVersionChecker,
21
+ is_installed_in_editable_mode,
22
+ update_package,
23
+ )
17
24
  from dfsync.filters import add_user_ignored_patterns_filter, ALL_FILTERS
18
25
  from dfsync.config import read_config
19
26
  from dfsync.char_ui import KeyController
@@ -253,12 +260,36 @@ def main():
253
260
 
254
261
  @main.command()
255
262
  def version():
263
+ """
264
+ Print the currently installed version
265
+ """
256
266
  installed_version = get_installed_version()
257
267
  click.echo(f"{installed_version}")
258
268
 
259
269
  latest_version = get_latest_version()
260
- if is_older_version(installed_version, latest_version):
261
- click.echo(f"Latest version is {latest_version}, please upgrade!")
270
+ if not is_older_version(installed_version, latest_version):
271
+ return
272
+ elif is_installed_in_editable_mode("dfsync"):
273
+ click.echo(f"Latest version is {latest_version}, please update!")
274
+ else:
275
+ click.echo(f"Latest version is {latest_version}, please update using `dfsync self-update`")
276
+
277
+
278
+ @main.command()
279
+ def self_update():
280
+ """
281
+ Update dfsync to the latest version published on pypi
282
+ """
283
+ latest_version = get_latest_version()
284
+ installed_version = get_installed_version()
285
+ if not is_older_version(installed_version, latest_version):
286
+ click.echo(f"The latest version is already installed: {installed_version}")
287
+ return
288
+ elif is_installed_in_editable_mode("dfsync"):
289
+ click.echo("dfsync is installed in editable mode, please update manually")
290
+ return
291
+ else:
292
+ update_package("dfsync")
262
293
 
263
294
 
264
295
  @main.command()
@@ -419,9 +450,9 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync):
419
450
  no_errors = False
420
451
  break
421
452
  with controller.getch_lock():
422
- if checker.should_emit_upgrade_warning:
423
- checker.set_emitted_upgrade_warning()
424
- click.echo(checker.get_upgrade_warning())
453
+ if checker.should_emit_update_warning:
454
+ checker.set_emitted_update_warning()
455
+ click.echo(checker.get_update_warning())
425
456
  controller.raise_exceptions()
426
457
 
427
458
  except KeyboardInterrupt:
@@ -3,10 +3,6 @@ from tempfile import NamedTemporaryFile
3
3
  from dfsync.filters import EmacsBufferFilter, UntrackedGitFilesFilter
4
4
 
5
5
 
6
- def test_version():
7
- assert False, "Test version not implemented"
8
-
9
-
10
6
  def test_emacs_buffer_filter():
11
7
  emacs = EmacsBufferFilter()
12
8
 
@@ -0,0 +1,30 @@
1
+ import pytest
2
+ from dfsync.distribution import is_installed_in_editable_mode, get_package_version, update_package
3
+
4
+
5
+ def test_is_installed_in_editable_mode():
6
+ assert is_installed_in_editable_mode("dfsync") is True
7
+
8
+
9
+ def test_is_installed_in_editable_mode_missing_package():
10
+ assert is_installed_in_editable_mode("does-not-exist") is False
11
+
12
+
13
+ def test_is_installed_in_editable_mode_required_and_installed_package():
14
+ assert pytest is not None, "pytest must be installed"
15
+ assert is_installed_in_editable_mode("pytest") is False
16
+
17
+ assert is_installed_in_editable_mode("click") is False
18
+
19
+
20
+ def test_update_package():
21
+ tqdm_version = get_package_version("tqdm")
22
+
23
+ update_package("tqdm==4.66.2")
24
+ assert get_package_version("tqdm") == "4.66.2"
25
+
26
+ update_package("tqdm==4.66.0")
27
+ assert get_package_version("tqdm") == "4.66.0"
28
+
29
+ update_package(f"tqdm=={tqdm_version}")
30
+ assert get_package_version("tqdm") == tqdm_version
@@ -1,6 +1,7 @@
1
+ import os
1
2
  import pytest
2
- import yaml
3
3
  import tempfile
4
+ import yaml
4
5
 
5
6
  from dfsync.kube_credentials import update_local_kube_config, normalized_k8s_url
6
7
 
@@ -12,7 +13,7 @@ def mock_kube_credentials(mocker):
12
13
  return temp.name
13
14
 
14
15
 
15
- def test_update_missing_kube_config(mock_kube_credentials):
16
+ def test_update_missing_kube_config_and_check_permissions(mock_kube_credentials):
16
17
  new_kube_credentials = {
17
18
  "clusters": [{"cluster": {"server": "test_server"}, "name": "test_cluster"}],
18
19
  "contexts": [{"context": {"cluster": "test_cluster", "user": "test_user"}, "name": "test_context"}],
@@ -23,6 +24,9 @@ def test_update_missing_kube_config(mock_kube_credentials):
23
24
  assert "test_context" in result
24
25
  assert "test_user" in result
25
26
 
27
+ # Check file permissions
28
+ assert oct(os.stat(mock_kube_credentials).st_mode)[-3:] == "600"
29
+
26
30
 
27
31
  def test_update_empty_kube_config(mock_kube_credentials):
28
32
  with open(mock_kube_credentials, "wb") as f:
@@ -53,6 +57,15 @@ def test_update_empty_kube_config(mock_kube_credentials):
53
57
  assert credentials["users"][0]["user"]["username"] == "test_username"
54
58
  assert credentials["users"][0]["user"]["password"] == "test_password"
55
59
 
60
+ # Verify that the old file was saved
61
+ with open(f"{mock_kube_credentials}.old", "r") as f:
62
+ credentials = yaml.safe_load(f)
63
+ assert credentials == empty_config
64
+
65
+ # Check file permissions for both old and new config file
66
+ assert oct(os.stat(mock_kube_credentials).st_mode)[-3:] == "600"
67
+ assert oct(os.stat(f"{mock_kube_credentials}.old").st_mode)[-3:] == "600"
68
+
56
69
 
57
70
  def test_update_existing_kube_config(mock_kube_credentials):
58
71
  with open(mock_kube_credentials, "wb") as f:
@@ -103,5 +116,5 @@ def test_normalized_k8s_url():
103
116
  assert normalized_k8s_url("192.168.0.123:6443") == "https://192.168.0.123:6443"
104
117
  with pytest.raises(ValueError):
105
118
  normalized_k8s_url("")
106
- with pytest.raises(ValueError):
107
- normalized_k8s_url(None)
119
+
120
+ assert normalized_k8s_url(None) is None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dfsync"
3
- version = "0.4.7"
3
+ version = "0.4.8"
4
4
  description = ""
5
5
  authors = ["Mihai Balint <balint.mihai@gmail.com>"]
6
6
 
@@ -14,7 +14,7 @@ toml = "^0.10.2"
14
14
  tenacity = "^8.0.1"
15
15
  click-default-group = "^1.2.2"
16
16
  paramiko = {version = "^3.2.0", extras = ["invoke"]}
17
- black = "23.7.0"
17
+ black = "24.3.0"
18
18
 
19
19
  [tool.poetry.dev-dependencies]
20
20
  pytest = "^7.4"
@@ -23,6 +23,7 @@ ipython = "^8"
23
23
  ipdb = "^0.13"
24
24
  flake8 = "^4.0.1"
25
25
  pycodestyle = "^2.8.0"
26
+ tqdm = "4.66.0"
26
27
 
27
28
  [tool.black]
28
29
  line-length = 120
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes