6 Commits

Author SHA1 Message Date
woggioni b05851d667 improved format_size function
CI / build (push) Successful in 45s
2026-04-21 09:12:56 +08:00
woggioni a417c7484b fixed mypy
CI / build (push) Successful in 42s
2024-12-01 15:38:54 +08:00
woggioni 04aae1f976 switched builder to hostinger
CI / build (push) Failing after 40s
2024-12-01 15:29:34 +08:00
woggioni 79246f70c4 improved index_of_with_escape
CI / build (push) Waiting to run
2024-11-18 09:00:23 +08:00
woggioni 36f7031fea added index_of_with_escape function 2024-11-11 06:06:22 +08:00
woggioni 37591f78d9 simplified notification code 2024-11-03 23:02:01 +08:00
5 changed files with 142 additions and 74 deletions
+57 -50
View File
@@ -1,95 +1,103 @@
# #
# This file is autogenerated by pip-compile with Python 3.12 # This file is autogenerated by pip-compile with Python 3.14
# by the following command: # by the following command:
# #
# pip-compile --extra=dev --output-file=requirements.txt # pip-compile --allow-unsafe --extra=dev --output-file=requirements.txt pyproject.toml
# #
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple --extra-index-url https://pypi.org/simple
asttokens==2.4.1 asttokens==3.0.1
# via stack-data # via stack-data
build==1.2.2.post1 build==1.4.3
# via # via
# pip-tools # pip-tools
# pwo (pyproject.toml) # pwo (pyproject.toml)
certifi==2024.8.30 certifi==2026.2.25
# via requests # via requests
cffi==1.17.1 cffi==2.0.0
# via cryptography # via cryptography
charset-normalizer==3.4.0 charset-normalizer==3.4.7
# via requests # via requests
click==8.1.7 click==8.3.2
# via pip-tools # via pip-tools
cryptography==43.0.3 cryptography==46.0.7
# via secretstorage # via secretstorage
decorator==5.1.1 decorator==5.2.1
# via # via
# ipdb # ipdb
# ipython # ipython
docutils==0.21.2 docutils==0.22.4
# via readme-renderer # via readme-renderer
executing==2.1.0 executing==2.2.1
# via stack-data # via stack-data
idna==3.10 id==1.6.1
# via requests
importlib-metadata==8.5.0
# via twine # via twine
idna==3.11
# via requests
ipdb==0.13.13 ipdb==0.13.13
# via pwo (pyproject.toml) # via pwo (pyproject.toml)
ipython==8.29.0 ipython==9.12.0
# via ipdb # via ipdb
ipython-pygments-lexers==1.1.1
# via ipython
jaraco-classes==3.4.0 jaraco-classes==3.4.0
# via keyring # via keyring
jaraco-context==6.0.1 jaraco-context==6.1.2
# via keyring # via keyring
jaraco-functools==4.1.0 jaraco-functools==4.4.0
# via keyring # via keyring
jedi==0.19.1 jedi==0.19.2
# via ipython # via ipython
jeepney==0.8.0 jeepney==0.9.0
# via # via
# keyring # keyring
# secretstorage # secretstorage
keyring==25.5.0 keyring==25.7.0
# via twine # via twine
markdown-it-py==3.0.0 librt==0.9.0
# via mypy
markdown-it-py==4.0.0
# via rich # via rich
matplotlib-inline==0.1.7 matplotlib-inline==0.2.1
# via ipython # via ipython
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
more-itertools==10.5.0 more-itertools==11.0.2
# via # via
# jaraco-classes # jaraco-classes
# jaraco-functools # jaraco-functools
mypy==1.13.0 mypy==1.20.1
# via pwo (pyproject.toml) # via pwo (pyproject.toml)
mypy-extensions==1.0.0 mypy-extensions==1.1.0
# via mypy # via mypy
nh3==0.2.18 nh3==0.3.4
# via readme-renderer # via readme-renderer
packaging==24.1 packaging==26.1
# via build # via
parso==0.8.4 # build
# twine
# wheel
parso==0.8.6
# via jedi # via jedi
pathspec==1.0.4
# via mypy
pexpect==4.9.0 pexpect==4.9.0
# via ipython # via ipython
pip-tools==7.4.1 pip-tools==7.5.3
# via pwo (pyproject.toml) # via pwo (pyproject.toml)
pkginfo==1.10.0 prompt-toolkit==3.0.52
# via twine
prompt-toolkit==3.0.48
# via ipython # via ipython
ptyprocess==0.7.0 ptyprocess==0.7.0
# via pexpect # via pexpect
pure-eval==0.2.3 pure-eval==0.2.3
# via stack-data # via stack-data
pycparser==2.22 pycparser==3.0
# via cffi # via cffi
pygments==2.18.0 pygments==2.20.0
# via # via
# ipython # ipython
# ipython-pygments-lexers
# readme-renderer # readme-renderer
# rich # rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
@@ -98,7 +106,7 @@ pyproject-hooks==1.2.0
# pip-tools # pip-tools
readme-renderer==44.0 readme-renderer==44.0
# via twine # via twine
requests==2.32.3 requests==2.33.1
# via # via
# requests-toolbelt # requests-toolbelt
# twine # twine
@@ -106,35 +114,34 @@ requests-toolbelt==1.0.0
# via twine # via twine
rfc3986==2.0.0 rfc3986==2.0.0
# via twine # via twine
rich==13.9.4 rich==15.0.0
# via twine # via twine
secretstorage==3.3.3 secretstorage==3.5.0
# via keyring # via keyring
six==1.16.0
# via asttokens
stack-data==0.6.3 stack-data==0.6.3
# via ipython # via ipython
traitlets==5.14.3 traitlets==5.14.3
# via # via
# ipython # ipython
# matplotlib-inline # matplotlib-inline
twine==5.1.1 twine==6.2.0
# via pwo (pyproject.toml) # via pwo (pyproject.toml)
typing-extensions==4.12.2 typing-extensions==4.15.0
# via # via
# mypy # mypy
# pwo (pyproject.toml) # pwo (pyproject.toml)
urllib3==2.2.3 urllib3==2.6.3
# via # via
# id
# requests # requests
# twine # twine
wcwidth==0.2.13 wcwidth==0.6.0
# via prompt-toolkit # via prompt-toolkit
wheel==0.44.0 wheel==0.46.3
# via pip-tools # via pip-tools
zipp==3.20.2
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip pip==26.0.1
# setuptools # via pip-tools
setuptools==82.0.1
# via pip-tools
+3 -1
View File
@@ -9,6 +9,7 @@ from .private import (
classproperty, classproperty,
AsyncQueueIterator, AsyncQueueIterator,
aenumerate, aenumerate,
index_of_with_escape
) )
from .maybe import Maybe from .maybe import Maybe
from .notification import TopicManager, Subscriber from .notification import TopicManager, Subscriber
@@ -28,5 +29,6 @@ __all__ = [
'Subscriber', 'Subscriber',
'AsyncQueueIterator', 'AsyncQueueIterator',
'aenumerate', 'aenumerate',
'Try' 'Try',
'index_of_with_escape'
] ]
+8 -17
View File
@@ -1,5 +1,5 @@
from .private import AsyncQueueIterator from .private import AsyncQueueIterator
from asyncio import Queue, AbstractEventLoop, Future, CancelledError from asyncio import Queue, AbstractEventLoop, Future, CancelledError, timeout
from typing import Callable, Optional from typing import Callable, Optional
from logging import getLogger from logging import getLogger
@@ -23,25 +23,16 @@ class Subscriber:
self._unsubscribe_callback(self) self._unsubscribe_callback(self)
log.debug('Deleted subscriber %s', id(self)) log.debug('Deleted subscriber %s', id(self))
async def wait(self, tout: float) -> bool: async def wait(self, tout: Optional[float]) -> bool:
self._event = self._loop.create_future()
def callback() -> None: future: Future[bool] = self._loop.create_future()
evt = self._event self._event = future
if evt is None:
raise ValueError('Event is None')
evt.cancel()
if not evt.done():
evt.set_result(False)
handle = self._loop.call_later(tout, callback)
try: try:
log.debug('Subscriber %s is waiting for an event', id(self)) async with timeout(tout):
return await self._event log.debug('Subscriber %s is waiting for an event', id(self))
except CancelledError: return await future
except TimeoutError:
return False return False
finally:
handle.cancel()
def notify(self) -> None: def notify(self) -> None:
log.debug('Subscriber %s notified', id(self)) log.debug('Subscriber %s notified', id(self))
+39 -5
View File
@@ -16,7 +16,8 @@ from typing import (
Any, Any,
Coroutine, Coroutine,
Never, Never,
Tuple Tuple,
no_type_check
) )
@@ -88,14 +89,14 @@ def decorator_with_kwargs(decorator: Callable[..., Any]) -> Callable[..., Any]:
_size_uoms = ('B', 'KiB', 'MiB', 'GiB', 'KiB') _size_uoms = ('B', 'KiB', 'MiB', 'GiB', 'KiB')
def format_filesize(size: int) -> str: def format_filesize(size: int, width: int = 5, decimals : int = 2) -> str:
counter = 0 counter = 0
tmp_size = size tmp_size = size
while tmp_size > 0: while tmp_size > 0:
tmp_size //= 1024 tmp_size //= 1024
counter += 1 counter += 1
counter -= 1 counter -= 1
return '%.2f ' % (size / math.pow(1024, counter)) + _size_uoms[counter] return f'%{width}.{decimals}f ' % (size / math.pow(1024, counter)) + _size_uoms[counter]
class ExceptionHandlerOutcome(Enum): class ExceptionHandlerOutcome(Enum):
@@ -165,7 +166,8 @@ def async_test(coro: Callable[..., Coroutine[Never, Never, None]]) -> Callable[.
return wrapper return wrapper
@decorator_with_kwargs # type: ignore @no_type_check
@decorator_with_kwargs
def tmpdir(f, def tmpdir(f,
argument_name='temp_dir', argument_name='temp_dir',
suffix=None, suffix=None,
@@ -173,7 +175,8 @@ def tmpdir(f,
dir=None, dir=None,
ignore_cleanup_errors=False, ignore_cleanup_errors=False,
delete=True): delete=True):
@wraps(f) # type: ignore @no_type_check
@wraps(f)
def result(*args, **kwargs): def result(*args, **kwargs):
with TemporaryDirectory( with TemporaryDirectory(
suffix=suffix, suffix=suffix,
@@ -252,3 +255,34 @@ class aenumerate[T](AsyncIterator[Tuple[int, T]]):
val = await self._ait.__anext__() val = await self._ait.__anext__()
self._i += 1 self._i += 1
return self._i, val return self._i, val
def index_of_with_escape(haystack: str, needle: str, escape: str, begin: int, end: int = 0) -> int:
result = -1
cursor = begin
if end == 0:
end = len(haystack)
escape_count = 0
while cursor < end:
c = haystack[cursor]
if escape_count > 0:
escape_count -= 1
if c[0] == escape:
result = -1
elif escape_count == 0:
if c[0] == escape:
escape_count += 1
if cursor + len(needle) <= len(haystack):
test = haystack[cursor:cursor + len(needle)]
if test == needle:
result = cursor
if result >= 0 and escape_count == 0:
break
cursor += 1
return result
+35 -1
View File
@@ -1,6 +1,6 @@
import unittest import unittest
from pwo import retry, async_retry, async_test, AsyncQueueIterator, aenumerate from pwo import retry, async_retry, async_test, AsyncQueueIterator, aenumerate, index_of_with_escape
from asyncio import Queue from asyncio import Queue
@@ -93,3 +93,37 @@ class PrivateTest(unittest.TestCase):
self.assertEqual(queue_size, processed) self.assertEqual(queue_size, processed)
class TestIndexOfWithEscape(unittest.TestCase):
def run_test_case(self, haystack, needle, escape, expected_solution):
solution = []
i = 0
while True:
i = index_of_with_escape(haystack, needle, escape, i, len(haystack))
if i < 0:
break
solution.append(i)
i += 1
self.assertEqual(expected_solution, solution)
def test_simple(self):
self.run_test_case(" dsds $sdsa \\$dfivbdsf \\\\$sdgsga", '$', '\\', [6, 25])
def test_simple2(self):
self.run_test_case("asdasd$$vdfv$", '$', '$', [12])
def test_no_needle(self):
self.run_test_case("asdasd$$vdfv$", '#', '\\', [])
def test_escaped_needle(self):
self.run_test_case("asdasd$$vdfv$#sdfs", '#', '$', [])
def test_not_escaped_needle(self):
self.run_test_case("asdasd$$#vdfv$#sdfs", '#', '$', [8])
def test_special_case(self):
self.run_test_case("\n${sys:user.home}${env:HOME}", ':', '\\', [6, 22])
def test_wide_needle(self):
self.run_test_case("asdasd\\${#vdfv|${#sdfs}", '${', '\\', [15])