@@ -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+
263315class 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