Skip to content

Commit

Permalink
Use correct meaning for 'Name' key
Browse files Browse the repository at this point in the history
  • Loading branch information
alexshpilkin committed May 1, 2019
1 parent 2814fd8 commit bc12a9e
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 61 deletions.
13 changes: 5 additions & 8 deletions dvrip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ def request(self, request):


class Client(Connection):
__slots__ = ('username', '_logininfo')
__slots__ = ('_logininfo',)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.username = None
self._logininfo = None

def login(self, username, password, hash=Hash.XMMD5, # pylint: disable=redefined-builtin
Expand All @@ -63,13 +62,11 @@ def login(self, username, password, hash=Hash.XMMD5, # pylint: disable=redefine
reply = self.request(request)
DVRIPRequestError.signal(request, reply)
self.session = reply.session
self.username = username
self._logininfo = reply

def logout(self):
assert self.session is not None
request = ClientLogout(username=self.username,
session=self.session)
request = ClientLogout(session=self.session)
self.request(request)
self.session = None

Expand All @@ -78,22 +75,22 @@ def connect(self, address, *args, **named):
return self.login(*args, **named)

def systeminfo(self):
reply = self.request(GetInfo(category=Info.SYSTEM,
reply = self.request(GetInfo(command=Info.SYSTEM,
session=self.session))
if reply.system is NotImplemented:
raise DVRIPDecodeError('invalid system info reply')
reply.system.chassis = self._logininfo.chassis
return reply.system

def storageinfo(self):
reply = self.request(GetInfo(category=Info.STORAGE,
reply = self.request(GetInfo(command=Info.STORAGE,
session=self.session))
if reply.storage is NotImplemented:
raise DVRIPDecodeError('invalid system info reply')
return reply.storage

def activityinfo(self):
reply = self.request(GetInfo(category=Info.ACTIVITY,
reply = self.request(GetInfo(command=Info.ACTIVITY,
session=self.session))
if reply.activity is NotImplemented:
raise DVRIPDecodeError('invalid system info reply')
Expand Down
8 changes: 4 additions & 4 deletions dvrip/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def json_to(cls, datum):
try:
return cls(json_to(str)(datum))
except ValueError:
raise DVRIPDecodeError('not a known info category')
raise DVRIPDecodeError('not a known info command')

SYSTEM = 'SystemInfo'
STORAGE = 'StorageInfo'
Expand Down Expand Up @@ -114,7 +114,7 @@ class GetInfoReply(Object, ControlMessage):
type = 1021

status: member[Status] = member('Ret')
category: member[Info] = member('Name')
command: member[Info] = member('Name')
session: member[Session] = member('SessionID')
system: optionalmember[SystemInfo] = optionalmember('SystemInfo')
storage: optionalmember[StorageInfo] = optionalmember('StorageInfo')
Expand All @@ -127,5 +127,5 @@ class GetInfo(Object, ControlRequest):
type = 1020
reply = GetInfoReply

category: member[Info] = member('Name')
session: member[Session] = member('SessionID')
command: member[Info] = member('Name')
session: member[Session] = member('SessionID')
14 changes: 7 additions & 7 deletions dvrip/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from string import ascii_lowercase, ascii_uppercase, digits
from .message import ControlMessage, ControlRequest, Session, Status
from .errors import DVRIPDecodeError
from .typing import Object, for_json, json_to, member, optionalmember
from .typing import Object, fixedmember, for_json, json_to, member, \
optionalmember

__all__ = ('xmmd5', 'Hash', 'ClientLoginReply', 'ClientLogin',
'ClientLogoutReply', 'ClientLogout')
Expand Down Expand Up @@ -72,15 +73,14 @@ class ClientLogin(Object, ControlRequest):
class ClientLogoutReply(Object, ControlMessage):
type = 1003

status: member[Status] = member('Ret')
username: member[str] = member('Name')
session: member[Session] = member('SessionID')
status: member[Status] = member('Ret')
command: fixedmember = fixedmember('Name', '')
session: member[Session] = member('SessionID')


class ClientLogout(Object, ControlRequest):
type = 1002
reply = ClientLogoutReply

# FIXME 'username' unused?
username: member[str] = member('Name')
session: member[Session] = member('SessionID')
command: fixedmember = fixedmember('Name', '')
session: member[Session] = member('SessionID')
62 changes: 43 additions & 19 deletions dvrip/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,36 @@ def _compose(*args: Callable[[Any], Any]) -> Callable[[Any], Any]:
return env['composition']


class absentmember(Member[Union['NotImplemented', T]]): # see python/mypy#4791
default = NotImplemented
key = NotImplemented
class fixedmember(Member[object]):
__slots__ = ('key', 'default')

def __init__(self, key: str, datum: object) -> None:
self.key = key
self.default = datum

def __get__(self,
obj: 'Object',
cls: type
) -> Union['fixedmember', object]:
if obj is None:
return self
return self.default

def __set__(self, obj: 'Object', value: object) -> None:
if value != self.default:
raise ValueError('not the fixed value')

def push(self, push: Callable[[str, object], None], value: object) -> None:
push(self.key, self.default)

def pop(self, pop: Callable[[str], object]) -> object:
if pop(self.key) != self.default:
raise DVRIPDecodeError('not the fixed value')
return self.default


class AttributeMember(Member[T]):
__slots__ = ()

def __get__(self, obj: 'Object', _type: type) -> Union['member[T]', T]:
if obj is None:
Expand All @@ -226,17 +253,24 @@ def __get__(self, obj: 'Object', _type: type) -> Union['member[T]', T]:
def __set__(self, obj: 'Object', value: T) -> None:
return setattr(obj._values_, self.name, value) # pylint: disable=protected-access

def push(self, push, value):

# NotImplemented is not allowed as a type, see python/mypy#4791
class absentmember(AttributeMember[Union['NotImplemented', T]]):
__slots__ = ()
default = NotImplemented

def push(self, push, value): # pylint: disable=no-self-use
if value is not NotImplemented:
raise ValueError('value provided for absent member {!r}'
.format(self.name))

def pop(self, pop):
def pop(self, pop): # pylint: disable=no-self-use
return NotImplemented


class member(Member[T]):
__slots__ = ('key', 'pipe', 'default', 'json_to', 'for_json')
class member(AttributeMember[T]):
__slots__ = ('key', 'pipe', 'json_to', 'for_json')
# Pylint incorrectly reports assignments below due to PyCQA/pylint#2807

def __init__(self,
key: str,
Expand All @@ -246,7 +280,7 @@ def __init__(self,
*args: Tuple[Callable[[Any], Any],
Callable[[Any], Any]],
) -> None:
self.key = key
self.key = key
if conv is not None:
self.pipe = (conv, *args)
else:
Expand All @@ -269,14 +303,6 @@ def __set_name__(self, cls: 'ObjectMeta', name: str) -> None:
self.json_to = _compose(*(p[0] for p in self.pipe))
self.for_json = _compose(*(p[1] for p in reversed(self.pipe)))

def __get__(self, obj: 'Object', _type: type) -> Union['member[T]', T]:
if obj is None:
return self
return getattr(obj._values_, self.name) # pylint: disable=protected-access

def __set__(self, obj: 'Object', value: T) -> None:
return setattr(obj._values_, self.name, value) # pylint: disable=protected-access

def push(self, push, value):
super().push(push, value)
push(self.key, self.for_json(value))
Expand Down Expand Up @@ -314,9 +340,7 @@ class ObjectMeta(ABCMeta):
def __new__(cls, name, bases, namespace, **kwargs) -> 'ObjectMeta':
names: MutableMapping[str, Member] = OrderedDict()
for mname, value in namespace.items():
if (_isunder(mname) or
not isinstance(value, Member) or
not hasattr(value, 'key')):
if _isunder(mname) or not isinstance(value, Member):
# Pytest-cov mistakenly thinks this branch is
# not taken. Place a print statement here to
# verify.
Expand Down
2 changes: 1 addition & 1 deletion dvrip_test
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ dvrip.packet._write = _write # pylint: disable=protected-access
conn = Client(Socket(AF_INET, SOCK_STREAM))
conn.connect((argv[1], 34567), argv[2], argv[3])
print(conn.systeminfo())
print(conn.request(GetInfo(session=conn.session, category=Info.STORAGE))
print(conn.request(GetInfo(session=conn.session, command=Info.STORAGE))
.storage)
conn.logout()
36 changes: 16 additions & 20 deletions test_dvrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,41 +359,37 @@ def test_ControlFilter_accept_invalid_overlap():
replies.accept(q)

def test_ClientLogout_topackets(session):
p, = (ClientLogout(username='admin', session=session)
.topackets(session, 0))
p, = (ClientLogout(session=session).topackets(session, 0))
assert p.encode() == (b'\xFF\x01\x00\x00\x57\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\xEA\x03\x2E\x00\x00\x00'
b'{"Name": "admin", "SessionID": "0x00000057"}'
b'\x00\x00\x00\x00\xEA\x03\x29\x00\x00\x00'
b'{"Name": "", "SessionID": "0x00000057"}'
b'\x0A\x00')

def test_ClientLogoutReply_accept():
data = (b'\xFF\x01\x00\x00\x57\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\xeb\x03\x3A\x00\x00\x00'
b'{ "Name" : "", "Ret" : 100, '
b'"SessionID" : "0x00000057" }\x0A\x00')
b'"SessionID" : "0x00000057" }'
b'\x0A\x00')
replies = ClientLogout.replies(0)
(n, m), = replies.accept(Packet.decode(data))
assert n == 0
assert (m.username == "" and m.status == Status(100) and # pylint: disable=no-value-for-parameter
m.session == Session(0x57))
assert m.status == Status(100) and m.session == Session(0x57)

def test_Client_logout(capsys, session, clinoconn, clitosrv, srvtocli):
p, = (ClientLogoutReply(status=Status.OK,
username='admin',
session=session)
.topackets(session, 2))
p.dump(srvtocli)
p, = (ClientLogoutReply(status=Status.OK,
username='admin',
session=session)
.topackets(session, 1))
p.dump(srvtocli)
srvtocli.seek(0)

clinoconn.username = 'admin'
clinoconn.logout()
clitosrv.seek(0); m = ClientLogout.frompackets([Packet.load(clitosrv)])
assert m == ClientLogout(username='admin', session=session)
assert m == ClientLogout(session=session)
out1, out2 = capsys.readouterr().out.split('\n')
assert out1.startswith('unrecognized packet: ') and out2 == ''

Expand Down Expand Up @@ -448,19 +444,19 @@ def test_version():
assert _versiontype == (_json_to_version, _version_for_json)

@given(sampled_from(list(Info.__members__.values())))
def test_Info_repr(cat):
assert repr(cat) == 'Info.{}'.format(cat.name)
def test_Info_repr(cmd):
assert repr(cmd) == 'Info.{}'.format(cmd.name)

@given(sampled_from(list(Info.__members__.values())))
def test_Info_str(cat):
assert str(cat) == cat.value
def test_Info_str(cmd):
assert str(cmd) == cmd.value

@given(sampled_from(list(Info.__members__.values())))
def test_Info_forjson(cat):
assert cat.for_json() == cat.value
def test_Info_forjson(cmd):
assert cmd.for_json() == cmd.value

@given(sampled_from(list(Info.__members__.values())))
def test_info_jsonto(cat):
assert Info.json_to(cat.value) == cat
with raises(DVRIPDecodeError, match='not a known info category'):
def test_info_jsonto(cmd):
assert Info.json_to(cmd.value) == cmd
with raises(DVRIPDecodeError, match='not a known info command'):
Info.json_to('SPAM')
25 changes: 23 additions & 2 deletions test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

from dvrip.errors import DVRIPDecodeError
from dvrip.typing import EnumValue, Member, Object, Value, absentmember, \
_compose, for_json, json_to, jsontype, member, \
optionalmember
_compose, fixedmember, for_json, json_to, jsontype, \
member, optionalmember


def test_forjson():
Expand Down Expand Up @@ -197,6 +197,27 @@ def hextext():
return (text(sampled_from('0123456789abcdef'))
.filter(lambda s: len(s) % 2 == 0))

class FixedExample(Object):
mint: fixedmember = fixedmember('Int', 57)

def test_fixedmember_get():
assert FixedExample().mint == 57

def test_fixedmember_set():
obj = FixedExample()
obj.mint = 57
with raises(ValueError, match='not the fixed value'):
obj.mint = 58
assert obj.mint == 57

def test_fixedmember_forjson():
assert FixedExample().for_json() == {'Int': 57}

def test_fixedmember_jsonto():
assert FixedExample.json_to({'Int': 57}) == FixedExample()
with raises(DVRIPDecodeError, match='not the fixed value'):
FixedExample.json_to({'Int': 58})

class AbsentExample(Object):
mint: absentmember[int] = absentmember()

Expand Down

0 comments on commit bc12a9e

Please sign in to comment.