11from __future__ import annotations
22
33import datetime
4+ import json
5+ import tempfile
46import warnings
57from collections .abc import Iterator
6- from threading import Lock
8+ from pathlib import Path
79from typing import Any
810
11+ from filelock import FileLock
12+
913EPOCH = datetime .datetime .fromtimestamp (0 , tz = datetime .timezone .utc )
1014
1115
@@ -19,64 +23,13 @@ class seq:
1923 The class maintains separate sequences for different parameter combinations using
2024 class-level state, protected by locks for thread safety. It supports numbers,
2125 strings, dates, times, and datetimes.
22-
23- Examples:
24- Simple number sequence:
25- >>> seq(1000)
26- 1001
27- >>> seq(1000)
28- 1002
29- >>> seq(1000)
30- 1003
31-
32- String sequence with suffix:
33- >>> seq("User-", suffix="-test")
34- 'User-1-test'
35- >>> seq("User-", suffix="-test")
36- 'User-2-test'
37-
38- String sequence with custom start:
39- >>> seq("User-", start=10)
40- 'User-10'
41- >>> seq("User-", start=10)
42- 'User-11'
43-
44- Number sequence with custom increment:
45- >>> seq(1000, increment_by=10)
46- 1010
47- >>> seq(1000, increment_by=10)
48- 1020
49-
50- DateTime sequence:
51- >>> start_date = datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc)
52- >>> first = seq(start_date, increment_by=datetime.timedelta(days=1))
53- >>> first.isoformat()
54- '2024-01-02T00:00:00+00:00'
55- >>> second = seq(start_date, increment_by=datetime.timedelta(days=1))
56- >>> second.isoformat()
57- '2024-01-03T00:00:00+00:00'
58-
59- Date sequence:
60- >>> start = datetime.date(2024, 1, 1)
61- >>> first = seq(start, increment_by=datetime.timedelta(days=1))
62- >>> str(first)
63- '2024-01-02'
64- >>> second = seq(start, increment_by=datetime.timedelta(days=1))
65- >>> str(second)
66- '2024-01-03'
67-
68- Time sequence:
69- >>> start = datetime.time(12, 0)
70- >>> first = seq(start, increment_by=datetime.timedelta(hours=1))
71- >>> str(first)
72- '13:00:00'
73- >>> second = seq(start, increment_by=datetime.timedelta(hours=1))
74- >>> str(second)
75- '14:00:00'
7626 """
7727
7828 _instances = {}
7929 _locks = {}
30+ _process_lock_file = Path (tempfile .gettempdir (), "seq_process_lock" )
31+ _process_lock = FileLock (_process_lock_file )
32+ _state_file = Path (tempfile .gettempdir (), "seq_state.json" )
8033
8134 def __init__ (
8235 self ,
@@ -128,13 +81,23 @@ def __new__(
12881 ):
12982 key = (value , increment_by , start , suffix )
13083
131- if key not in cls ._locks :
132- # Use a temporary lock to protect lock creation
133- with Lock ():
134- if key not in cls ._locks :
135- cls ._locks [key ] = Lock ()
84+ # Use process-safe lock for instance creation
85+ with cls ._process_lock :
86+ if key not in cls ._locks :
87+ cls ._locks [key ] = cls ._get_process_safe_lock (key )
13688
137- with cls ._locks [key ]:
89+ # Load saved state if instance doesn't exist
90+ if key not in cls ._instances :
91+ saved_state = cls ._load_state ()
92+ if key in saved_state :
93+ instance = super ().__new__ (cls )
94+ instance .__init__ (value , increment_by , start , suffix )
95+ instance ._current = saved_state [key ]
96+ instance ._initialized = True
97+ cls ._instances [key ] = instance
98+
99+ lock = cls ._locks [key ]
100+ with lock :
138101 if key not in cls ._instances :
139102 instance = super ().__new__ (cls )
140103 instance .__init__ (value , increment_by , start , suffix )
@@ -148,6 +111,9 @@ def __new__(
148111 instance = cls ._instances [key ]
149112 instance ._current += instance ._increment
150113
114+ # Save state after updating
115+ cls ._save_state ()
116+
151117 if isinstance (value , (datetime .datetime , datetime .date )):
152118 return instance ._generate_datetime_value ()
153119 elif isinstance (value , datetime .time ):
@@ -212,8 +178,42 @@ def _generate_text_value(self) -> str:
212178 @classmethod
213179 def _reset (cls ):
214180 """Reset all sequence state. Used for testing purposes."""
215- cls ._instances .clear ()
216- cls ._locks .clear ()
181+ with cls ._process_lock :
182+ cls ._instances .clear ()
183+ cls ._locks .clear ()
184+ if cls ._state_file .exists ():
185+ cls ._state_file .unlink ()
186+
187+ @classmethod
188+ def _load_state (cls ):
189+ """Load sequence state from file."""
190+ try :
191+ with cls ._process_lock :
192+ if cls ._state_file .exists ():
193+ state = json .loads (cls ._state_file .read_text ())
194+ return {
195+ tuple (json .loads (k )): v .get ("_current" , 0 )
196+ for k , v in state .items ()
197+ }
198+ except Exception :
199+ return {}
200+ return {}
201+
202+ @classmethod
203+ def _save_state (cls ):
204+ """Save sequence state to file."""
205+ with cls ._process_lock :
206+ state = {
207+ json .dumps ([str (k ) for k in key ]): {"_current" : instance ._current }
208+ for key , instance in cls ._instances .items ()
209+ }
210+ cls ._state_file .write_text (json .dumps (state ))
211+
212+ @classmethod
213+ def _get_process_safe_lock (cls , key ):
214+ """Get or create a process-safe lock for the given key."""
215+ lock_file = Path (tempfile .gettempdir (), f"seq_lock_{ hash (key )} " )
216+ return FileLock (lock_file )
217217
218218 @classmethod
219219 def iter (
0 commit comments