def finalize( cls, error_handler: ErrorHandler, config: Config, fallback: Optional[str] = None, ): if fallback: deprecation( "Setting the ErrorHandler fallback value via finalize() " "is deprecated and no longer supported. This feature will " "be removed in v22.6. Instead, use " "app.config.FALLBACK_ERROR_FORMAT.", 22.6, ) if not fallback: fallback = config.FALLBACK_ERROR_FORMAT if fallback != DEFAULT_FORMAT: if error_handler._fallback is not _default: error_logger.warning( f"Setting the fallback value to {fallback}. This changes " "the current non-default value " f"'{error_handler._fallback}'.") error_handler._fallback = fallback if not isinstance(error_handler, cls): error_logger.warning( f"Error handler is non-conforming: {type(error_handler)}")
def _precheck(self): if self.args.debug and self.main_process: error_logger.warning( "Starting in v22.3, --debug will no " "longer automatically run the auto-reloader.\n Switch to " "--dev to continue using that functionality." ) # # Custom TLS mismatch handling for better diagnostics if self.main_process and ( # one of cert/key missing bool(self.args.cert) != bool(self.args.key) # new and old style self.args used together or self.args.tls and self.args.cert # strict host checking without certs would always fail or self.args.tlshost and not self.args.tls and not self.args.cert ): self.parser.print_usage(sys.stderr) message = ( "TLS certificates must be specified by either of:\n" " --cert certdir/fullchain.pem --key certdir/privkey.pem\n" " --tls certdir (equivalent to the above)" ) error_logger.error(message) sys.exit(1)
async def get_courier(request: Request, courier_id: int) -> response.HTTPResponse: courier_status = await app.db.courier_status(courier_id) if not courier_status: error_logger.warning("Courier id=%s not found", courier_id) return response.HTTPResponse(status=404) # TODO: move it to thread_pool_executor completed_orders_ids = { status.order_id for status in courier_status.statuses if status.completed_time is not None } json = courier_status.courier.external() if not completed_orders_ids: return response.json(json, indent=4) completed_orders = [ order for order in courier_status.orders if order.order_id in completed_orders_ids ] completed_orders.sort(key=lambda order: order.completed_time) return response.json(json, indent=4)
def FALLBACK_ERROR_FORMAT(self, value): self._check_error_format(value) if (self._FALLBACK_ERROR_FORMAT is not _default and value != self._FALLBACK_ERROR_FORMAT): error_logger.warning( "Setting config.FALLBACK_ERROR_FORMAT on an already " "configured value may have unintended consequences.") self._FALLBACK_ERROR_FORMAT = value
def _get_fallback_value(cls, error_handler: ErrorHandler, config: Config): if error_handler._fallback is not _default: if config._FALLBACK_ERROR_FORMAT is _default: return error_handler.fallback error_logger.warning( "Conflicting error fallback values were found in the " "error handler and in the app.config while handling an " "exception. Using the value from app.config.") return config.FALLBACK_ERROR_FORMAT
def register_type(self, converter: Callable[[str], Any]) -> None: """ Allows for adding custom function to cast from a string value to any other type. The function should raise ValueError if it is not the correct type. """ if converter in self._converters: error_logger.warning( f"Configuration value converter '{converter.__name__}' has " "already been registered") return self._converters.append(converter)
def finalize( cls, error_handler: ErrorHandler, fallback: Optional[str] = None, config: Optional[Config] = None, ): if fallback: deprecation( "Setting the ErrorHandler fallback value via finalize() " "is deprecated and no longer supported. This feature will " "be removed in v22.6. Instead, use " "app.config.FALLBACK_ERROR_FORMAT.", 22.6, ) if config is None: deprecation( "Starting in v22.3, config will be a required argument " "for ErrorHandler.finalize().", 22.3, ) if fallback and fallback != DEFAULT_FORMAT: if error_handler._fallback is not _default: error_logger.warning( f"Setting the fallback value to {fallback}. This changes " "the current non-default value " f"'{error_handler._fallback}'." ) error_handler._fallback = fallback if not isinstance(error_handler, cls): error_logger.warning( f"Error handler is non-conforming: {type(error_handler)}" ) sig = signature(error_handler.lookup) if len(sig.parameters) == 1: deprecation( "You are using a deprecated error handler. The lookup " "method should accept two positional parameters: " "(exception, route_name: Optional[str]). " "Until you upgrade your ErrorHandler.lookup, Blueprint " "specific exceptions will not work properly. Beginning " "in v22.3, the legacy style lookup method will not " "work at all.", 22.3, ) legacy_lookup = error_handler._legacy_lookup error_handler._lookup = legacy_lookup # type: ignore
async def _dispatch( self, event: str, context: Optional[Dict[str, Any]] = None, condition: Optional[Dict[str, str]] = None, fail_not_found: bool = True, reverse: bool = False, ) -> Any: try: group, handlers, params = self.get(event, condition=condition) except NotFound as e: is_reserved = event.split(".", 1)[0] in RESERVED_NAMESPACES if fail_not_found and (not is_reserved or self.allow_fail_builtin): raise e else: if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1: error_logger.warning(str(e)) return None events = [signal.ctx.event for signal in group] for signal_event in events: signal_event.set() if context: params.update(context) signals = group.routes if not reverse: signals = signals[::-1] try: for signal in signals: params.pop("__trigger__", None) if ((condition is None and signal.ctx.exclusive is False) or (condition is None and not signal.handler.__requirements__) or (condition == signal.handler.__requirements__)) and ( signal.ctx.trigger or event == signal.ctx.definition): maybe_coroutine = signal.handler(**params) if isawaitable(maybe_coroutine): retval = await maybe_coroutine if retval: return retval elif maybe_coroutine: return maybe_coroutine return None finally: for signal_event in events: signal_event.clear()
def finalize(cls, error_handler): if not isinstance(error_handler, cls): error_logger.warning( f"Error handler is non-conforming: {type(error_handler)}") sig = signature(error_handler.lookup) if len(sig.parameters) == 1: error_logger.warning( DeprecationWarning( "You are using a deprecated error handler. The lookup " "method should accept two positional parameters: " "(exception, route_name: Optional[str]). " "Until you upgrade your ErrorHandler.lookup, Blueprint " "specific exceptions will not work properly. Beginning " "in v22.3, the legacy style lookup method will not " "work at all."), ) error_handler._lookup = error_handler._legacy_lookup
async def keepalive_ping(self) -> None: """ Send a Ping frame and wait for a Pong frame at regular intervals. This coroutine exits when the connection terminates and one of the following happens: - :meth:`ping` raises :exc:`ConnectionClosed`, or - :meth:`auto_close_connection` cancels :attr:`keepalive_ping_task`. """ if self.ping_interval is None: return try: while True: await asyncio.sleep(self.ping_interval) # ping() raises CancelledError if the connection is closed, # when auto_close_connection() cancels keepalive_ping_task. # ping() raises ConnectionClosed if the connection is lost, # when connection_lost() calls abort_pings(). ping_waiter = await self.ping() if self.ping_timeout is not None: try: await asyncio.wait_for(ping_waiter, self.ping_timeout) except asyncio.TimeoutError: error_logger.warning( "Websocket timed out waiting for pong" ) self.fail_connection(1011) break except asyncio.CancelledError: # It is expected for this task to be cancelled during during # normal operation, when the connection is closed. logger.debug("Websocket keepalive ping task was cancelled.") except (ConnectionClosed, WebsocketClosed): logger.debug("Websocket closed. Keepalive ping task exiting.") except Exception as e: error_logger.warning( "Unexpected exception in websocket keepalive ping task." ) logger.debug(str(e))
async def add_couriers(request: Request) -> response.HTTPResponse: couriers, invalid_couriers_id = [], [] for courier in request.json['data']: try: courier = CourierModel(**courier) except ValidationError as e: invalid_couriers_id += [courier.get('courier_id', -1)] error_logger.warning(e.json(indent=4)) else: couriers += [courier] if invalid_couriers_id: error_logger.warning( "Request rejected, it contains invalid couriers (%s)", len(invalid_couriers_id)) context = validation_error('couriers', invalid_couriers_id) return response.json(context, status=400) added_couriers = await app.db.add_couriers(couriers) return response.json(added_couriers, status=201)
def _build_run_kwargs(self): ssl: Union[None, dict, str, list] = [] if self.args.tlshost: ssl.append(None) if self.args.cert is not None or self.args.key is not None: ssl.append(dict(cert=self.args.cert, key=self.args.key)) if self.args.tls: ssl += self.args.tls if not ssl: ssl = None elif len(ssl) == 1 and ssl[0] is not None: # Use only one cert, no TLSSelector. ssl = ssl[0] kwargs = { "access_log": self.args.access_log, "debug": self.args.debug, "fast": self.args.fast, "host": self.args.host, "motd": self.args.motd, "noisy_exceptions": self.args.noisy_exceptions, "port": self.args.port, "ssl": ssl, "unix": self.args.unix, "verbosity": self.args.verbosity or 0, "workers": self.args.workers, } if self.args.auto_reload: kwargs["auto_reload"] = True if self.args.path: if self.args.auto_reload or self.args.debug: kwargs["reload_dir"] = self.args.path else: error_logger.warning( "Ignoring '--reload-dir' since auto reloading was not " "enabled. If you would like to watch directories for " "changes, consider using --debug or --auto-reload." ) return kwargs
def __init__( self, *args, websocket_timeout: float = 10.0, websocket_max_size: Optional[int] = None, websocket_max_queue: Optional[int] = None, # max_queue is deprecated websocket_read_limit: Optional[int] = None, # read_limit is deprecated websocket_write_limit: Optional[int] = None, # write_limit deprecated websocket_ping_interval: Optional[float] = 20.0, websocket_ping_timeout: Optional[float] = 20.0, **kwargs, ): super().__init__(*args, **kwargs) self.websocket: Optional[WebsocketImplProtocol] = None self.websocket_timeout = websocket_timeout self.websocket_max_size = websocket_max_size if websocket_max_queue is not None and websocket_max_queue > 0: # TODO: Reminder remove this warning in v22.3 error_logger.warning( DeprecationWarning( "Websocket no longer uses queueing, so websocket_max_queue" " is no longer required.")) if websocket_read_limit is not None and websocket_read_limit > 0: # TODO: Reminder remove this warning in v22.3 error_logger.warning( DeprecationWarning("Websocket no longer uses read buffers, so " "websocket_read_limit is not required.")) if websocket_write_limit is not None and websocket_write_limit > 0: # TODO: Reminder remove this warning in v22.3 error_logger.warning( DeprecationWarning( "Websocket no longer uses write buffers, so " "websocket_write_limit is not required.")) self.websocket_ping_interval = websocket_ping_interval self.websocket_ping_timeout = websocket_ping_timeout
async def _dispatch( self, event: str, context: Optional[Dict[str, Any]] = None, condition: Optional[Dict[str, str]] = None, fail_not_found: bool = True, reverse: bool = False, ) -> Any: try: group, handlers, params = self.get(event, condition=condition) except NotFound as e: if fail_not_found: raise e else: if self.ctx.app.debug: error_logger.warning(str(e)) return None events = [signal.ctx.event for signal in group] for signal_event in events: signal_event.set() if context: params.update(context) if not reverse: handlers = handlers[::-1] try: for handler in handlers: if condition is None or condition == handler.__requirements__: maybe_coroutine = handler(**params) if isawaitable(maybe_coroutine): retval = await maybe_coroutine if retval: return retval elif maybe_coroutine: return maybe_coroutine return None finally: for signal_event in events: signal_event.clear()
def try_use_uvloop() -> None: """ Use uvloop instead of the default asyncio loop. """ if OS_IS_WINDOWS: error_logger.warning( "You are trying to use uvloop, but uvloop is not compatible " "with your system. You can disable uvloop completely by setting " "the 'USE_UVLOOP' configuration value to false, or simply not " "defining it and letting Sanic handle it for you. Sanic will now " "continue to run using the default event loop.") return try: import uvloop # type: ignore except ImportError: error_logger.warning( "You are trying to use uvloop, but uvloop is not " "installed in your system. In order to use uvloop " "you must first install it. Otherwise, you can disable " "uvloop completely by setting the 'USE_UVLOOP' " "configuration value to false. Sanic will now continue " "to run with the default event loop.") return uvloop_install_removed = strtobool(getenv("SANIC_NO_UVLOOP", "no")) if uvloop_install_removed: error_logger.info( "You are requesting to run Sanic using uvloop, but the " "install-time 'SANIC_NO_UVLOOP' environment variable (used to " "opt-out of installing uvloop with Sanic) is set to true. If " "you want to prevent Sanic from overriding the event loop policy " "during runtime, set the 'USE_UVLOOP' configuration value to " "false.") if not isinstance(asyncio.get_event_loop_policy(), uvloop.EventLoopPolicy): asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
async def complete(request: Request) -> response.HTTPResponse: try: complete = CompleteModel(**request.json) except ValidationError as e: error_logger.warning(e.json(indent=4)) return response.HTTPResponse(status=400) courier_status = await app.db.courier_status(complete.courier_id) if courier_status is None: error_logger.warning("Courier id=%s not found", complete.courier_id) return response.HTTPResponse(status=400) has_the_courier_order = any(order.order_id == complete.order_id for order in courier_status.orders) if not has_the_courier_order: error_logger.warning("Courier id=%s has no order id=%s", complete.courier_id, complete.order_id) return response.HTTPResponse(status=400) await app.db.complete_order(complete.order_id, complete.complete_time) return response.json({"order_id": complete.order_id})
async def update_courier(request: Request, courier_id: int) -> response.HTTPResponse: if invalid_fields := is_json_patching_courier_valid(request.json): error_logger.warning("Only %s might be updated, but %s found", PATCHABLE_FIELDS, invalid_fields) return response.HTTPResponse(status=400)
async def auto_close_connection(self) -> None: """ Close the WebSocket Connection When the opening handshake succeeds, :meth:`connection_open` starts this coroutine in a task. It waits for the data transfer phase to complete then it closes the TCP connection cleanly. When the opening handshake fails, :meth:`fail_connection` does the same. There's no data transfer phase in that case. """ try: # Wait for the data transfer phase to complete. if self.data_finished_fut: try: await self.data_finished_fut logger.debug( "Websocket task finished. Closing the connection." ) except asyncio.CancelledError: # Cancelled error is called when data phase is cancelled # if an error occurred or the client closed the connection logger.debug( "Websocket handler cancelled. Closing the connection." ) # Cancel the keepalive ping task. if self.keepalive_ping_task: self.keepalive_ping_task.cancel() self.keepalive_ping_task = None # Half-close the TCP connection if possible (when there's no TLS). if ( self.io_proto and self.io_proto.transport and self.io_proto.transport.can_write_eof() ): logger.debug("Websocket half-closing TCP connection") self.io_proto.transport.write_eof() if self.connection_lost_waiter: if await self.wait_for_connection_lost(timeout=0): return except asyncio.CancelledError: ... finally: # The try/finally ensures that the transport never remains open, # even if this coroutine is cancelled (for example). if (not self.io_proto) or (not self.io_proto.transport): # we were never open, or done. Can't do any finalization. return elif ( self.connection_lost_waiter and self.connection_lost_waiter.done() ): # connection confirmed closed already, proceed to abort waiter ... elif self.io_proto.transport.is_closing(): # Connection is already closing (due to half-close above) # proceed to abort waiter ... else: self.io_proto.transport.close() if not self.connection_lost_waiter: # Our connection monitor task isn't running. try: await asyncio.sleep(self.close_timeout) except asyncio.CancelledError: ... if self.io_proto and self.io_proto.transport: self.io_proto.transport.abort() else: if await self.wait_for_connection_lost( timeout=self.close_timeout ): # Connection aborted before the timeout expired. return error_logger.warning( "Timeout waiting for TCP connection to close. Aborting" ) if self.io_proto and self.io_proto.transport: self.io_proto.transport.abort()
async def create_server( self, host: Optional[str] = None, port: Optional[int] = None, *, debug: bool = False, ssl: Union[None, SSLContext, dict, str, list, tuple] = None, sock: Optional[socket] = None, protocol: Type[Protocol] = None, backlog: int = 100, access_log: Optional[bool] = None, unix: Optional[str] = None, return_asyncio_server: bool = False, asyncio_server_kwargs: Dict[str, Any] = None, noisy_exceptions: Optional[bool] = None, ) -> Optional[AsyncioServer]: """ Asynchronous version of :func:`run`. This method will take care of the operations necessary to invoke the *before_start* events via :func:`trigger_events` method invocation before starting the *sanic* app in Async mode. .. note:: This does not support multiprocessing and is not the preferred way to run a :class:`Sanic` application. :param host: Address to host on :type host: str :param port: Port to host on :type port: int :param debug: Enables debug output (slows server) :type debug: bool :param ssl: SSLContext, or location of certificate and key for SSL encryption of worker(s) :type ssl: SSLContext or dict :param sock: Socket for the server to accept connections from :type sock: socket :param protocol: Subclass of asyncio Protocol class :type protocol: type[Protocol] :param backlog: a number of unaccepted connections that the system will allow before refusing new connections :type backlog: int :param access_log: Enables writing access logs (slows server) :type access_log: bool :param return_asyncio_server: flag that defines whether there's a need to return asyncio.Server or start it serving right away :type return_asyncio_server: bool :param asyncio_server_kwargs: key-value arguments for asyncio/uvloop create_server method :type asyncio_server_kwargs: dict :param noisy_exceptions: Log exceptions that are normally considered to be quiet/silent :type noisy_exceptions: bool :return: AsyncioServer if return_asyncio_server is true, else Nothing """ if sock is None: host, port = host or "127.0.0.1", port or 8000 if protocol is None: protocol = ( WebSocketProtocol if self.websocket_enabled else HttpProtocol ) # Set explicitly passed configuration values for attribute, value in { "ACCESS_LOG": access_log, "NOISY_EXCEPTIONS": noisy_exceptions, }.items(): if value is not None: setattr(self.config, attribute, value) server_settings = self._helper( host=host, port=port, debug=debug, ssl=ssl, sock=sock, unix=unix, loop=get_event_loop(), protocol=protocol, backlog=backlog, run_async=return_asyncio_server, ) if self.config.USE_UVLOOP is not _default: error_logger.warning( "You are trying to change the uvloop configuration, but " "this is only effective when using the run(...) method. " "When using the create_server(...) method Sanic will use " "the already existing loop." ) main_start = server_settings.pop("main_start", None) main_stop = server_settings.pop("main_stop", None) if main_start or main_stop: logger.warning( "Listener events for the main process are not available " "with create_server()" ) return await serve( asyncio_server_kwargs=asyncio_server_kwargs, **server_settings )
def main(): parser = SanicArgumentParser( prog="sanic", description=BASE_LOGO, formatter_class=lambda prog: RawTextHelpFormatter( prog, max_help_position=33), ) parser.add_argument( "-v", "--version", action="version", version=f"Sanic {__version__}; Routing {__routing_version__}", ) parser.add_argument( "--factory", action="store_true", help=("Treat app as an application factory, " "i.e. a () -> <Sanic app> callable"), ) parser.add_argument( "-s", "--simple", dest="simple", action="store_true", help="Run Sanic as a Simple Server (module arg should be a path)\n ", ) parser.add_argument( "-H", "--host", dest="host", type=str, default="127.0.0.1", help="Host address [default 127.0.0.1]", ) parser.add_argument( "-p", "--port", dest="port", type=int, default=8000, help="Port to serve on [default 8000]", ) parser.add_argument( "-u", "--unix", dest="unix", type=str, default="", help="location of unix socket\n ", ) parser.add_argument( "--cert", dest="cert", type=str, help="Location of fullchain.pem, bundle.crt or equivalent", ) parser.add_argument( "--key", dest="key", type=str, help="Location of privkey.pem or equivalent .key file", ) parser.add_argument( "--tls", metavar="DIR", type=str, action="append", help="TLS certificate folder with fullchain.pem and privkey.pem\n" "May be specified multiple times to choose of multiple certificates", ) parser.add_argument( "--tls-strict-host", dest="tlshost", action="store_true", help="Only allow clients that send an SNI matching server certs\n ", ) parser.add_bool_arguments("--access-logs", dest="access_log", help="display access logs") parser.add_argument( "-w", "--workers", dest="workers", type=int, default=1, help="number of worker processes [default 1]\n ", ) parser.add_argument("-d", "--debug", dest="debug", action="store_true") parser.add_bool_arguments( "--noisy-exceptions", dest="noisy_exceptions", help="print stack traces for all exceptions", ) parser.add_argument( "-r", "--reload", "--auto-reload", dest="auto_reload", action="store_true", help="Watch source directory for file changes and reload on changes", ) parser.add_argument( "-R", "--reload-dir", dest="path", action="append", help="Extra directories to watch and reload on changes\n ", ) parser.add_argument( "module", help=("Path to your Sanic app. Example: path.to.server:app\n" "If running a Simple Server, path to directory to serve. " "Example: ./\n"), ) args = parser.parse_args() # Custom TLS mismatch handling for better diagnostics if ( # one of cert/key missing bool(args.cert) != bool(args.key) # new and old style args used together or args.tls and args.cert # strict host checking without certs would always fail or args.tlshost and not args.tls and not args.cert): parser.print_usage(sys.stderr) error_logger.error( "sanic: error: TLS certificates must be specified by either of:\n" " --cert certdir/fullchain.pem --key certdir/privkey.pem\n" " --tls certdir (equivalent to the above)") sys.exit(1) try: module_path = os.path.abspath(os.getcwd()) if module_path not in sys.path: sys.path.append(module_path) if args.simple: path = Path(args.module) app = create_simple_server(path) else: delimiter = ":" if ":" in args.module else "." module_name, app_name = args.module.rsplit(delimiter, 1) if app_name.endswith("()"): args.factory = True app_name = app_name[:-2] module = import_module(module_name) app = getattr(module, app_name, None) if args.factory: app = app() app_type_name = type(app).__name__ if not isinstance(app, Sanic): raise ValueError( f"Module is not a Sanic app, it is a {app_type_name}. " f"Perhaps you meant {args.module}.app?") ssl: Union[None, dict, str, list] = [] if args.tlshost: ssl.append(None) if args.cert is not None or args.key is not None: ssl.append(dict(cert=args.cert, key=args.key)) if args.tls: ssl += args.tls if not ssl: ssl = None elif len(ssl) == 1 and ssl[0] is not None: # Use only one cert, no TLSSelector. ssl = ssl[0] kwargs = { "host": args.host, "port": args.port, "unix": args.unix, "workers": args.workers, "debug": args.debug, "access_log": args.access_log, "ssl": ssl, "noisy_exceptions": args.noisy_exceptions, } if args.auto_reload: kwargs["auto_reload"] = True if args.path: if args.auto_reload or args.debug: kwargs["reload_dir"] = args.path else: error_logger.warning( "Ignoring '--reload-dir' since auto reloading was not " "enabled. If you would like to watch directories for " "changes, consider using --debug or --auto-reload.") app.run(**kwargs) except ImportError as e: if module_name.startswith(e.name): error_logger.error( f"No module named {e.name} found.\n" " Example File: project/sanic_server.py -> app\n" " Example Module: project.sanic_server.app") else: raise e except ValueError: error_logger.exception("Failed to run app")
def _helper( self, host: Optional[str] = None, port: Optional[int] = None, debug: bool = False, ssl: Union[None, SSLContext, dict, str, list, tuple] = None, sock: Optional[socket] = None, unix: Optional[str] = None, workers: int = 1, loop: AbstractEventLoop = None, protocol: Type[Protocol] = HttpProtocol, backlog: int = 100, register_sys_signals: bool = True, run_async: bool = False, ) -> Dict[str, Any]: """Helper function used by `run` and `create_server`.""" if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0: raise ValueError( "PROXIES_COUNT cannot be negative. " "https://sanic.readthedocs.io/en/latest/sanic/config.html" "#proxy-configuration" ) ssl = process_to_context(ssl) if not self.state.is_debug: self.state.mode = Mode.DEBUG if debug else Mode.PRODUCTION self.state.host = host or "" self.state.port = port or 0 self.state.workers = workers self.state.ssl = ssl self.state.unix = unix self.state.sock = sock server_settings = { "protocol": protocol, "host": host, "port": port, "sock": sock, "unix": unix, "ssl": ssl, "app": self, "signal": ServerSignal(), "loop": loop, "register_sys_signals": register_sys_signals, "backlog": backlog, } self.motd(self.serve_location) if sys.stdout.isatty() and not self.state.is_debug: error_logger.warning( f"{Colors.YELLOW}Sanic is running in PRODUCTION mode. " "Consider using '--debug' or '--dev' while actively " f"developing your application.{Colors.END}" ) # Register start/stop events for event_name, settings_name, reverse in ( ("main_process_start", "main_start", False), ("main_process_stop", "main_stop", True), ): listeners = self.listeners[event_name].copy() if reverse: listeners.reverse() # Prepend sanic to the arguments when listeners are triggered listeners = [partial(listener, self) for listener in listeners] server_settings[settings_name] = listeners # type: ignore if run_async: server_settings["run_async"] = True return server_settings
async def _start_servers( self, primary: Sanic, _, apps: List[Sanic], ) -> None: for app in apps: if ( app.name is not primary.name and app.state.workers != primary.state.workers and app.state.server_info ): message = ( f"The primary application {repr(primary)} is running " f"with {primary.state.workers} worker(s). All " "application instances will run with the same number. " f"You requested {repr(app)} to run with " f"{app.state.workers} worker(s), which will be ignored " "in favor of the primary application." ) if sys.stdout.isatty(): message = "".join( [ Colors.YELLOW, message, Colors.END, ] ) error_logger.warning(message, exc_info=True) for server_info in app.state.server_info: if server_info.stage is not ServerStage.SERVING: app.state.primary = False handlers = [ *server_info.settings.pop("main_start", []), *server_info.settings.pop("main_stop", []), ] if handlers: error_logger.warning( f"Sanic found {len(handlers)} listener(s) on " "secondary applications attached to the main " "process. These will be ignored since main " "process listeners can only be attached to your " "primary application: " f"{repr(primary)}" ) if not server_info.settings["loop"]: server_info.settings["loop"] = get_running_loop() try: server_info.server = await serve( **server_info.settings, run_async=True, reuse_port=bool(primary.state.workers - 1), ) except OSError as e: # no cov first_message = ( "An OSError was detected on startup. " "The encountered error was: " ) second_message = str(e) if sys.stdout.isatty(): message_parts = [ Colors.YELLOW, first_message, Colors.RED, second_message, Colors.END, ] else: message_parts = [first_message, second_message] message = "".join(message_parts) error_logger.warning(message, exc_info=True) continue primary.add_task( self._run_server(app, server_info), name="RunServer" )
def main(): parser = SanicArgumentParser( prog="sanic", description=BASE_LOGO, formatter_class=lambda prog: RawTextHelpFormatter( prog, max_help_position=33), ) parser.add_argument( "-v", "--version", action="version", version=f"Sanic {__version__}; Routing {__routing_version__}", ) parser.add_argument( "--factory", action="store_true", help=("Treat app as an application factory, " "i.e. a () -> <Sanic app> callable"), ) parser.add_argument( "-s", "--simple", dest="simple", action="store_true", help="Run Sanic as a Simple Server (module arg should be a path)\n ", ) parser.add_argument( "-H", "--host", dest="host", type=str, default="127.0.0.1", help="Host address [default 127.0.0.1]", ) parser.add_argument( "-p", "--port", dest="port", type=int, default=8000, help="Port to serve on [default 8000]", ) parser.add_argument( "-u", "--unix", dest="unix", type=str, default="", help="location of unix socket\n ", ) parser.add_argument("--cert", dest="cert", type=str, help="Location of certificate for SSL") parser.add_argument("--key", dest="key", type=str, help="location of keyfile for SSL\n ") parser.add_bool_arguments("--access-logs", dest="access_log", help="display access logs") parser.add_argument( "-w", "--workers", dest="workers", type=int, default=1, help="number of worker processes [default 1]\n ", ) parser.add_argument("-d", "--debug", dest="debug", action="store_true") parser.add_argument( "-r", "--reload", "--auto-reload", dest="auto_reload", action="store_true", help="Watch source directory for file changes and reload on changes", ) parser.add_argument( "-R", "--reload-dir", dest="path", action="append", help="Extra directories to watch and reload on changes\n ", ) parser.add_argument( "module", help=("Path to your Sanic app. Example: path.to.server:app\n" "If running a Simple Server, path to directory to serve. " "Example: ./\n"), ) args = parser.parse_args() try: module_path = os.path.abspath(os.getcwd()) if module_path not in sys.path: sys.path.append(module_path) if args.simple: path = Path(args.module) app = create_simple_server(path) else: delimiter = ":" if ":" in args.module else "." module_name, app_name = args.module.rsplit(delimiter, 1) if app_name.endswith("()"): args.factory = True app_name = app_name[:-2] module = import_module(module_name) app = getattr(module, app_name, None) if args.factory: app = app() app_type_name = type(app).__name__ if not isinstance(app, Sanic): raise ValueError( f"Module is not a Sanic app, it is a {app_type_name}. " f"Perhaps you meant {args.module}.app?") if args.cert is not None or args.key is not None: ssl: Optional[Dict[str, Any]] = { "cert": args.cert, "key": args.key, } else: ssl = None kwargs = { "host": args.host, "port": args.port, "unix": args.unix, "workers": args.workers, "debug": args.debug, "access_log": args.access_log, "ssl": ssl, } if args.auto_reload: kwargs["auto_reload"] = True if args.path: if args.auto_reload or args.debug: kwargs["reload_dir"] = args.path else: error_logger.warning( "Ignoring '--reload-dir' since auto reloading was not " "enabled. If you would like to watch directories for " "changes, consider using --debug or --auto-reload.") app.run(**kwargs) except ImportError as e: if module_name.startswith(e.name): error_logger.error( f"No module named {e.name} found.\n" " Example File: project/sanic_server.py -> app\n" " Example Module: project.sanic_server.app") else: raise e except ValueError: error_logger.exception("Failed to run app")
}), required=True, location="body", content_type="application/json") @doc.response(200, CourierModel.schema(), description="Courier updated") @doc.response(404, None, description="Courier not found") @doc.response(400, None, description="Bad request") async def update_courier(request: Request, courier_id: int) -> response.HTTPResponse: if invalid_fields := is_json_patching_courier_valid(request.json): error_logger.warning("Only %s might be updated, but %s found", PATCHABLE_FIELDS, invalid_fields) return response.HTTPResponse(status=400) if (courier := await app.db.get_courier(courier_id)) is None: error_logger.warning("Courier id=%s not found", courier_id) return response.HTTPResponse(status=404) courier = CourierModel(**courier.external()) try: courier_data = {**courier.dict(), **request.json} updated_courier = CourierModel(**courier_data) except ValidationError as e: error_logger.warning(e.json(indent=4)) return response.HTTPResponse(status=400) await app.db.update_courier(courier_id=courier.courier_id, **request.json) return response.json(updated_courier.dict(), indent=4)
async def assign(request: Request) -> response.HTTPResponse: courier_id = request.json.get('courier_id', -1) if (courier := await app.db.get_courier(courier_id)) is None: error_logger.warning("Courier id=%s not found", courier_id) return response.HTTPResponse(status=400)