Decorator arguments lost in self - python-3.x

I have a decorator
class Utilities(commands.Cog):
def has_any_role(self, *items):
async def predicate(ctx):
ctx.command.needed_roles = items
return await commands.has_any_role(*items).predicate(ctx)
return commands.check(predicate)
#has_any_role('Owner', 'Moderator')
#commands.command()
async def foo(...):
...
However now when I try to access a Commands needed_roles attribute, it'll only return Moderator for foo, since "Owner" is lost in self. How can I fix this without just putting the function outside of the class?

Try:
#staticmethod
def has_any_role(*items)
...

Related

what is the return type of __aenter__ and __aexit__?

I have the following class
class MyService:
def __init__(self, my_api: MyAPI) -> None:
self._my_api = my_api
self._session_usage_count = 0
async def __aenter__(self) -> "MyService":
if not self._session_usage_count:
await self._my_api.init() # returns None
self._session_usage_count += 1
return self
async def __aexit__(self, *exc: Any) -> None:
self._session_usage_count -= 1
if not self._session_usage_count:
await self._my_api.close() # returns None
According to the doc both __aenter__ and __aexit__ must return an awaitable,
but in my case it's not. Also I read that __aexit__ returns bool implicitly.
So what should be the return type in my case?
TLDR: Annotating an async def statement with just the return type of awaiting it is fine.
An async def statement creates a callable that always returns an awaitable. Repeating this in the return type is thus not necessary.
from typing import Callable, Awaitable
async def greetings() -> str:
return "Hello World!"
c: Callable[..., Awaitable[str]] = greetings # mypy: Success: no issues found in 1 source file
This allows to use the same return type annotation for an async protocol as one would use for the corresponding sync protocol – e.g. __exit__/__aexit__ in this case.
The (async) Context Manager Protocol specifically only says that __exit__/__aexit__ "should return a true value" if "the method wishes to suppress the exception". It is perfectly fine to return another value/type, including None, if the exception should not be suppressed.

Is it possible to add an argument to a property in aiogram?

I'm making a multi-lingual functionality for a aiogram bot, it's pretty simple at the moment:
class Localization:
locales = {
'en': {
'command': {
'start': 'Hello!'
}
}
}
def __init__(self):
self.__user_lang = dict()
#property
def available_langs(self):
return tuple(self.locales.keys())
def text(self, id):
return self.locales[self.user_lang(id)]
def user_lang(self, id):
return self.__user_lang.get(id, 'en')
def set_user_lang(self, id, lang):
if lang in self.available_langs:
self.__user_lang[id] = lang
I have to call it like this:
#dp.message_handler(commands=['start'])
async def start_command(message: types.Message):
id = message.from_user.id
await message.answer(locale.text(id)['commands']['start'])
I want to change text in a #property to shorten the entry.
#property
def text(self):
return self.locales[self.user_lang(id)]
This will also allow me to make constructs like this:
#dp.message_handler(lambda m: m.text == locale.text['buttons']['about'])
async def about(message: types.Message):
pass
Not like this:
#dp.message_handler(lambda m: m.text == locale.text(m.from_user.id)['buttons']['about'])
async def about(message: types.Message):
pass
But I absolutely cannot think of how to specify the property for which particular user to get localization.
P.S. I know about i18n, but I'm learning and want to try to write everything myself.
Ok, i'm definitely stupid.
I could just get the current user in any function that is called from the handler:
from aiogram import types
#property
def user_lang(self):
user = types.User.get_current()
return self.__user_lang.get(user.id, 'ru')
And so on...

Tons of Cog errors

In discord.py 1.0.1 (Only version Repl.it has), the cogs are giving me a hard time.
import discord
from discord.ext import commands
class Coding(commands, Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_ready(self):
print("Kahoot Bot 0.1 ALPHA")
client.remove_command("help")
#commands.command()
async def clear(self, ctx, amount = 5):
await ctx.channel.purge(limit = amount + 1)
#commands.command()
async def ping(self, ctx):
await ctx.send(f"Pong! {round(client.latency * 1000)}ms.")
#client.command(pass_context = True, aliases = ["print"])
async def printing(ctx, *, what_to_print):
await ctx.send(what_to_print)
print(what_to_print)
def setup(client):
client.add_cog(Coding(client))
The gist of the errors is:
A) client is not defined
B) init() should return None, not coroutine
I've tried changing all my code to bot and back to client, but nothing's helped. No idea what's going on.
You do the inheritance wrong. You dont inherit from class: "commands" and "Cog". You inherit from class: "commands.Cog". thus changing: class Coding(commands, Cog): to class Coding(commands.Cog): will fix some of the errors.
You also do the following wrong (the "client does not exist" error):
#commands.Cog.listener()
async def on_ready(self):
print("Kahoot Bot 0.1 ALPHA")
client.remove_command("help") # this line
When we want to access a class variable we use self. in the beginning of that variable to indicate that we are using the class variable. This case you dont use self.client. but client.
As client is not defined in that function it will give an error. But it is defined as a class variable (the "init" function). To access it use: self.client.

RuntimeError when running coroutine from __init__

Here's a sample code.
class Foo:
def __init__(self):
self._run_coro()
def _run_coro(self):
async def init():
bar = #some I/O op
self.bar = bar
loop = asyncio.get_event_loop()
loop.run_until_complete(init())
async def spam(self):
return await #I/O op
async def main():
foo = Foo()
await foo.spam()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
When I run this code, I get following exception:
RuntimeError: This event loop is already running
If I initialize Foo outside main, the code runs without any exception. I want to initialize Foo such that during initialization it runs a coroutine which creates a class attribute bar.
I am unable to figure how to do it correctly. How can I run a coroutine from __init__.
Any help would be highly appreciated.
class Foo:
def __init__(self):
self.session = requests.Session()
self.async_session = None
#I guess this can be done to initialize it.
s = self.init_async_session()
try:
s.send(None)
except StopIteration:
pass
finally:
s.close()
async def init_async_session(self):
#ClientSession should be created inside a coroutine.
self.async_session = aiohttp.ClientSession()
What would be the right way to initialize self.async_session
If some method uses something asynchronous it should be explicitly defined as asynchronous either. This is a core idea behind asyncio: make you write code a way you always know if some arbitrary method may do something asynchronous.
In your snippet you want to do async thing (bar I/O) inside sync method __init__ and asyncio prohibits it. You should make _run_coro async and initialize Foo asynchronously, for example, using __await__ method:
import asyncio
class Foo:
def __await__(self):
return self._run_coro().__await__()
async def _run_coro(self): # real async initializer
async def init():
await asyncio.sleep(1) # bar I/O
self.bar = 123
await init()
return self
async def spam(self):
return await asyncio.sleep(1) # I/O op
async def main():
foo = await Foo()
await foo.spam()
asyncio.run(main()) # instead of two lines in Python 3.7+
You may be interested in reading this answer to understand better how asyncio works and how to handle it.
Upd:
s = self.init_async_session()
try:
s.send(None)
Don't do such things: generator's method are only details of implementation in regard of coroutines. You can predict how coroutine will react on calling .send() method and you can rely on this behavior.
If you want to execute coroutine use await, if you want to start it "in background" use task or other functions from asyncio doc.
What would be the right way to initialize self.async_session
When it comes to aiohttp.ClientSession it should not only be created, but properly closed also. Best way to do it is to use async context manager as shown in aiohttp doc.
If you want to hide this operation inside Foo you can make it async manager either. Complete example:
import aiohttp
class Foo:
async def __aenter__(self):
self._session = aiohttp.ClientSession()
await self._session.__aenter__()
return self
async def __aexit__(self, *args):
await self._session.__aexit__(*args)
async def spam(self):
url = 'http://httpbin.org/delay/1'
resp = await self._session.get(url)
text = await resp.text()
print(text)
async def main():
async with Foo() as foo:
await foo.spam()
asyncio.run(main())
Upd2:
You can combine ways to init/close object from above to achive result you like. As long as you keep in mind both operations are asynchronous and thus should be awaited, everything should be fine.
One more possible way:
import asyncio
import aiohttp
class Foo:
def __await__(self):
return self._init().__await__()
async def _init(self):
self._session = aiohttp.ClientSession()
await self._session.__aenter__()
return self
async def close(self):
await self._session.__aexit__(None, None, None)
async def spam(self):
url = 'http://httpbin.org/delay/1'
resp = await self._session.get(url)
text = await resp.text()
print(text)
async def main():
foo = await Foo()
try:
await foo.spam()
finally:
await foo.close()
asyncio.run(main())
Here's my solution.
class Session:
def __init__(self, headers):
self._headers = headers
self._session = requests.Session()
self._async_session = None
async def _init(self):
self._session = aiohttp.ClientSession(headers=headers)
async def async_request(self, url):
while True:
try:
async with self._async_session.get(url) as resp:
resp.raise_for_status()
return await resp.text()
except aiohttp.client_exceptions.ClientError:
#retry or raise
except AttributeError:
if isinstance(self._async_session, aiohttp.ClientSession):
raise
await self._init()
def request(self, url):
return self._session.get(url).text
async def close(self):
if isinstance(self._async_session, aiohttp.ClientSession):
await self._session.close()
async def main():
session = Session({})
print(await session.async_request('https://httpstat.us/200')
await session.close()
asyncio.run(main())
I can initialize the Session class and make synchronous as well as asynchronous requests. I do not have to explicitly call await session._init() to initialize self._async_session as when session._async_request is called and self._async_session is None, then await session._init() will be called and the request will be retried.

How to mock "async with" statements?

I'm trying to write tests for a method that uses "async with" statements (in this case, aioredis's connection pool), i want to mock the connection to redis, but i'm having trouble figuring out how.
Here's what i have so far:
from asyncio import Future
from unittest.mock import MagicMock
import pytest
# The thing i'm trying to test
async def set_value(redis, value):
# Do things
async with redis.get() as conn:
await conn.set("key", value)
#My Mock classes
class MockRedis():
def get(self):
return MockAsyncPool()
class MockAsyncPool(MagicMock):
async def __aenter__(self):
conn = MagicMock()
f = Future()
f.set_result(True)
conn.set = MagicMock(return_value=f)
return conn
def __aexit__(self, exc_type, exc_val, exc_tb):
pass
# The actual test
#pytest.mark.asyncio
async def test_get_token():
redis = MockRedis()
token = await set_value(redis, 'something')
assert token is not None
I run it with:
py.test path/to/file.py
And i'm getting this error:
> await conn.set("key", value)
E TypeError: object NoneType can't be used in 'await' expression
__aexit__ needs to also be asyncronous (needs to return an awaitable):
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
Without being async it is returning None instead of a coroutine so it raises an error, as for the very misleading error message I have created this issue to point out that the error message could be greatly improved.

Resources