haiway 0.12.1__py3-none-any.whl → 0.13.0__py3-none-any.whl

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.
haiway/context/state.py CHANGED
@@ -91,11 +91,6 @@ class ScopeState:
91
91
  class StateContext:
92
92
  _context = ContextVar[ScopeState]("StateContext")
93
93
 
94
- __slots__ = (
95
- "_state",
96
- "_token",
97
- )
98
-
99
94
  @classmethod
100
95
  def current[StateType: State](
101
96
  cls,
@@ -122,6 +117,11 @@ class StateContext:
122
117
  except LookupError: # create root scope when missing
123
118
  return cls(state=ScopeState(state))
124
119
 
120
+ __slots__ = (
121
+ "_state",
122
+ "_token",
123
+ )
124
+
125
125
  def __init__(
126
126
  self,
127
127
  state: ScopeState,
haiway/helpers/caching.py CHANGED
@@ -1,11 +1,11 @@
1
- from asyncio import AbstractEventLoop, Task, get_running_loop, iscoroutinefunction, shield
1
+ from asyncio import iscoroutinefunction
2
2
  from collections import OrderedDict
3
3
  from collections.abc import Callable, Coroutine, Hashable
4
- from functools import _make_key, partial # pyright: ignore[reportPrivateUsage]
4
+ from functools import _make_key # pyright: ignore[reportPrivateUsage]
5
5
  from time import monotonic
6
- from typing import NamedTuple, cast, overload
7
- from weakref import ref
6
+ from typing import NamedTuple, Protocol, cast, overload
8
7
 
8
+ from haiway.context.access import ctx
9
9
  from haiway.utils.mimic import mimic_function
10
10
 
11
11
  __all__ = [
@@ -13,6 +13,29 @@ __all__ = [
13
13
  ]
14
14
 
15
15
 
16
+ class CacheMakeKey[**Args, Key](Protocol):
17
+ def __call__(
18
+ self,
19
+ *args: Args.args,
20
+ **kwargs: Args.kwargs,
21
+ ) -> Key: ...
22
+
23
+
24
+ class CacheRead[Key, Value](Protocol):
25
+ async def __call__(
26
+ self,
27
+ key: Key,
28
+ ) -> Value | None: ...
29
+
30
+
31
+ class CacheWrite[Key, Value](Protocol):
32
+ async def __call__(
33
+ self,
34
+ key: Key,
35
+ value: Value,
36
+ ) -> None: ...
37
+
38
+
16
39
  @overload
17
40
  def cache[**Args, Result](
18
41
  function: Callable[Args, Result],
@@ -21,58 +44,156 @@ def cache[**Args, Result](
21
44
 
22
45
 
23
46
  @overload
24
- def cache[**Args, Result](
47
+ def cache[**Args, Result, Key: Hashable](
25
48
  *,
26
- limit: int = 1,
49
+ limit: int | None = None,
27
50
  expiration: float | None = None,
51
+ make_key: CacheMakeKey[Args, Key] | None = None,
28
52
  ) -> Callable[[Callable[Args, Result]], Callable[Args, Result]]: ...
29
53
 
30
54
 
31
- def cache[**Args, Result](
55
+ @overload
56
+ def cache[**Args, Result, Key](
57
+ *,
58
+ make_key: CacheMakeKey[Args, Key],
59
+ read: CacheRead[Key, Result],
60
+ write: CacheWrite[Key, Result],
61
+ ) -> Callable[
62
+ [Callable[Args, Coroutine[None, None, Result]]], Callable[Args, Coroutine[None, None, Result]]
63
+ ]: ...
64
+
65
+
66
+ def cache[**Args, Result, Key]( # noqa: PLR0913
32
67
  function: Callable[Args, Result] | None = None,
33
68
  *,
34
- limit: int = 1,
69
+ limit: int | None = None,
35
70
  expiration: float | None = None,
36
- ) -> Callable[[Callable[Args, Result]], Callable[Args, Result]] | Callable[Args, Result]:
37
- """\
38
- Simple lru function result cache with optional expire time. \
39
- Works for both sync and async functions. \
40
- It is not allowed to be used on class methods. \
41
- This wrapper is not thread safe.
71
+ make_key: CacheMakeKey[Args, Key] | None = None,
72
+ read: CacheRead[Key, Result] | None = None,
73
+ write: CacheWrite[Key, Result] | None = None,
74
+ ) -> (
75
+ Callable[
76
+ [Callable[Args, Coroutine[None, None, Result]]],
77
+ Callable[Args, Coroutine[None, None, Result]],
78
+ ]
79
+ | Callable[[Callable[Args, Result]], Callable[Args, Result]]
80
+ | Callable[Args, Result]
81
+ ):
82
+ """
83
+ Memoize the result of a function using a configurable cache.
42
84
 
43
85
  Parameters
44
86
  ----------
45
- function: Callable[_Args, _Result]
46
- function to wrap in cache, either sync or async
47
- limit: int
48
- limit of cache entries to keep, default is 1
49
- expiration: float | None
50
- entries expiration time in seconds, default is None (not expiring)
87
+ function : Callable[Args, Result] | None
88
+ The function to be memoized.
89
+ When used as a simple decorator (i.e., `@cache`), this is the decorated function.
90
+ Should be omitted when cache is called with configuration arguments.
91
+ limit : int | None
92
+ The maximum number of entries to keep in the cache.
93
+ Defaults to 1 if not specified.
94
+ Ignored when using custom cache implementations (read/write).
95
+ expiration : float | None
96
+ Time in seconds after which a cache entry expires and will be recomputed.
97
+ Defaults to None, meaning entries don't expire based on time.
98
+ Ignored when using custom cache implementations (read/write).
99
+ make_key : CacheMakeKey[Args, Key] | None
100
+ Function to generate a cache key from function arguments.
101
+ If None, uses a default implementation that handles most cases.
102
+ Required when using custom cache implementations (read/write).
103
+ read : CacheRead[Key, Result] | None
104
+ Custom asynchronous function to read values from cache.
105
+ Must be provided together with `write` and `make_key`.
106
+ Only available for async functions.
107
+ write : CacheWrite[Key, Result] | None
108
+ Custom asynchronous function to write values to cache.
109
+ Must be provided together with `read` and `make_key`.
110
+ Only available for async functions.
51
111
 
52
112
  Returns
53
113
  -------
54
- Callable[[Callable[_Args, _Result]], Callable[_Args, _Result]] | Callable[_Args, _Result]
55
- provided function wrapped in cache
114
+ Callable
115
+ If `function` is provided as a positional argument, returns the memoized function.
116
+ Otherwise returns a decorator that can be applied to a function to memoize it
117
+ with the given configuration.
118
+
119
+ Notes
120
+ -----
121
+ This decorator supports both synchronous and asynchronous functions.
122
+ The default implementation uses a simple in-memory LRU cache.
123
+ For asynchronous functions, you can provide custom cache implementations
124
+ via the `read` and `write` parameters.
125
+
126
+ The default cache is not thread-safe and should not be used in multi-threaded
127
+ applications without external synchronization.
128
+
129
+ Examples
130
+ --------
131
+ Simple usage as a decorator:
132
+
133
+ >>> @cache
134
+ ... def my_function(x: int) -> int:
135
+ ... print("Function called")
136
+ ... return x * 2
137
+ >>> my_function(5)
138
+ Function called
139
+ 10
140
+ >>> my_function(5) # Cache hit, function body not executed
141
+ 10
142
+
143
+ With configuration parameters:
144
+
145
+ >>> @cache(limit=10, expiration=60.0)
146
+ ... def my_function(x: int) -> int:
147
+ ... return x * 2
148
+
149
+ With custom cache for async functions:
150
+
151
+ >>> @cache(make_key=custom_key_maker, read=redis_read, write=redis_write)
152
+ ... async def fetch_data(user_id: str) -> dict:
153
+ ... return await api_call(user_id)
56
154
  """
57
155
 
58
156
  def _wrap(function: Callable[Args, Result]) -> Callable[Args, Result]:
59
157
  if iscoroutinefunction(function):
60
- return cast(
61
- Callable[Args, Result],
62
- _AsyncCache(
63
- function,
64
- limit=limit,
65
- expiration=expiration,
66
- ),
67
- )
158
+ if read is not None and write is not None and make_key is not None:
159
+ assert limit is None and expiration is None # nosec: B101
160
+ return cast(
161
+ Callable[Args, Result],
162
+ _CustomCache(
163
+ function,
164
+ make_key=make_key,
165
+ read=read,
166
+ write=write,
167
+ ),
168
+ )
169
+
170
+ else:
171
+ assert read is None and write is None # nosec: B101
172
+ return cast(
173
+ Callable[Args, Result],
174
+ _AsyncCache(
175
+ function,
176
+ limit=limit if limit is not None else 1,
177
+ expiration=expiration,
178
+ make_key=cast(
179
+ CacheMakeKey[Args, Hashable],
180
+ make_key if make_key is not None else _default_make_key,
181
+ ),
182
+ ),
183
+ )
68
184
 
69
185
  else:
186
+ assert read is None and write is None, "Custom sync cache is not supported" # nosec: B101
70
187
  return cast(
71
188
  Callable[Args, Result],
72
189
  _SyncCache(
73
190
  function,
74
- limit=limit,
191
+ limit=limit if limit is not None else 1,
75
192
  expiration=expiration,
193
+ make_key=cast(
194
+ CacheMakeKey[Args, Hashable],
195
+ make_key if make_key is not None else _default_make_key,
196
+ ),
76
197
  ),
77
198
  )
78
199
 
@@ -101,6 +222,7 @@ class _SyncCache[**Args, Result]:
101
222
  "_cached",
102
223
  "_function",
103
224
  "_limit",
225
+ "_make_key",
104
226
  "_next_expire_time",
105
227
  )
106
228
 
@@ -110,10 +232,13 @@ class _SyncCache[**Args, Result]:
110
232
  /,
111
233
  limit: int,
112
234
  expiration: float | None,
235
+ make_key: CacheMakeKey[Args, Hashable],
113
236
  ) -> None:
114
237
  self._function: Callable[Args, Result] = function
115
238
  self._cached: OrderedDict[Hashable, _CacheEntry[Result]] = OrderedDict()
116
239
  self._limit: int = limit
240
+ self._make_key: CacheMakeKey[Args, Hashable] = make_key
241
+
117
242
  if expiration := expiration:
118
243
 
119
244
  def next_expire_time() -> float | None:
@@ -135,27 +260,17 @@ class _SyncCache[**Args, Result]:
135
260
  owner: type | None = None,
136
261
  /,
137
262
  ) -> Callable[Args, Result]:
138
- if owner is None or instance is None:
139
- return self
140
-
141
- else:
142
- return mimic_function(
143
- self._function,
144
- within=partial(
145
- self.__method_call__,
146
- instance,
147
- ),
148
- )
263
+ assert owner is None and instance is None, "cache does not work for classes" # nosec: B101
264
+ return self
149
265
 
150
266
  def __call__(
151
267
  self,
152
268
  *args: Args.args,
153
269
  **kwargs: Args.kwargs,
154
270
  ) -> Result:
155
- key: Hashable = _make_key(
156
- args=args,
157
- kwds=kwargs,
158
- typed=True,
271
+ key: Hashable = self._make_key(
272
+ *args,
273
+ **kwargs,
159
274
  )
160
275
 
161
276
  match self._cached.get(key):
@@ -164,7 +279,6 @@ class _SyncCache[**Args, Result]:
164
279
 
165
280
  case entry:
166
281
  if (expire := entry[1]) and expire < monotonic():
167
- # if still running let it complete if able
168
282
  del self._cached[key] # continue the same way as if empty
169
283
 
170
284
  else:
@@ -178,47 +292,11 @@ class _SyncCache[**Args, Result]:
178
292
  )
179
293
 
180
294
  if len(self._cached) > self._limit:
181
- # if still running let it complete if able
295
+ # keep the size limit
182
296
  self._cached.popitem(last=False)
183
297
 
184
298
  return result
185
299
 
186
- def __method_call__(
187
- self,
188
- __method_self: object,
189
- *args: Args.args,
190
- **kwargs: Args.kwargs,
191
- ) -> Result:
192
- key: Hashable = _make_key(
193
- args=(ref(__method_self), *args),
194
- kwds=kwargs,
195
- typed=True,
196
- )
197
-
198
- match self._cached.get(key):
199
- case None:
200
- pass
201
-
202
- case entry:
203
- if (expire := entry[1]) and expire < monotonic():
204
- # if still running let it complete if able
205
- del self._cached[key] # continue the same way as if empty
206
-
207
- else:
208
- self._cached.move_to_end(key)
209
- return entry[0]
210
-
211
- result: Result = self._function(__method_self, *args, **kwargs) # pyright: ignore[reportUnknownVariableType, reportCallIssue]
212
- self._cached[key] = _CacheEntry(
213
- value=result, # pyright: ignore[reportUnknownArgumentType]
214
- expire=self._next_expire_time(),
215
- )
216
- if len(self._cached) > self._limit:
217
- # if still running let it complete if able
218
- self._cached.popitem(last=False)
219
-
220
- return result # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
221
-
222
300
 
223
301
  class _AsyncCache[**Args, Result]:
224
302
  __slots__ = (
@@ -233,6 +311,7 @@ class _AsyncCache[**Args, Result]:
233
311
  "_cached",
234
312
  "_function",
235
313
  "_limit",
314
+ "_make_key",
236
315
  "_next_expire_time",
237
316
  )
238
317
 
@@ -242,10 +321,13 @@ class _AsyncCache[**Args, Result]:
242
321
  /,
243
322
  limit: int,
244
323
  expiration: float | None,
324
+ make_key: CacheMakeKey[Args, Hashable],
245
325
  ) -> None:
246
326
  self._function: Callable[Args, Coroutine[None, None, Result]] = function
247
- self._cached: OrderedDict[Hashable, _CacheEntry[Task[Result]]] = OrderedDict()
327
+ self._cached: OrderedDict[Hashable, _CacheEntry[Result]] = OrderedDict()
248
328
  self._limit: int = limit
329
+ self._make_key: CacheMakeKey[Args, Hashable] = make_key
330
+
249
331
  if expiration := expiration:
250
332
 
251
333
  def next_expire_time() -> float | None:
@@ -267,28 +349,17 @@ class _AsyncCache[**Args, Result]:
267
349
  owner: type | None = None,
268
350
  /,
269
351
  ) -> Callable[Args, Coroutine[None, None, Result]]:
270
- if owner is None or instance is None:
271
- return self
272
-
273
- else:
274
- return mimic_function(
275
- self._function,
276
- within=partial(
277
- self.__method_call__,
278
- instance,
279
- ),
280
- )
352
+ assert owner is None and instance is None, "cache does not work for classes" # nosec: B101
353
+ return self
281
354
 
282
355
  async def __call__(
283
356
  self,
284
357
  *args: Args.args,
285
358
  **kwargs: Args.kwargs,
286
359
  ) -> Result:
287
- loop: AbstractEventLoop = get_running_loop()
288
- key: Hashable = _make_key(
289
- args=args,
290
- kwds=kwargs,
291
- typed=True,
360
+ key: Hashable = self._make_key(
361
+ *args,
362
+ **kwargs,
292
363
  )
293
364
 
294
365
  match self._cached.get(key):
@@ -297,60 +368,88 @@ class _AsyncCache[**Args, Result]:
297
368
 
298
369
  case entry:
299
370
  if (expire := entry[1]) and expire < monotonic():
300
- # if still running let it complete if able
301
371
  del self._cached[key] # continue the same way as if empty
302
372
 
303
373
  else:
304
374
  self._cached.move_to_end(key)
305
- return await shield(entry[0])
375
+ return entry[0]
306
376
 
307
- task: Task[Result] = loop.create_task(self._function(*args, **kwargs)) # pyright: ignore[reportCallIssue]
377
+ result: Result = await self._function(*args, **kwargs)
308
378
  self._cached[key] = _CacheEntry(
309
- value=task,
379
+ value=result,
310
380
  expire=self._next_expire_time(),
311
381
  )
312
382
  if len(self._cached) > self._limit:
313
- # if still running let it complete if able
383
+ # keep the size limit
314
384
  self._cached.popitem(last=False)
315
385
 
316
- return await shield(task)
386
+ return result
387
+
317
388
 
318
- async def __method_call__(
389
+ class _CustomCache[**Args, Result, Key]:
390
+ __slots__ = (
391
+ "__annotations__",
392
+ "__defaults__",
393
+ "__doc__",
394
+ "__globals__",
395
+ "__kwdefaults__",
396
+ "__name__",
397
+ "__qualname__",
398
+ "__wrapped__",
399
+ "_expiration",
400
+ "_function",
401
+ "_make_key",
402
+ "_read",
403
+ "_write",
404
+ )
405
+
406
+ def __init__(
407
+ self,
408
+ function: Callable[Args, Coroutine[None, None, Result]],
409
+ /,
410
+ make_key: CacheMakeKey[Args, Key],
411
+ read: CacheRead[Key, Result],
412
+ write: CacheWrite[Key, Result],
413
+ ) -> None:
414
+ self._function: Callable[Args, Coroutine[None, None, Result]] = function
415
+ self._make_key: CacheMakeKey[Args, Key] = make_key
416
+ self._read: CacheRead[Key, Result] = read
417
+ self._write: CacheWrite[Key, Result] = write
418
+
419
+ # mimic function attributes if able
420
+ mimic_function(function, within=self)
421
+
422
+ async def __call__(
319
423
  self,
320
- __method_self: object,
321
424
  *args: Args.args,
322
425
  **kwargs: Args.kwargs,
323
426
  ) -> Result:
324
- loop: AbstractEventLoop = get_running_loop()
325
- key: Hashable = _make_key(
326
- args=(ref(__method_self), *args),
327
- kwds=kwargs,
328
- typed=True,
427
+ key: Key = self._make_key(
428
+ *args,
429
+ **kwargs,
329
430
  )
330
431
 
331
- match self._cached.get(key):
432
+ match await self._read(key):
332
433
  case None:
333
- pass
334
-
335
- case entry:
336
- if (expire := entry[1]) and expire < monotonic():
337
- # if still running let it complete if able
338
- del self._cached[key] # continue the same way as if empty
434
+ result: Result = await self._function(*args, **kwargs)
435
+ ctx.spawn( # write the value asnychronously
436
+ self._write,
437
+ key=key,
438
+ value=result,
439
+ )
339
440
 
340
- else:
341
- self._cached.move_to_end(key)
342
- return await shield(entry[0])
441
+ return result
343
442
 
344
- task: Task[Result] = loop.create_task(
345
- self._function(__method_self, *args, **kwargs), # pyright: ignore[reportCallIssue, reportUnknownArgumentType]
346
- )
347
- self._cached[key] = _CacheEntry(
348
- value=task,
349
- expire=self._next_expire_time(),
350
- )
443
+ case entry:
444
+ return entry
351
445
 
352
- if len(self._cached) > self._limit:
353
- # if still running let it complete if able
354
- self._cached.popitem(last=False)
355
446
 
356
- return await shield(task)
447
+ def _default_make_key[**Args](
448
+ *args: Args.args,
449
+ **kwargs: Args.kwargs,
450
+ ) -> Hashable:
451
+ return _make_key(
452
+ args=args,
453
+ kwds=kwargs,
454
+ typed=True,
455
+ )
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Framework for dependency injection and state management within structured concurrency model.
5
5
  Project-URL: Homepage, https://miquido.com
6
6
  Project-URL: Repository, https://github.com/miquido/haiway.git
7
7
  Maintainer-email: Kacper Kaliński <kacper.kalinski@miquido.com>
8
8
  License: MIT License
9
9
 
10
- Copyright (c) 2024 Miquido
10
+ Copyright (c) 2024-2025 Miquido
11
11
 
12
12
  Permission is hereby granted, free of charge, to any person obtaining a copy
13
13
  of this software and associated documentation files (the "Software"), to deal
@@ -40,12 +40,12 @@ Requires-Dist: pyright~=1.1; extra == 'dev'
40
40
  Requires-Dist: pytest-asyncio~=0.23; extra == 'dev'
41
41
  Requires-Dist: pytest-cov~=4.1; extra == 'dev'
42
42
  Requires-Dist: pytest~=7.4; extra == 'dev'
43
- Requires-Dist: ruff~=0.9; extra == 'dev'
43
+ Requires-Dist: ruff~=0.11; extra == 'dev'
44
44
  Description-Content-Type: text/markdown
45
45
 
46
46
  # 🚗 haiway 🚕 🚚 🚙
47
47
 
48
- haiway is a framework helping to build better project codebase by leveraging concepts of structured concurrency and functional programming.
48
+ haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context vars, haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
49
49
 
50
50
  ## 🖥️ Install
51
51
 
@@ -65,7 +65,7 @@ We welcome any feedback and suggestions! Feel free to open an issue or pull requ
65
65
 
66
66
  MIT License
67
67
 
68
- Copyright (c) 2024 Miquido
68
+ Copyright (c) 2024-2025 Miquido
69
69
 
70
70
  Permission is hereby granted, free of charge, to any person obtaining a copy
71
71
  of this software and associated documentation files (the "Software"), to deal
@@ -6,12 +6,12 @@ haiway/context/disposables.py,sha256=vcsh8jRaJ8Q1ob7oh5LsrSPw9f5AMTcaD_p_Gb7tXAI
6
6
  haiway/context/identifier.py,sha256=lz-FuspOtsaEsfb7QPrEVWYfbcMJgd3A6BGG3kLbaV0,3914
7
7
  haiway/context/logging.py,sha256=F3dr6MLjodg3MX5WTInxn3r3JuihG-giBzumI0GGUQw,5590
8
8
  haiway/context/metrics.py,sha256=N20XQtC8au_e_3iWrsZdej78YBEIWF44fdtWcZBWono,5223
9
- haiway/context/state.py,sha256=E3Z49XeE_EsXfYcPGtd-5_YLD5idK1WMMLZQ02of6cg,4554
9
+ haiway/context/state.py,sha256=qskYoNwN5Ad0OgnyhL-PyGzTZltwVVdE9CEqWWn4lm8,4554
10
10
  haiway/context/tasks.py,sha256=J1BFQJis_15SIXbFclppxL-AOIThg2KS4SX8Hg_-YRY,2828
11
11
  haiway/context/types.py,sha256=VvJA7wAPZ3ISpgyThVguioYUXqhHf0XkPfRd0M1ERiQ,142
12
12
  haiway/helpers/__init__.py,sha256=8XRJWNhidWuBKqRZ1Hyc2xqt7DeWLcoOs2V-oexl8VY,579
13
13
  haiway/helpers/asynchrony.py,sha256=-yJsttRhKw0GgnzMc0FqIigS5UJl_G0YgkK12V5IzJg,6292
14
- haiway/helpers/caching.py,sha256=mz8IMKs6KWWVY62PosPbNQ9sGstC6xCWAAWLYZT2oSg,10132
14
+ haiway/helpers/caching.py,sha256=iy2upZnlpLWc1FjQP0EjAu8j-Vl0yHZmIZ93K6Gc-yY,13232
15
15
  haiway/helpers/metrics.py,sha256=lCSvat3IrkmytFdqTvsqkVqYcVOK_bByfwYAe0hJIWg,13614
16
16
  haiway/helpers/retries.py,sha256=gIkyUlqJLDYaxIZd3qzeqGFY9y5Gp8dgZLlZ6hs8hoc,7538
17
17
  haiway/helpers/throttling.py,sha256=RfQn8GGPqTuPWzA1CJvZvb1s00vryVpo-eqz5EY9vD4,4150
@@ -36,7 +36,7 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
36
36
  haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
37
37
  haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
38
38
  haiway/utils/queue.py,sha256=Tk1bXvuNbEgapeC3-h_PYBASqVjhEoL8mUvtJnM29xI,4000
39
- haiway-0.12.1.dist-info/METADATA,sha256=9FgAnUCIw62Vr7u8MOmDbriyyURb-5aOQVSjv1qwMs0,3857
40
- haiway-0.12.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- haiway-0.12.1.dist-info/licenses/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
42
- haiway-0.12.1.dist-info/RECORD,,
39
+ haiway-0.13.0.dist-info/METADATA,sha256=vlLkeTbiQjf4ilZEEqTwrj18ZzgFzl_rPSHExuTxb0A,4299
40
+ haiway-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ haiway-0.13.0.dist-info/licenses/LICENSE,sha256=3phcpHVNBP8jsi77gOO0E7rgKeDeu99Pi7DSnK9YHoQ,1069
42
+ haiway-0.13.0.dist-info/RECORD,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Miquido
3
+ Copyright (c) 2024-2025 Miquido
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.