limits 5.1.0__tar.gz → 5.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {limits-5.1.0 → limits-5.2.0}/HISTORY.rst +12 -0
- {limits-5.1.0 → limits-5.2.0}/PKG-INFO +1 -1
- {limits-5.1.0 → limits-5.2.0}/doc/source/theme_config.py +0 -1
- {limits-5.1.0 → limits-5.2.0}/limits/_version.py +3 -3
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/base.py +15 -1
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/memcached/__init__.py +6 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/memory.py +6 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/mongodb.py +3 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/redis/__init__.py +7 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/strategies.py +5 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/base.py +15 -1
- {limits-5.1.0 → limits-5.2.0}/limits/storage/memcached.py +6 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/memory.py +6 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/mongodb.py +3 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/redis.py +6 -0
- {limits-5.1.0 → limits-5.2.0}/limits/strategies.py +5 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/PKG-INFO +1 -1
- {limits-5.1.0 → limits-5.2.0}/tests/test_storage.py +12 -0
- {limits-5.1.0 → limits-5.2.0}/tests/test_strategy.py +3 -0
- {limits-5.1.0 → limits-5.2.0}/CLASSIFIERS +0 -0
- {limits-5.1.0 → limits-5.2.0}/CONTRIBUTIONS.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/LICENSE.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/MANIFEST.in +0 -0
- {limits-5.1.0 → limits-5.2.0}/README.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/Makefile +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/_static/custom.css +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/api.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/async.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/changelog.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/conf.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/custom-storage.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/_static/benchmark-chart.css +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/_static/js/benchmark-chart.js +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/_static/js/benchmark-details.js +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/_static/js/benchmark-loader.js +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/_templates/git_info.js +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/ext/bench_chart.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/index.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/installation.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/performance.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/quickstart.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/storage.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/doc/source/strategies.rst +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/__init__.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/__init__.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/__init__.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/memcached/bridge.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/memcached/emcache.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/memcached/memcachio.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/redis/bridge.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/redis/coredis.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/redis/redispy.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/aio/storage/redis/valkey.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/errors.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/limits.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/py.typed +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/acquire_moving_window.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/acquire_sliding_window.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/clear_keys.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/incr_expire.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/moving_window.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/resources/redis/lua_scripts/sliding_window.lua +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/__init__.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/redis_cluster.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/redis_sentinel.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/storage/registry.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/typing.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/util.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits/version.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/SOURCES.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/dependency_links.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/not-zip-safe +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/requires.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/limits.egg-info/top_level.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/pyproject.toml +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/ci.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/dev.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/docs.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/main.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/async-memcached.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/async-mongodb.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/async-redis.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/async-valkey.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/memcached.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/mongodb.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/redis.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/rediscluster.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/storage/valkey.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/requirements/test.txt +0 -0
- {limits-5.1.0 → limits-5.2.0}/setup.cfg +0 -0
- {limits-5.1.0 → limits-5.2.0}/setup.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/tests/test_limit_granularities.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/tests/test_limits.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/tests/test_ratelimit_parser.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/tests/test_utils.py +0 -0
- {limits-5.1.0 → limits-5.2.0}/versioneer.py +0 -0
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
Changelog
|
|
4
4
|
=========
|
|
5
5
|
|
|
6
|
+
v5.2.0
|
|
7
|
+
------
|
|
8
|
+
Release Date: 2025-05-16
|
|
9
|
+
|
|
10
|
+
* Bug Fix
|
|
11
|
+
|
|
12
|
+
* Fix incorrect behavior of the ``clear`` method for sliding window
|
|
13
|
+
counter which effectively did not clear the sliding window for
|
|
14
|
+
redis, memcached & in memory storage implementations.
|
|
15
|
+
`Issue 276 <https://github.com/alisaifee/limits/issues/276>`_
|
|
16
|
+
|
|
6
17
|
v5.1.0
|
|
7
18
|
------
|
|
8
19
|
Release Date: 2025-04-23
|
|
@@ -836,3 +847,4 @@ Release Date: 2015-01-08
|
|
|
836
847
|
|
|
837
848
|
|
|
838
849
|
|
|
850
|
+
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-05-16T12:15:52-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "5.
|
|
14
|
+
"full-revisionid": "0c8d73757f54788d5fa213a678dfbdf3fdd7ccfb",
|
|
15
|
+
"version": "5.2.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -175,7 +175,11 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
175
175
|
"""
|
|
176
176
|
|
|
177
177
|
def __init_subclass__(cls, **kwargs: Any) -> None: # type: ignore[explicit-any]
|
|
178
|
-
for method in {
|
|
178
|
+
for method in {
|
|
179
|
+
"acquire_sliding_window_entry",
|
|
180
|
+
"get_sliding_window",
|
|
181
|
+
"clear_sliding_window",
|
|
182
|
+
}:
|
|
179
183
|
setattr(
|
|
180
184
|
cls,
|
|
181
185
|
method,
|
|
@@ -218,3 +222,13 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
218
222
|
- current window TTL
|
|
219
223
|
"""
|
|
220
224
|
raise NotImplementedError
|
|
225
|
+
|
|
226
|
+
@abstractmethod
|
|
227
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Resets the rate limit key(s) for the sliding window
|
|
230
|
+
|
|
231
|
+
:param key: the key to clear rate limits for
|
|
232
|
+
:param expiry: the rate limit expiry, needed to compute the key in some implemenations
|
|
233
|
+
"""
|
|
234
|
+
...
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import time
|
|
4
5
|
from math import floor
|
|
5
6
|
|
|
@@ -167,6 +168,11 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
167
168
|
previous_key, current_key, expiry, now
|
|
168
169
|
)
|
|
169
170
|
|
|
171
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
172
|
+
now = time.time()
|
|
173
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
174
|
+
await asyncio.gather(self.clear(previous_key), self.clear(current_key))
|
|
175
|
+
|
|
170
176
|
async def _get_sliding_window_info(
|
|
171
177
|
self, previous_key: str, current_key: str, expiry: int, now: float
|
|
172
178
|
) -> tuple[int, float, int, float]:
|
|
@@ -241,6 +241,12 @@ class MemoryStorage(
|
|
|
241
241
|
previous_key, current_key, expiry, now
|
|
242
242
|
)
|
|
243
243
|
|
|
244
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
245
|
+
now = time.time()
|
|
246
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
247
|
+
await self.clear(current_key)
|
|
248
|
+
await self.clear(previous_key)
|
|
249
|
+
|
|
244
250
|
async def _get_sliding_window_info(
|
|
245
251
|
self,
|
|
246
252
|
previous_key: str,
|
|
@@ -513,5 +513,8 @@ class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
513
513
|
)
|
|
514
514
|
return 0, 0.0, 0, 0.0
|
|
515
515
|
|
|
516
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
517
|
+
return await self.clear(key)
|
|
518
|
+
|
|
516
519
|
def __del__(self) -> None:
|
|
517
520
|
self.storage and self.storage.close()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
3
5
|
from deprecated.sphinx import versionadded, versionchanged
|
|
4
6
|
from packaging.version import Version
|
|
5
7
|
|
|
@@ -219,6 +221,11 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
219
221
|
current_key = self._current_window_key(key)
|
|
220
222
|
return await self.bridge.get_sliding_window(previous_key, current_key, expiry)
|
|
221
223
|
|
|
224
|
+
async def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
225
|
+
previous_key = self._previous_window_key(key)
|
|
226
|
+
current_key = self._current_window_key(key)
|
|
227
|
+
await asyncio.gather(self.clear(previous_key), self.clear(current_key))
|
|
228
|
+
|
|
222
229
|
async def get_expiry(self, key: str) -> float:
|
|
223
230
|
"""
|
|
224
231
|
:param key: the key to get the expiry for
|
|
@@ -302,6 +302,11 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
|
|
|
302
302
|
|
|
303
303
|
return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
|
|
304
304
|
|
|
305
|
+
async def clear(self, item: RateLimitItem, *identifiers: str) -> None:
|
|
306
|
+
return await cast(
|
|
307
|
+
SlidingWindowCounterSupport, self.storage
|
|
308
|
+
).clear_sliding_window(item.key_for(*identifiers), item.get_expiry())
|
|
309
|
+
|
|
305
310
|
|
|
306
311
|
STRATEGIES = {
|
|
307
312
|
"sliding-window-counter": SlidingWindowCounterRateLimiter,
|
|
@@ -167,7 +167,11 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
167
167
|
"""
|
|
168
168
|
|
|
169
169
|
def __init_subclass__(cls, **kwargs: Any) -> None: # type: ignore[explicit-any]
|
|
170
|
-
for method in {
|
|
170
|
+
for method in {
|
|
171
|
+
"acquire_sliding_window_entry",
|
|
172
|
+
"get_sliding_window",
|
|
173
|
+
"clear_sliding_window",
|
|
174
|
+
}:
|
|
171
175
|
setattr(
|
|
172
176
|
cls,
|
|
173
177
|
method,
|
|
@@ -207,6 +211,16 @@ class SlidingWindowCounterSupport(ABC):
|
|
|
207
211
|
"""
|
|
208
212
|
raise NotImplementedError
|
|
209
213
|
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Resets the rate limit key(s) for the sliding window
|
|
218
|
+
|
|
219
|
+
:param key: the key to clear rate limits for
|
|
220
|
+
:param expiry: the rate limit expiry, needed to compute the key in some implemenations
|
|
221
|
+
"""
|
|
222
|
+
...
|
|
223
|
+
|
|
210
224
|
|
|
211
225
|
class TimestampedSlidingWindow:
|
|
212
226
|
"""Helper class for storage that support the sliding window counter, with timestamp based keys."""
|
|
@@ -282,6 +282,12 @@ class MemcachedStorage(Storage, SlidingWindowCounterSupport, TimestampedSlidingW
|
|
|
282
282
|
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
283
283
|
return self._get_sliding_window_info(previous_key, current_key, expiry, now)
|
|
284
284
|
|
|
285
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
286
|
+
now = time.time()
|
|
287
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
288
|
+
self.clear(previous_key)
|
|
289
|
+
self.clear(current_key)
|
|
290
|
+
|
|
285
291
|
def _get_sliding_window_info(
|
|
286
292
|
self, previous_key: str, current_key: str, expiry: int, now: float
|
|
287
293
|
) -> tuple[int, float, int, float]:
|
|
@@ -237,6 +237,12 @@ class MemoryStorage(
|
|
|
237
237
|
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
238
238
|
return self._get_sliding_window_info(previous_key, current_key, expiry, now)
|
|
239
239
|
|
|
240
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
241
|
+
now = time.time()
|
|
242
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
243
|
+
self.clear(previous_key)
|
|
244
|
+
self.clear(current_key)
|
|
245
|
+
|
|
240
246
|
def check(self) -> bool:
|
|
241
247
|
"""
|
|
242
248
|
check if storage is healthy
|
|
@@ -470,6 +470,9 @@ class MongoDBStorageBase(
|
|
|
470
470
|
)
|
|
471
471
|
return cast(bool, result["_acquired"])
|
|
472
472
|
|
|
473
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
474
|
+
return self.clear(key)
|
|
475
|
+
|
|
473
476
|
def __del__(self) -> None:
|
|
474
477
|
if self.storage:
|
|
475
478
|
self.storage.close()
|
|
@@ -200,6 +200,12 @@ class RedisStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
|
200
200
|
)
|
|
201
201
|
return 0, 0.0, 0, 0.0
|
|
202
202
|
|
|
203
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
204
|
+
previous_key = self._previous_window_key(key)
|
|
205
|
+
current_key = self._current_window_key(key)
|
|
206
|
+
self.clear(previous_key)
|
|
207
|
+
self.clear(current_key)
|
|
208
|
+
|
|
203
209
|
def incr(
|
|
204
210
|
self,
|
|
205
211
|
key: str,
|
|
@@ -284,6 +284,11 @@ class SlidingWindowCounterRateLimiter(RateLimiter):
|
|
|
284
284
|
|
|
285
285
|
return WindowStats(now + min(previous_reset_in, current_reset_in), remaining)
|
|
286
286
|
|
|
287
|
+
def clear(self, item: RateLimitItem, *identifiers: str) -> None:
|
|
288
|
+
return cast(SlidingWindowCounterSupport, self.storage).clear_sliding_window(
|
|
289
|
+
item.key_for(*identifiers), item.get_expiry()
|
|
290
|
+
)
|
|
291
|
+
|
|
287
292
|
|
|
288
293
|
KnownStrategy = (
|
|
289
294
|
type[SlidingWindowCounterRateLimiter]
|
|
@@ -136,6 +136,9 @@ class TestBaseStorage:
|
|
|
136
136
|
) -> tuple[int, float, int, float]:
|
|
137
137
|
pass
|
|
138
138
|
|
|
139
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
140
|
+
pass
|
|
141
|
+
|
|
139
142
|
storage = storage_from_string("mystorage+sliding://")
|
|
140
143
|
assert isinstance(storage, MyStorage)
|
|
141
144
|
SlidingWindowCounterRateLimiter(storage)
|
|
@@ -370,6 +373,9 @@ class TestStorageErrors:
|
|
|
370
373
|
) -> tuple[int, float, int, float]:
|
|
371
374
|
raise self.MyError()
|
|
372
375
|
|
|
376
|
+
def clear_sliding_window(self, key: str, expiry: int) -> None:
|
|
377
|
+
raise self.MyError()
|
|
378
|
+
|
|
373
379
|
def assert_exception(self, exc, wrap_exceptions):
|
|
374
380
|
if wrap_exceptions:
|
|
375
381
|
assert isinstance(exc, StorageError)
|
|
@@ -436,3 +442,9 @@ class TestStorageErrors:
|
|
|
436
442
|
with pytest.raises(Exception) as exc:
|
|
437
443
|
self.MyStorage(wrap_exceptions=wrap_exceptions).get_sliding_window("", 1)
|
|
438
444
|
self.assert_exception(exc.value, wrap_exceptions)
|
|
445
|
+
|
|
446
|
+
def test_clear_sliding_window_exception(self, wrap_exceptions):
|
|
447
|
+
with pytest.raises(Exception) as exc:
|
|
448
|
+
self.MyStorage(wrap_exceptions=wrap_exceptions).clear_sliding_window("", 1)
|
|
449
|
+
|
|
450
|
+
self.assert_exception(exc.value, wrap_exceptions)
|
|
@@ -108,6 +108,9 @@ class TestSlidingWindow:
|
|
|
108
108
|
start + 2, 1e-2
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
+
limiter.clear(limit)
|
|
112
|
+
assert 10 == limiter.get_window_stats(limit).remaining
|
|
113
|
+
|
|
111
114
|
@pytest.mark.flaky
|
|
112
115
|
def test_sliding_window_counter_total_reset(self, uri, args, fixture):
|
|
113
116
|
storage = storage_from_string(uri, **args)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|