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 return f"Lazy(value: {self.__inner!r})" 108 109 __str__ = __repr__
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 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 __bool__(self) -> bool: 224 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
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 without knowing the output 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
112@typing.final 113class LazyFuture(typing.Generic[T]): 114 """A thread-safe value that gets lazily initialized asynchronously at first access. 115 116 This type is thread-safe and may be called from multiple threads since this value 117 can be initialized from multiple threads, however, any calls to `Lazy.get` will block 118 if another thread is initializing it. 119 120 Example 121 ------- 122 ```py 123 # some expensive call that returns a `str` 124 async def fetch_expensive_string(filtered: bool) -> str: 125 return "hehehe" if filtered else "whahaha" 126 127 STRING: LazyFuture[str] = LazyFuture(lambda: expensive_string(True)) 128 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 129 print(await STRING.get()) # The string is retrieved from the lazy lock. 130 ``` 131 """ 132 133 __slots__ = ("__inner", "__lock") 134 135 def __init__( 136 self, 137 f: collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]], 138 ) -> None: 139 self.__inner: ( 140 T 141 | collections.Callable[[], collections.Coroutine[typing.Any, typing.Any, T]] 142 ) = f 143 self.__lock: asyncio.Lock | None = asyncio.Lock() 144 145 async def get(self) -> T: 146 """Get the value if it was initialized, otherwise initialize it and return it. 147 148 Example 149 ------- 150 ```py 151 # some expensive call that returns a `str` 152 async def fetch_expensive_string(filtered: bool) -> str: 153 return "hehehe" if filtered else "whahaha" 154 155 STRING: LazyFuture[str] = LazyFuture(lambda: fetch_expensive_string(True)) 156 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 157 print(await STRING.get()) # The string is retrieved from the lazy lock. 158 ``` 159 """ 160 if not callable(self.__inner): 161 # value is already initialized, no need to make a call. 162 return self.__inner 163 164 if self.__lock is None: 165 self.__lock = asyncio.Lock() 166 167 async with self.__lock: 168 # calling self.__inner will make self.__inner type T 169 v = await self.__inner() # pyright: ignore 170 self.__inner = v 171 return v # pyright: ignore 172 173 def __repr__(self) -> str: 174 return f"LazyFuture(value: {self.__inner!r})" 175 176 __str__ = __repr__
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.
145 async def get(self) -> T: 146 """Get the value if it was initialized, otherwise initialize it and return it. 147 148 Example 149 ------- 150 ```py 151 # some expensive call that returns a `str` 152 async def fetch_expensive_string(filtered: bool) -> str: 153 return "hehehe" if filtered else "whahaha" 154 155 STRING: LazyFuture[str] = LazyFuture(lambda: fetch_expensive_string(True)) 156 print(await STRING.get()) # The string is built, stored in the lazy lock and returned. 157 print(await STRING.get()) # The string is retrieved from the lazy lock. 158 ``` 159 """ 160 if not callable(self.__inner): 161 # value is already initialized, no need to make a call. 162 return self.__inner 163 164 if self.__lock is None: 165 self.__lock = asyncio.Lock() 166 167 async with self.__lock: 168 # calling self.__inner will make self.__inner type T 169 v = await self.__inner() # pyright: ignore 170 self.__inner = v 171 return v # pyright: ignore
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: fetch_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.
227@typing.final 228class AsyncOnce(typing.Generic[T]): 229 """A synchronization primitive which can be written to only once. 230 231 This is an `async` version of `Once`. 232 233 Example 234 ------- 235 ```py 236 from sain.once import Once 237 from uuid import uuid4, UUID 238 239 # A global uuid 240 UUID: AsyncOnce[UUID] = AsyncOnce() 241 242 async def start() -> None: 243 assert UUID.get().is_none() 244 # First initialization. 245 await UUID.get_or(uuid4()) # some-uuid 246 # Won't set, returns the same uuid that got initialized first. 247 await UUID.get_or(uuid4()) # some-uuid 248 ``` 249 """ 250 251 __slots__ = ("_inner", "_lock") 252 253 def __init__(self) -> None: 254 self._lock: asyncio.Lock | None = None 255 self._inner: T | None = None 256 257 @property 258 def is_set(self) -> bool: 259 """Whether this inner value has ben initialized or not.""" 260 return self._inner is not None 261 262 def get(self) -> Option[T]: 263 """Gets the stored value. `Some(None)` is returned if nothing is stored. 264 265 This method will never block. 266 """ 267 return _option.Some(self._inner) if self.is_set else _option.NOTHING 268 269 @macros.unsafe 270 def get_unchecked(self) -> T: 271 """Get the contained value without checking if it was initialized. 272 273 Example 274 ------- 275 ```py 276 cell = AsyncOnce[float]() 277 inner = cell.get_unchecked() # Undefined Behavior!! 278 279 # Initialize it first. 280 cell.get_or(math.sqrt(2.0)) 281 282 # At this point of the program, 283 # it is guaranteed that the value is initialized. 284 inner = cell.get_unchecked() 285 ``` 286 """ 287 # SAFETY: The caller guarantees that the value is initialized. 288 return self.get().unwrap_unchecked() 289 290 async def set(self, v: T) -> _result.Result[None, T]: 291 """Set the const value if its not set. returning `T` if its already set. 292 293 if another thread is trying to initialize the value, The value is guaranteed to be set, 294 just not necessarily the one provided. 295 296 Example 297 -------- 298 ```py 299 flag = AsyncOnce[bool]() 300 # flag is empty. 301 assert await flag.get_or(True) is True. 302 # flag is not empty, so it returns the value we set first. 303 assert await flag.set(False) == Err(True) 304 ``` 305 306 Returns 307 ------- 308 `sain.Result[None, T]` 309 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 310 """ 311 if self._inner is not None: 312 return _result.Err(self._inner) 313 314 self._inner = await self.get_or(v) 315 self._lock = None 316 return _result.Ok(None) 317 318 def clear(self) -> None: 319 """Clear the inner value, Setting it to `None`.""" 320 self._lock = None 321 self._inner = None 322 323 async def get_or(self, init: T) -> T: 324 """Gets the contents of the cell, initializing it with `init` if the cell 325 was empty. 326 327 Many threads may call `get_or` concurrently with different 328 initializing functions, but it is guaranteed that only one function 329 will be executed. 330 331 Examples 332 -------- 333 ```py 334 from sain.sync import AsyncOnce 335 336 cell = AsyncOnce[int]() 337 value = await cell.get_or(92) 338 assert value == 92 339 340 value = await cell.get_or(0) 341 assert value == 92 342 ``` 343 """ 344 # If the value is not empty we return it immediately. 345 if self._inner is not None: 346 return self._inner 347 348 if self._lock is None: 349 self._lock = asyncio.Lock() 350 351 async with self._lock: 352 self._inner = init 353 return init 354 355 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 356 """Gets the contents of the cell, initializing it with `f` if the cell 357 was empty. 358 359 Many threads may call `get_or_with` concurrently with different 360 initializing functions, but it is guaranteed that only one function 361 will be executed. 362 363 Examples 364 -------- 365 ```py 366 from sain.sync import AsyncOnce 367 368 cell = AsyncOnce[int]() 369 value = await cell.get_or_with(lambda: 92) 370 assert value == 92 371 372 value = await cell.get_or_init(lambda: 0) 373 assert value == 92 374 ``` 375 """ 376 # If the value is not empty we return it immediately. 377 if self._inner is not None: 378 return self._inner 379 380 if self._lock is None: 381 self._lock = asyncio.Lock() 382 383 async with self._lock: 384 v = f() 385 self._inner = v 386 return v 387 388 def __repr__(self) -> str: 389 if self._inner is not None: 390 return f"AsyncOnce(value: {self._inner})" 391 return "<async_uninit>" 392 393 __str__ = __repr__ 394 395 def __bool__(self) -> bool: 396 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
257 @property 258 def is_set(self) -> bool: 259 """Whether this inner value has ben initialized or not.""" 260 return self._inner is not None
Whether this inner value has ben initialized or not.
262 def get(self) -> Option[T]: 263 """Gets the stored value. `Some(None)` is returned if nothing is stored. 264 265 This method will never block. 266 """ 267 return _option.Some(self._inner) if self.is_set else _option.NOTHING
Gets the stored value. Some(None)
is returned if nothing is stored.
This method will never block.
269 @macros.unsafe 270 def get_unchecked(self) -> T: 271 """Get the contained value without checking if it was initialized. 272 273 Example 274 ------- 275 ```py 276 cell = AsyncOnce[float]() 277 inner = cell.get_unchecked() # Undefined Behavior!! 278 279 # Initialize it first. 280 cell.get_or(math.sqrt(2.0)) 281 282 # At this point of the program, 283 # it is guaranteed that the value is initialized. 284 inner = cell.get_unchecked() 285 ``` 286 """ 287 # SAFETY: The caller guarantees that the value is initialized. 288 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 without knowing the output is considered undefined behavior.
290 async def set(self, v: T) -> _result.Result[None, T]: 291 """Set the const value if its not set. returning `T` if its already set. 292 293 if another thread is trying to initialize the value, The value is guaranteed to be set, 294 just not necessarily the one provided. 295 296 Example 297 -------- 298 ```py 299 flag = AsyncOnce[bool]() 300 # flag is empty. 301 assert await flag.get_or(True) is True. 302 # flag is not empty, so it returns the value we set first. 303 assert await flag.set(False) == Err(True) 304 ``` 305 306 Returns 307 ------- 308 `sain.Result[None, T]` 309 This cell returns `Ok(None)` if it was empty. otherwise `Err(T)` if it was full. 310 """ 311 if self._inner is not None: 312 return _result.Err(self._inner) 313 314 self._inner = await self.get_or(v) 315 self._lock = None 316 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.
318 def clear(self) -> None: 319 """Clear the inner value, Setting it to `None`.""" 320 self._lock = None 321 self._inner = None
Clear the inner value, Setting it to None
.
323 async def get_or(self, init: T) -> T: 324 """Gets the contents of the cell, initializing it with `init` if the cell 325 was empty. 326 327 Many threads may call `get_or` concurrently with different 328 initializing functions, but it is guaranteed that only one function 329 will be executed. 330 331 Examples 332 -------- 333 ```py 334 from sain.sync import AsyncOnce 335 336 cell = AsyncOnce[int]() 337 value = await cell.get_or(92) 338 assert value == 92 339 340 value = await cell.get_or(0) 341 assert value == 92 342 ``` 343 """ 344 # If the value is not empty we return it immediately. 345 if self._inner is not None: 346 return self._inner 347 348 if self._lock is None: 349 self._lock = asyncio.Lock() 350 351 async with self._lock: 352 self._inner = init 353 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
355 async def get_or_with(self, f: collections.Callable[..., T]) -> T: 356 """Gets the contents of the cell, initializing it with `f` if the cell 357 was empty. 358 359 Many threads may call `get_or_with` concurrently with different 360 initializing functions, but it is guaranteed that only one function 361 will be executed. 362 363 Examples 364 -------- 365 ```py 366 from sain.sync import AsyncOnce 367 368 cell = AsyncOnce[int]() 369 value = await cell.get_or_with(lambda: 92) 370 assert value == 92 371 372 value = await cell.get_or_init(lambda: 0) 373 assert value == 92 374 ``` 375 """ 376 # If the value is not empty we return it immediately. 377 if self._inner is not None: 378 return self._inner 379 380 if self._lock is None: 381 self._lock = asyncio.Lock() 382 383 async with self._lock: 384 v = f() 385 self._inner = v 386 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