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:
#
# 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
--extra-index-url https://pypi.org/simple
asttokens==2.4.1
asttokens==3.0.1
# via stack-data
build==1.2.2.post1
build==1.4.3
# via
# pip-tools
# pwo (pyproject.toml)
certifi==2024.8.30
certifi==2026.2.25
# via requests
cffi==1.17.1
cffi==2.0.0
# via cryptography
charset-normalizer==3.4.0
charset-normalizer==3.4.7
# via requests
click==8.1.7
click==8.3.2
# via pip-tools
cryptography==43.0.3
cryptography==46.0.7
# via secretstorage
decorator==5.1.1
decorator==5.2.1
# via
# ipdb
# ipython
docutils==0.21.2
docutils==0.22.4
# via readme-renderer
executing==2.1.0
executing==2.2.1
# via stack-data
idna==3.10
# via requests
importlib-metadata==8.5.0
id==1.6.1
# via twine
idna==3.11
# via requests
ipdb==0.13.13
# via pwo (pyproject.toml)
ipython==8.29.0
ipython==9.12.0
# via ipdb
ipython-pygments-lexers==1.1.1
# via ipython
jaraco-classes==3.4.0
# via keyring
jaraco-context==6.0.1
jaraco-context==6.1.2
# via keyring
jaraco-functools==4.1.0
jaraco-functools==4.4.0
# via keyring
jedi==0.19.1
jedi==0.19.2
# via ipython
jeepney==0.8.0
jeepney==0.9.0
# via
# keyring
# secretstorage
keyring==25.5.0
keyring==25.7.0
# via twine
markdown-it-py==3.0.0
librt==0.9.0
# via mypy
markdown-it-py==4.0.0
# via rich
matplotlib-inline==0.1.7
matplotlib-inline==0.2.1
# via ipython
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.5.0
more-itertools==11.0.2
# via
# jaraco-classes
# jaraco-functools
mypy==1.13.0
mypy==1.20.1
# via pwo (pyproject.toml)
mypy-extensions==1.0.0
mypy-extensions==1.1.0
# via mypy
nh3==0.2.18
nh3==0.3.4
# via readme-renderer
packaging==24.1
# via build
parso==0.8.4
packaging==26.1
# via
# build
# twine
# wheel
parso==0.8.6
# via jedi
pathspec==1.0.4
# via mypy
pexpect==4.9.0
# via ipython
pip-tools==7.4.1
pip-tools==7.5.3
# via pwo (pyproject.toml)
pkginfo==1.10.0
# via twine
prompt-toolkit==3.0.48
prompt-toolkit==3.0.52
# via ipython
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.3
# via stack-data
pycparser==2.22
pycparser==3.0
# via cffi
pygments==2.18.0
pygments==2.20.0
# via
# ipython
# ipython-pygments-lexers
# readme-renderer
# rich
pyproject-hooks==1.2.0
@@ -98,7 +106,7 @@ pyproject-hooks==1.2.0
# pip-tools
readme-renderer==44.0
# via twine
requests==2.32.3
requests==2.33.1
# via
# requests-toolbelt
# twine
@@ -106,35 +114,34 @@ requests-toolbelt==1.0.0
# via twine
rfc3986==2.0.0
# via twine
rich==13.9.4
rich==15.0.0
# via twine
secretstorage==3.3.3
secretstorage==3.5.0
# via keyring
six==1.16.0
# via asttokens
stack-data==0.6.3
# via ipython
traitlets==5.14.3
# via
# ipython
# matplotlib-inline
twine==5.1.1
twine==6.2.0
# via pwo (pyproject.toml)
typing-extensions==4.12.2
typing-extensions==4.15.0
# via
# mypy
# pwo (pyproject.toml)
urllib3==2.2.3
urllib3==2.6.3
# via
# id
# requests
# twine
wcwidth==0.2.13
wcwidth==0.6.0
# via prompt-toolkit
wheel==0.44.0
wheel==0.46.3
# via pip-tools
zipp==3.20.2
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools
pip==26.0.1
# via pip-tools
setuptools==82.0.1
# via pip-tools
+3 -1
View File
@@ -9,6 +9,7 @@ from .private import (
classproperty,
AsyncQueueIterator,
aenumerate,
index_of_with_escape
)
from .maybe import Maybe
from .notification import TopicManager, Subscriber
@@ -28,5 +29,6 @@ __all__ = [
'Subscriber',
'AsyncQueueIterator',
'aenumerate',
'Try'
'Try',
'index_of_with_escape'
]
+8 -17
View File
@@ -1,5 +1,5 @@
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 logging import getLogger
@@ -23,25 +23,16 @@ class Subscriber:
self._unsubscribe_callback(self)
log.debug('Deleted subscriber %s', id(self))
async def wait(self, tout: float) -> bool:
self._event = self._loop.create_future()
async def wait(self, tout: Optional[float]) -> bool:
def callback() -> None:
evt = self._event
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)
future: Future[bool] = self._loop.create_future()
self._event = future
try:
log.debug('Subscriber %s is waiting for an event', id(self))
return await self._event
except CancelledError:
async with timeout(tout):
log.debug('Subscriber %s is waiting for an event', id(self))
return await future
except TimeoutError:
return False
finally:
handle.cancel()
def notify(self) -> None:
log.debug('Subscriber %s notified', id(self))
+39 -5
View File
@@ -16,7 +16,8 @@ from typing import (
Any,
Coroutine,
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')
def format_filesize(size: int) -> str:
def format_filesize(size: int, width: int = 5, decimals : int = 2) -> str:
counter = 0
tmp_size = size
while tmp_size > 0:
tmp_size //= 1024
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):
@@ -165,7 +166,8 @@ def async_test(coro: Callable[..., Coroutine[Never, Never, None]]) -> Callable[.
return wrapper
@decorator_with_kwargs # type: ignore
@no_type_check
@decorator_with_kwargs
def tmpdir(f,
argument_name='temp_dir',
suffix=None,
@@ -173,7 +175,8 @@ def tmpdir(f,
dir=None,
ignore_cleanup_errors=False,
delete=True):
@wraps(f) # type: ignore
@no_type_check
@wraps(f)
def result(*args, **kwargs):
with TemporaryDirectory(
suffix=suffix,
@@ -252,3 +255,34 @@ class aenumerate[T](AsyncIterator[Tuple[int, T]]):
val = await self._ait.__anext__()
self._i += 1
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
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
@@ -93,3 +93,37 @@ class PrivateTest(unittest.TestCase):
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])