Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add types to synapse.util. #10601

Merged
merged 41 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ff8f94d
fairly mechanical changes
reivilibre Aug 13, 2021
6deee28
stranger changes (REVIEW)
reivilibre Aug 13, 2021
3c2b4dd
Newsfile & mypy.ini
reivilibre Aug 13, 2021
1d0b435
Put the switch back in to the 'more magic' position
reivilibre Aug 16, 2021
22df193
Fix up some more types
reivilibre Aug 16, 2021
e36db3f
Update annotations in util
reivilibre Aug 18, 2021
db57064
Fix fallout (related annotations and assertions around codebase)
reivilibre Aug 18, 2021
cd15b4b
Merge remote-tracking branch 'origin/develop' into rei/types1
reivilibre Aug 18, 2021
348f9ff
antilint
reivilibre Aug 18, 2021
d081c83
add type parameters for Deferreds
reivilibre Aug 18, 2021
76c3b6b
Fix circular import of HomeServer
reivilibre Aug 23, 2021
30ffee4
Quote deferreds in method signatures
reivilibre Aug 23, 2021
10bd84f
Annotate more types
reivilibre Sep 1, 2021
0c26b7f
Use attrs class and fix ignored fields [WANTS REVIEW]
reivilibre Sep 1, 2021
715bfdc
Ignore import issues [WANTS REVIEW]
reivilibre Sep 1, 2021
1e4632f
Annotate more types
reivilibre Sep 1, 2021
05cc10c
Annotate more types
reivilibre Sep 2, 2021
1c6704c
Annotate types and ignore Twisted issues [WANTS REVIEW]
reivilibre Sep 2, 2021
c384373
Add IReactorThreads as parent of ISynapseReactor
reivilibre Sep 2, 2021
884a8b6
Annotate more types
reivilibre Sep 2, 2021
a22f4c0
Add type annotation fixes to fix CI
reivilibre Sep 2, 2021
029bf34
Merge remote-tracking branch 'origin/develop' into rei/types1
reivilibre Sep 2, 2021
9444ca1
Resolve type issue that arose from merge
reivilibre Sep 2, 2021
a0aef0b
Back out of generics due to python-attrs/attrs#313
reivilibre Sep 2, 2021
289df40
Quote return types with Deferreds
reivilibre Sep 3, 2021
8e719ed
Fix use of None as default
reivilibre Sep 6, 2021
34e327d
Use a cast to work around Mocks not working with isinstance
reivilibre Sep 6, 2021
cd9a68d
Fix up parameters which were previously silently ignored
reivilibre Sep 6, 2021
b4cded1
Apply suggestions
reivilibre Sep 8, 2021
6f7fac0
Use `cast` to IReactorTime [WANTS REVIEW]
reivilibre Sep 8, 2021
d4afbca
Add types and casts to `__exit__` [REVIEW]
reivilibre Sep 8, 2021
f5cee54
Fix adherence to Jinja2's interface [REVIEW]
reivilibre Sep 8, 2021
12cfb9a
Annotate `WheelTimer`, notably `bucket_size`
reivilibre Sep 8, 2021
e69a3d6
Update Newsfile
reivilibre Sep 8, 2021
9f301ae
Note that code was lifted from CPython
reivilibre Sep 8, 2021
e6618d7
Add more type annotations
reivilibre Sep 8, 2021
b1b4f1b
Enable stricter checking on applicable modules
reivilibre Sep 8, 2021
ea4f7e0
Merge remote-tracking branch 'origin/develop' into rei/types1
reivilibre Sep 8, 2021
8871674
Correct types used in `__exit__`
reivilibre Sep 8, 2021
20d63a0
Fix up manhole types after merge [REVIEW, SEE DESC]
reivilibre Sep 8, 2021
19a602e
Avoid using evil typecasts
reivilibre Sep 10, 2021
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
1 change: 1 addition & 0 deletions changelog.d/10601.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add type annotations to complete the synapse.util package.
12 changes: 1 addition & 11 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,7 @@ files =
synapse/storage/util,
synapse/streams,
synapse/types.py,
synapse/util/async_helpers.py,
synapse/util/caches,
synapse/util/daemonize.py,
synapse/util/hash.py,
synapse/util/iterutils.py,
synapse/util/linked_list.py,
synapse/util/metrics.py,
synapse/util/macaroons.py,
synapse/util/module_loader.py,
synapse/util/msisdn.py,
synapse/util/stringutils.py,
synapse/util,
synapse/visibility.py,
tests/replication,
tests/test_event_auth.py,
Expand Down
2 changes: 1 addition & 1 deletion synapse/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Clock:

@defer.inlineCallbacks
def sleep(self, seconds):
d = defer.Deferred()
d: defer.Deferred = defer.Deferred()
with context.PreserveLoggingContext():
self._reactor.callLater(seconds, d.callback, seconds)
res = yield d
Expand Down
2 changes: 1 addition & 1 deletion synapse/util/batching_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ async def add_to_queue(self, value: V, key: Hashable = ()) -> R:

# First we create a defer and add it and the value to the list of
# pending items.
d = defer.Deferred()
d: defer.Deferred = defer.Deferred()
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
self._next_values.setdefault(key, []).append((value, d))

# If we're not currently processing the key fire off a background
Expand Down
16 changes: 10 additions & 6 deletions synapse/util/file_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import queue
from typing import Optional

from twisted.internet import threads

Expand Down Expand Up @@ -51,15 +52,15 @@ def __init__(self, file_obj, reactor):

# Queue of slices of bytes to be written. When producer calls
# unregister a final None is sent.
self._bytes_queue = queue.Queue()
self._bytes_queue: queue.Queue[Optional[bytes]] = queue.Queue()

# Deferred that is resolved when finished writing
self._finished_deferred = None

# If the _writer thread throws an exception it gets stored here.
self._write_exception = None

def registerProducer(self, producer, streaming):
def registerProducer(self, producer, streaming) -> None:
"""Part of IConsumer interface

Args:
Expand All @@ -81,17 +82,19 @@ def registerProducer(self, producer, streaming):
if not streaming:
self._producer.resumeProducing()

def unregisterProducer(self):
def unregisterProducer(self) -> None:
"""Part of IProducer interface"""
self._producer = None
assert self._finished_deferred is not None
if not self._finished_deferred.called:
self._bytes_queue.put_nowait(None)

def write(self, bytes):
def write(self, bytes) -> None:
"""Part of IProducer interface"""
if self._write_exception:
raise self._write_exception

assert self._finished_deferred is not None
if self._finished_deferred.called:
raise Exception("consumer has closed")

Expand All @@ -101,9 +104,10 @@ def write(self, bytes):
# then we pause the producer.
if self.streaming and self._bytes_queue.qsize() >= self._PAUSE_ON_QUEUE_SIZE:
self._paused_producer = True
assert self._producer is not None
self._producer.pauseProducing()

def _writer(self):
def _writer(self) -> None:
"""This is run in a background thread to write to the file."""
try:
while self._producer or not self._bytes_queue.empty():
Expand Down Expand Up @@ -134,7 +138,7 @@ def wait(self):
"""Returns a deferred that resolves when finished writing to file"""
return make_deferred_yieldable(self._finished_deferred)

def _resume_paused_producer(self):
def _resume_paused_producer(self) -> None:
"""Gets called if we should resume producing after being paused"""
if self._paused_producer and self._producer:
self._paused_producer = False
Expand Down
17 changes: 10 additions & 7 deletions synapse/util/manhole.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ def manhole(username, password, globals):
checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(**{username: password})

rlm = manhole_ssh.TerminalRealm()
rlm.chainedProtocolFactory = lambda: insults.ServerProtocol(
# mypy ignored here because:
# - can't deduce types of lambdas
# - variable is Type[ServerProtocol], expr is Callable[[], ServerProtocol]
rlm.chainedProtocolFactory = lambda: insults.ServerProtocol( # type: ignore[misc,assignment]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably better to create a real temporary class here:

class SynapseServerProtocol(insults.ServerProtocol):
    def __init__(self):
        super().__init__(SynapseManhole, dict(globals, __name__="__console__"))

I think should work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried something of that sort, it then said (paraphrased): found Type[SynapseServerProtocol] expected Type[ServerProtocol].

SynapseManhole, dict(globals, __name__="__console__")
)

Expand Down Expand Up @@ -110,6 +113,7 @@ def showsyntaxerror(self, filename=None):
any syntax errors to be sent to the terminal, rather than sentry.
"""
type, value, tb = sys.exc_info()
assert value is not None
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
Expand All @@ -135,9 +139,8 @@ def showtraceback(self):
"""
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
sys.last_traceback = last_tb
try:
# We remove the first stack item because it is our own code.
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
self.write("".join(lines))
finally:
last_tb = ei = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think it is safe to remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last_tb and ei are dead.

can you see a reason why it's like that? It really needs a comment if there's a reason :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the original was cargo-culted from https://github.com/python/cpython/blob/main/Lib/code.py#L150, if that helps at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last_tb and ei are dead.

Technically not, an exception raised here keeps the local frame around to allow inspection (which is how things like Sentry can report the local variables of each frame in the stack).

I don't know if or why that is an important detail (maybe to prevent recursion if the exception thrown goes back via this function? Or just preventing circular references?), but I'm loathed to remove something like that unless we know why it was put there. (Obligatory reference to Chesterton's fence)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminds me of http://www.catb.org/jargon/html/magic-story.html.

I did wonder if there was some subtle reason behind it -- that's why I marked this commit as REVIEW after all.

I guess I will reinstate it, your explanation makes sense enough to hint at a possible context for it being there, pity nobody thought of explaining it though (from the original source) :).

(I wonder why they didn't use del if there was a good reason for it being here? Nonetheless, I'm minded to leave it as it was -- in the more magic position -- I was just curious if you knew what it was!)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically not, an exception raised here keeps the local frame around to allow inspection (which is how things like Sentry can report the local variables of each frame in the stack).

I don't follow this. This method doesn't raise an exception, so how would last_db and ei be retained in a stack frame?

I think it's worth digging into the history of the cpython code. It looks to me like those lines were introduced before @reivilibre was born, and at the time there was more code where it might have been useful that the traceback be gc-able.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming self.write could throw, otherwise there's also a question over why it uses try/finally

assert last_tb is not None

# We remove the first stack item because it is our own code.
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
self.write("".join(lines))
21 changes: 14 additions & 7 deletions synapse/util/ratelimitutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,38 @@
import collections
import contextlib
import logging
from typing import DefaultDict

from twisted.internet import defer

from synapse.api.errors import LimitExceededError
from synapse.config.ratelimiting import FederationRateLimitConfig
from synapse.logging.context import (
PreserveLoggingContext,
make_deferred_yieldable,
run_in_background,
)
from synapse.util import Clock

logger = logging.getLogger(__name__)


class FederationRateLimiter:
def __init__(self, clock, config):
def __init__(self, clock: Clock, config: FederationRateLimitConfig):
"""
Args:
clock (Clock)
config (FederationRateLimitConfig)
clock
config
"""

def new_limiter():
return _PerHostRatelimiter(clock=clock, config=config)

self.ratelimiters = collections.defaultdict(new_limiter)
self.ratelimiters: DefaultDict[
str, "_PerHostRatelimiter"
] = collections.defaultdict(new_limiter)

def ratelimit(self, host):
def ratelimit(self, host: str):
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
"""Used to ratelimit an incoming request from a given host

Example usage:
Expand Down Expand Up @@ -79,7 +84,9 @@ def __init__(self, clock, config):

# map from request_id object to Deferred for requests which are ready
# for processing but have been queued
self.ready_request_queue = collections.OrderedDict()
self.ready_request_queue: collections.OrderedDict[
object, defer.Deferred
] = collections.OrderedDict()

# request id objects for requests which are in progress
self.current_processing = set()
Expand Down Expand Up @@ -122,7 +129,7 @@ def _on_enter(self, request_id):

def queue_request():
if len(self.current_processing) >= self.concurrent_requests:
queue_defer = defer.Deferred()
queue_defer: defer.Deferred = defer.Deferred()
reivilibre marked this conversation as resolved.
Show resolved Hide resolved
self.ready_request_queue[request_id] = queue_defer
logger.info(
"Ratelimiter: queueing request (queue now %i items)",
Expand Down
5 changes: 3 additions & 2 deletions synapse/util/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
def build_jinja_env(
template_search_directories: Iterable[str],
config: "HomeServerConfig",
autoescape: Union[bool, Callable[[str], bool], None] = None,
autoescape: Union[bool, Callable[[Optional[str]], bool], None] = None,
) -> jinja2.Environment:
"""Set up a Jinja2 environment to load templates from the given search path

Expand Down Expand Up @@ -56,7 +56,8 @@ def build_jinja_env(
if autoescape is None:
autoescape = jinja2.select_autoescape()

loader = jinja2.FileSystemLoader(template_search_directories)
# the type signature of this is wrong
loader = jinja2.FileSystemLoader(template_search_directories) # type: ignore[arg-type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it? It looks like it should accept a Sequence[str]? Which suggests our types our wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I suppose the types are an interface detail, and the actual implementation-accepted types can change. Will address.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm don't understand what you mean there, sorry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jinja2 currently accepts Iterable[str] but says it will accept Sequence[str].
So morally speaking, they might change the implementation that breaks something so that Iterable[str] doesn't work anymore, and it would be silly us for not trusting their type annotations.
(Hence I've converted our iterator to a List.)

env = jinja2.Environment(loader=loader, autoescape=autoescape)

# Update the environment with our custom filters
Expand Down