Skip to content
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

Add python-3.10-dev to travis test matrix #193

Merged
merged 3 commits into from
Mar 12, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ python:
- "3.7"
- "3.8"
- "3.9"
- "3.10-dev"
install: pip install tox-travis
script: tox
130 changes: 5 additions & 125 deletions pgactivity/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
)
Expand All @@ -26,91 +25,6 @@
T = TypeVar("T")


class Deserializable:
"""Mixin class adding deserialization support.

>>> @attr.s(auto_attribs=True)
... class Point(Deserializable):
... x: int
... y: int
... label: Optional[str] = None

>>> data = {"x": 1, "y": 1, "label": "a"}
>>> Point.deserialize(data)
Point(x=1, y=1, label='a')

>>> @attr.s(auto_attribs=True)
... class Line(Deserializable):
... start: Point
... end: Point
... color: str = "black"
... label: Optional[str] = None

>>> data = {"start": {"x": 1, "y": 1}, "end": {"x": 2, "y": 2, "label": "p2"}}
>>> Line.deserialize(data)
Line(start=Point(x=1, y=1, label=None), end=Point(x=2, y=2, label='p2'), color='black', label=None)

>>> data = {"start": {"x": 1, "y": 1}, "end": {"x": 2, "y": 2}, "colour": "red"}
>>> Line.deserialize(data)
Traceback (most recent call last):
...
ValueError: unknown field(s): colour

>>> data = {"start": {"x": 1, "y": 1}}
>>> Line.deserialize(data)
Traceback (most recent call last):
...
ValueError: missing required field 'end'

>>> data = {"start": {"x": 1, "y": 1}, "end": {"x": 2, "y": 2}, "color": (255, 5, 2)}
>>> Line.deserialize(data)
Traceback (most recent call last):
...
TypeError: invalid type for field 'color', expecting <class 'str'>
"""

@classmethod
def deserialize(cls: Type[T], data: Mapping[str, Any]) -> T:
args = {}
for field in attr.fields(cls):
name = field.name
try:
value = data[name]
except KeyError:
if field.default != attr.NOTHING:
continue
raise ValueError(f"missing required field '{name}'") from None
else:
try:
deserializer = getattr(field.type, "deserialize")
except AttributeError:
assert field.type is not None, "fields should be typed"
try:
is_subtype = isinstance(value, field.type)
except TypeError:
# This might happen for Union types (e.g.
# Optional[X]), we assume the type is okay waiting for
# a better strategy.
pass
else:
if not is_subtype:
raise TypeError(
f"invalid type for field '{name}', expecting {field.type}"
) from None
else:
value = deserializer(value)

args[name] = value

unknown = set(data) - set(args)
if unknown:
raise ValueError(
f"unknown field(s): {', '.join(sorted(unknown))}"
) from None

return cls(**args) # type: ignore[call-arg]


E = TypeVar("E", bound=enum.IntEnum)


Expand All @@ -135,17 +49,7 @@ def enum_next(e: E) -> E:

@enum.unique
class Flag(enum.IntFlag):
"""Column flag.

>>> f = Flag(3)
>>> f
<Flag.APPNAME|DATABASE: 3>
>>> f | Flag.CLIENT
<Flag.CLIENT|APPNAME|DATABASE: 7>
>>> f ^= Flag.APPNAME
>>> f
<Flag.DATABASE: 1>
"""
"""Column flag."""

DATABASE = 1
APPNAME = 2
Expand Down Expand Up @@ -185,31 +89,7 @@ def from_options(
nowrite: bool,
**kwargs: Any,
) -> "Flag":
"""Build a Flag value from command line options.

>>> options = {
... 'noappname': False,
... 'noclient': False,
... 'nocpu': False,
... 'nodb': False,
... 'nomem': False,
... 'nopid': False,
... 'noread': False,
... 'notime': False,
... 'nouser': False,
... 'nowait': False,
... 'nowrite': False,
... }
>>> Flag.from_options(is_local=True, **options)
<Flag.PID|IOWAIT|MODE|TYPE|RELATION|WAIT|TIME|WRITE|READ|MEM|CPU|USER|CLIENT|APPNAME|DATABASE: 32767>
>>> Flag.from_options(is_local=False, **options)
<Flag.PID|MODE|TYPE|RELATION|WAIT|TIME|USER|CLIENT|APPNAME|DATABASE: 24335>
>>> options['nodb'] = True
>>> options['notime'] = True
>>> options['nopid'] = True
>>> Flag.from_options(is_local=False, **options)
<Flag.MODE|TYPE|RELATION|WAIT|USER|CLIENT|APPNAME: 7694>
"""
"""Build a Flag value from command line options."""
flag = cls.all()
if nodb:
flag ^= cls.DATABASE
Expand Down Expand Up @@ -725,7 +605,7 @@ def default(cls) -> "LoadAverage":


@attr.s(auto_attribs=True, frozen=True, slots=True)
class IOCounter(Deserializable):
class IOCounter:
count: int
bytes: int
chars: int = 0
Expand Down Expand Up @@ -813,7 +693,7 @@ class BaseProcess:


@attr.s(auto_attribs=True, frozen=True, slots=True)
class RunningProcess(BaseProcess, Deserializable):
class RunningProcess(BaseProcess):
"""Process for a running query."""

wait: bool
Expand All @@ -835,7 +715,7 @@ class BWProcess(BaseProcess):


@attr.s(auto_attribs=True, frozen=True, slots=True)
class SystemProcess(Deserializable):
class SystemProcess:
meminfo: Tuple[int, ...]
io_read: IOCounter
io_write: IOCounter
Expand Down
18 changes: 12 additions & 6 deletions tests/test_activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,24 @@ def system_processes(datadir):

running_process_fields = {a.name for a in attr.fields(RunningProcess)}

def system_process(extras):
for k in ("io_read", "io_write"):
try:
extras[k] = IOCounter(**extras.pop(k))
except KeyError:
pass
return SystemProcess(**extras)

for new_proc in input_data["new_processes"].values():
new_system_procs[new_proc["pid"]] = SystemProcess.deserialize(
new_proc["extras"]
)
new_system_procs[new_proc["pid"]] = system_process(new_proc["extras"])
pg_processes.append(
RunningProcess.deserialize(
{k: v for k, v in new_proc.items() if k in running_process_fields}
RunningProcess(
**{k: v for k, v in new_proc.items() if k in running_process_fields}
)
)

system_procs = {
proc["pid"]: SystemProcess.deserialize(proc["extras"])
proc["pid"]: system_process(proc["extras"])
for proc in input_data["processes"].values()
}

Expand Down
72 changes: 72 additions & 0 deletions tests/test_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from pgactivity.types import Flag


def test_flag():
f = Flag(3)
assert f == Flag.APPNAME | Flag.DATABASE
assert f | Flag.CLIENT == Flag.CLIENT | Flag.APPNAME | Flag.DATABASE
f ^= Flag.APPNAME
assert f == Flag.DATABASE


def test_flag_from_options():
options = {
"noappname": False,
"noclient": False,
"nocpu": False,
"nodb": False,
"nomem": False,
"nopid": False,
"noread": False,
"notime": False,
"nouser": False,
"nowait": False,
"nowrite": False,
}
flag = Flag.from_options(is_local=True, **options)
assert (
flag
== Flag.PID
| Flag.IOWAIT
| Flag.MODE
| Flag.TYPE
| Flag.RELATION
| Flag.WAIT
| Flag.TIME
| Flag.WRITE
| Flag.READ
| Flag.MEM
| Flag.CPU
| Flag.USER
| Flag.CLIENT
| Flag.APPNAME
| Flag.DATABASE
)
flag = Flag.from_options(is_local=False, **options)
assert (
flag
== Flag.PID
| Flag.MODE
| Flag.TYPE
| Flag.RELATION
| Flag.WAIT
| Flag.TIME
| Flag.USER
| Flag.CLIENT
| Flag.APPNAME
| Flag.DATABASE
)
options["nodb"] = True
options["notime"] = True
options["nopid"] = True
flag = Flag.from_options(is_local=False, **options)
assert (
flag
== Flag.MODE
| Flag.TYPE
| Flag.RELATION
| Flag.WAIT
| Flag.USER
| Flag.CLIENT
| Flag.APPNAME
)