Package: serverly
serverlyA really simple-to-use server framework with integrated user management, built on Uvicorn & SQLAlchemy!Table of ContentsConfigurationCustom functionsMethodsObjectsRequestResponseRequest & ResponseResourceserverly.userserverly.statisticspluginsConfigurationaddress = ('localhost', 8080)The address used to register the server. Needs to be set before running start()name = 'serverly'The name of the server. Used for logging purposes only.logger: fileloghelper.Logger = Logger()The logger used for logging (surprise!!). See the docs of fileloghelper for reference.Custom (dynamic) functionsWhen you create custom functions you need to tell serverly by either using theserves(method: str, path: str)decorator or register it by callingregister_function(see below). Your function should accept one parameter which is of type Request. You can then process it in whatever way you want.
Your function must return a Response object. See Objects for more info.Methodsstatic_page(file_path: str, path: str)Register a static page where the file is located underfile_pathand will servepathregister_function(method: str, path: str, function)Register a dynamic function that will servepathviamethodunregister(method: str, path: str)Unregister any page (static or dynamic). Only affect themethod-path (GET / POST)start(superpath: str='/')Start the server after applying all relevant attributes like address.superpathwill replace every occurence of SUPERPATH/ or /SUPERPATH/ withsuperpath. Especially useful for servers orchestrating other servers.register_error_response(code: int, msg_base: str, mode='enumerate')Register an error response template forcodebased off the message-stemmsg_baseand accepting *args as defined bymodeerror_template(code: int, *args)Modes:enumerate: append every arg by comma and space to the basebase: only return the base messageExample:register_error_response(404,'Page not found.','base')You can now get the 404-Response by callingerror_response(404)-> Response(code=404, body='Page not found.')Or in enumerate mode:register_error_response(999,'I want to buy: ','enumerate')error_response(999, 'apples', 'pineapples', 'bananas')-> Response(code=9l9, body='I want to buy: apples, pineapples, bananas')error_response(code: int, *args)Return Response registered by register_error_response (See above)ObjectsRequestAttributeDescriptionmethod: strHTTP method (GET, POST, PUT, DELETE)path: strPath the request was sent to (e.g. /projects/doorbell)address: tuple[str, int]Address of client as a tuple, i.e. ("localhost": 50760)authenticated: boolIs user authenticated using the Authorization-Header?auth_type: strType of authorization (Authorization-Header). Currently Basic & Bearer supported.user_cred: Union[str, tuple[str, str]]Credentials of the user passed in the Authorization-Header. Ifauth_typeis Basic,user_credis a tuple containing username & password. The password is already decrypted from base64. Ifauth_typeis Bearer,user_credis the bearer token (str).ResponseAttributeDescriptioncode: intResponse code to send to the clientbandwidth: intMaximum bandwidth used when sending to client (bytes per sec). None for no regulation.Request & ResponseAttributeDescriptionheaders: dictHeaders as a dict[str, Union[str, int]] recieved by the client or yet to return to it(set) body: Union[str, dict, list, fb]setbodyto a str, dict or list. Translating it into JSON, if necessary is handled automatically. Can be set to str, dict, list or file-like object(get) body: strget the JSON representation of the request/response if available, otherwise just the string.(get) obj: objectget the object representation of the request/response. If there is none (body is a non-JSON string for example) it is set to None. NOT SET-able.Due to the properties above the probably best way to use requests/responses is by assigning values like dictionaries to the body attribute of responses and accessing the json of requests by using body (i.e. to store it in a database)StaticSiteStaticSite(path: str, file_path: str)A static site usingfile_pathfor it's data to serve. Will be registered forpath(if you register it), if not overriden in the process (don'treallyhave to mind). Instead registering it manually, you can call.use().ResourceYou can subclassserverly.objects.Resourceto specify your endpoints in an more OO-way.Example:classMyAPI(Resource):@staticmethod@basic_authdefinfo(request:Request):# do something and return a Responsedef__init__(self):self.__path__='/api/'self.__map__={'/overview/?':'static/json/overview.json',# local filesystem'/info/?':self.info,'/products/':ProductsAPI}whereProductsAPIis another Resource subclass:classProductsAPI(Resource):@staticmethoddefget_all(request:Request):# DB lookup and other stuffdef__init__(self):self.__path__='/products/'self.__map__={'/getall':self.get_all,'/new':self.new# ...}When you callMyAPI().use(), the following endpoints will be registered:pathfunction/api/overview/?StaticSite (static/json/overview.json)/info/?MyAPI().info/api/products/getallProductsAPI().get_all/api/products/newProductsAPI().newAnd yes, it's recursive!!!StaticResourceThis allows you to serve an entire folder recursively with just one call:serverly.objects.StaticResource(folder_path:str,endpoint_path:str,file_extensions=True)file_extensionsspecifies whether the endpoints should be include the file_extension of the original file (ex. /folder/hello.py)serverly.userThis subpackage allows very easy user-management right through serverly. SeeSERVERLY.USER.mdfor more information.serverly.statisticsThis module is used to save statistics of the server as well as individual endpoints and print them after shutdown as well as saving them tofilename(defaults to 'statistics.json'). It has the following attributes for holding stats:overall_performance={'min':2.7182818284,'max':11.11111,'mean':3.1415926535,'len':42}endpoint_performance={'my_endpoint':{'min':3.292932383827,'max':17.223480972,'mean':6.182849,'len':42}}Also, it has the functionsnew_statistic(function: str, time: float)andprint_stats(), both of which you probably don't need 🧐pluginsserverly provides a very basic interface for plugins. The following options are available to you to subclass:HeaderPluginServerLifespanPluginHeaderPluginThemanipulateHeaders(response: Response) -> Responsemethod gets called of a subclass. It's supposed to alter the headers of the Response just before getting sent to the client. The default implementation raises a NotImplemtedError. The default constructor takes an array of regex patterns, asstrs, used as exceptions where the plugin will NOT do it's work. There are a few implementations of commonHeaderPlugins:Content_Security_PolicyHeaderPlugin(policy: str, exceptions=[])X_Frame_OptionsHeaderPlugin(policy: str, exceptions=[])X_Content_TypeOptionsHeaderPlugin(policy: str, exceptions=[])ServerLifespanPluginThe methods of this plugin get called with the lifecycle of the (main) server. Available are:onServerStart()onServerShutdown()