From 6f9af0f02bedeb67753828d1ab34920188ea7a38 Mon Sep 17 00:00:00 2001
From: Nebulizer1213 <fixingg@gmail.com>
Date: Sun, 27 Nov 2022 20:19:24 -0500
Subject: [PATCH] Added support for views and changed how RedisLimiter and
 MemcachedLimiter are imported.

---
 .github/ISSUE_TEMPLATE/bug-report.md  |   1 -
 .idea/aiohttp-ratelimiter.iml         |   2 +-
 .idea/misc.xml                        |   2 +-
 README.md                             |  16 +++++++++++++++-
 aiohttp_ratelimiter.egg-info/PKG-INFO |  18 ++++++++++++++++--
 aiohttplimiter/__init__.py            |   2 --
 aiohttplimiter/limiter.py             |  12 ++++++++----
 aiohttplimiter/memcached_limiter.py   |   5 +++--
 aiohttplimiter/memory_limiter.py      |   5 +++--
 aiohttplimiter/redis_limiter.py       |   5 +++--
 dist/aiohttp-ratelimiter-4.0.1.tar.gz | Bin 5242 -> 0 bytes
 dist/aiohttp-ratelimiter-4.1.1.tar.gz | Bin 0 -> 5264 bytes
 setup.py                              |   2 +-
 13 files changed, 51 insertions(+), 19 deletions(-)
 delete mode 100644 dist/aiohttp-ratelimiter-4.0.1.tar.gz
 create mode 100644 dist/aiohttp-ratelimiter-4.1.1.tar.gz

diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 9163304..7b0d76b 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -10,7 +10,6 @@ assignees: Nebulizer1213
 # Bug Report
 
 Before you submit this request make sure you are using the latest version of aiohttp-ratelimiter.
-If you want faster support you can go to https://jgltechnologies.com/contact and link this issue in the message box.
 
 ### Python version
 
diff --git a/.idea/aiohttp-ratelimiter.iml b/.idea/aiohttp-ratelimiter.iml
index 1959cc0..8e5446a 100644
--- a/.idea/aiohttp-ratelimiter.iml
+++ b/.idea/aiohttp-ratelimiter.iml
@@ -4,7 +4,7 @@
     <content url="file://$MODULE_DIR$">
       <excludeFolder url="file://$MODULE_DIR$/venv" />
     </content>
-    <orderEntry type="jdk" jdkName="Python 3.10 (aiohttp-ratelimiter)" jdkType="Python SDK" />
+    <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
   <component name="PyDocumentationSettings">
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 339dd29..23c9186 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (aiohttp-ratelimiter)" project-jdk-type="Python SDK" />
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (aiohttp-ratelimiter)" project-jdk-type="Python SDK" />
   <component name="PyCharmProfessionalAdvertiser">
     <option name="shown" value="true" />
   </component>
diff --git a/README.md b/README.md
index 60457b7..0003c48 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,9 @@ Example
 
 ```python
 from aiohttp import web
-from aiohttplimiter import default_keyfunc, Limiter, RedisLimiter, MemcachedLimiter
+from aiohttplimiter import default_keyfunc, Limiter
+from aiohttplimiter.redis_limiter import RedisLimiter
+from aiohttplimiter.memcached_limiter import MemcachedLimiter
 
 app = web.Application()
 routes = web.RouteTableDef()
@@ -116,5 +118,17 @@ def home(request):
     return web.Response(text="Hello")
 ```
 
+<br>
+
+Views Example 
+
+```python
+@routes.view("/")
+class Home(View):
+    @limiter.limit("1/second")
+    def get(self: web.Request):
+        return web.Response(text="hello")
+```
+
 
 
diff --git a/aiohttp_ratelimiter.egg-info/PKG-INFO b/aiohttp_ratelimiter.egg-info/PKG-INFO
index 33b434e..6b6b633 100644
--- a/aiohttp_ratelimiter.egg-info/PKG-INFO
+++ b/aiohttp_ratelimiter.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: aiohttp-ratelimiter
-Version: 4.0.1
+Version: 4.1.1
 Summary: A simple rate limiter for aiohttp.web
 Home-page: https://jgltechnologies.com/aiohttplimiter
 Author: George Luca
@@ -48,7 +48,9 @@ Example
 
 ```python
 from aiohttp import web
-from aiohttplimiter import default_keyfunc, Limiter, RedisLimiter, MemcachedLimiter
+from aiohttplimiter import default_keyfunc, Limiter
+from aiohttplimiter.redis_limiter import RedisLimiter
+from aiohttplimiter.memcached_limiter import MemcachedLimiter
 
 app = web.Application()
 routes = web.RouteTableDef()
@@ -134,6 +136,18 @@ def home(request):
     return web.Response(text="Hello")
 ```
 
+<br>
+
+Views Example 
+
+```python
+@routes.view("/")
+class Home(View):
+    @limiter.limit("1/second")
+    def get(self: web.Request):
+        return web.Response(text="hello")
+```
+
 
 
 
diff --git a/aiohttplimiter/__init__.py b/aiohttplimiter/__init__.py
index 93a0fd4..445ff68 100644
--- a/aiohttplimiter/__init__.py
+++ b/aiohttplimiter/__init__.py
@@ -1,4 +1,2 @@
 from .limiter import default_keyfunc, Allow, RateLimitExceeded
 from .memory_limiter import Limiter
-from .redis_limiter import RedisLimiter
-from .memcached_limiter import MemcachedLimiter
diff --git a/aiohttplimiter/limiter.py b/aiohttplimiter/limiter.py
index 6c4e4f8..d594f8e 100644
--- a/aiohttplimiter/limiter.py
+++ b/aiohttplimiter/limiter.py
@@ -2,16 +2,18 @@
 import json
 from typing import Callable, Awaitable, Union, Optional, Coroutine, Any
 import asyncio
-from aiohttp.web import Request, Response
+from aiohttp.web import Request, Response, View
 from limits.aio.storage import Storage, MemoryStorage
 from limits.aio.strategies import MovingWindowRateLimiter
 from limits import parse
 
 
-def default_keyfunc(request: Request) -> str:
+def default_keyfunc(request: Union[Request, View]) -> str:
     """
     Returns the user's IP
     """
+    if isinstance(request, View):
+        request = request.request
     ip = request.headers.get(
         "X-Forwarded-For") or request.remote or "127.0.0.1"
     ip = ip.split(",")[0]
@@ -51,9 +53,11 @@ def __init__(self, db: Storage, path_id: str, keyfunc: Callable,
         self.path_id = path_id
         self.moving_window = moving_window
 
-    def __call__(self, func: Union[Callable, Awaitable]) -> Coroutine[Any, Any, Response]:
+    def __call__(self, func: Union[Callable, Awaitable]) -> Callable[[Union[Request, View]], Coroutine[Any, Any, Response]]:
         @wraps(func)
-        async def wrapper(request: Request) -> Response:
+        async def wrapper(request: Union[Request, View]) -> Response:
+            if isinstance(request, View):
+                request = request.request
             key = self.keyfunc(request)
             db_key = f"{key}:{self.path_id or request.path}"
 
diff --git a/aiohttplimiter/memcached_limiter.py b/aiohttplimiter/memcached_limiter.py
index 537c15f..86390d0 100644
--- a/aiohttplimiter/memcached_limiter.py
+++ b/aiohttplimiter/memcached_limiter.py
@@ -1,4 +1,5 @@
-from typing import Callable, Awaitable, Union, Optional
+from typing import Callable, Awaitable, Union, Optional, Any, Coroutine
+from aiohttp.web import Request, Response, View
 from limits.aio.storage import MemcachedStorage
 from .limiter import BaseRateLimitDecorator
 from limits.aio.strategies import MovingWindowRateLimiter
@@ -26,7 +27,7 @@ def __init__(self, keyfunc: Callable, uri: str, exempt_ips: Optional[set] = None
 
     def limit(self, ratelimit: str, keyfunc: Callable = None, exempt_ips: Optional[set] = None,
               error_handler: Optional[Union[Callable, Awaitable]] = None, path_id: str = None) -> Callable:
-        def wrapper(func: Callable) -> Awaitable:
+        def wrapper(func: Callable) -> Callable[[Union[Request, View]], Coroutine[Any, Any, Response]]:
             _exempt_ips = exempt_ips or self.exempt_ips
             _keyfunc = keyfunc or self.keyfunc
             _error_handler = self.error_handler or error_handler
diff --git a/aiohttplimiter/memory_limiter.py b/aiohttplimiter/memory_limiter.py
index 05fb03f..9febc89 100644
--- a/aiohttplimiter/memory_limiter.py
+++ b/aiohttplimiter/memory_limiter.py
@@ -1,7 +1,8 @@
+from aiohttp.web import Request, Response, View
 from limits.aio.storage import MemoryStorage
 from limits.aio.strategies import MovingWindowRateLimiter
 from .limiter import BaseRateLimitDecorator
-from typing import Callable, Optional, Union, Awaitable
+from typing import Callable, Optional, Union, Awaitable, Any, Coroutine
 
 
 class Limiter:
@@ -26,7 +27,7 @@ def __init__(self, keyfunc: Callable, exempt_ips: Optional[set] = None,
 
     def limit(self, ratelimit: str, keyfunc: Callable = None, exempt_ips: Optional[set] = None,
               error_handler: Optional[Union[Callable, Awaitable]] = None, path_id: str = None) -> Callable:
-        def wrapper(func: Callable) -> Awaitable:
+        def wrapper(func: Callable) -> Callable[[Union[Request, View]], Coroutine[Any, Any, Response]]:
             _exempt_ips = exempt_ips or self.exempt_ips
             _keyfunc = keyfunc or self.keyfunc
             _error_handler = self.error_handler or error_handler
diff --git a/aiohttplimiter/redis_limiter.py b/aiohttplimiter/redis_limiter.py
index 8814863..6fb7e78 100644
--- a/aiohttplimiter/redis_limiter.py
+++ b/aiohttplimiter/redis_limiter.py
@@ -1,5 +1,6 @@
-from typing import Callable, Awaitable, Union, Optional
+from typing import Callable, Awaitable, Union, Optional, Any, Coroutine
 import coredis
+from aiohttp.web import Request, Response, View
 from limits.aio.storage import RedisStorage
 from .limiter import BaseRateLimitDecorator
 from limits.aio.strategies import MovingWindowRateLimiter
@@ -27,7 +28,7 @@ def __init__(self, keyfunc: Callable, uri: str, exempt_ips: Optional[set] = None
 
     def limit(self, ratelimit: str, keyfunc: Callable = None, exempt_ips: Optional[set] = None,
               error_handler: Optional[Union[Callable, Awaitable]] = None, path_id: str = None) -> Callable:
-        def wrapper(func: Callable) -> Awaitable:
+        def wrapper(func: Callable) -> Callable[[Union[Request, View]], Coroutine[Any, Any, Response]]:
             _exempt_ips = exempt_ips or self.exempt_ips
             _keyfunc = keyfunc or self.keyfunc
             _error_handler = self.error_handler or error_handler
diff --git a/dist/aiohttp-ratelimiter-4.0.1.tar.gz b/dist/aiohttp-ratelimiter-4.0.1.tar.gz
deleted file mode 100644
index 44d1484d6cf1436685c65eb9e1d1add4842b99b1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 5242
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

diff --git a/dist/aiohttp-ratelimiter-4.1.1.tar.gz b/dist/aiohttp-ratelimiter-4.1.1.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..9cf8645a2184a1884803eab3b3a6db6adb50f400
GIT binary patch
literal 5264
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
CRhxJK

literal 0
HcmV?d00001

diff --git a/setup.py b/setup.py
index 51014dc..e36fe80 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def get_long_description():
         return file.read()
 
 
-VERSION = "4.0.1"
+VERSION = "4.1.1"
 
 classifiers = [
     "Development Status :: 5 - Production/Stable",