Skip to content

Commit 8cb335b

Browse files
committed
Restructure source module
Implements #52 and flattens the structure a bit like suggested in #49
1 parent 13d6f81 commit 8cb335b

File tree

4 files changed

+139
-136
lines changed

4 files changed

+139
-136
lines changed

valve/source/__init__.py

Lines changed: 5 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,6 @@
1-
# -*- coding: utf-8 -*-
2-
# Copyright (C) 2017 Oliver Ainsworth
1+
from __future__ import absolute_import
32

4-
from __future__ import (absolute_import,
5-
unicode_literals, print_function, division)
6-
7-
import functools
8-
import select
9-
import socket
10-
import warnings
11-
12-
import six
13-
14-
15-
class NoResponseError(Exception):
16-
"""Raised when a server querier doesn't receive a response."""
17-
18-
19-
class QuerierClosedError(Exception):
20-
"""Raised when attempting to use a querier after it's closed."""
21-
22-
23-
class BaseQuerier(object):
24-
"""Base class for implementing source server queriers.
25-
26-
When an instance of this class is initialised a socket is created.
27-
It's important that, once a querier is to be discarded, the associated
28-
socket be closed via :meth:`close`. For example:
29-
30-
.. code-block:: python
31-
32-
querier = valve.source.BaseQuerier(('...', 27015))
33-
try:
34-
querier.request(...)
35-
finally:
36-
querier.close()
37-
38-
When server queriers are used as context managers, the socket will
39-
be cleaned up automatically. Hence it's preferably to use the `with`
40-
statement over the `try`-`finally` pattern described above:
41-
42-
.. code-block:: python
43-
44-
with valve.source.BaseQuerier(('...', 27015)) as querier:
45-
querier.request(...)
46-
47-
Once a querier has been closed, any attempts to make additional requests
48-
will result in a :exc:`QuerierClosedError` to be raised.
49-
50-
:ivar host: Host requests will be sent to.
51-
:ivar port: Port number requests will be sent to.
52-
:ivar timeout: How long to wait for a response to a request.
53-
"""
54-
55-
def __init__(self, address, timeout=5.0):
56-
self.host = address[0]
57-
self.port = address[1]
58-
self.timeout = timeout
59-
self._contextual = False
60-
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
61-
62-
def __enter__(self):
63-
self._contextual = True
64-
return self
65-
66-
def __exit__(self, type_, exception, traceback):
67-
self._contextual = False
68-
self.close()
69-
70-
def _check_open(function):
71-
# Wrap methods to raise QuerierClosedError when called
72-
# after the querier has been closed.
73-
74-
@functools.wraps(function)
75-
def wrapper(self, *args, **kwargs):
76-
if self._socket is None:
77-
raise QuerierClosedError
78-
return function(self, *args, **kwargs)
79-
80-
return wrapper
81-
82-
def close(self):
83-
"""Close the querier's socket.
84-
85-
It is safe to call this multiple times.
86-
"""
87-
if self._contextual:
88-
warnings.warn("{0.__class__.__name__} used as context "
89-
"manager but close called before exit".format(self))
90-
if self._socket is not None:
91-
self._socket.close()
92-
self._socket = None
93-
94-
@_check_open
95-
def request(self, *request):
96-
"""Issue a request.
97-
98-
The given request segments will be encoded and combined to
99-
form the final message that is sent to the configured address.
100-
101-
:param request: Request message segments.
102-
:type request: valve.source.messages.Message
103-
104-
:raises QuerierClosedError: If the querier has been closed.
105-
"""
106-
request_final = b"".join(segment.encode() for segment in request)
107-
self._socket.sendto(request_final, (self.host, self.port))
108-
109-
@_check_open
110-
def get_response(self):
111-
"""Wait for a response to a request.
112-
113-
:raises NoResponseError: If the configured :attr:`timeout` is
114-
reached before a response is received.
115-
:raises QuerierClosedError: If the querier has been closed.
116-
117-
:returns: The raw response as a :class:`bytes`.
118-
"""
119-
ready = select.select([self._socket], [], [], self.timeout)
120-
if not ready[0]:
121-
raise NoResponseError("Timed out waiting for response")
122-
try:
123-
data = ready[0][0].recv(1400)
124-
except socket.error as exc:
125-
six.raise_from(NoResponseError(exc))
126-
return data
127-
128-
del _check_open
3+
from .basequerier import BaseQuerier, NoResponseError, QuerierClosedError
4+
from .a2s import ServerQuerier
5+
from .master_server import MasterServerQuerier, Duplicates
6+
from .util import Platform, ServerType

valve/source/a2s.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@
66

77
import monotonic
88

9-
import valve.source
9+
from .basequerier import BaseQuerier, NoResponseError
1010
from . import messages
1111

1212

13-
# NOTE: backwards compatability; remove soon(tm)
14-
NoResponseError = valve.source.NoResponseError
1513

16-
17-
class ServerQuerier(valve.source.BaseQuerier):
14+
class ServerQuerier(BaseQuerier):
1815
"""Implements the A2S Source server query protocol.
1916
2017
https://developer.valvesoftware.com/wiki/Server_queries
@@ -30,7 +27,7 @@ def request(self, request):
3027

3128
def get_response(self):
3229

33-
data = valve.source.BaseQuerier.get_response(self)
30+
data = BaseQuerier.get_response(self)
3431

3532
# According to https://developer.valvesoftware.com/wiki/Server_queries
3633
# "TF2 currently does not split replies, expect A2S_PLAYER and

valve/source/basequerier.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (C) 2017 Oliver Ainsworth
3+
4+
from __future__ import (absolute_import,
5+
unicode_literals, print_function, division)
6+
7+
import functools
8+
import select
9+
import socket
10+
import warnings
11+
12+
import six
13+
14+
15+
class NoResponseError(Exception):
16+
"""Raised when a server querier doesn't receive a response."""
17+
18+
19+
class QuerierClosedError(Exception):
20+
"""Raised when attempting to use a querier after it's closed."""
21+
22+
23+
class BaseQuerier(object):
24+
"""Base class for implementing source server queriers.
25+
26+
When an instance of this class is initialised a socket is created.
27+
It's important that, once a querier is to be discarded, the associated
28+
socket be closed via :meth:`close`. For example:
29+
30+
.. code-block:: python
31+
32+
querier = valve.source.BaseQuerier(('...', 27015))
33+
try:
34+
querier.request(...)
35+
finally:
36+
querier.close()
37+
38+
When server queriers are used as context managers, the socket will
39+
be cleaned up automatically. Hence it's preferably to use the `with`
40+
statement over the `try`-`finally` pattern described above:
41+
42+
.. code-block:: python
43+
44+
with valve.source.BaseQuerier(('...', 27015)) as querier:
45+
querier.request(...)
46+
47+
Once a querier has been closed, any attempts to make additional requests
48+
will result in a :exc:`QuerierClosedError` to be raised.
49+
50+
:ivar host: Host requests will be sent to.
51+
:ivar port: Port number requests will be sent to.
52+
:ivar timeout: How long to wait for a response to a request.
53+
"""
54+
55+
def __init__(self, address, timeout=5.0):
56+
self.host = address[0]
57+
self.port = address[1]
58+
self.timeout = timeout
59+
self._contextual = False
60+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
61+
62+
def __enter__(self):
63+
self._contextual = True
64+
return self
65+
66+
def __exit__(self, type_, exception, traceback):
67+
self._contextual = False
68+
self.close()
69+
70+
def _check_open(function):
71+
# Wrap methods to raise QuerierClosedError when called
72+
# after the querier has been closed.
73+
74+
@functools.wraps(function)
75+
def wrapper(self, *args, **kwargs):
76+
if self._socket is None:
77+
raise QuerierClosedError
78+
return function(self, *args, **kwargs)
79+
80+
return wrapper
81+
82+
def close(self):
83+
"""Close the querier's socket.
84+
85+
It is safe to call this multiple times.
86+
"""
87+
if self._contextual:
88+
warnings.warn("{0.__class__.__name__} used as context "
89+
"manager but close called before exit".format(self))
90+
if self._socket is not None:
91+
self._socket.close()
92+
self._socket = None
93+
94+
@_check_open
95+
def request(self, *request):
96+
"""Issue a request.
97+
98+
The given request segments will be encoded and combined to
99+
form the final message that is sent to the configured address.
100+
101+
:param request: Request message segments.
102+
:type request: valve.source.messages.Message
103+
104+
:raises QuerierClosedError: If the querier has been closed.
105+
"""
106+
request_final = b"".join(segment.encode() for segment in request)
107+
self._socket.sendto(request_final, (self.host, self.port))
108+
109+
@_check_open
110+
def get_response(self):
111+
"""Wait for a response to a request.
112+
113+
:raises NoResponseError: If the configured :attr:`timeout` is
114+
reached before a response is received.
115+
:raises QuerierClosedError: If the querier has been closed.
116+
117+
:returns: The raw response as a :class:`bytes`.
118+
"""
119+
ready = select.select([self._socket], [], [], self.timeout)
120+
if not ready[0]:
121+
raise NoResponseError("Timed out waiting for response")
122+
try:
123+
data = ready[0][0].recv(1400)
124+
except socket.error as exc:
125+
six.raise_from(NoResponseError(exc))
126+
return data
127+
128+
del _check_open

valve/source/master_server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import six
1111

12-
import valve.source
12+
from .basequerier import BaseQuerier, NoResponseError
1313
from . import messages
1414
from . import util
1515

@@ -44,7 +44,7 @@ class Duplicates(enum.Enum):
4444
STOP = "stop"
4545

4646

47-
class MasterServerQuerier(valve.source.BaseQuerier):
47+
class MasterServerQuerier(BaseQuerier):
4848
"""Implements the Source master server query protocol
4949
5050
https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol
@@ -103,7 +103,7 @@ def _query(self, region, filter_string):
103103
region=region, address=last_addr, filter=filter_string))
104104
try:
105105
raw_response = self.get_response()
106-
except valve.source.NoResponseError:
106+
except NoResponseError:
107107
return
108108
else:
109109
response = messages.MasterServerResponse.decode(raw_response)

0 commit comments

Comments
 (0)