# atomics
This library implements a wrapper around the lower level
[patomic](https://github.com/doodspav/patomic) C library (which is provided as
part of this library through the `build_patomic` command in `setup.py`).
It exposes hardware level lock-free (and address-free) atomic operations on a
memory buffer, either internally allocated or externally provided, via a set of
atomic classes.
The operations in these classes are both thread-safe and process-safe,
meaning that they can be used on a shared memory buffer for interprocess
communication (including with other languages such as C/C++).
## Table of Contents
<!--ts-->
* [Installing](#installing)
* [Examples](#examples)
* [Incorrect](#incorrect)
* [Multi-Threading](#multi-threading)
* [Multi-Processing](#multi-processing)
* [Docs](#docs)
* [Types](#types)
* [Construction](#construction)
* [Lifetime](#lifetime)
* [Contract](#contract)
* [Alignment](#alignment)
* [Properties](#properties)
* [Operations](#operations)
* [Special Methods](#special-methods)
* [Memory Order](#memory-order)
* [Exceptions](#exceptions)
* [Building](#building)
* [Future Thoughts](#future-thoughts)
* [Contributing](#contributing)
<!--te-->
## Installing
Linux/MacOS:
```shell
$ python3 -m pip install atomics
```
Windows:
```shell
$ py -m pip install atomics
```
This library requires Python3.6+, and has a dependency on the `cffi` library.
While the code here has no dependency on any implementation specific features,
the `cffi` library functions used are likely to not work outside of CPython and
PyPy.
Binaries are provided for the following platforms:
- Windows `[x86, amd64]`
- MacOSX `[x86_64, universal2]`
- Linux `[i686, x86_64, aarch64, ppc64le, s390x]` `[manylinux2014, musllinux_1_1]`
- Linux `[i686, x86_64]` `[manylinux1]`
If you are on one of these platforms and `pip` tries to build from source or
fails to install, make sure that you have the latest version of `pip` installed.
This can be done like so:
Linux/MacOS:
```shell
$ python3 -m pip install --upgrade pip
```
Windows:
```shell
$ py -m pip install --upgrade pip
```
If you need to build from source, check out the [Building](#building) section
as there are additional requirements for that.
## Examples
### Incorrect
The following example has a data race (`a`is modified from multiple threads).
The program is not correct, and `a`'s value will not equal `total` at the end.
```python
from threading import Thread
a = 0
def fn(n: int) -> None:
global a
for _ in range(n):
a += 1
if __name__ == "__main__":
# setup
total = 10_000_000
# run threads to completion
t1 = Thread(target=fn, args=(total // 2,))
t2 = Thread(target=fn, args=(total // 2,))
t1.start(), t2.start()
t1.join(), t2.join()
# print results
print(f"a[{a}] != total[{total}]")
```
### Multi-Threading
This example implements the previous example but `a` is now an `AtomicInt` which
can be safely modified from multiple threads (as opposed to `int` which can't).
The program is correct, and `a` will equal `total` at the end.
```python
import atomics
from threading import Thread
def fn(ai: atomics.INTEGRAL, n: int) -> None:
for _ in range(n):
ai.inc()
if __name__ == "__main__":
# setup
a = atomics.atomic(width=4, atype=atomics.INT)
total = 10_000
# run threads to completion
t1 = Thread(target=fn, args=(a, total // 2))
t2 = Thread(target=fn, args=(a, total // 2))
t1.start(), t2.start()
t1.join(), t2.join()
# print results
print(f"a[{a.load()}] == total[{total}]")
```
### Multi-Processing
This example is the counterpart to the above correct code, but using processes
to demonstrate that atomic operations are also safe across processes. This
program is also correct, and `a` will equal `total` at the end. It is also how
one might communicate with processes written in other languages such as C/C++.
```python
import atomics
from multiprocessing import Process, shared_memory
def fn(shmem_name: str, width: int, n: int) -> None:
shmem = shared_memory.SharedMemory(name=shmem_name)
buf = shmem.buf[:width]
with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:
for _ in range(n):
a.inc()
del buf
shmem.close()
if __name__ == "__main__":
# setup
width = 4
shmem = shared_memory.SharedMemory(create=True, size=width)
buf = shmem.buf[:width]
total = 10_000
# run processes to completion
p1 = Process(target=fn, args=(shmem.name, width, total // 2))
p2 = Process(target=fn, args=(shmem.name, width, total // 2))
p1.start(), p2.start()
p1.join(), p2.join()
# print results and cleanup
with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:
print(f"a[{a.load()}] == total[{total}]")
del buf
shmem.close()
shmem.unlink()
```
**NOTE:** Although `shared_memory` is showcased here, `atomicview` accepts any
type that supports the buffer protocol as its buffer argument, so other sources
of shared memory such as `mmap` could be used instead.
## Docs
### Types
The following helper (abstract-ish base) types are available in `atomics`:
- [`ANY`, `INTEGRAL`, `BYTES`, `INT`, `UINT`]
This library provides the following `Atomic` classes in `atomics.base`:
- `Atomic --- ANY`
- `AtomicIntegral --- INTEGRAL`
- `AtomicBytes --- BYTES`
- `AtomicInt --- INT`
- `AtomicUint --- UINT`
These `Atomic` classes are constructable on their own, but it is strongly
suggested using the `atomic()` function to construct them. Each class
corresponds to one of the above helper types (as indicated).
This library also provides `Atomic*View` (in `atomics.view`) and
`Atomic*ViewContext` (in `atomics.ctx`) counterparts to the `Atomic*` classes,
corresponding to the same helper types.
The latter of the two sets of classes can be constructed manually, although it
is strongly suggested using the `atomicview()` function to construct them. The
former set of classes cannot be constructed manually with the available types,
and should only be obtained by called `.__enter__()` on a corresponding
`Atomic*ViewContext` object.
Even though you should never need to directly use these classes (apart from the
helper types), they are provided to be used in type hinting. The inheritance
hierarchies are detailed in the [ARCHITECTURE.md](ARCHITECTURE.md) file
(available on GitHub).
### Construction
This library provides the functions `atomic` and `atomicview`, along with the
types `BYTES`, `INT`, and `UINT` (as well as `ANY` and `INTEGRAL`) to construct
atomic objects like so:
```python
import atomics
a = atomics.atomic(width=4, atype=atomics.INT)
print(a) # AtomicInt(value=0, width=4, readonly=False, signed=True)
buf = bytearray(2)
with atomics.atomicview(buffer=buf, atype=atomics.BYTES) as a:
print(a) # AtomicBytesView(value=b'\x00\x00', width=2, readonly=True)
```
You should only need to construct objects with an `atype` of `BYTES`, `INT`, or
`UINT`. Using an `atype` of `ANY` or `INTGERAL` will require additional kwargs,
and an `atype` of `ANY` will result in an object that doesn't actually expose
any atomic operations (only properties, explained in sections further on).
The `atomic()` function returns a corresponding `Atomic*` object.
The `atomicview()` function returns a corresponding `Atomic*ViewContext` object.
You can use this context object in a `with` statement to obtain an `Atomic*View`
object. The `buffer` parameter may be any object that supports the buffer
protocol.
Construction can raise `UnsupportedWidthException` and `AlignmentError`.
**NOTE:** the `width` property of `Atomic*View` objects is derived from the
buffer's length as if it were contiguous. It is equivalent to calling
`memoryview(buf).nbytes`.
### Lifetime
Objects of `Atomic*` classes (i.e. objects returned by the `atomic()` function)
have a self-contained buffer which is automatically freed. They can be passed
around and stored liked regular variables, and there is nothing special about
their lifetime.
Objects of `Atomic*ViewContext` classes (i.e. objects returned by the
`atomicview()` function) and `Atomic*View` objects obtained from said objects
have a much stricter usage contract.
#### Contract
The buffer used to construct an `Atomic*ViewContext` object (either directly or
through `atomicview()`) **MUST NOT** be invalidated until `.release()` is
called. This is aided by the fact that `.release()` is called automatically
in `.__exit__(...)` and `.__del__()`. As long as you immediately use the context
object in a `with` statement, and **DO NOT** invalidate the buffer inside that
`with` scope, you will always be safe.
The protections implemented are shown in this example:
```python
import atomics
buf = bytearray(4)
ctx = atomics.atomicview(buffer=buf, atype=atomics.INT)
# ctx.release() here will cause ctx.__enter__() to raise:
# ValueError("Cannot open context after calling 'release'.")
with ctx as a: # this calls ctx.__enter__()
# ctx.release() here will raise:
# ValueError("Cannot call 'release' while context is open.")
# ctx.__enter__() here will raise:
# ValueError("Cannot open context multiple times.")
print(a.load()) # ok
# ctx.__exit__(...) now called
# we can safely invalidate object 'buf' now
# ctx.__enter__() will raise:
# ValueError("Cannot open context after calling 'release'.")
# accessing object 'a' in any way will also raise an exception
```
Furthermore, in CPython, all built-in types supporting the buffer protocol will
throw a `BufferError` exception if you try to invalidate them while they're in
use (i.e. before calling `.release()`).
As a last resort, if you absolutely must invalidate the buffer inside the `with`
context (where you can't call `.release()`), you may call `.__exit__(...)`
manually on the `Atomic*ViewContext` object. This is to force explicitness
about something considered to be bad practice and dangerous.
Where it's allowed, `.release()` may be called multiple times with no
ill-effects. This also applies to `.__exit__(...)`, which has no restrictions
on where it can be called.
### Alignment
Different platforms may each have their own alignment requirements for atomic
operations of given widths. This library provides the `Alignment` class in
`atomics` to ensure that a given buffer meets these requirements.
```python
from atomics import Alignment
buf = bytearray(8)
align = Alignment(len(buf))
assert align.is_valid(buf)
```
If an atomic class is constructed from a misaligned buffer, the constructor will
raise `AlignmentError`.
By default, `.is_valid` calls `.is_valid_recommended`. The class `Alignment`
also exposes `.is_valid_minimum`. Currently, no atomic class makes use of the
minimum alignment, so checking for it is pointless. Support for it will be
added in a future release.
### Properties
All `Atomic*` and `Atomic*View` classes have the following properties:
- `width`: width in bytes of the underlying buffer (as if it were contiguous)
- `readonly`: whether the object supports modifying operations
- `ops_supported`: a sorted list of `OpType` enum values representing which
operations are supported on the object
Integral `Atomic*` and `Atomic*View` classes also have the following property:
- `signed`: whether arithmetic operations are signed or unsigned
In both cases, the behaviour on overflow is defined to wraparound.
### Operations
Base `Atomic` and `AtomicView` objects (corresponding to `ANY`) expose no atomic
operations.
`AtomicBytes` and `AtomicBytesView` objects support the following operations:
- **[base]**: `load`, `store`
- **[xchg]**: `exchange`, `cmpxchg_weak`, `cmpxchg_strong`
- **[bitwise]**: `bit_test`, `bit_compl`, `bit_set`, `bit_reset`
- **[binary]**: `bin_or`, `bin_xor`, `bin_and`, `bin_not`
- **[binary]**: `bin_fetch_or`, `bin_fetch_xor`, `bin_fetch_and`,
`bin_fetch_not`
Integral `Atomic*` and `Atomic*View` classes additionally support the following
operations:
- **[arithmetic]**: `add`, `sub`, `inc`, `dec`, `neg`
- **[arithmetic]**: `fetch_add`, `fetch_sub`, `fetch_inc`, `fetch_dec`,
`fetch_neg`
The usage of (most of) these functions is modelled directly on the C++11
`std::atomic` implementation found
[here](https://en.cppreference.com/w/cpp/atomic/atomic).
#### Compare Exchange (`cmpxchg_*`)
The `cmpxchg_*` functions return `CmpxchgResult`. This has the attributes
`.success: bool` which indicates whether the exchange took place, and
`.expected: T` which holds the original value of the atomic object.
The `cmpxchg_weak` function may fail spuriously, even if `expected` matches
the actual value. It should be used as shown below:
```python
import atomics
def atomic_mul(a: atomics.INTEGRAL, operand: int):
res = atomics.CmpxchgResult(success=False, expected=a.load())
while not res:
desired = res.expected * operand
res = a.cmpxchg_weak(expected=res.expected, desired=desired)
```
In a real implementation of `atomic_mul`, care should be taken to ensure that
`desired` fits in `a` (i.e. `desired.bit_length() < (a.width * 8)`, assuming 8
bits in a byte).
#### Exceptions
All operations can raise `UnsupportedOperationException` (so check
`.ops_supported` if you need to be sure).
Operations `load`, `store`, and `cmpxchg_*` can raise `MemoryOrderError` if
called with an invalid memory order. `MemoryOrder` enum values expose the
functions `is_valid_store_order()`, `is_valid_load_order()`, and
`is_valid_fail_order()` to check with.
### Special Methods
`AtomicBytes` and `AtomicBytesView` implement the `__bytes__` special method.
Integral `Atomic*` and `Atomic*View` classes implement the `__int__` special
method. They intentionally do not implement `__index__`.
There is a notable lack of any classes implementing special methods
corresponding to atomic operations; this is intentional. Assignment in Python is
not available as a special method, and we do not want to encourage people to
use other special methods with this class, lest it lead to them accidentally
using assignment when they meant `.store(...)`.
### Memory Order
The `MemoryOrder` enum class is provided in `atomics`, and the memory orders
are directly copied from C++11's `std::memory_order` documentation found
[here](https://en.cppreference.com/w/cpp/atomic/memory_order), except for
`CONSUME` (which would be pointless to expose in this library).
All operations have a default memory order, `SEQ_CST`. This will enforce
sequential consistency, and essentially make your multi-threaded and/or
multi-processed program be as correct as if it were to run in a single thread.
**IF YOU DO NOT UNDERSTAND THE LINKED DOCUMENTATION, DO NOT USE YOUR OWN
MEMORY ORDERS!!!**
Stick with the defaults to be safe. (And realistically, this is Python, you
won't get a noticeable performance boost from using a more lax memory order).
The following helper functions are provided:
- `.is_valid_store_order()` (for `store` op)
- `.is_valid_load_order()` ( for `load` op)
- `.is_valid_fail_order()` (for the `fail` ordering in `cmpxchg_*` ops)
Passing an invalid memory order to one of these ops will raise
`MemoryOrderError`.
### Exceptions
The following exceptions are available in `atomics.exc`:
- `AlignmentError`
- `MemoryOrderError`
- `UnsupportedWidthException`
- `UnsupportedOperationException`
## Building
**IMPORTANT:** Make sure you have the latest version of `pip` installed.
Using `setup.py`'s `build` or `bdist_wheel` commands will run the
`build_patomic` command (which you can also run directly).
This clones the `patomic` library into a temporary directory, builds it, and
then copies the shared library into `atomics._clib`.
This requires that `git` be installed on your system (a requirement of the
`GitPython` module). You will also need an ANSI/C90 compliant C compiler
(although ideally a more recent compiler should be used). `CMake` is also
required but should be automatically `pip install`'d if not available.
If you absolutely cannot get `build_patomic` to work, go to
[patomic](https://github.com/doodspav/patomic), follow the instructions on
building it (making sure to build the shared library version), and then
copy-paste the shared library file into `atomics._clib` manually.
**NOTE:**
Currently, the library builds a dummy extension in order to trick `setuptools`
into building a non-purepython wheel. If you are ok with a purepython wheel,
then feel free to remove the code for that from `setup.py` (at the bottom).
Otherwise, you will need a C99 compliant C compiler, and probably the
development libraries/headers for whichever version of Python you're using.
## Future Thoughts
- add docstrings
- add tests
- add support for `minimum` alignment
- add support for constructing `Atomic` classes' buffers in shared memory
- add support for passing `Atomic` objects to sub-processes and sub-interpreters
- reimplement in C or Cython for performance gains (preliminary benchmarks
put such implementations at 2x the speed of a raw `int`)
## Contributing
I don't have a guide for contributing yet. This section is here to make the
following two points:
- new operations must first be implemented in `patomic` before this library can
be updated
- new architectures, widths, and existing unsupported operations must be
supported in `patomic` (no change required in this library)
Raw data
{
"_id": null,
"home_page": "https://github.com/doodspav/atomics",
"name": "atomics",
"maintainer": "",
"docs_url": null,
"requires_python": "<4,>=3.6",
"maintainer_email": "",
"keywords": "atomic,atomics,lock-free,lock free",
"author": "doodspav",
"author_email": "doodspav@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/cf/15/317e77646da3b316d49c93b8ea89e57c21b128e918f640c7130e7bff1c9e/atomics-1.0.2.tar.gz",
"platform": "any",
"description": "# atomics\nThis library implements a wrapper around the lower level \n[patomic](https://github.com/doodspav/patomic) C library (which is provided as\npart of this library through the `build_patomic` command in `setup.py`).\n\nIt exposes hardware level lock-free (and address-free) atomic operations on a \nmemory buffer, either internally allocated or externally provided, via a set of\natomic classes.\n\nThe operations in these classes are both thread-safe and process-safe, \nmeaning that they can be used on a shared memory buffer for interprocess \ncommunication (including with other languages such as C/C++).\n\n## Table of Contents\n<!--ts-->\n* [Installing](#installing)\n* [Examples](#examples)\n * [Incorrect](#incorrect)\n * [Multi-Threading](#multi-threading)\n * [Multi-Processing](#multi-processing)\n* [Docs](#docs)\n * [Types](#types)\n * [Construction](#construction)\n * [Lifetime](#lifetime)\n * [Contract](#contract)\n * [Alignment](#alignment)\n * [Properties](#properties)\n * [Operations](#operations)\n * [Special Methods](#special-methods)\n * [Memory Order](#memory-order)\n * [Exceptions](#exceptions)\n* [Building](#building)\n* [Future Thoughts](#future-thoughts)\n* [Contributing](#contributing)\n<!--te-->\n\n## Installing\n\nLinux/MacOS:\n```shell\n$ python3 -m pip install atomics\n```\nWindows:\n```shell\n$ py -m pip install atomics\n```\nThis library requires Python3.6+, and has a dependency on the `cffi` library.\nWhile the code here has no dependency on any implementation specific features,\nthe `cffi` library functions used are likely to not work outside of CPython and \nPyPy.\n\nBinaries are provided for the following platforms:\n- Windows `[x86, amd64]`\n- MacOSX `[x86_64, universal2]`\n- Linux `[i686, x86_64, aarch64, ppc64le, s390x]` `[manylinux2014, musllinux_1_1]`\n- Linux `[i686, x86_64]` `[manylinux1]`\n\nIf you are on one of these platforms and `pip` tries to build from source or\nfails to install, make sure that you have the latest version of `pip` installed.\nThis can be done like so:\n\nLinux/MacOS:\n```shell\n$ python3 -m pip install --upgrade pip\n```\nWindows:\n```shell\n$ py -m pip install --upgrade pip\n```\n\nIf you need to build from source, check out the [Building](#building) section\nas there are additional requirements for that.\n\n## Examples\n\n### Incorrect\nThe following example has a data race (`a`is modified from multiple threads).\nThe program is not correct, and `a`'s value will not equal `total` at the end.\n```python\nfrom threading import Thread\n\n\na = 0\n\n\ndef fn(n: int) -> None:\n global a\n for _ in range(n):\n a += 1\n\n\nif __name__ == \"__main__\":\n # setup\n total = 10_000_000\n # run threads to completion\n t1 = Thread(target=fn, args=(total // 2,))\n t2 = Thread(target=fn, args=(total // 2,))\n t1.start(), t2.start()\n t1.join(), t2.join()\n # print results\n print(f\"a[{a}] != total[{total}]\")\n```\n\n### Multi-Threading\nThis example implements the previous example but `a` is now an `AtomicInt` which\ncan be safely modified from multiple threads (as opposed to `int` which can't).\nThe program is correct, and `a` will equal `total` at the end.\n```python\nimport atomics\nfrom threading import Thread\n\n\ndef fn(ai: atomics.INTEGRAL, n: int) -> None:\n for _ in range(n):\n ai.inc()\n\n\nif __name__ == \"__main__\":\n # setup\n a = atomics.atomic(width=4, atype=atomics.INT)\n total = 10_000\n # run threads to completion\n t1 = Thread(target=fn, args=(a, total // 2))\n t2 = Thread(target=fn, args=(a, total // 2))\n t1.start(), t2.start()\n t1.join(), t2.join()\n # print results\n print(f\"a[{a.load()}] == total[{total}]\")\n```\n\n### Multi-Processing\nThis example is the counterpart to the above correct code, but using processes\nto demonstrate that atomic operations are also safe across processes. This \nprogram is also correct, and `a` will equal `total` at the end. It is also how\none might communicate with processes written in other languages such as C/C++.\n```python\nimport atomics\nfrom multiprocessing import Process, shared_memory\n\n\ndef fn(shmem_name: str, width: int, n: int) -> None:\n shmem = shared_memory.SharedMemory(name=shmem_name)\n buf = shmem.buf[:width]\n with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:\n for _ in range(n):\n a.inc()\n del buf\n shmem.close()\n\n\nif __name__ == \"__main__\":\n # setup\n width = 4\n shmem = shared_memory.SharedMemory(create=True, size=width)\n buf = shmem.buf[:width]\n total = 10_000\n # run processes to completion\n p1 = Process(target=fn, args=(shmem.name, width, total // 2))\n p2 = Process(target=fn, args=(shmem.name, width, total // 2))\n p1.start(), p2.start()\n p1.join(), p2.join()\n # print results and cleanup\n with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:\n print(f\"a[{a.load()}] == total[{total}]\")\n del buf\n shmem.close()\n shmem.unlink()\n```\n**NOTE:** Although `shared_memory` is showcased here, `atomicview` accepts any\ntype that supports the buffer protocol as its buffer argument, so other sources\nof shared memory such as `mmap` could be used instead.\n\n## Docs\n\n### Types\nThe following helper (abstract-ish base) types are available in `atomics`:\n- [`ANY`, `INTEGRAL`, `BYTES`, `INT`, `UINT`]\n\nThis library provides the following `Atomic` classes in `atomics.base`:\n- `Atomic --- ANY`\n- `AtomicIntegral --- INTEGRAL`\n- `AtomicBytes --- BYTES`\n- `AtomicInt --- INT`\n- `AtomicUint --- UINT`\n\nThese `Atomic` classes are constructable on their own, but it is strongly \nsuggested using the `atomic()` function to construct them. Each class \ncorresponds to one of the above helper types (as indicated).\n\nThis library also provides `Atomic*View` (in `atomics.view`) and \n`Atomic*ViewContext` (in `atomics.ctx`) counterparts to the `Atomic*` classes, \ncorresponding to the same helper types. \n\nThe latter of the two sets of classes can be constructed manually, although it\nis strongly suggested using the `atomicview()` function to construct them. The \nformer set of classes cannot be constructed manually with the available types,\nand should only be obtained by called `.__enter__()` on a corresponding\n`Atomic*ViewContext` object.\n\nEven though you should never need to directly use these classes (apart from the\nhelper types), they are provided to be used in type hinting. The inheritance\nhierarchies are detailed in the [ARCHITECTURE.md](ARCHITECTURE.md) file\n(available on GitHub).\n\n### Construction\nThis library provides the functions `atomic` and `atomicview`, along with the \ntypes `BYTES`, `INT`, and `UINT` (as well as `ANY` and `INTEGRAL`) to construct \natomic objects like so:\n```python\nimport atomics\n\na = atomics.atomic(width=4, atype=atomics.INT)\nprint(a) # AtomicInt(value=0, width=4, readonly=False, signed=True)\n\nbuf = bytearray(2)\nwith atomics.atomicview(buffer=buf, atype=atomics.BYTES) as a:\n print(a) # AtomicBytesView(value=b'\\x00\\x00', width=2, readonly=True)\n```\nYou should only need to construct objects with an `atype` of `BYTES`, `INT`, or\n`UINT`. Using an `atype` of `ANY` or `INTGERAL` will require additional kwargs,\nand an `atype` of `ANY` will result in an object that doesn't actually expose\nany atomic operations (only properties, explained in sections further on).\n\nThe `atomic()` function returns a corresponding `Atomic*` object.\n\nThe `atomicview()` function returns a corresponding `Atomic*ViewContext` object.\nYou can use this context object in a `with` statement to obtain an `Atomic*View`\nobject. The `buffer` parameter may be any object that supports the buffer\nprotocol.\n\nConstruction can raise `UnsupportedWidthException` and `AlignmentError`.\n\n**NOTE:** the `width` property of `Atomic*View` objects is derived from the \nbuffer's length as if it were contiguous. It is equivalent to calling\n`memoryview(buf).nbytes`.\n\n### Lifetime\nObjects of `Atomic*` classes (i.e. objects returned by the `atomic()` function)\nhave a self-contained buffer which is automatically freed. They can be passed\naround and stored liked regular variables, and there is nothing special about\ntheir lifetime.\n\nObjects of `Atomic*ViewContext` classes (i.e. objects returned by the\n`atomicview()` function) and `Atomic*View` objects obtained from said objects\nhave a much stricter usage contract.\n\n#### Contract\n\nThe buffer used to construct an `Atomic*ViewContext` object (either directly or\nthrough `atomicview()`) **MUST NOT** be invalidated until `.release()` is \ncalled. This is aided by the fact that `.release()` is called automatically\nin `.__exit__(...)` and `.__del__()`. As long as you immediately use the context\nobject in a `with` statement, and **DO NOT** invalidate the buffer inside that\n`with` scope, you will always be safe.\n\nThe protections implemented are shown in this example:\n```python\nimport atomics\n\n\nbuf = bytearray(4)\nctx = atomics.atomicview(buffer=buf, atype=atomics.INT)\n\n# ctx.release() here will cause ctx.__enter__() to raise:\n# ValueError(\"Cannot open context after calling 'release'.\")\n\nwith ctx as a: # this calls ctx.__enter__()\n # ctx.release() here will raise:\n # ValueError(\"Cannot call 'release' while context is open.\")\n\n # ctx.__enter__() here will raise:\n # ValueError(\"Cannot open context multiple times.\")\n \n print(a.load()) # ok\n\n# ctx.__exit__(...) now called\n# we can safely invalidate object 'buf' now\n\n# ctx.__enter__() will raise:\n# ValueError(\"Cannot open context after calling 'release'.\")\n\n# accessing object 'a' in any way will also raise an exception\n```\n\nFurthermore, in CPython, all built-in types supporting the buffer protocol will\nthrow a `BufferError` exception if you try to invalidate them while they're in\nuse (i.e. before calling `.release()`).\n\nAs a last resort, if you absolutely must invalidate the buffer inside the `with`\ncontext (where you can't call `.release()`), you may call `.__exit__(...)`\nmanually on the `Atomic*ViewContext` object. This is to force explicitness \nabout something considered to be bad practice and dangerous.\n\nWhere it's allowed, `.release()` may be called multiple times with no\nill-effects. This also applies to `.__exit__(...)`, which has no restrictions\non where it can be called.\n\n### Alignment\nDifferent platforms may each have their own alignment requirements for atomic\noperations of given widths. This library provides the `Alignment` class in\n`atomics` to ensure that a given buffer meets these requirements.\n```python\nfrom atomics import Alignment\n\nbuf = bytearray(8)\nalign = Alignment(len(buf))\nassert align.is_valid(buf)\n```\nIf an atomic class is constructed from a misaligned buffer, the constructor will\nraise `AlignmentError`.\n\nBy default, `.is_valid` calls `.is_valid_recommended`. The class `Alignment` \nalso exposes `.is_valid_minimum`. Currently, no atomic class makes use of the\nminimum alignment, so checking for it is pointless. Support for it will be \nadded in a future release.\n\n### Properties\n\nAll `Atomic*` and `Atomic*View` classes have the following properties:\n- `width`: width in bytes of the underlying buffer (as if it were contiguous)\n- `readonly`: whether the object supports modifying operations\n- `ops_supported`: a sorted list of `OpType` enum values representing which \n operations are supported on the object\n\nIntegral `Atomic*` and `Atomic*View` classes also have the following property:\n- `signed`: whether arithmetic operations are signed or unsigned\n\nIn both cases, the behaviour on overflow is defined to wraparound.\n\n### Operations\n\nBase `Atomic` and `AtomicView` objects (corresponding to `ANY`) expose no atomic\noperations.\n\n`AtomicBytes` and `AtomicBytesView` objects support the following operations:\n- **[base]**: `load`, `store`\n- **[xchg]**: `exchange`, `cmpxchg_weak`, `cmpxchg_strong`\n- **[bitwise]**: `bit_test`, `bit_compl`, `bit_set`, `bit_reset`\n- **[binary]**: `bin_or`, `bin_xor`, `bin_and`, `bin_not`\n- **[binary]**: `bin_fetch_or`, `bin_fetch_xor`, `bin_fetch_and`, \n `bin_fetch_not`\n\nIntegral `Atomic*` and `Atomic*View` classes additionally support the following\noperations:\n- **[arithmetic]**: `add`, `sub`, `inc`, `dec`, `neg`\n- **[arithmetic]**: `fetch_add`, `fetch_sub`, `fetch_inc`, `fetch_dec`, \n `fetch_neg`\n\nThe usage of (most of) these functions is modelled directly on the C++11 \n`std::atomic` implementation found \n[here](https://en.cppreference.com/w/cpp/atomic/atomic). \n\n#### Compare Exchange (`cmpxchg_*`)\n\nThe `cmpxchg_*` functions return `CmpxchgResult`. This has the attributes\n`.success: bool` which indicates whether the exchange took place, and \n`.expected: T` which holds the original value of the atomic object. \nThe `cmpxchg_weak` function may fail spuriously, even if `expected` matches\nthe actual value. It should be used as shown below:\n```python\nimport atomics\n\n\ndef atomic_mul(a: atomics.INTEGRAL, operand: int):\n res = atomics.CmpxchgResult(success=False, expected=a.load())\n while not res:\n desired = res.expected * operand\n res = a.cmpxchg_weak(expected=res.expected, desired=desired)\n```\nIn a real implementation of `atomic_mul`, care should be taken to ensure that \n`desired` fits in `a` (i.e. `desired.bit_length() < (a.width * 8)`, assuming 8\nbits in a byte).\n\n#### Exceptions\n\nAll operations can raise `UnsupportedOperationException` (so check \n`.ops_supported` if you need to be sure).\n\nOperations `load`, `store`, and `cmpxchg_*` can raise `MemoryOrderError` if\ncalled with an invalid memory order. `MemoryOrder` enum values expose the\nfunctions `is_valid_store_order()`, `is_valid_load_order()`, and\n`is_valid_fail_order()` to check with.\n\n### Special Methods\n`AtomicBytes` and `AtomicBytesView` implement the `__bytes__` special method.\n\nIntegral `Atomic*` and `Atomic*View` classes implement the `__int__` special\nmethod. They intentionally do not implement `__index__`.\n\nThere is a notable lack of any classes implementing special methods \ncorresponding to atomic operations; this is intentional. Assignment in Python is\nnot available as a special method, and we do not want to encourage people to\nuse other special methods with this class, lest it lead to them accidentally\nusing assignment when they meant `.store(...)`.\n\n### Memory Order\n\nThe `MemoryOrder` enum class is provided in `atomics`, and the memory orders \nare directly copied from C++11's `std::memory_order` documentation found \n[here](https://en.cppreference.com/w/cpp/atomic/memory_order), except for \n`CONSUME` (which would be pointless to expose in this library).\n\nAll operations have a default memory order, `SEQ_CST`. This will enforce \nsequential consistency, and essentially make your multi-threaded and/or \nmulti-processed program be as correct as if it were to run in a single thread.\n\n**IF YOU DO NOT UNDERSTAND THE LINKED DOCUMENTATION, DO NOT USE YOUR OWN\nMEMORY ORDERS!!!**\nStick with the defaults to be safe. (And realistically, this is Python, you \nwon't get a noticeable performance boost from using a more lax memory order).\n\nThe following helper functions are provided:\n- `.is_valid_store_order()` (for `store` op)\n- `.is_valid_load_order()` ( for `load` op)\n- `.is_valid_fail_order()` (for the `fail` ordering in `cmpxchg_*` ops)\n\nPassing an invalid memory order to one of these ops will raise\n`MemoryOrderError`.\n\n### Exceptions\nThe following exceptions are available in `atomics.exc`:\n- `AlignmentError`\n- `MemoryOrderError`\n- `UnsupportedWidthException`\n- `UnsupportedOperationException`\n\n## Building\n\n**IMPORTANT:** Make sure you have the latest version of `pip` installed.\n\nUsing `setup.py`'s `build` or `bdist_wheel` commands will run the \n`build_patomic` command (which you can also run directly).\n\nThis clones the `patomic` library into a temporary directory, builds it, and\nthen copies the shared library into `atomics._clib`.\n\nThis requires that `git` be installed on your system (a requirement of the\n`GitPython` module). You will also need an ANSI/C90 compliant C compiler\n(although ideally a more recent compiler should be used). `CMake` is also \nrequired but should be automatically `pip install`'d if not available.\n\nIf you absolutely cannot get `build_patomic` to work, go to\n[patomic](https://github.com/doodspav/patomic), follow the instructions on\nbuilding it (making sure to build the shared library version), and then\ncopy-paste the shared library file into `atomics._clib` manually.\n\n**NOTE:**\nCurrently, the library builds a dummy extension in order to trick `setuptools`\ninto building a non-purepython wheel. If you are ok with a purepython wheel,\nthen feel free to remove the code for that from `setup.py` (at the bottom). \nOtherwise, you will need a C99 compliant C compiler, and probably the\ndevelopment libraries/headers for whichever version of Python you're using.\n\n## Future Thoughts\n- add docstrings\n- add tests\n- add support for `minimum` alignment\n- add support for constructing `Atomic` classes' buffers in shared memory\n- add support for passing `Atomic` objects to sub-processes and sub-interpreters\n- reimplement in C or Cython for performance gains (preliminary benchmarks\nput such implementations at 2x the speed of a raw `int`)\n\n## Contributing\nI don't have a guide for contributing yet. This section is here to make the \nfollowing two points:\n- new operations must first be implemented in `patomic` before this library can\nbe updated\n- new architectures, widths, and existing unsupported operations must be \nsupported in `patomic` (no change required in this library)\n\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Atomic lock-free primitives",
"version": "1.0.2",
"split_keywords": [
"atomic",
"atomics",
"lock-free",
"lock free"
],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "a6f5b7e6e9ad05270467e6c0b8725dff",
"sha256": "5bba08c8ed9dff560a7ce0285b00d72731700e64cb22ce8f9636c88c6dce4d74"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-macosx_10_9_universal2.whl",
"has_sig": false,
"md5_digest": "a6f5b7e6e9ad05270467e6c0b8725dff",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 73786,
"upload_time": "2021-12-10T07:11:50",
"upload_time_iso_8601": "2021-12-10T07:11:50.181227Z",
"url": "https://files.pythonhosted.org/packages/80/fe/8965740c8fbe8760363c94261ff08db1d1402ff2c44fc7e4189311efdd45/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-macosx_10_9_universal2.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "76b152828a6e043d63d242ab3cfb712d",
"sha256": "947b43a4958ed5c9ecfd21793d849cc2869ef003ff380432e01356e6e5b2e55a"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-macosx_10_9_x86_64.whl",
"has_sig": false,
"md5_digest": "76b152828a6e043d63d242ab3cfb712d",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 72953,
"upload_time": "2021-12-10T07:11:51",
"upload_time_iso_8601": "2021-12-10T07:11:51.736791Z",
"url": "https://files.pythonhosted.org/packages/5a/31/f7419def42c532150e4f49f457cdbf738aef576b6883b03f1e3b7e87c928/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-macosx_10_9_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "5e3ae14afecea5cd07106b58fa7b97d9",
"sha256": "ee035424d0c75d72c2ddc53172ddee37259cbe397c9847868601b509fc8f6ea0"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "5e3ae14afecea5cd07106b58fa7b97d9",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 237269,
"upload_time": "2021-12-10T07:11:53",
"upload_time_iso_8601": "2021-12-10T07:11:53.087956Z",
"url": "https://files.pythonhosted.org/packages/7a/99/6cb9f45707ebee5f23b7869faea8b4a432bd7736c43e8f162bc6c8321d5c/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "77c180ecde14988bdbd24ecb2bb54e24",
"sha256": "a57621b58a881be07c3b0eb3dd34cfcf1a7e9ec84f8f1cffaaa3682a3f6fde65"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
"has_sig": false,
"md5_digest": "77c180ecde14988bdbd24ecb2bb54e24",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 202968,
"upload_time": "2021-12-10T07:11:54",
"upload_time_iso_8601": "2021-12-10T07:11:54.082480Z",
"url": "https://files.pythonhosted.org/packages/00/25/59212d816c647f2752de85ae8fd0e4b4790176380c70f949ebe409f860cd/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "3e1f397f3425a9ba11db7857d4c52cc4",
"sha256": "ac1a677e6fd41200951ad6116f23d27285697da05fb622bf669ab7d80d17d0dc"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",
"has_sig": false,
"md5_digest": "3e1f397f3425a9ba11db7857d4c52cc4",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 219068,
"upload_time": "2021-12-10T07:11:55",
"upload_time_iso_8601": "2021-12-10T07:11:55.020893Z",
"url": "https://files.pythonhosted.org/packages/d8/b8/92a67692288f781ea8250f703a54a2340756b61c32aa72e5e273abb09076/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "e591335ddace03d9d90c44caea4e176c",
"sha256": "d50afc7ca07f59b6cb38aea2d0eb4f7ea4ef6a79184a1f30309699c149a7c2c4"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl",
"has_sig": false,
"md5_digest": "e591335ddace03d9d90c44caea4e176c",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 205296,
"upload_time": "2021-12-10T07:11:56",
"upload_time_iso_8601": "2021-12-10T07:11:56.457792Z",
"url": "https://files.pythonhosted.org/packages/44/83/7c64fef8daf4ac2ae26837f24193cc9f0b2e52f1bed54208c36d969c067a/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "8eef396aaceec6477dfc899ecda09b2a",
"sha256": "6c2ee992777c03ca3530c9f4a2d78a88c65f564a511c9698d42905f57a04344b"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl",
"has_sig": false,
"md5_digest": "8eef396aaceec6477dfc899ecda09b2a",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 203811,
"upload_time": "2021-12-10T07:11:57",
"upload_time_iso_8601": "2021-12-10T07:11:57.998688Z",
"url": "https://files.pythonhosted.org/packages/1c/e4/c0b3d45d7bb35068ef7e7716d6cc2eb48dc102c8564ca1166612af7a9353/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "37448fab9d36d9bfd6fa7ee1b33c1a63",
"sha256": "123df222d052308257de3b86a9f7400322483955b398c7464898ce0ecd48bf3d"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_aarch64.whl",
"has_sig": false,
"md5_digest": "37448fab9d36d9bfd6fa7ee1b33c1a63",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 200738,
"upload_time": "2021-12-10T07:11:59",
"upload_time_iso_8601": "2021-12-10T07:11:59.412462Z",
"url": "https://files.pythonhosted.org/packages/ee/50/134f776bca6bb4732b9273101b8b66d5e2fa0fbf2a18310abb4b25923557/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "01a8ee05018bb7983af6201f170a86f3",
"sha256": "ca823e5dfbcda305e6084ef99f1a4fab88dda3995378212985cfe5f0a9c8dafa"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_i686.whl",
"has_sig": false,
"md5_digest": "01a8ee05018bb7983af6201f170a86f3",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 194041,
"upload_time": "2021-12-10T07:12:00",
"upload_time_iso_8601": "2021-12-10T07:12:00.391456Z",
"url": "https://files.pythonhosted.org/packages/e9/2d/4cc6c215c0ee38f340341377ce340ee6da4a9f9e809a30876e6c2ce881eb/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_i686.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "619e256464a40840079c3332eda0f7d5",
"sha256": "ac852fa341bf7562a809176701af0825f449badfd20a1ff2dd6de84fc45a3160"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_ppc64le.whl",
"has_sig": false,
"md5_digest": "619e256464a40840079c3332eda0f7d5",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 202724,
"upload_time": "2021-12-10T07:12:01",
"upload_time_iso_8601": "2021-12-10T07:12:01.821888Z",
"url": "https://files.pythonhosted.org/packages/11/32/f7bb1e0a3ffb92e7f1788a48c4d600c680fdda002ad1a4fbf0035c4c7f17/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_ppc64le.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "610dbc9d734718507e5525ed30ee07d7",
"sha256": "c9518fc7255103625444af7eed7a0643f5d93c7ea974a063893f3c4db62e6571"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_s390x.whl",
"has_sig": false,
"md5_digest": "610dbc9d734718507e5525ed30ee07d7",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 221240,
"upload_time": "2021-12-10T07:12:03",
"upload_time_iso_8601": "2021-12-10T07:12:03.245171Z",
"url": "https://files.pythonhosted.org/packages/da/63/08edcae0d1334a7523db613f2c4eff1d34aeab14dbd3aeb39f262f5a5bb9/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_s390x.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "9d814a33040080de657c39ef9a9a9304",
"sha256": "58ecf69d4b67d3792d8bf0ae076f109d8c2cb4185c8d8a62cc672a74d80a09cc"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_x86_64.whl",
"has_sig": false,
"md5_digest": "9d814a33040080de657c39ef9a9a9304",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 192958,
"upload_time": "2021-12-10T07:12:04",
"upload_time_iso_8601": "2021-12-10T07:12:04.820984Z",
"url": "https://files.pythonhosted.org/packages/23/28/b72c195f4199899d5aab2230ffbfdc2abf6d73f3810cba948a6187408509/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-musllinux_1_1_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "2ffae0a7a19c994b053b35ee813df89b",
"sha256": "aa4ee4ff8e4b3f445fe0640f3f2ccfaa61ce099d7cc03415d96d2d7ae05b7a0b"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-win32.whl",
"has_sig": false,
"md5_digest": "2ffae0a7a19c994b053b35ee813df89b",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 58369,
"upload_time": "2021-12-10T07:12:05",
"upload_time_iso_8601": "2021-12-10T07:12:05.763733Z",
"url": "https://files.pythonhosted.org/packages/b8/a3/aa503779b1a42aad73518b9fca89581e7fc6c8713585205c0d7fb315b6fc/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-win32.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "6e1d9cc280a7b516b7160c81b3ab32ef",
"sha256": "b0ba7d14a27158425b4b3d6b73d0e9f10bfcf55af2e3fb51a8727e2293ecf338"
},
"downloads": -1,
"filename": "atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-win_amd64.whl",
"has_sig": false,
"md5_digest": "6e1d9cc280a7b516b7160c81b3ab32ef",
"packagetype": "bdist_wheel",
"python_version": "py36.py37.py38.py39.py310.py311",
"requires_python": "<4,>=3.6",
"size": 61971,
"upload_time": "2021-12-10T07:12:06",
"upload_time_iso_8601": "2021-12-10T07:12:06.626903Z",
"url": "https://files.pythonhosted.org/packages/c1/08/c02d9c2cacfe0b254a222eeb1b827929f89de754a3ce69d1d3cc4a3703b4/atomics-1.0.2-py36.py37.py38.py39.py310.py311-none-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "8d179cb19fdc9933e9c54d7e89540b40",
"sha256": "04141e2ed22220ca3faa2057d13f55e4a511e91b5ae8c787fa701e9320217211"
},
"downloads": -1,
"filename": "atomics-1.0.2.tar.gz",
"has_sig": false,
"md5_digest": "8d179cb19fdc9933e9c54d7e89540b40",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.6",
"size": 44187,
"upload_time": "2021-12-10T07:12:07",
"upload_time_iso_8601": "2021-12-10T07:12:07.956288Z",
"url": "https://files.pythonhosted.org/packages/cf/15/317e77646da3b316d49c93b8ea89e57c21b128e918f640c7130e7bff1c9e/atomics-1.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2021-12-10 07:12:07",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "doodspav",
"github_project": "atomics",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "cffi",
"specs": [
[
">=",
"1.10"
]
]
}
],
"lcname": "atomics"
}