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
@typing.final
class Lazy(typing.Generic[~T]):
 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.
Lazy(f: collections.abc.Callable[[], ~T])
72    def __init__(self, f: collections.Callable[[], T]) -> None:
73        self.__inner: T | collections.Callable[[], T] = f
74        self.__lock = threading.Lock()
def get(self) -> ~T:
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.
@typing.final
class Once(typing.Generic[~T]):
 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
is_set: bool
83    @property
84    def is_set(self) -> bool:
85        return self._inner is not None
def get(self) -> sain.Some[~T]:
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.

@macros.unsafe
def get_unchecked(self) -> ~T:
 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.

def set(self, v: ~T) -> Union[sain.Ok[NoneType], sain.Err[~T]]:
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 returns Ok(None) if it was empty. otherwise Err(T) if it was full.
def clear(self) -> None:
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.

def get_or(self, init: ~T) -> ~T:
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
   
def get_or_with(self, f: collections.abc.Callable[..., ~T]) -> ~T:
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
@typing.final
class LazyFuture(typing.Generic[~T]):
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.
LazyFuture( f: collections.abc.Callable[[], collections.abc.Coroutine[typing.Any, typing.Any, ~T]])
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()
@macros.unsafe
async def get(self) -> ~T:
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.

@typing.final
class AsyncOnce(typing.Generic[~T]):
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
is_set: bool
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.

def get(self) -> sain.Some[~T]:
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.

@macros.unsafe
def get_unchecked(self) -> ~T:
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.

async def set(self, v: ~T) -> Union[sain.Ok[NoneType], sain.Err[~T]]:
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 returns Ok(None) if it was empty. otherwise Err(T) if it was full.
def clear(self) -> None:
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.

async def get_or(self, init: ~T) -> ~T:
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
async def get_or_with(self, f: collections.abc.Callable[..., ~T]) -> ~T:
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