..
SPDX-FileCopyrightText: 2024 Ilya Egorov <0x42005e1f@gmail.com>
SPDX-License-Identifier: CC-BY-4.0
=======
culsans
=======
|pypi-dw| |pypi-impl| |pypi-pyv| |pypi-types|
.. |pypi-dw| image:: https://img.shields.io/pypi/dw/culsans
:target: https://pypistats.org/packages/culsans
:alt:
.. |pypi-impl| image:: https://img.shields.io/pypi/implementation/culsans
:target: #features
:alt:
.. |pypi-pyv| image:: https://img.shields.io/pypi/pyversions/culsans
:target: #features
:alt:
.. |pypi-types| image:: https://img.shields.io/pypi/types/culsans
:target: #features
:alt:
Mixed sync-async queue, supposed to be used for communicating between classic
synchronous (threaded) code and asynchronous one, between two asynchronous
codes in different threads, and for any other combination that you want. Based
on the `queue <https://docs.python.org/3/library/queue.html>`_ module. Built on
the `aiologic <https://github.com/x42005e1f/aiologic>`_ package. Inspired by
the `janus <https://github.com/aio-libs/janus>`_ library.
Like `Culsans god <https://en.wikipedia.org/wiki/Culsans>`_, the queue object
from the library has two faces: synchronous and asynchronous interface. Unlike
`Janus library <https://github.com/aio-libs/janus>`_, synchronous interface
supports `eventlet <https://github.com/eventlet/eventlet>`_, `gevent <https://
github.com/gevent/gevent>`_, and `threading <https://docs.python.org/3/library/
threading.html>`_, while asynchronous interface supports `asyncio <https://
docs.python.org/3/library/asyncio.html>`_, `curio <https://github.com/dabeaz/
curio>`_, and `trio <https://github.com/python-trio/trio>`_.
Synchronous is fully compatible with `standard queue <https://docs.python.org/
3/library/queue.html>`_, asynchronous one follows `asyncio queue design
<https://docs.python.org/3/library/asyncio-queue.html>`_.
Installation
============
Install from `PyPI <https://pypi.org/project/culsans/>`_ (stable):
.. code:: console
pip install culsans
Or from `GitHub <https://github.com/x42005e1f/culsans>`_ (latest):
.. code:: console
pip install git+https://github.com/x42005e1f/culsans.git
You can also use other package managers, such as `uv <https://github.com/
astral-sh/uv>`_.
Usage
=====
Three queues are available:
* ``Queue``
* ``LifoQueue``
* ``PriorityQueue``
Each has two properties: ``sync_q`` and ``async_q``.
Use the first to get synchronous interface and the second to get asynchronous
one.
Example
-------
.. code:: python
import anyio
import culsans
def sync_run(sync_q: culsans.SyncQueue[int]) -> None:
for i in range(100):
sync_q.put(i)
else:
sync_q.join()
async def async_run(async_q: culsans.AsyncQueue[int]) -> None:
for i in range(100):
value = await async_q.get()
assert value == i
async_q.task_done()
async def main() -> None:
queue: culsans.Queue[int] = culsans.Queue()
async with anyio.create_task_group() as tasks:
tasks.start_soon(anyio.to_thread.run_sync, sync_run, queue.sync_q)
tasks.start_soon(async_run, queue.async_q)
queue.shutdown()
anyio.run(main)
Extras
------
Both interfaces support some additional features that are not found in the
original queues.
growing & shrinking
^^^^^^^^^^^^^^^^^^^
You can dynamically change the upperbound limit on the number of items that can
be placed in the queue with ``queue.maxsize = N``. If it increases (growing),
the required number of waiting putters will be woken up. If it decreases
(shrinking), items exceeding the new limit will remain in the queue, but all
putters will be blocked until enough items are retrieved from the queue. And if
*maxsize* is less than or equal to zero, all putters will be woken up.
.. code:: python
async with anyio.create_task_group() as tasks:
async_q = culsans.Queue(1).async_q
for i in range(4):
tasks.start_soon(async_q.put, i)
await anyio.sleep(1e-3)
assert async_q.qsize() == 1
async_q.maxsize = 2 # growing
await anyio.sleep(1e-3)
assert async_q.qsize() == 2
async_q.maxsize = 1 # shrinking
await anyio.sleep(1e-3)
assert async_q.qsize() == 2
async_q.get_nowait()
await anyio.sleep(1e-3)
assert async_q.qsize() == 1
async_q.maxsize = 0 # now the queue size is infinite
await anyio.sleep(1e-3)
assert async_q.qsize() == 3
peek() & peek_nowait()
^^^^^^^^^^^^^^^^^^^^^^
If you want to check the first item of the queue, but do not want to remove
that item from the queue, you can use the ``peek()`` and ``peek_nowait()``
methods instead of the ``get()`` and ``get_nowait()`` methods.
.. code:: python
sync_q = culsans.Queue().sync_q
sync_q.put("spam")
assert sync_q.peekable()
assert sync_q.peek() == "spam"
assert sync_q.peek_nowait() == "spam"
assert sync_q.qsize() == 1
These methods can be considered an implementation of partial compatibility with
`gevent queues <https://www.gevent.org/api/gevent.queue.html>`_.
clear()
^^^^^^^
In some scenarios it may be necessary to clear the queue. But it is inefficient
to do this through a loop, and it causes additional difficulties when it is
also necessary to ensure that no new items can be added during the clearing
process. For this purpose, there is the atomic ``clear()`` method that clears
the queue most efficiently.
.. code:: python
async with anyio.create_task_group() as tasks:
async_q = culsans.Queue(3).async_q
for i in range(5):
tasks.start_soon(async_q.put, i)
await anyio.sleep(1e-3)
assert async_q.qsize() == 3
async_q.clear() # clearing
await anyio.sleep(1e-3)
assert async_q.qsize() == 2
assert async_q.get_nowait() == 3
assert async_q.get_nowait() == 4
Roughly equivalent to:
.. code:: python
def clear(queue):
while True:
try:
queue.get_nowait()
except Empty:
break
else:
queue.task_done()
Subclasses
----------
You can create your own queues by inheriting from existing queue classes as if
you were using the queue module. For example, this is how you can create an
unordered queue that contains only unique items:
.. code:: python
from culsans import Queue
class UniqueQueue(Queue):
def _init(self, maxsize):
self.data = set()
def _qsize(self):
return len(self.data)
def _put(self, item):
self.data.add(item)
def _get(self):
return self.data.pop()
_peek = None
def _peekable(self):
return False
def _clear(self):
self.data.clear()
.. code:: python
sync_q = UniqueQueue().sync_q
sync_q.put_nowait(23)
sync_q.put_nowait(42)
sync_q.put_nowait(23)
assert sync_q.qsize() == 2
assert sorted(sync_q.get_nowait() for _ in range(2)) == [23, 42]
All seven of these methods are called in exclusive access mode, so you can
freely create your subclasses without thinking about whether your methods are
thread-safe or not.
Checkpoints
-----------
Sometimes it is useful when each asynchronous call switches execution to the
next task and checks for cancellation and timeouts. For example, if you want to
distribute CPU usage across all tasks.
The culsans library adopts aiologic's checkpoints, but unlike it does not
guarantee that there will only be one per asynchronous call, due to design
specifics.
See the aiologic documentation for details on how to control checkpoints.
Compatibility
=============
The interfaces are compliant with the Python API version 3.13, and the culsans
library itself is fully compatible with the janus library version 2.0.0. If you
are using janus in your application and want to switch to culsans, all you have
to do is replace this:
.. code:: python
import janus
with this:
.. code:: python
import culsans as janus
and everything will work!
Performance
===========
Being built on the aiologic package, the culsans library has speed advantages.
When communication is performed within a single thread using the asynchronous
API, ``culsans.Queue`` is typically 2 times faster than ``janus.Queue``:
+-------------+-------------+-------------+-------------+-------------+
| python | janus | culsans | aiologic | asyncio |
+=============+=============+=============+=============+=============+
| python3.8 | x1.00 | x2.26 | x2.89 | x1.96 |
+-------------+-------------+-------------+-------------+-------------+
| python3.9 | x1.00 | x2.24 | x2.85 | x1.96 |
+-------------+-------------+-------------+-------------+-------------+
| python3.10 | x1.00 | x2.31 | x2.99 | x1.84 |
+-------------+-------------+-------------+-------------+-------------+
| python3.11 | x1.00 | x2.35 | x3.00 | x1.83 |
+-------------+-------------+-------------+-------------+-------------+
| python3.12 | x1.00 | x2.45 | x3.14 | x1.77 |
+-------------+-------------+-------------+-------------+-------------+
| python3.13 | x1.00 | x2.61 | x3.25 | x1.75 |
+-------------+-------------+-------------+-------------+-------------+
| python3.13t | x1.00 | x2.35 | x2.97 | x1.98 |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.8 | x1.00 | x2.76 | x5.48 | x1.88 |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.9 | x1.00 | x2.81 | x5.07 | x1.86 |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.10 | x1.00 | x2.38 | x5.17 | x1.81 |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.11 | x1.00 | x2.48 | x5.29 | x1.88 |
+-------------+-------------+-------------+-------------+-------------+
And when communication is performed within two threads, they are the same:
+-------------+-------------+-------------+-------------+-------------+
| python | janus | culsans | aiologic | asyncio |
+=============+=============+=============+=============+=============+
| python3.8 | x1.00 | +8.70% | x1.51 | +0.65% |
+-------------+-------------+-------------+-------------+-------------+
| python3.9 | x1.00 | +7.60% | +36.58% | -5.69% |
+-------------+-------------+-------------+-------------+-------------+
| python3.10 | x1.00 | +6.38% | +23.84% | -13.91% |
+-------------+-------------+-------------+-------------+-------------+
| python3.11 | x1.00 | +5.86% | +22.55% | -20.12% |
+-------------+-------------+-------------+-------------+-------------+
| python3.12 | x1.00 | +0.29% | +15.12% | -22.31% |
+-------------+-------------+-------------+-------------+-------------+
| python3.13 | x1.00 | +6.00% | +17.36% | -17.74% |
+-------------+-------------+-------------+-------------+-------------+
| python3.13t | x1.00 | +8.49% | +24.35% | -22.25% |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.8 | x1.00 | -0.12% | +12.64% | +0.83% |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.9 | x1.00 | +6.25% | +9.59% | -0.15% |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.10 | x1.00 | +11.37% | +8.19% | +1.10% |
+-------------+-------------+-------------+-------------+-------------+
| pypy3.11 | x1.00 | +7.03% | +10.68% | -2.20% |
+-------------+-------------+-------------+-------------+-------------+
However, on your hardware the performance results may be different, especially
for the PyPy case, which on older hardware may show a tenfold speedup or more
in both tables, so you may find it useful to run benchmarks yourself to measure
actual relative performance.
Documentation
=============
DeepWiki: https://deepwiki.com/x42005e1f/culsans (AI generated)
Communication channels
======================
GitHub Discussions: https://github.com/x42005e1f/culsans/discussions (ideas,
questions)
GitHub Issues: https://github.com/x42005e1f/culsans/issues (bug tracker)
You can also send an email to 0x42005e1f@gmail.com with any feedback.
Support
=======
If you like culsans and want to support its development, please star `its
repository on GitHub <https://github.com/x42005e1f/culsans>`_.
.. image:: https://starchart.cc/x42005e1f/culsans.svg?variant=adaptive
:target: https://starchart.cc/x42005e1f/culsans
License
=======
The culsans library is `REUSE-compliant <https://api.reuse.software/info/
github.com/x42005e1f/culsans>`_ and is offered under multiple licenses:
* All original source code is licensed under `ISC`_.
* All original test code is licensed under `0BSD`_.
* All documentation is licensed under `CC-BY-4.0`_.
* All configuration is licensed under `CC0-1.0`_.
* Some test code borrowed from `python/cpython <https://github.com/python/
cpython>`_ is licensed under `PSF-2.0`_.
For more accurate information, check the individual files.
.. _ISC: https://choosealicense.com/licenses/isc/
.. _0BSD: https://choosealicense.com/licenses/0bsd/
.. _CC-BY-4.0: https://choosealicense.com/licenses/cc-by-4.0/
.. _CC0-1.0: https://choosealicense.com/licenses/cc0-1.0/
.. _PSF-2.0: https://docs.python.org/3/license.html
Raw data
{
"_id": null,
"home_page": null,
"name": "culsans",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "anyio, async, async-await, asyncio, communication, concurrency, eventlet, gevent, greenlet, library, mypy, python, queue, thread-safety, threading, trio",
"author": null,
"author_email": "Ilya Egorov <0x42005e1f@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/90/5d/12e7e16b0caafaa8cca0728dd817204afd1274ddb35531b029b1c5cf7b2a/culsans-0.9.0.tar.gz",
"platform": null,
"description": "..\n SPDX-FileCopyrightText: 2024 Ilya Egorov <0x42005e1f@gmail.com>\n SPDX-License-Identifier: CC-BY-4.0\n\n=======\nculsans\n=======\n\n|pypi-dw| |pypi-impl| |pypi-pyv| |pypi-types|\n\n.. |pypi-dw| image:: https://img.shields.io/pypi/dw/culsans\n :target: https://pypistats.org/packages/culsans\n :alt:\n.. |pypi-impl| image:: https://img.shields.io/pypi/implementation/culsans\n :target: #features\n :alt:\n.. |pypi-pyv| image:: https://img.shields.io/pypi/pyversions/culsans\n :target: #features\n :alt:\n.. |pypi-types| image:: https://img.shields.io/pypi/types/culsans\n :target: #features\n :alt:\n\nMixed sync-async queue, supposed to be used for communicating between classic\nsynchronous (threaded) code and asynchronous one, between two asynchronous\ncodes in different threads, and for any other combination that you want. Based\non the `queue <https://docs.python.org/3/library/queue.html>`_ module. Built on\nthe `aiologic <https://github.com/x42005e1f/aiologic>`_ package. Inspired by\nthe `janus <https://github.com/aio-libs/janus>`_ library.\n\nLike `Culsans god <https://en.wikipedia.org/wiki/Culsans>`_, the queue object\nfrom the library has two faces: synchronous and asynchronous interface. Unlike\n`Janus library <https://github.com/aio-libs/janus>`_, synchronous interface\nsupports `eventlet <https://github.com/eventlet/eventlet>`_, `gevent <https://\ngithub.com/gevent/gevent>`_, and `threading <https://docs.python.org/3/library/\nthreading.html>`_, while asynchronous interface supports `asyncio <https://\ndocs.python.org/3/library/asyncio.html>`_, `curio <https://github.com/dabeaz/\ncurio>`_, and `trio <https://github.com/python-trio/trio>`_.\n\nSynchronous is fully compatible with `standard queue <https://docs.python.org/\n3/library/queue.html>`_, asynchronous one follows `asyncio queue design\n<https://docs.python.org/3/library/asyncio-queue.html>`_.\n\nInstallation\n============\n\nInstall from `PyPI <https://pypi.org/project/culsans/>`_ (stable):\n\n.. code:: console\n\n pip install culsans\n\nOr from `GitHub <https://github.com/x42005e1f/culsans>`_ (latest):\n\n.. code:: console\n\n pip install git+https://github.com/x42005e1f/culsans.git\n\nYou can also use other package managers, such as `uv <https://github.com/\nastral-sh/uv>`_.\n\nUsage\n=====\n\nThree queues are available:\n\n* ``Queue``\n* ``LifoQueue``\n* ``PriorityQueue``\n\nEach has two properties: ``sync_q`` and ``async_q``.\n\nUse the first to get synchronous interface and the second to get asynchronous\none.\n\nExample\n-------\n\n.. code:: python\n\n import anyio\n import culsans\n\n\n def sync_run(sync_q: culsans.SyncQueue[int]) -> None:\n for i in range(100):\n sync_q.put(i)\n else:\n sync_q.join()\n\n\n async def async_run(async_q: culsans.AsyncQueue[int]) -> None:\n for i in range(100):\n value = await async_q.get()\n\n assert value == i\n\n async_q.task_done()\n\n\n async def main() -> None:\n queue: culsans.Queue[int] = culsans.Queue()\n\n async with anyio.create_task_group() as tasks:\n tasks.start_soon(anyio.to_thread.run_sync, sync_run, queue.sync_q)\n tasks.start_soon(async_run, queue.async_q)\n\n queue.shutdown()\n\n\n anyio.run(main)\n\nExtras\n------\n\nBoth interfaces support some additional features that are not found in the\noriginal queues.\n\ngrowing & shrinking\n^^^^^^^^^^^^^^^^^^^\n\nYou can dynamically change the upperbound limit on the number of items that can\nbe placed in the queue with ``queue.maxsize = N``. If it increases (growing),\nthe required number of waiting putters will be woken up. If it decreases\n(shrinking), items exceeding the new limit will remain in the queue, but all\nputters will be blocked until enough items are retrieved from the queue. And if\n*maxsize* is less than or equal to zero, all putters will be woken up.\n\n.. code:: python\n\n async with anyio.create_task_group() as tasks:\n async_q = culsans.Queue(1).async_q\n\n for i in range(4):\n tasks.start_soon(async_q.put, i)\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 1\n\n async_q.maxsize = 2 # growing\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 2\n\n async_q.maxsize = 1 # shrinking\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 2\n\n async_q.get_nowait()\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 1\n\n async_q.maxsize = 0 # now the queue size is infinite\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 3\n\npeek() & peek_nowait()\n^^^^^^^^^^^^^^^^^^^^^^\n\nIf you want to check the first item of the queue, but do not want to remove\nthat item from the queue, you can use the ``peek()`` and ``peek_nowait()``\nmethods instead of the ``get()`` and ``get_nowait()`` methods.\n\n.. code:: python\n\n sync_q = culsans.Queue().sync_q\n\n sync_q.put(\"spam\")\n\n assert sync_q.peekable()\n assert sync_q.peek() == \"spam\"\n assert sync_q.peek_nowait() == \"spam\"\n assert sync_q.qsize() == 1\n\nThese methods can be considered an implementation of partial compatibility with\n`gevent queues <https://www.gevent.org/api/gevent.queue.html>`_.\n\nclear()\n^^^^^^^\n\nIn some scenarios it may be necessary to clear the queue. But it is inefficient\nto do this through a loop, and it causes additional difficulties when it is\nalso necessary to ensure that no new items can be added during the clearing\nprocess. For this purpose, there is the atomic ``clear()`` method that clears\nthe queue most efficiently.\n\n.. code:: python\n\n async with anyio.create_task_group() as tasks:\n async_q = culsans.Queue(3).async_q\n\n for i in range(5):\n tasks.start_soon(async_q.put, i)\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 3\n\n async_q.clear() # clearing\n\n await anyio.sleep(1e-3)\n assert async_q.qsize() == 2\n assert async_q.get_nowait() == 3\n assert async_q.get_nowait() == 4\n\nRoughly equivalent to:\n\n.. code:: python\n\n def clear(queue):\n while True:\n try:\n queue.get_nowait()\n except Empty:\n break\n else:\n queue.task_done()\n\nSubclasses\n----------\n\nYou can create your own queues by inheriting from existing queue classes as if\nyou were using the queue module. For example, this is how you can create an\nunordered queue that contains only unique items:\n\n.. code:: python\n\n from culsans import Queue\n\n\n class UniqueQueue(Queue):\n def _init(self, maxsize):\n self.data = set()\n\n def _qsize(self):\n return len(self.data)\n\n def _put(self, item):\n self.data.add(item)\n\n def _get(self):\n return self.data.pop()\n\n _peek = None\n\n def _peekable(self):\n return False\n\n def _clear(self):\n self.data.clear()\n\n.. code:: python\n\n sync_q = UniqueQueue().sync_q\n\n sync_q.put_nowait(23)\n sync_q.put_nowait(42)\n sync_q.put_nowait(23)\n\n assert sync_q.qsize() == 2\n assert sorted(sync_q.get_nowait() for _ in range(2)) == [23, 42]\n\nAll seven of these methods are called in exclusive access mode, so you can\nfreely create your subclasses without thinking about whether your methods are\nthread-safe or not.\n\nCheckpoints\n-----------\n\nSometimes it is useful when each asynchronous call switches execution to the\nnext task and checks for cancellation and timeouts. For example, if you want to\ndistribute CPU usage across all tasks.\n\nThe culsans library adopts aiologic's checkpoints, but unlike it does not\nguarantee that there will only be one per asynchronous call, due to design\nspecifics.\n\nSee the aiologic documentation for details on how to control checkpoints.\n\nCompatibility\n=============\n\nThe interfaces are compliant with the Python API version 3.13, and the culsans\nlibrary itself is fully compatible with the janus library version 2.0.0. If you\nare using janus in your application and want to switch to culsans, all you have\nto do is replace this:\n\n.. code:: python\n\n import janus\n\nwith this:\n\n.. code:: python\n\n import culsans as janus\n\nand everything will work!\n\nPerformance\n===========\n\nBeing built on the aiologic package, the culsans library has speed advantages.\nWhen communication is performed within a single thread using the asynchronous\nAPI, ``culsans.Queue`` is typically 2 times faster than ``janus.Queue``:\n\n+-------------+-------------+-------------+-------------+-------------+\n| python | janus | culsans | aiologic | asyncio |\n+=============+=============+=============+=============+=============+\n| python3.8 | x1.00 | x2.26 | x2.89 | x1.96 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.9 | x1.00 | x2.24 | x2.85 | x1.96 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.10 | x1.00 | x2.31 | x2.99 | x1.84 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.11 | x1.00 | x2.35 | x3.00 | x1.83 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.12 | x1.00 | x2.45 | x3.14 | x1.77 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.13 | x1.00 | x2.61 | x3.25 | x1.75 |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.13t | x1.00 | x2.35 | x2.97 | x1.98 |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.8 | x1.00 | x2.76 | x5.48 | x1.88 |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.9 | x1.00 | x2.81 | x5.07 | x1.86 |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.10 | x1.00 | x2.38 | x5.17 | x1.81 |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.11 | x1.00 | x2.48 | x5.29 | x1.88 |\n+-------------+-------------+-------------+-------------+-------------+\n\nAnd when communication is performed within two threads, they are the same:\n\n+-------------+-------------+-------------+-------------+-------------+\n| python | janus | culsans | aiologic | asyncio |\n+=============+=============+=============+=============+=============+\n| python3.8 | x1.00 | +8.70% | x1.51 | +0.65% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.9 | x1.00 | +7.60% | +36.58% | -5.69% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.10 | x1.00 | +6.38% | +23.84% | -13.91% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.11 | x1.00 | +5.86% | +22.55% | -20.12% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.12 | x1.00 | +0.29% | +15.12% | -22.31% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.13 | x1.00 | +6.00% | +17.36% | -17.74% |\n+-------------+-------------+-------------+-------------+-------------+\n| python3.13t | x1.00 | +8.49% | +24.35% | -22.25% |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.8 | x1.00 | -0.12% | +12.64% | +0.83% |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.9 | x1.00 | +6.25% | +9.59% | -0.15% |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.10 | x1.00 | +11.37% | +8.19% | +1.10% |\n+-------------+-------------+-------------+-------------+-------------+\n| pypy3.11 | x1.00 | +7.03% | +10.68% | -2.20% |\n+-------------+-------------+-------------+-------------+-------------+\n\nHowever, on your hardware the performance results may be different, especially\nfor the PyPy case, which on older hardware may show a tenfold speedup or more\nin both tables, so you may find it useful to run benchmarks yourself to measure\nactual relative performance.\n\nDocumentation\n=============\n\nDeepWiki: https://deepwiki.com/x42005e1f/culsans (AI generated)\n\nCommunication channels\n======================\n\nGitHub Discussions: https://github.com/x42005e1f/culsans/discussions (ideas,\nquestions)\n\nGitHub Issues: https://github.com/x42005e1f/culsans/issues (bug tracker)\n\nYou can also send an email to 0x42005e1f@gmail.com with any feedback.\n\nSupport\n=======\n\nIf you like culsans and want to support its development, please star `its\nrepository on GitHub <https://github.com/x42005e1f/culsans>`_.\n\n.. image:: https://starchart.cc/x42005e1f/culsans.svg?variant=adaptive\n :target: https://starchart.cc/x42005e1f/culsans\n\nLicense\n=======\n\nThe culsans library is `REUSE-compliant <https://api.reuse.software/info/\ngithub.com/x42005e1f/culsans>`_ and is offered under multiple licenses:\n\n* All original source code is licensed under `ISC`_.\n* All original test code is licensed under `0BSD`_.\n* All documentation is licensed under `CC-BY-4.0`_.\n* All configuration is licensed under `CC0-1.0`_.\n* Some test code borrowed from `python/cpython <https://github.com/python/\n cpython>`_ is licensed under `PSF-2.0`_.\n\nFor more accurate information, check the individual files.\n\n.. _ISC: https://choosealicense.com/licenses/isc/\n.. _0BSD: https://choosealicense.com/licenses/0bsd/\n.. _CC-BY-4.0: https://choosealicense.com/licenses/cc-by-4.0/\n.. _CC0-1.0: https://choosealicense.com/licenses/cc0-1.0/\n.. _PSF-2.0: https://docs.python.org/3/license.html\n",
"bugtrack_url": null,
"license": null,
"summary": "Thread-safe async-aware queue for Python",
"version": "0.9.0",
"project_urls": {
"Homepage": "https://github.com/x42005e1f/culsans"
},
"split_keywords": [
"anyio",
" async",
" async-await",
" asyncio",
" communication",
" concurrency",
" eventlet",
" gevent",
" greenlet",
" library",
" mypy",
" python",
" queue",
" thread-safety",
" threading",
" trio"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6fb41e3cccb48f09e89e0cfc06925182cbcd36abf80b8eda2489430b41c7eaff",
"md5": "515f34a325182b1442d564954311da49",
"sha256": "d3537b65bbb341c2ac72e7d152deb8ab893b2a00452d2a68702a1a1a41619d6f"
},
"downloads": -1,
"filename": "culsans-0.9.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "515f34a325182b1442d564954311da49",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 12058,
"upload_time": "2025-07-16T17:18:20",
"upload_time_iso_8601": "2025-07-16T17:18:20.526686Z",
"url": "https://files.pythonhosted.org/packages/6f/b4/1e3cccb48f09e89e0cfc06925182cbcd36abf80b8eda2489430b41c7eaff/culsans-0.9.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "905d12e7e16b0caafaa8cca0728dd817204afd1274ddb35531b029b1c5cf7b2a",
"md5": "ecee898a1ff74b2377ef0e20b19a1a83",
"sha256": "942dd3c3c77f20e9ac3383d9a5ef8b7b24c0dac1a593bdb20d46c8a38720a5f3"
},
"downloads": -1,
"filename": "culsans-0.9.0.tar.gz",
"has_sig": false,
"md5_digest": "ecee898a1ff74b2377ef0e20b19a1a83",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 42937,
"upload_time": "2025-07-16T17:18:21",
"upload_time_iso_8601": "2025-07-16T17:18:21.545324Z",
"url": "https://files.pythonhosted.org/packages/90/5d/12e7e16b0caafaa8cca0728dd817204afd1274ddb35531b029b1c5cf7b2a/culsans-0.9.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-16 17:18:21",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "x42005e1f",
"github_project": "culsans",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "culsans"
}