Skip to content

Migrate cryptocurrency.py to use pydantic models #691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .coveragerc

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ config.json
.vagrant/
.mypy_cache/
.DS_Store
*.bak
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ repos:
args:
- "--py38-plus"

- repo: https://github.com/PyCQA/autoflake
rev: 0544741e2b4a22b472d9d93e37d4ea9153820bb1 # frozen: v2.3.1
hooks:
- id: autoflake


- repo: local
hooks:
- id: mypy
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"evenBetterToml.formatter.allowedBlankLines": 1,
"evenBetterToml.formatter.arrayAutoCollapse": true,
"evenBetterToml.formatter.arrayAutoExpand": false,
"evenBetterToml.formatter.arrayTrailingComma": true
"evenBetterToml.formatter.arrayTrailingComma": true,
"python.analysis.diagnosticMode": "workspace"
}
4 changes: 2 additions & 2 deletions cloudbot/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import enum
import logging
from functools import partial
from typing import Any, Iterator, Mapping
from typing import Any, Iterator, Mapping, Optional

from irclib.parser import Message

Expand Down Expand Up @@ -245,7 +245,7 @@ def admin_log(self, message, broadcast=False):
if conn and conn.connected:
conn.admin_log(message, console=not broadcast)

def reply(self, *messages, target=None):
def reply(self, *messages: str, target: Optional[str] = None) -> None:
"""sends a message to the current channel/user with a prefix"""
reply_ping = self.conn.config.get("reply_ping", True)
if target is None:
Expand Down
68 changes: 61 additions & 7 deletions cloudbot/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
import re
import warnings
from enum import Enum, IntEnum, unique
from typing import (
Any,
Callable,
List,
Optional,
Sequence,
TypeVar,
Union,
overload,
)

from typing_extensions import ParamSpec

from cloudbot.event import EventType
from cloudbot.util import HOOK_ATTR
Expand Down Expand Up @@ -186,10 +198,36 @@ def _hook_warn():
)


def command(*args, **kwargs):
_T = TypeVar("_T")
_P = ParamSpec("_P")
_Func = Callable[_P, _T]


@overload
def command(arg: Callable[_P, _T], /) -> Callable[_P, _T]: ...


@overload
def command(
arg: Optional[Union[str, Sequence[str]]] = None,
/,
*args: Union[str, Sequence[str]],
**kwargs: Any,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...


def command(
arg: Optional[Union[Callable[_P, _T], str, Sequence[str]]] = None,
/,
*args: Union[str, Sequence[str]],
**kwargs: Any,
) -> Union[Callable[_P, _T], Callable[[Callable[_P, _T]], Callable[_P, _T]]]:
"""External command decorator. Can be used directly as a decorator, or with args to return a decorator."""

def _command_hook(func, alias_param=None):
def _command_hook(
func: Callable[_P, _T],
alias_param: Optional[Sequence[Union[Sequence[str], str]]] = None,
) -> Callable[_P, _T]:
hook = _get_hook(func, "command")
if hook is None:
hook = _CommandHook(func)
Expand All @@ -198,13 +236,17 @@ def _command_hook(func, alias_param=None):
hook.add_hook(alias_param, kwargs)
return func

if len(args) == 1 and callable(args[0]):
if arg is not None and not isinstance(arg, (str, collections.abc.Sequence)):
# this decorator is being used directly
_hook_warn()
return _command_hook(args[0])
return _command_hook(arg)

arg_list: List[Union[str, Sequence[str]]] = list(args)
if arg:
arg_list.insert(0, arg)

# this decorator is being used indirectly, so return a decorator function
return lambda func: _command_hook(func, alias_param=args)
return lambda func: _command_hook(func, alias_param=arg_list)


def irc_raw(triggers_param, **kwargs):
Expand Down Expand Up @@ -332,10 +374,22 @@ def _config_hook(func):
return _config_hook


def on_start(param=None, **kwargs):
@overload
def on_start(
**kwargs: Any,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...


@overload
def on_start(param: Callable[_P, _T], /) -> Callable[_P, _T]: ...


def on_start(
param: Optional[Callable[_P, _T]] = None, /, **kwargs: Any
) -> Union[Callable[_P, _T], Callable[[Callable[_P, _T]], Callable[_P, _T]]]:
"""External on_start decorator. Can be used directly as a decorator, or with args to return a decorator"""

def _on_start_hook(func):
def _on_start_hook(func: Callable[_P, _T]) -> Callable[_P, _T]:
hook = _get_hook(func, "on_start")
if hook is None:
hook = _Hook(func, "on_start")
Expand Down
2 changes: 1 addition & 1 deletion cloudbot/util/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def get_available_colours():
return ret[:-2]


def parse(string):
def parse(string: str) -> str:
"""
parse: Formats a string, replacing words wrapped in $( ) with actual colours or formatting.
example:
Expand Down
10 changes: 9 additions & 1 deletion cloudbot/util/func_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import inspect
from typing import Any, Callable, Mapping, TypeVar


class ParameterError(Exception):
Expand All @@ -12,7 +13,14 @@ def __init__(self, name, valid_args):
self.valid_args = list(valid_args)


def call_with_args(func, arg_data):
_T = TypeVar("_T")


def call_with_args(func: Callable[..., _T], arg_data: Mapping[str, Any]) -> _T:
"""
>>> call_with_args(lambda a: a, {'a':1, 'b':2})
1
"""
sig = inspect.signature(func, follow_wrapped=False)
try:
args = [
Expand Down
93 changes: 55 additions & 38 deletions cloudbot/util/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
import logging
import time
from operator import attrgetter
from typing import Dict, Optional, Union
from typing import (
Dict,
Generic,
Iterable,
Iterator,
Optional,
Tuple,
TypeVar,
Union,
)

import requests
from requests import (
Expand All @@ -41,76 +50,79 @@

# Public API

_T = TypeVar("_T")

class Registry:
class Item:
def __init__(self, item):
self.item = item
self.working = True
self.last_check = 0.0
self.uses = 0

def failed(self):
self.working = False
self.last_check = time.time()
class RegistryItem(Generic[_T]):
def __init__(self, item: _T) -> None:
self.item = item
self.working = True
self.last_check = 0.0
self.uses = 0

def failed(self) -> None:
self.working = False
self.last_check = time.time()

@property
def should_use(self):
if self.working:
return True
@property
def should_use(self) -> bool:
if self.working:
return True

if (time.time() - self.last_check) > (5 * 60):
# It's been 5 minutes, try again
self.working = True
return True

if (time.time() - self.last_check) > (5 * 60):
# It's been 5 minutes, try again
self.working = True
return True
return False

return False

class Registry(Generic[_T]):
def __init__(self):
self._items: Dict[str, "Registry.Item"] = {}
self._items: Dict[str, RegistryItem[_T]] = {}

def register(self, name, item):
def register(self, name: str, item: _T) -> None:
if name in self._items:
raise ValueError("Attempt to register duplicate item")

self._items[name] = self.Item(item)
self._items[name] = RegistryItem(item)

def get(self, name):
def get(self, name: str) -> Optional[_T]:
val = self._items.get(name)
if val:
return val.item

return val
return None

def get_item(self, name):
def get_item(self, name: str) -> Optional[RegistryItem[_T]]:
return self._items.get(name)

def get_working(self) -> Optional["Item"]:
def get_working(self) -> Optional[RegistryItem[_T]]:
working = [item for item in self._items.values() if item.should_use]

if not working:
return None

return min(working, key=attrgetter("uses"))

def remove(self, name):
def remove(self, name: str) -> None:
del self._items[name]

def items(self):
def items(self) -> Iterable[Tuple[str, RegistryItem[_T]]]:
return self._items.items()

def __iter__(self):
def __iter__(self) -> Iterator[str]:
return iter(self._items)

def __getitem__(self, item):
def __getitem__(self, item: str) -> _T:
return self._items[item].item

def set_working(self):
def set_working(self) -> None:
for item in self._items.values():
item.working = True


def shorten(url, custom=None, key=None, service=DEFAULT_SHORTENER):
def shorten(url: str, custom=None, key=None, service=DEFAULT_SHORTENER):
impl = shorteners[service]
return impl.shorten(url, custom, key)

Expand Down Expand Up @@ -140,7 +152,12 @@ class NoPasteException(Exception):
"""No pastebins succeeded"""


def paste(data, ext="txt", service=DEFAULT_PASTEBIN, raise_on_no_paste=False):
def paste(
data: Union[str, bytes],
ext="txt",
service=DEFAULT_PASTEBIN,
raise_on_no_paste=False,
) -> str:
if service:
impl = pastebins.get_item(service)
else:
Expand Down Expand Up @@ -218,12 +235,12 @@ class Pastebin:
def __init__(self):
pass

def paste(self, data, ext):
def paste(self, data, ext) -> str:
raise NotImplementedError


shorteners = Registry()
pastebins = Registry()
shorteners = Registry[Shortener]()
pastebins = Registry[Pastebin]()

# Internal Implementations

Expand Down Expand Up @@ -346,7 +363,7 @@ def __init__(self, base_url):
super().__init__()
self.url = base_url

def paste(self, data, ext):
def paste(self, data, ext) -> str:
if isinstance(data, str):
encoded = data.encode()
else:
Expand Down
Loading