Skip to content

Commit a2de70a

Browse files
committed
git revision tracker + config cache updater implementation
1 parent 476418d commit a2de70a

2 files changed

Lines changed: 143 additions & 4 deletions

File tree

coperniFUS/__init__.py

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
print(f"Launching CoperniFUS v{version('coperniFUS')}")
55

6-
import sys, pathlib, trimesh, pymeshfix, copy, hashlib, base64, warnings, re
6+
import sys, shutil, os, subprocess, pathlib, trimesh, pymeshfix, copy, hashlib, base64, warnings, re
77
import PyQt6.QtGui as pyqtg
88
import PyQt6.QtCore as pyqtc
99
import PyQt6.QtWidgets as pyqtw
1010
from si_prefix import si_format, si_parse
11+
from datetime import datetime
1112
import pyqtgraph.opengl as gl
1213
import numpy as np
1314

@@ -17,13 +18,145 @@
1718
from coperniFUS.modules import _jsonshelve
1819

1920
import coperniFUS
20-
coperniFUS_location = coperniFUS.__file__
21+
coperniFUS_location = pathlib.Path(coperniFUS.__file__)
2122

2223
def clean_string(string):
2324
clean_string = ''.join(filter(str.isalnum, string))
2425
return clean_string
2526

2627

28+
29+
class GitVersionTrackerInterface(object):
30+
31+
GITHUB_BASE_URL = 'https://github.com/Tomaubier/CoperniFUS/' # commit/hash
32+
33+
def __init__(self, repo_path):
34+
35+
if shutil.which('git') is None:
36+
raise ValueError('Git command not found')
37+
38+
self.repo_path = repo_path
39+
40+
def get_current_git_revision_hash(self) -> str:
41+
return subprocess.check_output(
42+
['git', 'rev-parse', 'HEAD'],
43+
cwd=self.repo_path
44+
).decode('ascii').strip()
45+
46+
def get_commit_datetime(self, commit_hash):
47+
output = subprocess.check_output(
48+
['git', 'show', '-s', '--format=%cI', commit_hash],
49+
cwd=self.repo_path
50+
).decode().strip()
51+
return datetime.fromisoformat(output)
52+
53+
def get_commit_message(self, commit_hash):
54+
return subprocess.check_output(
55+
['git', 'show', '-s', '--format=%B', commit_hash],
56+
cwd=self.repo_path
57+
).decode('utf-8').strip()
58+
59+
60+
class CacheUpdater(object):
61+
62+
"""
63+
Reference the breaking revisions that require a cache update in CACHE_UPDATER_PROCEDURES.
64+
"""
65+
66+
CACHE_UPDATER_PROCEDURES = {
67+
# '2026-08-04T15:00:12+01:00': { # revision date using iso format
68+
# 'commit_hash': 'ad44071b61f825a77c9f12f762cdd89faab817ad',
69+
# 'commit_message': 'trimesh 2d path extrusion bug fix -> bumping mapbox_earcut to v2.0.0',
70+
# },
71+
}
72+
73+
def __init__(self, cache_data_handler):
74+
self.cache_data_handler = cache_data_handler
75+
self.git_handler = GitVersionTrackerInterface(coperniFUS_location.parent.parent)
76+
77+
self.cache_updater_procedures = {
78+
commit_datetime: {
79+
**commit_update_dict,
80+
'cache_upgrade_function': self._get_updater_function(commit_update_dict['commit_hash'])}
81+
for (commit_datetime, commit_update_dict) in self.CACHE_UPDATER_PROCEDURES.items()
82+
if self._is_updater_function_available(commit_update_dict['commit_hash'])
83+
}
84+
85+
self.check_for_cache_updates()
86+
87+
def check_for_cache_updates(self):
88+
89+
# Get the revision status of the configuration file being loaded
90+
self.cache_last_update_githash = self.cache_data_handler.get_attr(
91+
'cache_last_update_githash', default_value=None)
92+
self.cache_last_update_githash_datetime = self.cache_data_handler.get_attr(
93+
'cache_last_update_githash_datetime', default_value=None)
94+
if self.cache_last_update_githash is None:
95+
warnings.warn('No git hash was found in this configuration file -> Assuming it was created using CoperniFUS v0.1.2')
96+
self.cache_last_update_githash = '8c5f1cab59f03ffb4012c54b98d87270b6fc6320' # '2025-06-18 15:51:54' -> v0.1.2
97+
if self.cache_last_update_githash_datetime is None:
98+
self.cache_last_update_githash_datetime = self.git_handler.get_commit_datetime(
99+
self.cache_last_update_githash
100+
)
101+
else:
102+
self.cache_last_update_githash_datetime = datetime.fromisoformat(self.cache_last_update_githash_datetime) # decode str -> datetime object
103+
104+
# Get the revision status of the CoperniFUS instance
105+
self.current_coperniFUS_revision_hash = self.git_handler.get_current_git_revision_hash()
106+
self.current_coperniFUS_revision_datetime = self.git_handler.get_commit_datetime(
107+
self.current_coperniFUS_revision_hash
108+
)
109+
110+
# Outdated CoperniFUS instance
111+
if self.cache_last_update_githash_datetime > self.current_coperniFUS_revision_datetime:
112+
warning.warn(f'This configuration file appears to have been created using a more recent revision of CoperniFUS.\n\t-> Config. file {self.current_coperniFUS_revision_datetime} (git hash: {self.cache_last_update_githash})\n\t-> CoperniFUS: {self.cache_last_update_githash_datetime} (git hash: {self.current_coperniFUS_revision_hash})\nSome features might be missing in the version installed on this system. Please update CoperniFUS by pulling a newer version from its GitHub repo ({self.git_handler.GITHUB_BASE_URL}), or ignore this warning if you know what you are doing! :)')
113+
114+
# Outdated cache file
115+
if self.cache_last_update_githash_datetime < self.current_coperniFUS_revision_datetime:
116+
available_cache_updates_procedures = self._get_available_cache_updates(self.cache_last_update_githash_datetime)
117+
118+
# Check for updates available
119+
if len(available_cache_updates_procedures) > 0:
120+
legacy_cache_fpath = self.cache_data_handler.cache_dir / '_legacy_config_files' / f'{self.cache_data_handler.cached_settings_fname.split(".")[0]}_{self.cache_last_update_githash}.json'
121+
os.makedirs(self.cache_data_handler.cache_dir / '_legacy_config_files', exist_ok=True)
122+
shutil.copy(self.cache_data_handler.cached_settings_fpath, legacy_cache_fpath)
123+
print(f'\n> {len(available_cache_updates_procedures)} cache updates available -> the current version of the cache file has been backed-up to\n\t-> {str(legacy_cache_fpath)}')
124+
125+
for commit_datetime, commit_update_dict in available_cache_updates_procedures.items():
126+
print(f'\n=== Running {commit_datetime} cache update ({commit_update_dict['commit_message']}) ===')
127+
commit_update_dict['cache_upgrade_function']()
128+
129+
# Udpate cache_last_update_githash is procedure successful
130+
self.cache_data_handler.set_attr('cache_last_update_githash', self.current_coperniFUS_revision_hash)
131+
self.cache_data_handler.set_attr('cache_last_update_githash_datetime', self.current_coperniFUS_revision_datetime.isoformat())
132+
133+
def _get_updater_func_name(self, commit_hash):
134+
return f'updater_func_{commit_hash}'
135+
136+
def _is_updater_function_available(self, commit_hash):
137+
if hasattr(self, self._get_updater_func_name(commit_hash)):
138+
return True
139+
else:
140+
return False
141+
142+
def _get_updater_function(self, commit_hash):
143+
return getattr(self, self._get_updater_func_name(commit_hash))
144+
145+
def _get_available_cache_updates(self, cache_last_update_datetime: datetime):
146+
available_cache_update_procedures = {
147+
commit_datetime: commit_update_dict
148+
for commit_datetime, commit_update_dict in self.cache_updater_procedures.items()
149+
if datetime.fromisoformat(commit_datetime) > cache_last_update_datetime
150+
}
151+
return available_cache_update_procedures
152+
153+
# === ADD UPDATER FUNCTIONS HERE ===
154+
155+
def updater_func_ad44071b61f825a77c9f12f762cdd89faab817ad(self):
156+
# implement cache update procedure here
157+
pass
158+
159+
27160
class CachedDataHandler:
28161

29162
def __init__(self, cache_dir_name='.cachedDir', cached_settings_fname=None):
@@ -63,6 +196,12 @@ def __init__(self, cache_dir_name='.cachedDir', cached_settings_fname=None):
63196

64197
print(f'\n> Info: Cached configuration file location: {self.cached_settings_fpath}')
65198

199+
# === Run cache updater ===
200+
try:
201+
self._updater = CacheUpdater(self)
202+
except Exception as e:
203+
warnings.warn(f'{str(e)} -> Skipping cache version checks')
204+
66205
def is_cached_filename_already_defined(self, cache_fname):
67206
""" Checks the availibility of a cache_fname (regardless of the file extension). """
68207
directory_path = pathlib.Path(self.cache_dir)
@@ -74,7 +213,7 @@ def is_cached_filename_already_defined(self, cache_fname):
74213

75214
@property
76215
def cached_settings_fpath(self):
77-
""" FIle path of the cached file. """
216+
""" File path of the cached file. """
78217
return self.cache_dir / self.cached_settings_fname
79218

80219
def _attribute_str_id(self, attribute_id):

coperniFUS/viewer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def show_error_popup(self, error_title, error_description=None):
143143
def load_example_configuration(self, example_configuration_name):
144144
""" Switch to a configuration provided in CoperniFUS example directory """
145145
extension_free_name = example_configuration_name.split('.')[0]
146-
examples_dir_path = pathlib.Path(coperniFUS_location).parent / 'examples'
146+
examples_dir_path = coperniFUS_location.parent / 'examples'
147147
example_configuration_fpath = examples_dir_path / f'{extension_free_name}.json'
148148
if not example_configuration_fpath.exists():
149149
raise ValueError(f'{example_configuration_name} does not exist in {examples_dir_path}.')

0 commit comments

Comments
 (0)