diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 8a5781a9..bcc8e4ea 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -32,7 +32,7 @@ jobs: pip install wheel - name: Get previous tag id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" + uses: WyriHaximus/github-action-get-previous-tag@v1 - name: Replace version run: ver=${{ steps.previoustag.outputs.tag }} && new_ver=${ver#v} && sed -i "s/__RELEASE_VERSION__/${new_ver}/g" src/linktools/version.py - name: Build package diff --git a/src/linktools/cli/_command.py b/src/linktools/cli/_command.py index 6da1f5fe..5ea09b28 100644 --- a/src/linktools/cli/_command.py +++ b/src/linktools/cli/_command.py @@ -83,17 +83,18 @@ def create_argument_parser(self): description=description, conflict_handler="resolve" ) + self.add_log_arguments(parser) self.add_base_arguments(parser) self.add_arguments(parser) return parser - def add_base_arguments(self, parser: ArgumentParser): + def add_log_arguments(self, parser: ArgumentParser): class VerboseAction(Action): def __call__(self, parser, namespace, values, option_string=None): - environ.logger.setLevel(logging.DEBUG) + logging.root.setLevel(logging.DEBUG) class DebugAction(Action): @@ -118,8 +119,6 @@ def __call__(self, parser, namespace, values, option_string=None): if option_string in self.option_strings: environ.show_log_level = not option_string.startswith("--no-") - parser.add_argument("--version", action="version", version="%(prog)s " + __version__) - group = parser.add_argument_group(title="log arguments") group.add_argument("--verbose", action=VerboseAction, nargs=0, const=True, dest=SUPPRESS, help="increase log verbosity") @@ -132,6 +131,9 @@ def __call__(self, parser, namespace, values, option_string=None): group.add_argument("--level", "--no-level", action=LogLevelAction, nargs=0, dest=SUPPRESS, help="show log level") + def add_base_arguments(self, parser: ArgumentParser): + parser.add_argument("--version", action="version", version="%(prog)s " + __version__) + def main(self, *args, **kwargs) -> None: try: self.on_main_init() diff --git a/src/linktools/decorator.py b/src/linktools/decorator.py index 61f35a4c..6aba57f7 100644 --- a/src/linktools/decorator.py +++ b/src/linktools/decorator.py @@ -35,11 +35,14 @@ def singleton(cls: typing.Type[_T]) -> typing.Callable[..., _T]: instances = {} + lock = threading.RLock() @functools.wraps(cls) def wrapper(*args, **kwargs): if cls not in instances: - instances[cls] = cls(*args, **kwargs) + with lock: + if cls not in instances: + instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper diff --git a/src/linktools/utils/_asyncio.py b/src/linktools/utils/_asyncio.py index e9f846e0..239e63ce 100644 --- a/src/linktools/utils/_asyncio.py +++ b/src/linktools/utils/_asyncio.py @@ -2,29 +2,34 @@ # -*- coding:utf-8 -*- import asyncio +import atexit import threading from typing import Optional, Callable, Any, Coroutine from .._logging import get_logger +from ..decorator import singleton _logger = get_logger("utils.asyncio") -event_loop_thread: Optional["EventLoopThread"] = None -event_loop_thread_lock = threading.RLock() - +@singleton class EventLoopThread(threading.Thread): def __init__(self): - super().__init__() + + def run(): + self._loop = asyncio.new_event_loop() + event.set() + self._loop.run_forever() + + super().__init__(target=run) + + event = threading.Event() self.daemon = True self._loop: Optional[asyncio.AbstractEventLoop] = None - self._event = threading.Event() - - def run(self): - loop = self._loop = asyncio.new_event_loop() - self._event.set() - loop.run_forever() + self.start() + event.wait() + atexit.register(self.stop) def stop(self): loop = self.get_event_loop() @@ -39,18 +44,8 @@ def call_task_soon(self, coro: Coroutine): return loop.call_soon_threadsafe(lambda: loop.create_task(coro)) def get_event_loop(self) -> asyncio.AbstractEventLoop: - if self._loop is None: - self._event.wait() - assert self._loop is not None return self._loop def get_event_loop_thread(): - global event_loop_thread - global event_loop_thread_lock - if event_loop_thread is None: - with event_loop_thread_lock: - if event_loop_thread is None: - event_loop_thread = EventLoopThread() - event_loop_thread.start() - return event_loop_thread + return EventLoopThread() diff --git a/src/linktools/utils/_url.py b/src/linktools/utils/_url.py index 55c9d564..625cf3cc 100644 --- a/src/linktools/utils/_url.py +++ b/src/linktools/utils/_url.py @@ -41,29 +41,34 @@ from ._utils import Timeout, get_md5, ignore_error, parse_version from .._environ import resource, config, tools from .._logging import get_logger, create_log_progress -from ..decorator import cached_property +from ..decorator import cached_property, singleton +from ..references.fake_useragent import UserAgent, VERSION as FAKE_USERAGENT_VERSION _logger = get_logger("utils.url") -_user_agent = None DataType = Union[str, int, float] QueryType = Union[DataType, List[DataType], Tuple[DataType]] +@singleton +class _UserAgent(UserAgent): + + def __init__(self): + super().__init__( + path=resource.get_asset_path(f"fake_useragent_{FAKE_USERAGENT_VERSION}.json") + ) + + def user_agent(style=None) -> str: try: - from ..references.fake_useragent import UserAgent, VERSION - global _user_agent - if (not _user_agent) and style: - _user_agent = UserAgent( - path=resource.get_asset_path(f"fake_useragent_{VERSION}.json") - ) + user_agent = _UserAgent() if style: - return _user_agent[style] + print(user_agent[style]) + return user_agent[style] - return _user_agent.random + return user_agent.random except Exception as e: _logger.debug(f"fetch user agent error: {e}") @@ -152,7 +157,6 @@ def download(self, timeout: Timeout): "Range": f"bytes={initial}-", } - try: import requests fn = self._download_with_requests