Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/python:3.8@sha256:13822a0e211e5b99816ce3f44f064ee385f7679eb407f901f19ed5328ad557d0
FROM mcr.microsoft.com/devcontainers/python:3.12@sha256:7876580dc67fd460fd962f004cbeb480027e9bbc0657096f1087db11f9eaff39

RUN \
pipx uninstall mypy \
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ jobs:
fail-fast: false
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
Expand All @@ -47,4 +46,4 @@ jobs:
SKIP: nitpick

- name: Run tests
run: hatch test --python ${{ matrix.python-version }} --cover --randomize --parallel --retries 2 --retry-delay 1
run: hatch test --python ${{ matrix.python-version }} --cover --randomize --retries 2 --retry-delay 1
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ repos:
hooks:
- id: pyupgrade
args:
- --py38-plus
- --py39-plus
- repo: https://github.com/MarcoGorelli/auto-walrus
rev: 7855759486496a3248e9ff37dce7c6d57d39bfce
hooks:
Expand Down
59 changes: 31 additions & 28 deletions irclib/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@

import re
from abc import ABCMeta, abstractmethod
from collections.abc import Iterable, Iterator, Sequence
from typing import (
Dict,
Final,
Iterable,
Iterator,
List,
Literal,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
Expand All @@ -34,7 +33,7 @@
)
MsgTagList: TypeAlias = Optional["TagList"]
MsgPrefix: TypeAlias = Optional["Prefix"]
MessageTuple: TypeAlias = Tuple[MsgTagList, MsgPrefix, str, "ParamList"]
MessageTuple: TypeAlias = tuple[MsgTagList, MsgPrefix, str, "ParamList"]

TAGS_SENTINEL: Final = "@"
TAGS_SEP: Final = ";"
Expand Down Expand Up @@ -106,7 +105,7 @@ def value(self) -> Optional[str]:
"""CAP value."""
return self._value

def as_tuple(self) -> Tuple[str, Optional[str]]:
def as_tuple(self) -> tuple[str, Optional[str]]:
"""Get data as a tuple of values."""
return self.name, self.value

Expand Down Expand Up @@ -144,7 +143,7 @@ def __str__(self) -> str:
return self.name


class CapList(Parseable, List[Cap]):
class CapList(Parseable, list[Cap]):
"""Represents a list of CAP entities."""

@classmethod
Expand Down Expand Up @@ -293,7 +292,7 @@ def __str__(self) -> str:
return self.name


class TagList(Parseable, Dict[str, MessageTag]):
class TagList(Parseable, dict[str, MessageTag]):
"""Object representing the list of message tags on a line."""

def __init__(self, tags: Iterable[MessageTag] = ()) -> None:
Expand All @@ -305,23 +304,27 @@ def __init__(self, tags: Iterable[MessageTag] = ()) -> None:
super().__init__((tag.name, tag) for tag in tags)

@staticmethod
def _cmp_type_map(obj: object) -> Dict[str, MessageTag]:
def _cmp_type_map(
obj: object,
) -> Union[
tuple[dict[str, MessageTag], Literal[True]], tuple[None, Literal[False]]
]:
if isinstance(obj, str):
return TagList.parse(obj)
return TagList.parse(obj), True

if isinstance(obj, dict):
sample = next(iter(obj.values()), None)
if obj and (sample is None or isinstance(sample, str)):
# Handle str -> str dict
return TagList.from_dict(obj)
return TagList.from_dict(obj), True

# Handle str -> MessageTag dict
return dict(obj)
return dict(obj), True

if isinstance(obj, list):
return TagList(obj)
return TagList(obj), True

return NotImplemented
return None, False

@classmethod
def parse(cls, text: str) -> Self:
Expand All @@ -333,25 +336,25 @@ def parse(cls, text: str) -> Self:
return cls(map(MessageTag.parse, filter(None, text.split(TAGS_SEP))))

@classmethod
def from_dict(cls, tags: Dict[str, str]) -> Self:
def from_dict(cls, tags: dict[str, str]) -> Self:
"""Create a TagList from a dict of tags."""
return cls(MessageTag(k, v) for k, v in tags.items())

def __eq__(self, other: object) -> bool:
"""Compare to another tag list, string, or list of MessageTag objects."""
obj = self._cmp_type_map(other)
if obj is NotImplemented:
res = self._cmp_type_map(other)
if not res[1]:
return NotImplemented

return dict(self) == dict(obj)
return dict(self) == dict(res[0])

def __ne__(self, other: object) -> bool:
"""Compare to another tag list, string, or list of MessageTag objects."""
obj = self._cmp_type_map(other)
if obj is NotImplemented:
res = self._cmp_type_map(other)
if not res[1]:
return NotImplemented

return dict(self) != dict(obj)
return dict(self) != dict(res[0])

def __str__(self) -> str:
"""Represent the tag list as a string."""
Expand Down Expand Up @@ -405,7 +408,7 @@ def mask(self) -> str:
return mask

@property
def _data(self) -> Tuple[str, str, str]:
def _data(self) -> tuple[str, str, str]:
return self.nick, self.user, self.host

@classmethod
Expand Down Expand Up @@ -463,7 +466,7 @@ def __str__(self) -> str:
return self.mask


class ParamList(Parseable, List[str]):
class ParamList(Parseable, list[str]):
"""An object representing the parameter list from a line."""

def __init__(self, *params: str, has_trail: bool = False) -> None:
Expand Down Expand Up @@ -554,13 +557,13 @@ def __str__(self) -> str:


def _parse_tags(
tags: Union[TagList, Dict[str, str], str, None, List[str]],
tags: Union[TagList, dict[str, str], str, None, list[str]],
) -> MsgTagList:
if isinstance(tags, TagList):
return tags

if isinstance(tags, dict):
return TagList.from_dict(cast(Dict[str, str], tags))
return TagList.from_dict(cast(dict[str, str], tags))

if isinstance(tags, str):
return TagList.parse(tags)
Expand All @@ -585,7 +588,7 @@ def _parse_prefix(prefix: Union[Prefix, str, None, Iterable[str]]) -> MsgPrefix:


def _parse_params(
parameters: Tuple[Union[str, List[str], ParamList], ...],
parameters: tuple[Union[str, list[str], ParamList], ...],
) -> ParamList:
if len(parameters) == 1 and not isinstance(parameters[0], str):
# This seems to be a list
Expand All @@ -594,18 +597,18 @@ def _parse_params(

return ParamList.from_list(parameters[0])

return ParamList.from_list(cast(Tuple[str, ...], parameters))
return ParamList.from_list(cast(tuple[str, ...], parameters))


class Message(Parseable):
"""An object representing a parsed IRC line."""

def __init__(
self,
tags: Union[TagList, Dict[str, str], str, None, List[str]],
tags: Union[TagList, dict[str, str], str, None, list[str]],
prefix: Union[str, Prefix, None, Iterable[str]],
command: str,
*parameters: Union[str, List[str], ParamList],
*parameters: Union[str, list[str], ParamList],
) -> None:
"""Construct message object."""
self._tags = _parse_tags(tags)
Expand Down
2 changes: 2 additions & 0 deletions irclib/util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""IRC utils."""

from irclib.util import commands, compare, frozendict, numerics, string

__all__ = ("commands", "compare", "frozendict", "numerics", "string")
5 changes: 3 additions & 2 deletions irclib/util/commands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""IRC command data and utilities."""

from typing import Iterator, List, Mapping, Optional, cast
from collections.abc import Iterator, Mapping
from typing import List, Optional, cast

import attr

Expand Down Expand Up @@ -44,7 +45,7 @@ class Command:
"""A single IRC command."""

name: str
args: List[CommandArgument]
args: list[CommandArgument]
min_args: int = 0
max_args: Optional[int] = None

Expand Down
16 changes: 4 additions & 12 deletions irclib/util/frozendict.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
"""Frozen Dict."""

from typing import (
Dict,
Iterable,
Iterator,
Mapping,
Optional,
Tuple,
TypeVar,
Union,
)
from collections.abc import Iterable, Iterator, Mapping
from typing import Dict, Optional, Tuple, TypeVar, Union

from typing_extensions import Self

Expand All @@ -28,13 +20,13 @@ class FrozenDict(Mapping[str, _V]):

def __init__(
self,
seq: Union[Mapping[str, _V], Iterable[Tuple[str, _V]], None] = None,
seq: Union[Mapping[str, _V], Iterable[tuple[str, _V]], None] = None,
**kwargs: _V,
) -> None:
"""Construct a FrozenDict."""
d = dict(seq, **kwargs) if seq is not None else dict(**kwargs)

self.__data: Dict[str, _V] = d
self.__data: dict[str, _V] = d
self.__hash: Optional[int] = None

def copy(self, **kwargs: _V) -> Self:
Expand Down
2 changes: 1 addition & 1 deletion irclib/util/numerics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""IRC numeric mapping."""

from collections.abc import Iterator, Mapping
from dataclasses import dataclass
from typing import Iterator, Mapping

__all__ = ("Numeric", "numerics")

Expand Down
Loading