Package: ellar-throttler
Ellar - Python ASGI web framework for building fast, efficient, and scalable RESTful APIs and server-side applications.Full Documentation:HereIntroductionA rate limit module for EllarInstallation$(venv)pipinstallellar-throttlerUsageThrottlerModuleTheThrottleModuleis the main entry point for this package, and can be used in a synchronous or asynchronous manner.
All the needs to be passed is thettl, the time to live in seconds for the request tracker, and thelimit,
or how many times an endpoint can be hit before returning a 429 status code.fromellar.commonimportModulefromellar_throttlerimportThrottlerModule@Module(modules=[ThrottlerModule.setup(ttl=60,limit=10)])classApplicationModule:passThe above would mean that 10 requests from the same IP can be made to a single endpoint in 1 minute.fromellar.commonimportModulefromellar_throttlerimportThrottlerModule,ThrottlerGuardfromellar.coreimportConfig,ModuleSetup,DynamicModuledefthrottler_module_factory(module:ThrottlerModule,config:Config)->DynamicModule:returnmodule.setup(ttl=config['THROTTLE_TTL'],limit=config['THROTTLE_LIMIT'])@Module(modules=[ModuleSetup(ThrottlerModule,inject=[Config],factory=throttler_module_factory)])classApplicationModule:pass# server.pyapplication=AppFactory.create_from_app_module(ApplicationModule,config_module=os.environ.get(ELLAR_CONFIG_MODULE,"dialerai.config:DevelopmentConfig"),global_guards=[ThrottlerGuard])The above is also a valid configuration forThrottleModuleregistration if you want to work with config.NOTE: If you add theThrottlerGuardto your applicationglobal_guards, then all the incoming requests will be throttled by default.
This can also be omitted in favor of@UseGuards(ThrottlerGuard).
The global guard check can be skipped using the@skip_throttle()decorator mentioned later.Example with@UseGuards(ThrottlerGuard)# project_name/controller.pyfromellar.commonimportController,UseGuardsfromellar_throttlerimportthrottle,ThrottlerGuard@Controller()classAppController:@UseGuards(ThrottlerGuard)@throttle(limit=5,ttl=30)defnormal(self):passDecorators@throttle()@throttle(*, limit: int = 20, ttl: int = 60)This decorator will setTHROTTLER_LIMITandTHROTTLER_TTLmetadata on the route, for retrieval from the Reflector class.
It can be applied to controllers and routes.@skip_throttle()@skip_throttle(skip: bool = True)This decorator can be used to skip a route or a class or to negate the skipping of a route in
a class that is skipped.# project_name/controller.pyfromellar.commonimportController,UseGuardsfromellar_throttlerimportThrottlerGuard,skip_throttle@skip_throttle()@UseGuards(ThrottlerGuard)@Controller()classAppController:defdo_skip(self):pass@skip_throttle(skip=False)defdont_skip(self):passIn the above controller,dont_skipwould be counted against and
rate-limited whiledo_skipwould not be limited in any way.ThrottlerStorageInterface to define the methods to handle the details when it comes to keeping track of the requests.Currently, the key is seen as anMD5hash of the IP theclass nameand thefunction name,
to ensure that no unsafe characters are used.The interface looks like this:importtypingastfromabcimportABC,abstractmethodclassIThrottlerStorage(ABC):@property@abstractmethoddefstorage(self)->t.Dict[str,ThrottlerStorageOption]:"""The internal storage with all the request records.The key is a hashed key based on the current context and IP.:return:"""@abstractmethodasyncdefincrement(self,key:str,ttl:int)->ThrottlerStorageRecord:"""Increment the amount of requests for a given record. The record willautomatically be removed from the storage once its TTL has been reached.:param key::param ttl::return:"""So long as the Storage service implements this interface, it should be usable by theThrottlerGuard.ProxiesIf you are working with multiple proxies, you can override theget_tracker()method to pull the value from the header or installProxyHeadersMiddleware# throttler_behind_proxy.guard.pyfromellar_throttlerimportThrottlerGuardfromellar.diimportinjectablefromellar.core.connectionimportHTTPConnection@injectable()classThrottlerBehindProxyGuard(ThrottlerGuard):defget_tracker(self,connection:HTTPConnection)->str:returnconnection.client.host# individualize IP extraction to meet your own needs# project_name/controller.pyfrom.throttler_behind_proxyimportThrottlerBehindProxyGuard@Controller('')@UseGuards(ThrottlerBehindProxyGuard)classAppController:passWorking with WebSocketsTo work with Websockets you can extend theThrottlerGuardand override thehandle_requestmethod with the code below:fromellar_throttlerimportThrottlerGuardfromellar.diimportinjectablefromellar.commonimportIExecutionContextfromellar_throttlerimportThrottledException@injectable()classWsThrottleGuard(ThrottlerGuard):asyncdefhandle_request(self,context:IExecutionContext,limit:int,ttl:int)->bool:websocket_client=context.switch_to_websocket().get_client()host=websocket_client.client.hostkey=self.generate_key(context,host)result=awaitself.storage_service.increment(key,ttl)# Throw an error when the user reached their limit.ifresult.total_hits>limit:raiseThrottledException(wait=result.time_to_expire)returnTrue