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@rustc_diagnostic_item("Lazy") 50@typing.final 51class Lazy(typing.Generic[T]): 52 """A thread-safe value that gets lazily initialized at first access. 53 54 This type is thread-safe and may be called from multiple threads since this value 55 can be initialized from multiple threads, however, any calls to `Lazy.get` will block 56 if another thread is initializing it. 57 58 Example 59 ------- 60 ```py 61 # some expensive call that returns a `str` 62 def expensive_string() -> str: 63 return "hehehe" 64 65 STRING: Lazy[str] = Lazy(expensive_string) 66 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 67 print(STRING.get()) # The string is retrieved from the lazy lock. 68 ``` 69 """ 70 71 __slots__ = ("__inner", "__lock") 72 73 def __init__(self, f: collections.Callable[[], T]) -> None: 74 self.__inner: T | collections.Callable[[], T] = f 75 self.__lock: threading.Lock | None = None 76 77 def get(self) -> T: 78 """Get the value if it was initialized, otherwise initialize it and return it. 79 80 Its guaranteed to not block if the value has been initialized. 81 82 Example 83 ------- 84 ```py 85 # some expensive call that returns a `str` 86 def expensive_string() -> str: 87 return "hehehe" 88 89 STRING: Lazy[str] = Lazy(expensive_string) 90 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 91 print(STRING.get()) # The string is retrieved from the lazy lock. 92 ``` 93 """ 94 if not callable(self.__inner): 95 # value is already initialized, no need to make a call. 96 return self.__inner 97 98 if self.__lock is None: 99 self.__lock = threading.Lock() 100 101 with self.__lock: 102 # TYPE SAFETY: We know we need to call this function. 103 self.__inner = self.__inner() # type: ignore 104 return self.__inner # type: ignore 105 106 def __repr__(self) -> str: 107 if callable(self.__inner): 108 return "Lazy(uninit)" 109 110 return f"Lazy(value: {self.__inner.__repr__()})" 111 112 __str__ = __repr__ 113 114 def __eq__(self, other: object) -> bool: 115 if not callable(self.__inner): 116 return False 117 118 if isinstance(other, Lazy): 119 return self.__inner == other.get() 120 121 return NotImplemented 122 123 def __ne__(self, other: object) -> bool: 124 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 initialized from multiple 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 retrieved from the lazy lock.
Implementations
This class implements Lazy in Rust.
77 def get(self) -> T: 78 """Get the value if it was initialized, otherwise initialize it and return it. 79 80 Its guaranteed to not block if the value has been initialized. 81 82 Example 83 ------- 84 ```py 85 # some expensive call that returns a `str` 86 def expensive_string() -> str: 87 return "hehehe" 88 89 STRING: Lazy[str] = Lazy(expensive_string) 90 print(STRING.get()) # The string is built, stored in the lazy lock and returned. 91 print(STRING.get()) # The string is retrieved from the lazy lock. 92 ``` 93 """ 94 if not callable(self.__inner): 95 # value is already initialized, no need to make a call. 96 return self.__inner 97 98 if self.__lock is None: 99 self.__lock = threading.Lock() 100 101 with self.__lock: 102 # TYPE SAFETY: We know we need to call this function. 103 self.__inner = self.__inner() # type: ignore 104 return self.__inner # type: ignore
Get the value if it was initialized, otherwise initialize it and return it.
Its guaranteed to not block if the value has been initialized.
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 retrieved from the lazy lock.
54@macros.rustc_diagnostic_item("Once") 55@typing.final 56class Once(typing.Generic[T]): 57 """A synchronization primitive which can be written to only once. 58 59 Example 60 ------- 61 ```py 62 from sain.once import Once 63 from uuid import uuid4, UUID 64 65 UUID: Once[UUID] = Once() 66 67 def start() -> None: 68 assert UUID.get().is_none() 69 70 # First initialization. 71 UUID.get_or(uuid4()) # some-uuid 72 73 # Won't set, returns the same uuid that got initialized first. 74 UUID.get_or(uuid4()) # some-uuid 75 ``` 76 """ 77 78 __slots__ = ("_inner", "_lock") 79 80 def __init__(self) -> None: 81 self._lock: threading.Lock | None = None 82 self._inner: T | None = None 83 84 @property 85 def is_set(self) -> bool: 86 return self._inner is not None 87 88 def get(self) -> Option[T]: 89 """Gets the stored value, returning `None` if not initialized. 90 91 This method will never block. 92 """ 93 return _option.Some(self._inner) if self.is_set else _option.NOTHING # pyright: ignore 94 95 @macros.unsafe 96 def get_unchecked(self) -> T: 97 """Get the contained value without checking if it was initialized. 98 99 Example 100 ------- 101 ```py 102 cell = Once[float]() 103 inner = cell.get_unchecked() # Undefined Behavior!! 104 105 # Initialize it first. 106 cell.get_or(math.sqrt(2.0)) 107 108 # At this point of the program, 109 # it is guaranteed that the value is initialized. 110 inner = cell.get_unchecked() 111 ``` 112 """ 113 # SAFETY: The caller guarantees that the value is initialized. 114 return self.get().unwrap_unchecked() 115 116 def set(self, v: T) -> _result.Result[None, T]: 117 """Set the const value if its not set. returning `T` if its already set. 118 119 This method may block if another thread is trying to initialize the value. 120 The value is guaranteed to be set, just not necessarily the one provided. 121 122 Example 123 -------- 124 ```py 125 flag = Once[bool]() 126 # flag is empty. 127 assert flag.get_or(True) is True. 128 129 # flag is not empty, so it returns the value we set first. 130 assert flag.set(False) == Err(True) 131 ``` 132 133 Returns 134 ------- 135 `sain.Result[None, T]` 136 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 137 """ 138 if self._inner is not None: 139 return _result.Err(self._inner) 140 141 self._inner = self.get_or(v) 142 self._lock = None 143 return _result.Ok(None) 144 145 def clear(self) -> None: 146 """Clear the inner value, Setting it to `None`.""" 147 self._lock = None 148 self._inner = None 149 150 def get_or(self, init: T) -> T: 151 """Get the value if it was not initialized, Otherwise set `init` value and returning it. 152 153 Many threads may call `get_or` concurrently with different 154 initializing functions, but it is guaranteed that only one function 155 will be executed. 156 157 Example 158 ------- 159 ```py 160 UUID: Once[UUID] = Once() 161 162 def main() -> None: 163 assert UUID.get().is_none() 164 165 # First initialization. 166 UUID.get_or(uuid4()) # some-uuid 167 168 # Won't set, returns the same uuid that got initialized first. 169 UUID.get_or(uuid4()) # some-uuid 170 ``` 171 """ 172 173 # If the value is not empty we return it immediately. 174 if self._inner is not None: 175 return self._inner 176 177 if self._lock is None: 178 self._lock = threading.Lock() 179 180 with self._lock: 181 self._inner = init 182 return init 183 184 def get_or_with(self, f: collections.Callable[..., T]) -> T: 185 """Gets the contents of the cell, initializing it with `f` if the cell 186 was empty. 187 188 Many threads may call `get_or_with` concurrently with different 189 initializing functions, but it is guaranteed that only one function 190 will be executed. 191 192 Examples 193 -------- 194 ```py 195 cell = Once[int]() 196 value = cell.get_or_with(lambda: 92) 197 assert value == 92 198 199 value = cell.get_or_with(lambda: 0) 200 assert value == 92 201 ``` 202 """ 203 # If the value is not empty we return it immediately. 204 if self._inner is not None: 205 return self._inner 206 207 if self._lock is None: 208 self._lock = threading.Lock() 209 210 with self._lock: 211 v = f() 212 self._inner = v 213 return v 214 215 def __repr__(self) -> str: 216 if not self.is_set: 217 return "<uninit>" 218 219 return f"Once(inner: {self._inner!r})" 220 221 __str__ = __repr__ 222 223 def __eq__(self, __value: object) -> bool: 224 if isinstance(__value, Once): 225 return self._inner == __value._inner # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] 226 227 return NotImplemented 228 229 def __ne__(self, __value: object) -> bool: 230 return not self.__eq__(__value) 231 232 def __bool__(self) -> bool: 233 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
Implementations
This class implements Once in Rust.
88 def get(self) -> Option[T]: 89 """Gets the stored value, returning `None` if not initialized. 90 91 This method will never block. 92 """ 93 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.
95 @macros.unsafe 96 def get_unchecked(self) -> T: 97 """Get the contained value without checking if it was initialized. 98 99 Example 100 ------- 101 ```py 102 cell = Once[float]() 103 inner = cell.get_unchecked() # Undefined Behavior!! 104 105 # Initialize it first. 106 cell.get_or(math.sqrt(2.0)) 107 108 # At this point of the program, 109 # it is guaranteed that the value is initialized. 110 inner = cell.get_unchecked() 111 ``` 112 """ 113 # SAFETY: The caller guarantees that the value is initialized. 114 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.
116 def set(self, v: T) -> _result.Result[None, T]: 117 """Set the const value if its not set. returning `T` if its already set. 118 119 This method may block if another thread is trying to initialize the value. 120 The value is guaranteed to be set, just not necessarily the one provided. 121 122 Example 123 -------- 124 ```py 125 flag = Once[bool]() 126 # flag is empty. 127 assert flag.get_or(True) is True. 128 129 # flag is not empty, so it returns the value we set first. 130 assert flag.set(False) == Err(True) 131 ``` 132 133 Returns 134 ------- 135 `sain.Result[None, T]` 136 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 137 """ 138 if self._inner is not None: 139 return _result.Err(self._inner) 140 141 self._inner = self.get_or(v) 142 self._lock = None 143 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.
145 def clear(self) -> None: 146 """Clear the inner value, Setting it to `None`.""" 147 self._lock = None 148 self._inner = None
Clear the inner value, Setting it to None
.
150 def get_or(self, init: T) -> T: 151 """Get the value if it was not initialized, Otherwise set `init` value and returning it. 152 153 Many threads may call `get_or` concurrently with different 154 initializing functions, but it is guaranteed that only one function 155 will be executed. 156 157 Example 158 ------- 159 ```py 160 UUID: Once[UUID] = Once() 161 162 def main() -> None: 163 assert UUID.get().is_none() 164 165 # First initialization. 166 UUID.get_or(uuid4()) # some-uuid 167 168 # Won't set, returns the same uuid that got initialized first. 169 UUID.get_or(uuid4()) # some-uuid 170 ``` 171 """ 172 173 # If the value is not empty we return it immediately. 174 if self._inner is not None: 175 return self._inner 176 177 if self._lock is None: 178 self._lock = threading.Lock() 179 180 with self._lock: 181 self._inner = init 182 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
184 def get_or_with(self, f: collections.Callable[..., T]) -> T: 185 """Gets the contents of the cell, initializing it with `f` if the cell 186 was empty. 187 188 Many threads may call `get_or_with` concurrently with different 189 initializing functions, but it is guaranteed that only one function 190 will be executed. 191 192 Examples 193 -------- 194 ```py 195 cell = Once[int]() 196 value = cell.get_or_with(lambda: 92) 197 assert value == 92 198 199 value = cell.get_or_with(lambda: 0) 200 assert value == 92 201 ``` 202 """ 203 # If the value is not empty we return it immediately. 204 if self._inner is not None: 205 return self._inner 206 207 if self._lock is None: 208 self._lock = threading.Lock() 209 210 with self._lock: 211 v = f() 212 self._inner = v 213 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
127@typing.final 128class LazyFuture(typing.Generic[T]): 129 """A thread-safe value that gets lazily initialized asynchronously at first access. 130 131 This type is thread-safe and may be called from multiple threads since this value 132 can be initialized from multiple threads, however, any calls to `Lazy.get` will block 133 if another thread is initializing it. 134 135 Example 136 ------- 137 ```py 138 # some expensive call that returns a `str` 139 async def fetch_expensive_string(filtered: bool) -> str: 140 return "hehehe" if filtered else "whahaha" 141 142 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 143 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 144 print(await STRING.get()) # The string is retrieved from the lazy lock. 145 ``` 146 """ 147 148 __slots__ = ("__inner", "__lock") 149 150 def __init__( 151 self, 152 f: collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]], 153 ) -> None: 154 self.__inner: ( 155 T 156 | collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]] 157 ) = f 158 self.__lock: asyncio.Lock | None = asyncio.Lock() 159 160 async def get(self) -> T: 161 """Get the value if it was initialized, otherwise initialize it and return it. 162 163 Example 164 ------- 165 ```py 166 # some expensive call that returns a `str` 167 async def fetch_expensive_string(filtered: bool) -> str: 168 return "hehehe" if filtered else "whahaha" 169 170 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 171 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 172 print(await STRING.get()) # The string is retrieved from the lazy lock. 173 ``` 174 """ 175 if not callable(self.__inner): 176 # value is already initialized, no need to make a call. 177 return self.__inner 178 179 if self.__lock is None: 180 self.__lock = asyncio.Lock() 181 182 async with self.__lock: 183 # calling self.__inner will make self.__inner type T 184 v = await self.__inner() # pyright: ignore 185 self.__inner = v 186 return v 187 188 def __repr__(self) -> str: 189 if not callable(self.__inner): 190 return "LazyFuture(uninit)" 191 192 return f"LazyFuture(value: {self.__inner!r})" 193 194 __str__ = __repr__ 195 196 def __eq__(self, other: object) -> bool: 197 if not callable(self.__inner): 198 return False 199 200 if isinstance(other, Lazy): 201 return self._inner == other._inner # pyright: ignore 202 203 return NotImplemented 204 205 def __ne__(self, other: object) -> bool: 206 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 initialized from multiple 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 retrieved from the lazy lock.
160 async def get(self) -> T: 161 """Get the value if it was initialized, otherwise initialize it and return it. 162 163 Example 164 ------- 165 ```py 166 # some expensive call that returns a `str` 167 async def fetch_expensive_string(filtered: bool) -> str: 168 return "hehehe" if filtered else "whahaha" 169 170 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 171 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 172 print(await STRING.get()) # The string is retrieved from the lazy lock. 173 ``` 174 """ 175 if not callable(self.__inner): 176 # value is already initialized, no need to make a call. 177 return self.__inner 178 179 if self.__lock is None: 180 self.__lock = asyncio.Lock() 181 182 async with self.__lock: 183 # calling self.__inner will make self.__inner type T 184 v = await self.__inner() # pyright: ignore 185 self.__inner = v 186 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 retrieved from the lazy lock.
236@typing.final 237class AsyncOnce(typing.Generic[T]): 238 """A synchronization primitive which can be written to only once. 239 240 This is an `async` version of `Once`. 241 242 Example 243 ------- 244 ```py 245 from sain.once import Once 246 from uuid import uuid4, UUID 247 248 # A global uuid 249 UUID: AsyncOnce[UUID] = AsyncOnce() 250 251 async def start() -> None: 252 assert UUID.get().is_none() 253 # First initialization. 254 await UUID.get_or(uuid4()) # some-uuid 255 # Won't set, returns the same uuid that got initialized first. 256 await UUID.get_or(uuid4()) # some-uuid 257 ``` 258 """ 259 260 __slots__ = ("_inner", "_lock") 261 262 def __init__(self) -> None: 263 self._lock: asyncio.Lock | None = None 264 self._inner: T | None = None 265 266 @property 267 def is_set(self) -> bool: 268 """Whether this inner value has ben initialized or not.""" 269 return self._inner is not None 270 271 def get(self) -> Option[T]: 272 """Gets the stored value. `Some(None)` is returned if nothing is stored. 273 274 This method will never block. 275 """ 276 return _option.Some(self._inner) if self.is_set else _option.NOTHING # pyright: ignore 277 278 @macros.unsafe 279 def get_unchecked(self) -> T: 280 """Get the contained value without checking if it was initialized. 281 282 Example 283 ------- 284 ```py 285 cell = AsyncOnce[float]() 286 inner = cell.get_unchecked() # Undefined Behavior!! 287 288 # Initialize it first. 289 cell.get_or(math.sqrt(2.0)) 290 291 # At this point of the program, 292 # it is guaranteed that the value is initialized. 293 inner = cell.get_unchecked() 294 ``` 295 """ 296 # SAFETY: The caller guarantees that the value is initialized. 297 return self.get().unwrap_unchecked() 298 299 async def set(self, v: T) -> _result.Result[None, T]: 300 """Set the const value if its not set. returning `T` if its already set. 301 302 if another thread is trying to initialize the value, The value is guaranteed to be set, 303 just not necessarily the one provided. 304 305 Example 306 -------- 307 ```py 308 flag = AsyncOnce[bool]() 309 # flag is empty. 310 assert await flag.get_or(True) is True. 311 # flag is not empty, so it returns the value we set first. 312 assert await flag.set(False) == Err(True) 313 ``` 314 315 Returns 316 ------- 317 `sain.Result[None, T]` 318 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 319 """ 320 if self._inner is not None: 321 return _result.Err(self._inner) 322 323 self._inner = await self.get_or(v) 324 self._lock = None 325 return _result.Ok(None) 326 327 def clear(self) -> None: 328 """Clear the inner value, Setting it to `None`.""" 329 self._lock = None 330 self._inner = None 331 332 async def get_or(self, init: T) -> T: 333 """Gets the contents of the cell, initializing it with `init` if the cell 334 was empty. 335 336 Many threads may call `get_or` concurrently with different 337 initializing functions, but it is guaranteed that only one function 338 will be executed. 339 340 Examples 341 -------- 342 ```py 343 from sain.sync import AsyncOnce 344 345 cell = AsyncOnce[int]() 346 value = await cell.get_or(92) 347 assert value == 92 348 349 value = await cell.get_or(0) 350 assert value == 92 351 ``` 352 """ 353 # If the value is not empty we return it immediately. 354 if self._inner is not None: 355 return self._inner 356 357 if self._lock is None: 358 self._lock = asyncio.Lock() 359 360 async with self._lock: 361 self._inner = init 362 return init 363 364 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 365 """Gets the contents of the cell, initializing it with `f` if the cell 366 was empty. 367 368 Many threads may call `get_or_with` concurrently with different 369 initializing functions, but it is guaranteed that only one function 370 will be executed. 371 372 Examples 373 -------- 374 ```py 375 from sain.sync import AsyncOnce 376 377 cell = AsyncOnce[int]() 378 value = await cell.get_or_with(lambda: 92) 379 assert value == 92 380 381 value = await cell.get_or_init(lambda: 0) 382 assert value == 92 383 ``` 384 """ 385 # If the value is not empty we return it immediately. 386 if self._inner is not None: 387 return self._inner 388 389 if self._lock is None: 390 self._lock = asyncio.Lock() 391 392 async with self._lock: 393 v = f() 394 self._inner = v 395 return v 396 397 def __repr__(self) -> str: 398 if self._inner is not None: 399 return f"AsyncOnce(value: {self._inner})" 400 return "<async_uninit>" 401 402 __str__ = __repr__ 403 404 def __eq__(self, __value: object) -> bool: 405 if isinstance(__value, AsyncOnce): 406 return self._inner == __value._inner # pyright: ignore 407 408 return NotImplemented 409 410 def __ne__(self, __value: object) -> bool: 411 return not self.__eq__(__value) 412 413 def __bool__(self) -> bool: 414 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
266 @property 267 def is_set(self) -> bool: 268 """Whether this inner value has ben initialized or not.""" 269 return self._inner is not None
Whether this inner value has ben initialized or not.
271 def get(self) -> Option[T]: 272 """Gets the stored value. `Some(None)` is returned if nothing is stored. 273 274 This method will never block. 275 """ 276 return _option.Some(self._inner) if self.is_set else _option.NOTHING # pyright: ignore
Gets the stored value. Some(None)
is returned if nothing is stored.
This method will never block.
278 @macros.unsafe 279 def get_unchecked(self) -> T: 280 """Get the contained value without checking if it was initialized. 281 282 Example 283 ------- 284 ```py 285 cell = AsyncOnce[float]() 286 inner = cell.get_unchecked() # Undefined Behavior!! 287 288 # Initialize it first. 289 cell.get_or(math.sqrt(2.0)) 290 291 # At this point of the program, 292 # it is guaranteed that the value is initialized. 293 inner = cell.get_unchecked() 294 ``` 295 """ 296 # SAFETY: The caller guarantees that the value is initialized. 297 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.
299 async def set(self, v: T) -> _result.Result[None, T]: 300 """Set the const value if its not set. returning `T` if its already set. 301 302 if another thread is trying to initialize the value, The value is guaranteed to be set, 303 just not necessarily the one provided. 304 305 Example 306 -------- 307 ```py 308 flag = AsyncOnce[bool]() 309 # flag is empty. 310 assert await flag.get_or(True) is True. 311 # flag is not empty, so it returns the value we set first. 312 assert await flag.set(False) == Err(True) 313 ``` 314 315 Returns 316 ------- 317 `sain.Result[None, T]` 318 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 319 """ 320 if self._inner is not None: 321 return _result.Err(self._inner) 322 323 self._inner = await self.get_or(v) 324 self._lock = None 325 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.
327 def clear(self) -> None: 328 """Clear the inner value, Setting it to `None`.""" 329 self._lock = None 330 self._inner = None
Clear the inner value, Setting it to None
.
332 async def get_or(self, init: T) -> T: 333 """Gets the contents of the cell, initializing it with `init` if the cell 334 was empty. 335 336 Many threads may call `get_or` concurrently with different 337 initializing functions, but it is guaranteed that only one function 338 will be executed. 339 340 Examples 341 -------- 342 ```py 343 from sain.sync import AsyncOnce 344 345 cell = AsyncOnce[int]() 346 value = await cell.get_or(92) 347 assert value == 92 348 349 value = await cell.get_or(0) 350 assert value == 92 351 ``` 352 """ 353 # If the value is not empty we return it immediately. 354 if self._inner is not None: 355 return self._inner 356 357 if self._lock is None: 358 self._lock = asyncio.Lock() 359 360 async with self._lock: 361 self._inner = init 362 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
364 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 365 """Gets the contents of the cell, initializing it with `f` if the cell 366 was empty. 367 368 Many threads may call `get_or_with` concurrently with different 369 initializing functions, but it is guaranteed that only one function 370 will be executed. 371 372 Examples 373 -------- 374 ```py 375 from sain.sync import AsyncOnce 376 377 cell = AsyncOnce[int]() 378 value = await cell.get_or_with(lambda: 92) 379 assert value == 92 380 381 value = await cell.get_or_init(lambda: 0) 382 assert value == 92 383 ``` 384 """ 385 # If the value is not empty we return it immediately. 386 if self._inner is not None: 387 return self._inner 388 389 if self._lock is None: 390 self._lock = asyncio.Lock() 391 392 async with self._lock: 393 v = f() 394 self._inner = v 395 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