sain.sync
Synchronization primitives.
1# BSD 3-Clause License 2# 3# Copyright (c) 2022-Present, nxtlo 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, this 10# list of conditions and the following disclaimer. 11# 12# * Redistributions in binary form must reproduce the above copyright notice, 13# this list of conditions and the following disclaimer in the documentation 14# and/or other materials provided with the distribution. 15# 16# * Neither the name of the copyright holder nor the names of its 17# contributors may be used to endorse or promote products derived from 18# this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30"""Synchronization primitives.""" 31 32from __future__ import annotations 33 34__all__ = ("Lazy", "Once", "LazyFuture", "AsyncOnce") 35 36from .lazy import Lazy 37from .lazy import LazyFuture 38from .once import AsyncOnce 39from .once import Once
49@typing.final 50class Lazy(typing.Generic[T]): 51 """A thread-safe value that gets lazily initialized at first access. 52 53 This type is thread-safe and may be called from multiple threads since this value 54 can be initialzied from mutiple threads, however, any calls to `Lazy.get` will block 55 if another thread is initializing it. 56 57 Example 58 ------- 59 ```py 60 # some expensive call that returns a `str` 61 def expensive_string() -> str: 62 return "hehehe" 63 64 STRING: Lazy[str] = Lazy(expensive_string) 65 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 66 print(STRING.get()) # The string is retrived from the lazy lock. 67 ``` 68 """ 69 70 __slots__ = ("__inner", "__lock") 71 72 def __init__(self, f: collections.Callable[[], T]) -> None: 73 self.__inner: T | collections.Callable[[], T] = f 74 self.__lock = threading.Lock() 75 76 def get(self) -> T: 77 """Get the value if it was initialized, otherwise initialize it and return it. 78 79 Example 80 ------- 81 ```py 82 # some expensive call that returns a `str` 83 def expensive_string() -> str: 84 return "hehehe" 85 86 STRING: Lazy[str] = Lazy(expensive_string) 87 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 88 print(STRING.get()) # The string is retrived from the lazy lock. 89 ``` 90 """ 91 if not callable(self.__inner): 92 # value is already initialized, no need to make a call. 93 return self.__inner 94 95 with self.__lock: 96 inner = self.__inner = self.__inner() 97 return inner 98 99 def __repr__(self) -> str: 100 if callable(self.__inner): 101 return "Lazy(uninit)" 102 103 return f"Lazy(value: {self.__inner.__repr__()})" 104 105 __str__ = __repr__ 106 107 def __eq__(self, other: object) -> bool: 108 if not callable(self.__inner): 109 return False 110 111 if isinstance(other, Lazy): 112 return self.__inner == other.get() 113 114 return NotImplemented 115 116 def __ne__(self, other: object) -> bool: 117 return not self.__eq__(other)
A thread-safe value that gets lazily initialized at first access.
This type is thread-safe and may be called from multiple threads since this value
can be initialzied from mutiple threads, however, any calls to Lazy.get
will block
if another thread is initializing it.
Example
# some expensive call that returns a `str`
def expensive_string() -> str:
return "hehehe"
STRING: Lazy[str] = Lazy(expensive_string)
print(STRING.get()) # The string is built, stored in the lazy lock and returned.
print(STRING.get()) # The string is retrived from the lazy lock.
76 def get(self) -> T: 77 """Get the value if it was initialized, otherwise initialize it and return it. 78 79 Example 80 ------- 81 ```py 82 # some expensive call that returns a `str` 83 def expensive_string() -> str: 84 return "hehehe" 85 86 STRING: Lazy[str] = Lazy(expensive_string) 87 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 88 print(STRING.get()) # The string is retrived from the lazy lock. 89 ``` 90 """ 91 if not callable(self.__inner): 92 # value is already initialized, no need to make a call. 93 return self.__inner 94 95 with self.__lock: 96 inner = self.__inner = self.__inner() 97 return inner
Get the value if it was initialized, otherwise initialize it and return it.
Example
# some expensive call that returns a `str`
def expensive_string() -> str:
return "hehehe"
STRING: Lazy[str] = Lazy(expensive_string)
print(STRING.get()) # The string is built, stored in the lazy lock and returned.
print(STRING.get()) # The string is retrived from the lazy lock.
54@typing.final 55class Once(typing.Generic[T]): 56 """A synchronization primitive which can be written to only once. 57 58 Example 59 ------- 60 ```py 61 from sain.once import Once 62 from uuid import uuid4, UUID 63 64 UUID: Once[UUID] = Once() 65 66 def start() -> None: 67 assert UUID.get().is_none() 68 69 # First initialization. 70 UUID.get_or(uuid4()) # some-uuid 71 72 # Won't set, returns the same uuid that got initialized first. 73 UUID.get_or(uuid4()) # some-uuid 74 ``` 75 """ 76 77 __slots__ = ("_inner", "_lock") 78 79 def __init__(self) -> None: 80 self._lock: threading.Lock | None = None 81 self._inner: T | None = None 82 83 @property 84 def is_set(self) -> bool: 85 return self._inner is not None 86 87 def get(self) -> Option[T]: 88 """Gets the stored value, returning `None` if not initialized. 89 90 This method will never block. 91 """ 92 return _option.Some(self._inner) if self.is_set else _option.NOTHING # pyright: ignore 93 94 @macros.unsafe 95 def get_unchecked(self) -> T: 96 """Get the contained value without checking if it was initialized. 97 98 Example 99 ------- 100 ```py 101 cell = Once[float]() 102 inner = cell.get_unchecked() # Undefined Behavior!! 103 104 # Initialize it first. 105 cell.get_or(math.sqrt(2.0)) 106 107 # At this point of the program, 108 # it is guaranteed that the value is initialized. 109 inner = cell.get_unchecked() 110 ``` 111 """ 112 # SAFETY: The caller guarantees that the value is initialized. 113 return self.get().unwrap_unchecked() 114 115 def set(self, v: T) -> _result.Result[None, T]: 116 """Set the const value if its not set. returning `T` if its already set. 117 118 This method may block if another thread is trying to initialize the value. 119 The value is guaranteed to be set, just not necessarily the one provided. 120 121 Example 122 -------- 123 ```py 124 flag = Once[bool]() 125 # flag is empty. 126 assert flag.get_or(True) is True. 127 128 # flag is not empty, so it returns the value we set first. 129 assert flag.set(False) == Err(True) 130 ``` 131 132 Returns 133 ------- 134 `sain.Result[None, T]` 135 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 136 """ 137 if self._inner is not None: 138 return _result.Err(self._inner) 139 140 self._inner = self.get_or(v) 141 self._lock = None 142 return _result.Ok(None) 143 144 def clear(self) -> None: 145 """Clear the inner value, Setting it to `None`.""" 146 self._lock = None 147 self._inner = None 148 149 def get_or(self, init: T) -> T: 150 """Get the value if it was not initialized, Otherwise set `init` value and returning it. 151 152 Many threads may call `get_or` concurrently with different 153 initializing functions, but it is guaranteed that only one function 154 will be executed. 155 156 Example 157 ------- 158 ```py 159 UUID: Once[UUID] = Once() 160 161 def main() -> None: 162 assert UUID.get().is_none() 163 164 # First initialization. 165 UUID.get_or(uuid4()) # some-uuid 166 167 # Won't set, returns the same uuid that got initialized first. 168 UUID.get_or(uuid4()) # some-uuid 169 ``` 170 """ 171 172 # If the value is not empty we return it immediately. 173 if self._inner is not None: 174 return self._inner 175 176 if self._lock is None: 177 self._lock = threading.Lock() 178 179 with self._lock: 180 self._inner = init 181 return init 182 183 def get_or_with(self, f: collections.Callable[..., T]) -> T: 184 """Gets the contents of the cell, initializing it with `f` if the cell 185 was empty. 186 187 Many threads may call `get_or_with` concurrently with different 188 initializing functions, but it is guaranteed that only one function 189 will be executed. 190 191 Examples 192 -------- 193 ```py 194 cell = Once[int]() 195 value = cell.get_or_with(lambda: 92) 196 assert value == 92 197 198 value = cell.get_or_with(lambda: 0) 199 assert value == 92 200 ``` 201 """ 202 # If the value is not empty we return it immediately. 203 if self._inner is not None: 204 return self._inner 205 206 if self._lock is None: 207 self._lock = threading.Lock() 208 209 with self._lock: 210 v = f() 211 self._inner = v 212 return v 213 214 def __repr__(self) -> str: 215 if not self.is_set: 216 return "<uninit>" 217 218 return f"Once(inner: {self._inner!r})" 219 220 __str__ = __repr__ 221 222 def __eq__(self, __value: object) -> bool: 223 if isinstance(__value, Once): 224 return self._inner == __value._inner # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] 225 226 return NotImplemented 227 228 def __ne__(self, __value: object) -> bool: 229 return not self.__eq__(__value) 230 231 def __bool__(self) -> bool: 232 return self.is_set
A synchronization primitive which can be written to only once.
Example
from sain.once import Once
from uuid import uuid4, UUID
UUID: Once[UUID] = Once()
def start() -> None:
assert UUID.get().is_none()
# First initialization.
UUID.get_or(uuid4()) # some-uuid
# Won't set, returns the same uuid that got initialized first.
UUID.get_or(uuid4()) # some-uuid
87 def get(self) -> Option[T]: 88 """Gets the stored value, returning `None` if not initialized. 89 90 This method will never block. 91 """ 92 return _option.Some(self._inner) if self.is_set else _option.NOTHING # pyright: ignore
Gets the stored value, returning None
if not initialized.
This method will never block.
94 @macros.unsafe 95 def get_unchecked(self) -> T: 96 """Get the contained value without checking if it was initialized. 97 98 Example 99 ------- 100 ```py 101 cell = Once[float]() 102 inner = cell.get_unchecked() # Undefined Behavior!! 103 104 # Initialize it first. 105 cell.get_or(math.sqrt(2.0)) 106 107 # At this point of the program, 108 # it is guaranteed that the value is initialized. 109 inner = cell.get_unchecked() 110 ``` 111 """ 112 # SAFETY: The caller guarantees that the value is initialized. 113 return self.get().unwrap_unchecked()
Get the contained value without checking if it was initialized.
Example
cell = Once[float]()
inner = cell.get_unchecked() # Undefined Behavior!!
# Initialize it first.
cell.get_or(math.sqrt(2.0))
# At this point of the program,
# it is guaranteed that the value is initialized.
inner = cell.get_unchecked()
Safety ⚠️
Calling this method on None
is considered undefined behavior.
115 def set(self, v: T) -> _result.Result[None, T]: 116 """Set the const value if its not set. returning `T` if its already set. 117 118 This method may block if another thread is trying to initialize the value. 119 The value is guaranteed to be set, just not necessarily the one provided. 120 121 Example 122 -------- 123 ```py 124 flag = Once[bool]() 125 # flag is empty. 126 assert flag.get_or(True) is True. 127 128 # flag is not empty, so it returns the value we set first. 129 assert flag.set(False) == Err(True) 130 ``` 131 132 Returns 133 ------- 134 `sain.Result[None, T]` 135 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 136 """ 137 if self._inner is not None: 138 return _result.Err(self._inner) 139 140 self._inner = self.get_or(v) 141 self._lock = None 142 return _result.Ok(None)
Set the const value if its not set. returning T
if its already set.
This method may block if another thread is trying to initialize the value. The value is guaranteed to be set, just not necessarily the one provided.
Example
flag = Once[bool]()
# flag is empty.
assert flag.get_or(True) is True.
# flag is not empty, so it returns the value we set first.
assert flag.set(False) == Err(True)
Returns
sain.Result[None, T]
: This cell returnsOk(None)
if it was empty. otherwiseErr(T)
if it was full.
144 def clear(self) -> None: 145 """Clear the inner value, Setting it to `None`.""" 146 self._lock = None 147 self._inner = None
Clear the inner value, Setting it to None
.
149 def get_or(self, init: T) -> T: 150 """Get the value if it was not initialized, Otherwise set `init` value and returning it. 151 152 Many threads may call `get_or` concurrently with different 153 initializing functions, but it is guaranteed that only one function 154 will be executed. 155 156 Example 157 ------- 158 ```py 159 UUID: Once[UUID] = Once() 160 161 def main() -> None: 162 assert UUID.get().is_none() 163 164 # First initialization. 165 UUID.get_or(uuid4()) # some-uuid 166 167 # Won't set, returns the same uuid that got initialized first. 168 UUID.get_or(uuid4()) # some-uuid 169 ``` 170 """ 171 172 # If the value is not empty we return it immediately. 173 if self._inner is not None: 174 return self._inner 175 176 if self._lock is None: 177 self._lock = threading.Lock() 178 179 with self._lock: 180 self._inner = init 181 return init
Get the value if it was not initialized, Otherwise set init
value and returning it.
Many threads may call get_or
concurrently with different
initializing functions, but it is guaranteed that only one function
will be executed.
Example
UUID: Once[UUID] = Once()
def main() -> None:
assert UUID.get().is_none()
# First initialization.
UUID.get_or(uuid4()) # some-uuid
# Won't set, returns the same uuid that got initialized first.
UUID.get_or(uuid4()) # some-uuid
183 def get_or_with(self, f: collections.Callable[..., T]) -> T: 184 """Gets the contents of the cell, initializing it with `f` if the cell 185 was empty. 186 187 Many threads may call `get_or_with` concurrently with different 188 initializing functions, but it is guaranteed that only one function 189 will be executed. 190 191 Examples 192 -------- 193 ```py 194 cell = Once[int]() 195 value = cell.get_or_with(lambda: 92) 196 assert value == 92 197 198 value = cell.get_or_with(lambda: 0) 199 assert value == 92 200 ``` 201 """ 202 # If the value is not empty we return it immediately. 203 if self._inner is not None: 204 return self._inner 205 206 if self._lock is None: 207 self._lock = threading.Lock() 208 209 with self._lock: 210 v = f() 211 self._inner = v 212 return v
Gets the contents of the cell, initializing it with f
if the cell
was empty.
Many threads may call get_or_with
concurrently with different
initializing functions, but it is guaranteed that only one function
will be executed.
Examples
cell = Once[int]()
value = cell.get_or_with(lambda: 92)
assert value == 92
value = cell.get_or_with(lambda: 0)
assert value == 92
120@typing.final 121class LazyFuture(typing.Generic[T]): 122 """A thread-safe value that gets lazily initialized asynchronously at first access. 123 124 This type is thread-safe and may be called from multiple threads since this value 125 can be initialzied from mutiple threads, however, any calls to `Lazy.get` will block 126 if another thread is initializing it. 127 128 Example 129 ------- 130 ```py 131 # some expensive call that returns a `str` 132 async def fetch_expensive_string(filtered: bool) -> str: 133 return "hehehe" if filtered else "whahaha" 134 135 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 136 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 137 print(await STRING.get()) # The string is retrived from the lazy lock. 138 ``` 139 """ 140 141 __slots__ = ("__inner", "__lock") 142 143 def __init__( 144 self, 145 f: collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]], 146 ) -> None: 147 self.__inner: ( 148 T 149 | collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]] 150 ) = f 151 self.__lock = asyncio.Lock() 152 153 @macros.unsafe 154 async def get(self) -> T: 155 """Get the value if it was initialized, otherwise initialize it and return it. 156 157 Example 158 ------- 159 ```py 160 # some expensive call that returns a `str` 161 async def fetch_expensive_string(filtered: bool) -> str: 162 return "hehehe" if filtered else "whahaha" 163 164 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 165 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 166 print(await STRING.get()) # The string is retrived from the lazy lock. 167 ``` 168 """ 169 if not callable(self.__inner): 170 # value is already initialized, no need to make a call. 171 return self.__inner 172 173 async with self.__lock: 174 v = await self.__inner() 175 self.__inner = v 176 return v 177 178 def __repr__(self) -> str: 179 if not callable(self.__inner): 180 return "LazyFuture(uninit)" 181 182 return f"LazyFuture(value: {self.__inner!r})" 183 184 __str__ = __repr__ 185 186 def __eq__(self, other: object) -> bool: 187 if not callable(self.__inner): 188 return False 189 190 if isinstance(other, Lazy): 191 return self._inner == other._inner # pyright: ignore 192 193 return NotImplemented 194 195 def __ne__(self, other: object) -> bool: 196 return not self.__eq__(other)
A thread-safe value that gets lazily initialized asynchronously at first access.
This type is thread-safe and may be called from multiple threads since this value
can be initialzied from mutiple threads, however, any calls to Lazy.get
will block
if another thread is initializing it.
Example
# some expensive call that returns a `str`
async def fetch_expensive_string(filtered: bool) -> str:
return "hehehe" if filtered else "whahaha"
STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True))
print(await STRING.get()) # The string is built, stored in the lazy lock and returned.
print(await STRING.get()) # The string is retrived from the lazy lock.
153 @macros.unsafe 154 async def get(self) -> T: 155 """Get the value if it was initialized, otherwise initialize it and return it. 156 157 Example 158 ------- 159 ```py 160 # some expensive call that returns a `str` 161 async def fetch_expensive_string(filtered: bool) -> str: 162 return "hehehe" if filtered else "whahaha" 163 164 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 165 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 166 print(await STRING.get()) # The string is retrived from the lazy lock. 167 ``` 168 """ 169 if not callable(self.__inner): 170 # value is already initialized, no need to make a call. 171 return self.__inner 172 173 async with self.__lock: 174 v = await self.__inner() 175 self.__inner = v 176 return v
Get the value if it was initialized, otherwise initialize it and return it.
Example
# some expensive call that returns a `str`
async def fetch_expensive_string(filtered: bool) -> str:
return "hehehe" if filtered else "whahaha"
STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True))
print(await STRING.get()) # The string is built, stored in the lazy lock and returned.
print(await STRING.get()) # The string is retrived from the lazy lock.
Safety ⚠️
Calling this method on None
is considered undefined behavior.
235@typing.final 236class AsyncOnce(typing.Generic[T]): 237 """A synchronization primitive which can be written to only once. 238 239 This is an `async` version of `Once`. 240 241 Example 242 ------- 243 ```py 244 from sain.once import Once 245 from uuid import uuid4, UUID 246 247 # A global uuid 248 UUID: AsyncOnce[UUID] = AsyncOnce() 249 250 async def start() -> None: 251 assert UUID.get().is_none() 252 # First initialization. 253 await UUID.get_or(uuid4()) # some-uuid 254 # Won't set, returns the same uuid that got initialized first. 255 await UUID.get_or(uuid4()) # some-uuid 256 ``` 257 """ 258 259 __slots__ = ("_inner", "_lock") 260 261 def __init__(self) -> None: 262 self._lock: asyncio.Lock | None = None 263 self._inner: T | None = None 264 265 @property 266 def is_set(self) -> bool: 267 """Whether this inner value has ben initialized or not.""" 268 return self._inner is not None 269 270 def get(self) -> Option[T]: 271 """Gets the stored value. `Some(None)` is returned if nothing is stored. 272 273 This method will never block. 274 """ 275 return _option.Some(self._inner) 276 277 @macros.unsafe 278 def get_unchecked(self) -> T: 279 """Get the contained value without checking if it was initialized. 280 281 Example 282 ------- 283 ```py 284 cell = AsyncOnce[float]() 285 inner = cell.get_unchecked() # Undefined Behavior!! 286 287 # Initialize it first. 288 cell.get_or(math.sqrt(2.0)) 289 290 # At this point of the program, 291 # it is guaranteed that the value is initialized. 292 inner = cell.get_unchecked() 293 ``` 294 """ 295 # SAFETY: The caller guarantees that the value is initialized. 296 return self.get().unwrap_unchecked() 297 298 async def set(self, v: T) -> _result.Result[None, T]: 299 """Set the const value if its not set. returning `T` if its already set. 300 301 if another thread is trying to initialize the value, The value is guaranteed to be set, 302 just not necessarily the one provided. 303 304 Example 305 -------- 306 ```py 307 flag = AsyncOnce[bool]() 308 # flag is empty. 309 assert await flag.get_or(True) is True. 310 # flag is not empty, so it returns the value we set first. 311 assert await flag.set(False) == Err(True) 312 ``` 313 314 Returns 315 ------- 316 `sain.Result[None, T]` 317 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 318 """ 319 if self._inner is not None: 320 return _result.Err(self._inner) 321 322 self._inner = await self.get_or(v) 323 self._lock = None 324 return _result.Ok(None) 325 326 def clear(self) -> None: 327 """Clear the inner value, Setting it to `None`.""" 328 self._lock = None 329 self._inner = None 330 331 async def get_or(self, init: T) -> T: 332 """Gets the contents of the cell, initializing it with `init` if the cell 333 was empty. 334 335 Many threads may call `get_or` concurrently with different 336 initializing functions, but it is guaranteed that only one function 337 will be executed. 338 339 Examples 340 -------- 341 ```py 342 from sain.sync import AsyncOnce 343 344 cell = AsyncOnce[int]() 345 value = await cell.get_or(92) 346 assert value == 92 347 348 value = await cell.get_or(0) 349 assert value == 92 350 ``` 351 """ 352 # If the value is not empty we return it immediately. 353 if self._inner is not None: 354 return self._inner 355 356 if self._lock is None: 357 self._lock = asyncio.Lock() 358 359 async with self._lock: 360 self._inner = init 361 return init 362 363 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 364 """Gets the contents of the cell, initializing it with `f` if the cell 365 was empty. 366 367 Many threads may call `get_or_with` concurrently with different 368 initializing functions, but it is guaranteed that only one function 369 will be executed. 370 371 Examples 372 -------- 373 ```py 374 from sain.sync import AsyncOnce 375 376 cell = AsyncOnce[int]() 377 value = await cell.get_or_with(lambda: 92) 378 assert value == 92 379 380 value = await cell.get_or_init(lambda: 0) 381 assert value == 92 382 ``` 383 """ 384 # If the value is not empty we return it immediately. 385 if self._inner is not None: 386 return self._inner 387 388 if self._lock is None: 389 self._lock = asyncio.Lock() 390 391 async with self._lock: 392 v = f() 393 self._inner = v 394 return v 395 396 def __repr__(self) -> str: 397 if self._inner is not None: 398 return f"AsyncOnce(value: {self._inner})" 399 return "<async_uninit>" 400 401 __str__ = __repr__ 402 403 def __eq__(self, __value: object) -> bool: 404 if isinstance(__value, AsyncOnce): 405 return self._inner == __value._inner # pyright: ignore 406 407 return NotImplemented 408 409 def __ne__(self, __value: object) -> bool: 410 return not self.__eq__(__value) 411 412 def __bool__(self) -> bool: 413 return self.is_set
A synchronization primitive which can be written to only once.
This is an async
version of Once
.
Example
from sain.once import Once
from uuid import uuid4, UUID
# A global uuid
UUID: AsyncOnce[UUID] = AsyncOnce()
async def start() -> None:
assert UUID.get().is_none()
# First initialization.
await UUID.get_or(uuid4()) # some-uuid
# Won't set, returns the same uuid that got initialized first.
await UUID.get_or(uuid4()) # some-uuid
265 @property 266 def is_set(self) -> bool: 267 """Whether this inner value has ben initialized or not.""" 268 return self._inner is not None
Whether this inner value has ben initialized or not.
270 def get(self) -> Option[T]: 271 """Gets the stored value. `Some(None)` is returned if nothing is stored. 272 273 This method will never block. 274 """ 275 return _option.Some(self._inner)
Gets the stored value. Some(None)
is returned if nothing is stored.
This method will never block.
277 @macros.unsafe 278 def get_unchecked(self) -> T: 279 """Get the contained value without checking if it was initialized. 280 281 Example 282 ------- 283 ```py 284 cell = AsyncOnce[float]() 285 inner = cell.get_unchecked() # Undefined Behavior!! 286 287 # Initialize it first. 288 cell.get_or(math.sqrt(2.0)) 289 290 # At this point of the program, 291 # it is guaranteed that the value is initialized. 292 inner = cell.get_unchecked() 293 ``` 294 """ 295 # SAFETY: The caller guarantees that the value is initialized. 296 return self.get().unwrap_unchecked()
Get the contained value without checking if it was initialized.
Example
cell = AsyncOnce[float]()
inner = cell.get_unchecked() # Undefined Behavior!!
# Initialize it first.
cell.get_or(math.sqrt(2.0))
# At this point of the program,
# it is guaranteed that the value is initialized.
inner = cell.get_unchecked()
Safety ⚠️
Calling this method on None
is considered undefined behavior.
298 async def set(self, v: T) -> _result.Result[None, T]: 299 """Set the const value if its not set. returning `T` if its already set. 300 301 if another thread is trying to initialize the value, The value is guaranteed to be set, 302 just not necessarily the one provided. 303 304 Example 305 -------- 306 ```py 307 flag = AsyncOnce[bool]() 308 # flag is empty. 309 assert await flag.get_or(True) is True. 310 # flag is not empty, so it returns the value we set first. 311 assert await flag.set(False) == Err(True) 312 ``` 313 314 Returns 315 ------- 316 `sain.Result[None, T]` 317 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 318 """ 319 if self._inner is not None: 320 return _result.Err(self._inner) 321 322 self._inner = await self.get_or(v) 323 self._lock = None 324 return _result.Ok(None)
Set the const value if its not set. returning T
if its already set.
if another thread is trying to initialize the value, The value is guaranteed to be set, just not necessarily the one provided.
Example
flag = AsyncOnce[bool]()
# flag is empty.
assert await flag.get_or(True) is True.
# flag is not empty, so it returns the value we set first.
assert await flag.set(False) == Err(True)
Returns
sain.Result[None, T]
: This cell returnsOk(None)
if it was empty. otherwiseErr(T)
if it was full.
326 def clear(self) -> None: 327 """Clear the inner value, Setting it to `None`.""" 328 self._lock = None 329 self._inner = None
Clear the inner value, Setting it to None
.
331 async def get_or(self, init: T) -> T: 332 """Gets the contents of the cell, initializing it with `init` if the cell 333 was empty. 334 335 Many threads may call `get_or` concurrently with different 336 initializing functions, but it is guaranteed that only one function 337 will be executed. 338 339 Examples 340 -------- 341 ```py 342 from sain.sync import AsyncOnce 343 344 cell = AsyncOnce[int]() 345 value = await cell.get_or(92) 346 assert value == 92 347 348 value = await cell.get_or(0) 349 assert value == 92 350 ``` 351 """ 352 # If the value is not empty we return it immediately. 353 if self._inner is not None: 354 return self._inner 355 356 if self._lock is None: 357 self._lock = asyncio.Lock() 358 359 async with self._lock: 360 self._inner = init 361 return init
Gets the contents of the cell, initializing it with init
if the cell
was empty.
Many threads may call get_or
concurrently with different
initializing functions, but it is guaranteed that only one function
will be executed.
Examples
from sain.sync import AsyncOnce
cell = AsyncOnce[int]()
value = await cell.get_or(92)
assert value == 92
value = await cell.get_or(0)
assert value == 92
363 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 364 """Gets the contents of the cell, initializing it with `f` if the cell 365 was empty. 366 367 Many threads may call `get_or_with` concurrently with different 368 initializing functions, but it is guaranteed that only one function 369 will be executed. 370 371 Examples 372 -------- 373 ```py 374 from sain.sync import AsyncOnce 375 376 cell = AsyncOnce[int]() 377 value = await cell.get_or_with(lambda: 92) 378 assert value == 92 379 380 value = await cell.get_or_init(lambda: 0) 381 assert value == 92 382 ``` 383 """ 384 # If the value is not empty we return it immediately. 385 if self._inner is not None: 386 return self._inner 387 388 if self._lock is None: 389 self._lock = asyncio.Lock() 390 391 async with self._lock: 392 v = f() 393 self._inner = v 394 return v
Gets the contents of the cell, initializing it with f
if the cell
was empty.
Many threads may call get_or_with
concurrently with different
initializing functions, but it is guaranteed that only one function
will be executed.
Examples
from sain.sync import AsyncOnce
cell = AsyncOnce[int]()
value = await cell.get_or_with(lambda: 92)
assert value == 92
value = await cell.get_or_init(lambda: 0)
assert value == 92