summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2020-01-22 10:37:35 +0000
committerErik Johnston <erik@matrix.org>2020-01-22 10:37:35 +0000
commit83ae89a7bc26945779542383bba652da3d72485a (patch)
tree388a0d42396d5f7d117039570b2803df1eaf57b6
parentAdd a DeltaState to track changes to be made to current state (#6716) (diff)
downloadsynapse-83ae89a7bc26945779542383bba652da3d72485a.tar.xz
Refactor HomeServer object to work with type hints
-rw-r--r--synapse/server.py419
-rw-r--r--synapse/server.pyi99
2 files changed, 216 insertions, 302 deletions
diff --git a/synapse/server.py b/synapse/server.py

index 7926867b77..5a0ec4059e 100644 --- a/synapse/server.py +++ b/synapse/server.py
@@ -24,10 +24,15 @@ import abc import logging import os +from functools import wraps +from typing import Any, Callable, Dict, List, Optional, TypeVar, cast +from twisted.internet import tcp from twisted.mail.smtp import sendmail from twisted.web.client import BrowserLikePolicyForHTTPS +from twisted.web.iweb import IPolicyForHTTPS +import synapse from synapse.api.auth import Auth from synapse.api.filtering import Filtering from synapse.api.ratelimiting import Ratelimiter @@ -104,6 +109,54 @@ from synapse.util.distributor import Distributor logger = logging.getLogger(__name__) +FuncType = Callable[..., Any] +F = TypeVar("F", bound=FuncType) + + +class _InjectionDescriptor(object): + """A decorator for HomeServer class to implement dependency injection, i.e. + caching built classes and detecting cyclic dependencies. + """ + + def __init__(self, f: F): + self.f = f + + if not f.__name__.startswith("get_"): + raise Exception("Function must be named `get_*`") + + self.depname = self.f.__name__[len("get_") :] # type: str + + def __get__(self, obj, objtype=None): + @wraps(self.f) + def build(): + try: + return getattr(obj, self.depname) + except AttributeError: + pass + + # Prevent cyclic dependencies from deadlocking + if self.depname in obj._building: + raise ValueError( + "Cyclic dependency while building %s" % (self.depname,) + ) + obj._building[self.depname] = True + + dep = self.f(obj) + + setattr(obj, self.depname, dep) + + return dep + + return build + + +def builder(f: F) -> F: + """Decorator to wrap a HomeServer method to cache result and detect + cyclical dependencies. + """ + return cast(F, _InjectionDescriptor(f)) + + class HomeServer(object): """A basic homeserver object without lazy component builders. @@ -111,17 +164,6 @@ class HomeServer(object): constructor arguments, or the relevant methods overriding to create them. Typically this would only be used for unit tests. - For every dependency in the DEPENDENCIES list below, this class creates one - method, - def get_DEPENDENCY(self) - which returns the value of that dependency. If no value has yet been set - nor was provided to the constructor, it will attempt to call a lazy builder - method called - def build_DEPENDENCY(self) - which must be implemented by the subclass. This code may call any of the - required "get" methods on the instance to obtain the sub-dependencies that - one requires. - Attributes: config (synapse.config.homeserver.HomeserverConfig): _listening_services (list[twisted.internet.tcp.Port]): TCP ports that @@ -130,77 +172,6 @@ class HomeServer(object): __metaclass__ = abc.ABCMeta - DEPENDENCIES = [ - "http_client", - "federation_client", - "federation_server", - "handlers", - "auth", - "room_creation_handler", - "state_handler", - "state_resolution_handler", - "presence_handler", - "sync_handler", - "typing_handler", - "room_list_handler", - "acme_handler", - "auth_handler", - "device_handler", - "stats_handler", - "e2e_keys_handler", - "e2e_room_keys_handler", - "event_handler", - "event_stream_handler", - "initial_sync_handler", - "application_service_api", - "application_service_scheduler", - "application_service_handler", - "device_message_handler", - "profile_handler", - "event_creation_handler", - "deactivate_account_handler", - "set_password_handler", - "notifier", - "event_sources", - "keyring", - "pusherpool", - "event_builder_factory", - "filtering", - "http_client_context_factory", - "simple_http_client", - "proxied_http_client", - "media_repository", - "media_repository_resource", - "federation_transport_client", - "federation_sender", - "receipts_handler", - "macaroon_generator", - "tcp_replication", - "read_marker_handler", - "action_generator", - "user_directory_handler", - "groups_local_handler", - "groups_server_handler", - "groups_attestation_signing", - "groups_attestation_renewer", - "secrets", - "spam_checker", - "third_party_event_rules", - "room_member_handler", - "federation_registry", - "server_notices_manager", - "server_notices_sender", - "message_handler", - "pagination_handler", - "room_context_handler", - "sendmail", - "registration_handler", - "account_validity_handler", - "saml_handler", - "event_client_serializer", - "storage", - ] - REQUIRED_ON_MASTER_STARTUP = ["user_directory_handler", "stats_handler"] # This is overridden in derived application classes @@ -215,14 +186,16 @@ class HomeServer(object): config: The full config for the homeserver. """ if not reactor: - from twisted.internet import reactor + from twisted import internet + + reactor = internet.reactor self._reactor = reactor self.hostname = hostname self.config = config - self._building = {} - self._listening_services = [] - self.start_time = None + self._building = {} # type: Dict[str, bool] + self._listening_services = [] # type: List[tcp.Port] + self.start_time = None # type: Optional[int] self.clock = Clock(reactor) self.distributor = Distributor() @@ -230,7 +203,7 @@ class HomeServer(object): self.admin_redaction_ratelimiter = Ratelimiter() self.registration_ratelimiter = Ratelimiter() - self.datastores = None + self.datastores = None # type: Optional[DataStores] # Other kwargs are explicit dependencies for depname in kwargs: @@ -261,182 +234,231 @@ class HomeServer(object): # X-Forwarded-For is handled by our custom request type. return request.getClientIP() - def is_mine(self, domain_specific_string): + def is_mine(self, domain_specific_string) -> bool: return domain_specific_string.domain == self.hostname - def is_mine_id(self, string): + def is_mine_id(self, string: str) -> bool: return string.split(":", 1)[1] == self.hostname - def get_clock(self): + def get_clock(self) -> Clock: return self.clock def get_datastore(self): + if not self.datastores: + raise Exception("HomeServer has not been set up yet") + return self.datastores.main - def get_datastores(self): + def get_datastores(self) -> DataStores: + if not self.datastores: + raise Exception("HomeServer has not been set up yet") + return self.datastores - def get_config(self): + def get_config(self) -> HomeServerConfig: return self.config - def get_distributor(self): + def get_distributor(self) -> Distributor: return self.distributor - def get_ratelimiter(self): + def get_ratelimiter(self) -> Ratelimiter: return self.ratelimiter - def get_registration_ratelimiter(self): + def get_registration_ratelimiter(self) -> Ratelimiter: return self.registration_ratelimiter - def get_admin_redaction_ratelimiter(self): + def get_admin_redaction_ratelimiter(self) -> Ratelimiter: return self.admin_redaction_ratelimiter - def build_federation_client(self): + @builder + def get_federation_client(self) -> FederationClient: return FederationClient(self) - def build_federation_server(self): + @builder + def get_federation_server(self) -> FederationServer: return FederationServer(self) - def build_handlers(self): + @builder + def get_handlers(self) -> Handlers: return Handlers(self) - def build_notifier(self): + @builder + def get_notifier(self) -> Notifier: return Notifier(self) - def build_auth(self): + @builder + def get_auth(self) -> Auth: return Auth(self) - def build_http_client_context_factory(self): + @builder + def get_http_client_context_factory(self) -> IPolicyForHTTPS: return ( InsecureInterceptableContextFactory() if self.config.use_insecure_ssl_client_just_for_testing_do_not_use else BrowserLikePolicyForHTTPS() ) - def build_simple_http_client(self): + @builder + def get_simple_http_client(self) -> SimpleHttpClient: return SimpleHttpClient(self) - def build_proxied_http_client(self): + @builder + def get_proxied_http_client(self) -> SimpleHttpClient: return SimpleHttpClient( self, http_proxy=os.getenvb(b"http_proxy"), https_proxy=os.getenvb(b"HTTPS_PROXY"), ) - def build_room_creation_handler(self): + @builder + def get_room_creation_handler(self) -> RoomCreationHandler: return RoomCreationHandler(self) - def build_sendmail(self): + @builder + def get_sendmail(self) -> sendmail: return sendmail - def build_state_handler(self): + @builder + def get_state_handler(self) -> StateHandler: return StateHandler(self) - def build_state_resolution_handler(self): + @builder + def get_state_resolution_handler(self) -> StateResolutionHandler: return StateResolutionHandler(self) - def build_presence_handler(self): + @builder + def get_presence_handler(self) -> PresenceHandler: return PresenceHandler(self) - def build_typing_handler(self): + @builder + def get_typing_handler(self) -> TypingHandler: return TypingHandler(self) - def build_sync_handler(self): + @builder + def get_sync_handler(self) -> SyncHandler: return SyncHandler(self) - def build_room_list_handler(self): + @builder + def get_room_list_handler(self) -> RoomListHandler: return RoomListHandler(self) - def build_auth_handler(self): + @builder + def get_auth_handler(self) -> AuthHandler: return AuthHandler(self) - def build_macaroon_generator(self): + @builder + def get_macaroon_generator(self) -> MacaroonGenerator: return MacaroonGenerator(self) - def build_device_handler(self): + @builder + def get_device_handler(self) -> DeviceWorkerHandler: if self.config.worker_app: return DeviceWorkerHandler(self) else: return DeviceHandler(self) - def build_device_message_handler(self): + @builder + def get_device_message_handler(self) -> DeviceMessageHandler: return DeviceMessageHandler(self) - def build_e2e_keys_handler(self): + @builder + def get_e2e_keys_handler(self) -> E2eKeysHandler: return E2eKeysHandler(self) - def build_e2e_room_keys_handler(self): + @builder + def get_e2e_room_keys_handler(self) -> E2eRoomKeysHandler: return E2eRoomKeysHandler(self) - def build_acme_handler(self): + @builder + def get_acme_handler(self) -> AcmeHandler: return AcmeHandler(self) - def build_application_service_api(self): + @builder + def get_application_service_api(self) -> ApplicationServiceApi: return ApplicationServiceApi(self) - def build_application_service_scheduler(self): + @builder + def get_application_service_scheduler(self) -> ApplicationServiceScheduler: return ApplicationServiceScheduler(self) - def build_application_service_handler(self): + @builder + def get_application_service_handler(self) -> ApplicationServicesHandler: return ApplicationServicesHandler(self) - def build_event_handler(self): + @builder + def get_event_handler(self) -> EventHandler: return EventHandler(self) - def build_event_stream_handler(self): + @builder + def get_event_stream_handler(self) -> EventStreamHandler: return EventStreamHandler(self) - def build_initial_sync_handler(self): + @builder + def get_initial_sync_handler(self) -> InitialSyncHandler: return InitialSyncHandler(self) - def build_profile_handler(self): + @builder + def get_profile_handler(self): if self.config.worker_app: return BaseProfileHandler(self) else: return MasterProfileHandler(self) - def build_event_creation_handler(self): + @builder + def get_event_creation_handler(self) -> EventCreationHandler: return EventCreationHandler(self) - def build_deactivate_account_handler(self): + @builder + def get_deactivate_account_handler(self) -> DeactivateAccountHandler: return DeactivateAccountHandler(self) - def build_set_password_handler(self): + @builder + def get_set_password_handler(self) -> SetPasswordHandler: return SetPasswordHandler(self) - def build_event_sources(self): + @builder + def get_event_sources(self) -> EventSources: return EventSources(self) - def build_keyring(self): + @builder + def get_keyring(self) -> Keyring: return Keyring(self) - def build_event_builder_factory(self): + @builder + def get_event_builder_factory(self) -> EventBuilderFactory: return EventBuilderFactory(self) - def build_filtering(self): + @builder + def get_filtering(self) -> Filtering: return Filtering(self) - def build_pusherpool(self): + @builder + def get_pusherpool(self) -> PusherPool: return PusherPool(self) - def build_http_client(self): + @builder + def get_http_client(self) -> MatrixFederationHttpClient: tls_client_options_factory = context_factory.ClientTLSOptionsFactory( self.config ) return MatrixFederationHttpClient(self, tls_client_options_factory) - def build_media_repository_resource(self): + @builder + def get_media_repository_resource(self) -> MediaRepositoryResource: # build the media repo resource. This indirects through the HomeServer # to ensure that we only have a single instance of return MediaRepositoryResource(self) - def build_media_repository(self): + @builder + def get_media_repository(self) -> MediaRepository: return MediaRepository(self) - def build_federation_transport_client(self): + @builder + def get_federation_transport_client(self) -> TransportLayerClient: return TransportLayerClient(self) - def build_federation_sender(self): + @builder + def get_federation_sender(self): if self.should_send_federation(): return FederationSender(self) elif not self.config.worker_app: @@ -444,135 +466,126 @@ class HomeServer(object): else: raise Exception("Workers cannot send federation traffic") - def build_receipts_handler(self): + @builder + def get_receipts_handler(self) -> ReceiptsHandler: return ReceiptsHandler(self) - def build_read_marker_handler(self): + @builder + def get_read_marker_handler(self) -> ReadMarkerHandler: return ReadMarkerHandler(self) - def build_tcp_replication(self): + @builder + def get_tcp_replication(self): raise NotImplementedError() - def build_action_generator(self): + @builder + def get_action_generator(self) -> ActionGenerator: return ActionGenerator(self) - def build_user_directory_handler(self): + @builder + def get_user_directory_handler(self) -> UserDirectoryHandler: return UserDirectoryHandler(self) - def build_groups_local_handler(self): + @builder + def get_groups_local_handler(self) -> GroupsLocalHandler: return GroupsLocalHandler(self) - def build_groups_server_handler(self): + @builder + def get_groups_server_handler(self) -> GroupsServerHandler: return GroupsServerHandler(self) - def build_groups_attestation_signing(self): + @builder + def get_groups_attestation_signing(self) -> GroupAttestationSigning: return GroupAttestationSigning(self) - def build_groups_attestation_renewer(self): + @builder + def get_groups_attestation_renewer(self) -> GroupAttestionRenewer: return GroupAttestionRenewer(self) - def build_secrets(self): + @builder + def get_secrets(self): return Secrets() - def build_stats_handler(self): + @builder + def get_stats_handler(self) -> StatsHandler: return StatsHandler(self) - def build_spam_checker(self): + @builder + def get_spam_checker(self) -> SpamChecker: return SpamChecker(self) - def build_third_party_event_rules(self): + @builder + def get_third_party_event_rules(self) -> ThirdPartyEventRules: return ThirdPartyEventRules(self) - def build_room_member_handler(self): + @builder + def get_room_member_handler(self): if self.config.worker_app: return RoomMemberWorkerHandler(self) return RoomMemberMasterHandler(self) - def build_federation_registry(self): + @builder + def get_federation_registry(self): if self.config.worker_app: return ReplicationFederationHandlerRegistry(self) else: return FederationHandlerRegistry() - def build_server_notices_manager(self): + @builder + def get_server_notices_manager(self) -> ServerNoticesManager: if self.config.worker_app: raise Exception("Workers cannot send server notices") return ServerNoticesManager(self) - def build_server_notices_sender(self): + @builder + def get_server_notices_sender(self): if self.config.worker_app: return WorkerServerNoticesSender(self) return ServerNoticesSender(self) - def build_message_handler(self): + @builder + def get_message_handler(self) -> MessageHandler: return MessageHandler(self) - def build_pagination_handler(self): + @builder + def get_pagination_handler(self) -> PaginationHandler: return PaginationHandler(self) - def build_room_context_handler(self): + @builder + def get_room_context_handler(self) -> RoomContextHandler: return RoomContextHandler(self) - def build_registration_handler(self): + @builder + def get_registration_handler(self) -> RegistrationHandler: return RegistrationHandler(self) - def build_account_validity_handler(self): + @builder + def get_account_validity_handler(self) -> AccountValidityHandler: return AccountValidityHandler(self) - def build_saml_handler(self): + @builder + def get_saml_handler(self) -> "synapse.handlers.saml_handler.SamlHandler": from synapse.handlers.saml_handler import SamlHandler return SamlHandler(self) - def build_event_client_serializer(self): + @builder + def get_event_client_serializer(self) -> EventClientSerializer: return EventClientSerializer(self) - def build_storage(self) -> Storage: + @builder + def get_storage(self) -> Storage: + if self.datastores is None: + raise Exception("HomeServer has not been set up yet") + return Storage(self, self.datastores) - def remove_pusher(self, app_id, push_key, user_id): + def remove_pusher(self, app_id: str, push_key: str, user_id: str): return self.get_pusherpool().remove_pusher(app_id, push_key, user_id) - def should_send_federation(self): + def should_send_federation(self) -> bool: "Should this server be sending federation traffic directly?" return self.config.send_federation and ( not self.config.worker_app or self.config.worker_app == "synapse.app.federation_sender" ) - - -def _make_dependency_method(depname): - def _get(hs): - try: - return getattr(hs, depname) - except AttributeError: - pass - - try: - builder = getattr(hs, "build_%s" % (depname)) - except AttributeError: - builder = None - - if builder: - # Prevent cyclic dependencies from deadlocking - if depname in hs._building: - raise ValueError("Cyclic dependency while building %s" % (depname,)) - hs._building[depname] = 1 - - dep = builder() - setattr(hs, depname, dep) - - del hs._building[depname] - - return dep - - raise NotImplementedError( - "%s has no %s nor a builder for it" % (type(hs).__name__, depname) - ) - - setattr(HomeServer, "get_%s" % (depname), _get) - - -# Build magic accessors for every dependency -for depname in HomeServer.DEPENDENCIES: - _make_dependency_method(depname) diff --git a/synapse/server.pyi b/synapse/server.pyi deleted file mode 100644
index 0731403047..0000000000 --- a/synapse/server.pyi +++ /dev/null
@@ -1,99 +0,0 @@ -import twisted.internet - -import synapse.api.auth -import synapse.config.homeserver -import synapse.federation.sender -import synapse.federation.transaction_queue -import synapse.federation.transport.client -import synapse.handlers -import synapse.handlers.auth -import synapse.handlers.deactivate_account -import synapse.handlers.device -import synapse.handlers.e2e_keys -import synapse.handlers.message -import synapse.handlers.presence -import synapse.handlers.room -import synapse.handlers.room_member -import synapse.handlers.set_password -import synapse.http.client -import synapse.notifier -import synapse.rest.media.v1.media_repository -import synapse.server_notices.server_notices_manager -import synapse.server_notices.server_notices_sender -import synapse.state -import synapse.storage - -class HomeServer(object): - @property - def config(self) -> synapse.config.homeserver.HomeServerConfig: - pass - def get_auth(self) -> synapse.api.auth.Auth: - pass - def get_auth_handler(self) -> synapse.handlers.auth.AuthHandler: - pass - def get_datastore(self) -> synapse.storage.DataStore: - pass - def get_device_handler(self) -> synapse.handlers.device.DeviceHandler: - pass - def get_e2e_keys_handler(self) -> synapse.handlers.e2e_keys.E2eKeysHandler: - pass - def get_handlers(self) -> synapse.handlers.Handlers: - pass - def get_state_handler(self) -> synapse.state.StateHandler: - pass - def get_state_resolution_handler(self) -> synapse.state.StateResolutionHandler: - pass - def get_simple_http_client(self) -> synapse.http.client.SimpleHttpClient: - """Fetch an HTTP client implementation which doesn't do any blacklisting - or support any HTTP_PROXY settings""" - pass - def get_proxied_http_client(self) -> synapse.http.client.SimpleHttpClient: - """Fetch an HTTP client implementation which doesn't do any blacklisting - but does support HTTP_PROXY settings""" - pass - def get_deactivate_account_handler( - self, - ) -> synapse.handlers.deactivate_account.DeactivateAccountHandler: - pass - def get_room_creation_handler(self) -> synapse.handlers.room.RoomCreationHandler: - pass - def get_room_member_handler(self) -> synapse.handlers.room_member.RoomMemberHandler: - pass - def get_event_creation_handler( - self, - ) -> synapse.handlers.message.EventCreationHandler: - pass - def get_set_password_handler( - self, - ) -> synapse.handlers.set_password.SetPasswordHandler: - pass - def get_federation_sender(self) -> synapse.federation.sender.FederationSender: - pass - def get_federation_transport_client( - self, - ) -> synapse.federation.transport.client.TransportLayerClient: - pass - def get_media_repository_resource( - self, - ) -> synapse.rest.media.v1.media_repository.MediaRepositoryResource: - pass - def get_media_repository( - self, - ) -> synapse.rest.media.v1.media_repository.MediaRepository: - pass - def get_server_notices_manager( - self, - ) -> synapse.server_notices.server_notices_manager.ServerNoticesManager: - pass - def get_server_notices_sender( - self, - ) -> synapse.server_notices.server_notices_sender.ServerNoticesSender: - pass - def get_notifier(self) -> synapse.notifier.Notifier: - pass - def get_presence_handler(self) -> synapse.handlers.presence.PresenceHandler: - pass - def get_clock(self) -> synapse.util.Clock: - pass - def get_reactor(self) -> twisted.internet.base.ReactorBase: - pass