or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-limiting.mdfactory-patterns.mdindex.mdrate-configuration.mdstorage-backends.mdtime-sources.md

time-sources.mddocs/

0

# Time Sources

1

2

Different clock implementations for various deployment scenarios including local system time, monotonic time, and remote database-backed time sources. Clocks provide the time foundation for rate limiting calculations and can be chosen based on accuracy, consistency, and deployment requirements.

3

4

## Capabilities

5

6

### System Time Clock

7

8

Standard clock using system time for basic rate limiting scenarios.

9

10

```python { .api }

11

class TimeClock(AbstractClock):

12

def now(self) -> int:

13

"""

14

Get current system time in milliseconds.

15

16

Returns:

17

- int: Current time as milliseconds since epoch

18

19

Characteristics:

20

- Uses system time (time.time())

21

- Subject to system clock adjustments

22

- Fast and simple

23

- Default clock for most scenarios

24

"""

25

```

26

27

Usage example:

28

29

```python

30

from pyrate_limiter import TimeClock, Limiter, Rate, Duration

31

32

# Default clock (same as not specifying clock)

33

clock = TimeClock()

34

limiter = Limiter(Rate(10, Duration.SECOND), clock=clock)

35

36

# TimeClock is the default, so this is equivalent:

37

limiter = Limiter(Rate(10, Duration.SECOND))

38

```

39

40

### Monotonic Clock

41

42

Clock using monotonic time that doesn't go backwards, ideal for measuring intervals.

43

44

```python { .api }

45

class MonotonicClock(AbstractClock):

46

def __init__(self):

47

"""Initialize monotonic clock."""

48

49

def now(self) -> int:

50

"""

51

Get current monotonic time in milliseconds.

52

53

Returns:

54

- int: Monotonic time in milliseconds

55

56

Characteristics:

57

- Uses monotonic time (time.monotonic())

58

- Never goes backwards

59

- Not affected by system clock adjustments

60

- Ideal for measuring time intervals

61

- Cannot be compared across system restarts

62

"""

63

```

64

65

Usage example:

66

67

```python

68

from pyrate_limiter import MonotonicClock, Limiter, Rate, Duration

69

70

# Use monotonic clock for interval stability

71

clock = MonotonicClock()

72

limiter = Limiter(Rate(5, Duration.SECOND), clock=clock)

73

74

# Good for scenarios where system clock might be adjusted

75

# but rate limiting should continue smoothly

76

```

77

78

### Async Time Clock

79

80

Asynchronous clock for testing and async environments.

81

82

```python { .api }

83

class TimeAsyncClock(AbstractClock):

84

async def now(self) -> int:

85

"""

86

Get current system time asynchronously.

87

88

Returns:

89

- int: Current time as milliseconds since epoch

90

91

Characteristics:

92

- Async version of TimeClock

93

- Mainly for testing purposes

94

- Returns awaitable time value

95

"""

96

```

97

98

Usage example:

99

100

```python

101

from pyrate_limiter import TimeAsyncClock, BucketAsyncWrapper, InMemoryBucket

102

from pyrate_limiter import Rate, Duration

103

import asyncio

104

105

async def async_clock_example():

106

# Use async clock with async bucket

107

clock = TimeAsyncClock()

108

bucket = BucketAsyncWrapper(InMemoryBucket([Rate(10, Duration.SECOND)]))

109

110

# Clock will be used internally by the bucket/limiter

111

# This is primarily for testing scenarios

112

```

113

114

### SQLite Clock

115

116

Remote clock using SQLite database for distributed time consistency.

117

118

```python { .api }

119

class SQLiteClock(AbstractClock):

120

def __init__(self, conn: Union[sqlite3.Connection, SQLiteBucket]):

121

"""

122

Initialize SQLite clock with database connection.

123

124

Parameters:

125

- conn: SQLite connection or SQLiteBucket instance

126

127

Note: In multiprocessing, use SQLiteBucket to share locks

128

"""

129

130

@classmethod

131

def default(cls) -> "SQLiteClock":

132

"""

133

Create default SQLite clock with temporary database.

134

135

Returns:

136

- SQLiteClock: Clock using temporary SQLite database

137

"""

138

139

def now(self) -> int:

140

"""

141

Get current time from SQLite database.

142

143

Returns:

144

- int: Database time in milliseconds since epoch

145

146

Characteristics:

147

- Uses SQLite's datetime functions

148

- Consistent across processes using same database

149

- Slightly slower than system clocks

150

- Good for distributed consistency

151

"""

152

```

153

154

Usage example:

155

156

```python

157

from pyrate_limiter import SQLiteClock, SQLiteBucket, Limiter

158

from pyrate_limiter import Rate, Duration

159

import sqlite3

160

161

# Method 1: Default SQLite clock

162

clock = SQLiteClock.default()

163

limiter = Limiter(Rate(10, Duration.SECOND), clock=clock)

164

165

# Method 2: SQLite clock with custom connection

166

conn = sqlite3.connect("timesync.db")

167

clock = SQLiteClock(conn)

168

169

# Method 3: SQLite clock sharing bucket's connection and lock

170

bucket = SQLiteBucket.init_from_file(

171

rates=[Rate(5, Duration.SECOND)],

172

db_path="shared.db"

173

)

174

clock = SQLiteClock(bucket) # Shares connection and lock

175

limiter = Limiter(bucket, clock=clock)

176

```

177

178

### PostgreSQL Clock

179

180

Enterprise-grade clock using PostgreSQL for high-accuracy distributed time.

181

182

```python { .api }

183

class PostgresClock(AbstractClock):

184

def __init__(self, pool: ConnectionPool):

185

"""

186

Initialize PostgreSQL clock with connection pool.

187

188

Parameters:

189

- pool: PostgreSQL connection pool

190

"""

191

192

def now(self) -> int:

193

"""

194

Get current time from PostgreSQL database.

195

196

Returns:

197

- int: Database time in milliseconds since epoch

198

199

Characteristics:

200

- Uses PostgreSQL's timestamp functions

201

- High precision and accuracy

202

- Consistent across distributed systems

203

- Requires connection pool management

204

- Best for enterprise applications

205

"""

206

```

207

208

Usage example:

209

210

```python

211

from pyrate_limiter import PostgresClock, PostgresBucket, Limiter

212

from pyrate_limiter import Rate, Duration

213

from psycopg_pool import ConnectionPool

214

215

# Create connection pool

216

pool = ConnectionPool(

217

"host=localhost dbname=mydb user=myuser password=mypass",

218

min_size=1,

219

max_size=10

220

)

221

222

# Use PostgreSQL clock with PostgreSQL bucket

223

clock = PostgresClock(pool)

224

bucket = PostgresBucket([Rate(100, Duration.MINUTE)], pool)

225

limiter = Limiter(bucket, clock=clock)

226

227

# Both bucket and clock use the same connection pool

228

# for consistency and efficiency

229

```

230

231

## Abstract Base Classes

232

233

### AbstractClock Interface

234

235

All clocks implement the same interface for consistent behavior.

236

237

```python { .api }

238

class AbstractClock(ABC):

239

@abstractmethod

240

def now(self) -> Union[int, Awaitable[int]]:

241

"""

242

Get current time in milliseconds.

243

244

Returns:

245

- int: Time in milliseconds since epoch

246

- Awaitable[int]: For async clocks

247

"""

248

```

249

250

### BucketFactory Interface

251

252

Abstract factory for creating and managing buckets with custom routing logic.

253

254

```python { .api }

255

class BucketFactory(ABC):

256

leak_interval: int

257

258

@abstractmethod

259

def wrap_item(self, name: str, weight: int = 1) -> Union[RateItem, Awaitable[RateItem]]:

260

"""

261

Add timestamp to item using clock backend and create RateItem.

262

263

Parameters:

264

- name: Item identifier

265

- weight: Item weight (default: 1)

266

267

Returns:

268

- RateItem: Timestamped rate item

269

- Awaitable[RateItem]: For async factories

270

"""

271

272

@abstractmethod

273

def get(self, item: RateItem) -> Union[AbstractBucket, Awaitable[AbstractBucket]]:

274

"""

275

Get the corresponding bucket for this item.

276

277

Parameters:

278

- item: Rate item to get bucket for

279

280

Returns:

281

- AbstractBucket: Bucket for the item

282

- Awaitable[AbstractBucket]: For async factories

283

"""

284

285

def create(

286

self,

287

clock: AbstractClock,

288

bucket_class: Type[AbstractBucket],

289

*args,

290

**kwargs

291

) -> AbstractBucket:

292

"""Create bucket dynamically and schedule leak operations."""

293

294

def schedule_leak(self, bucket: AbstractBucket, clock: AbstractClock) -> None:

295

"""Schedule leak operations for bucket with associated clock."""

296

297

def get_buckets(self) -> List[AbstractBucket]:

298

"""Get list of all buckets in the factory."""

299

300

def dispose(self, bucket: Union[int, AbstractBucket]) -> bool:

301

"""Remove bucket from factory."""

302

303

def close(self) -> None:

304

"""Close factory and cleanup resources."""

305

```

306

307

## Clock Selection Guidelines

308

309

### Local Development

310

- **TimeClock**: Default choice for simple applications

311

- **MonotonicClock**: When system clock adjustments are a concern

312

313

### Single Server Deployment

314

- **TimeClock**: For most web applications

315

- **MonotonicClock**: For long-running services with precise timing

316

317

### Multi-Process Applications

318

- **SQLiteClock**: For shared time consistency across processes

319

- **TimeClock**: If minor time differences are acceptable

320

321

### Distributed Systems

322

- **PostgresClock**: For enterprise applications requiring precise time sync

323

- **SQLiteClock**: For moderate-scale distributed applications

324

325

### Testing

326

- **TimeAsyncClock**: For async testing scenarios

327

- **TimeClock**: For most test scenarios

328

329

## Time Synchronization Patterns

330

331

### Shared Database Time

332

333

Using database time ensures consistency across distributed components.

334

335

```python

336

from pyrate_limiter import SQLiteClock, SQLiteBucket, Limiter, Rate, Duration

337

338

# All processes use the same database for time and state

339

bucket = SQLiteBucket.init_from_file(

340

rates=[Rate(10, Duration.SECOND)],

341

db_path="/shared/rate_limits.db"

342

)

343

clock = SQLiteClock(bucket)

344

limiter = Limiter(bucket, clock=clock)

345

346

# Time and rate limiting state are both synchronized

347

```

348

349

### Clock Consistency

350

351

Ensuring bucket and clock use compatible time sources.

352

353

```python

354

from pyrate_limiter import PostgresClock, PostgresBucket, Limiter

355

from pyrate_limiter import Rate, Duration

356

from psycopg_pool import ConnectionPool

357

358

pool = ConnectionPool("postgresql://...")

359

360

# Use same connection pool for both time and storage

361

bucket = PostgresBucket([Rate(50, Duration.SECOND)], pool)

362

clock = PostgresClock(pool)

363

limiter = Limiter(bucket, clock=clock)

364

365

# Time calculations and storage operations use same database time

366

```

367

368

### Mixed Clock Scenarios

369

370

Different clocks for different purposes.

371

372

```python

373

from pyrate_limiter import TimeClock, MonotonicClock, InMemoryBucket

374

from pyrate_limiter import Rate, Duration, Limiter

375

376

# Use monotonic clock for stable intervals

377

# but system time for logging/debugging

378

monotonic_clock = MonotonicClock()

379

limiter = Limiter(Rate(10, Duration.SECOND), clock=monotonic_clock)

380

381

# System time for external coordination

382

import time

383

system_time = int(time.time() * 1000)

384

print(f"Rate limited at system time: {system_time}")

385

```