Skip to content

Commit 3ba0dcb

Browse files
committed
Refactor filename matching logic to separate classes
Signed-off-by: Olivier Mehani <shtrom@ssji.net>
1 parent 32e966c commit 3ba0dcb

File tree

1 file changed

+67
-38
lines changed

1 file changed

+67
-38
lines changed

rotate_backups/__init__.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,58 @@ def rotate_backups(directory, rotation_scheme, **options):
260260
program.rotate_backups(directory)
261261

262262

263+
class Match:
264+
def match_to_datetime(self) -> datetime.datetime:
265+
pass
266+
267+
268+
class Matcher:
269+
def search(self, entry: str) -> Match:
270+
pass
271+
272+
273+
class FilenameMatch(Match):
274+
match: re.Match = None
275+
276+
def __init__(self, match: re.Match):
277+
self.match = match
278+
279+
def match_to_datetime(self) -> datetime.datetime:
280+
"""
281+
Convert the regular expression match to a :class:`~datetime.datetime` value.
282+
283+
:returns: A :class:`~datetime.datetime` value.
284+
:raises: :exc:`exceptions.ValueError` when a required date component is
285+
not captured by the pattern, the captured value is an empty
286+
string or the captured value cannot be interpreted as a
287+
base-10 integer.
288+
289+
.. seealso:: :data:`SUPPORTED_DATE_COMPONENTS`
290+
"""
291+
kw = {}
292+
captures = self.match.groupdict()
293+
for component, required in SUPPORTED_DATE_COMPONENTS:
294+
value = captures.get(component)
295+
if value:
296+
kw[component] = int(value, 10)
297+
elif required:
298+
raise ValueError("Missing required date component! (%s)" % component)
299+
else:
300+
kw[component] = 0
301+
return datetime.datetime(**kw)
302+
303+
304+
class FilenameMatcher(Matcher):
305+
timestamp_pattern: re.Pattern = None
306+
307+
def __init__(self, timestamp_pattern: re.Pattern):
308+
self.timestamp_pattern = timestamp_pattern
309+
310+
def search(self, entry: str) -> FilenameMatch:
311+
if match := self.timestamp_pattern.search(entry):
312+
return FilenameMatch(match)
313+
314+
263315
class RotateBackups(PropertyManager):
264316

265317
"""Python API for the ``rotate-backups`` program."""
@@ -642,8 +694,10 @@ def collect_backups(self, location):
642694
location = coerce_location(location)
643695
logger.info("Scanning %s for backups ..", location)
644696
location.ensure_readable(self.force)
697+
698+
matcher = FilenameMatcher(self.timestamp_pattern)
645699
for entry in natsort(location.context.list_entries(location.directory)):
646-
match = self.timestamp_pattern.search(entry)
700+
match = matcher.search(entry)
647701
if match:
648702
if self.exclude_list and any(fnmatch.fnmatch(entry, p) for p in self.exclude_list):
649703
logger.verbose("Excluded %s (it matched the exclude list).", entry)
@@ -653,7 +707,7 @@ def collect_backups(self, location):
653707
try:
654708
backups.append(Backup(
655709
pathname=os.path.join(location.directory, entry),
656-
timestamp=self.match_to_datetime(match),
710+
timestamp=match.match_to_datetime(),
657711
))
658712
except ValueError as e:
659713
logger.notice("Ignoring %s due to invalid date (%s).", entry, e)
@@ -663,31 +717,6 @@ def collect_backups(self, location):
663717
logger.info("Found %i timestamped backups in %s.", len(backups), location)
664718
return sorted(backups)
665719

666-
def match_to_datetime(self, match):
667-
"""
668-
Convert a regular expression match to a :class:`~datetime.datetime` value.
669-
670-
:param match: A regular expression match object.
671-
:returns: A :class:`~datetime.datetime` value.
672-
:raises: :exc:`exceptions.ValueError` when a required date component is
673-
not captured by the pattern, the captured value is an empty
674-
string or the captured value cannot be interpreted as a
675-
base-10 integer.
676-
677-
.. seealso:: :data:`SUPPORTED_DATE_COMPONENTS`
678-
"""
679-
kw = {}
680-
captures = match.groupdict()
681-
for component, required in SUPPORTED_DATE_COMPONENTS:
682-
value = captures.get(component)
683-
if value:
684-
kw[component] = int(value, 10)
685-
elif required:
686-
raise ValueError("Missing required date component! (%s)" % component)
687-
else:
688-
kw[component] = 0
689-
return datetime.datetime(**kw)
690-
691720
def group_backups(self, backups):
692721
"""
693722
Group backups collected by :func:`collect_backups()` by rotation frequencies.
@@ -781,25 +810,25 @@ class Location(PropertyManager):
781810

782811
""":class:`Location` objects represent a root directory containing backups."""
783812

784-
@required_property
813+
@ required_property
785814
def context(self):
786815
"""An execution context created using :mod:`executor.contexts`."""
787816

788-
@required_property
817+
@ required_property
789818
def directory(self):
790819
"""The pathname of a directory containing backups (a string)."""
791820

792-
@lazy_property
821+
@ lazy_property
793822
def have_ionice(self):
794823
""":data:`True` when ionice_ is available, :data:`False` otherwise."""
795824
return self.context.have_ionice
796825

797-
@lazy_property
826+
@ lazy_property
798827
def have_wildcards(self):
799828
""":data:`True` if :attr:`directory` is a filename pattern, :data:`False` otherwise."""
800829
return '*' in self.directory
801830

802-
@lazy_property
831+
@ lazy_property
803832
def mount_point(self):
804833
"""
805834
The pathname of the mount point of :attr:`directory` (a string or :data:`None`).
@@ -814,17 +843,17 @@ def mount_point(self):
814843
except ExternalCommandFailed:
815844
return None
816845

817-
@lazy_property
846+
@ lazy_property
818847
def is_remote(self):
819848
""":data:`True` if the location is remote, :data:`False` otherwise."""
820849
return isinstance(self.context, RemoteContext)
821850

822-
@lazy_property
851+
@ lazy_property
823852
def ssh_alias(self):
824853
"""The SSH alias of a remote location (a string or :data:`None`)."""
825854
return self.context.ssh_alias if self.is_remote else None
826855

827-
@property
856+
@ property
828857
def key_properties(self):
829858
"""
830859
A list of strings with the names of the :attr:`~custom_property.key` properties.
@@ -975,15 +1004,15 @@ class Backup(PropertyManager):
9751004
:attr:`~property_manager.PropertyManager.key_properties`.
9761005
"""
9771006

978-
@key_property
1007+
@ key_property
9791008
def pathname(self):
9801009
"""The pathname of the backup (a string)."""
9811010

982-
@key_property
1011+
@ key_property
9831012
def timestamp(self):
9841013
"""The date and time when the backup was created (a :class:`~datetime.datetime` object)."""
9851014

986-
@property
1015+
@ property
9871016
def week(self):
9881017
"""The ISO week number of :attr:`timestamp` (a number)."""
9891018
return self.timestamp.isocalendar()[1]

0 commit comments

Comments
 (0)