#๐ followup post - check body for link
108 messages ยท Page 1 of 1 (latest)
@haughty glen
Remember to:
- Ask your Python question, not if you can ask or if there's an expert who can help.
- Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
- Explain what you expect to happen and what actually happens.
:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.
[fastapi] followup post - check body for link
@whole lark what is and how do you use anyio.create_memory_object_stream?
Show the code you have so far
Contribute to axololly/paste development by creating an account on GitHub.
Yeah I saw you tagged me in the commit
So you need to create a task group here https://github.com/axololly/paste/blob/main/backend/main.py#L17
backend/main.py line 17
async with create_pool("entries/index.sql") as pool:```
Yeah it sends and email
oh wow - never knew that. enjoy the email 
so i group together create_pool and the delete_in_background task i want to make?
the what
cmgr is short for context manager
create_pool(path, cmgr = ...)?
No I mean inside the with block
soo ```py
async with create_pool(...) as pool:
# Create task here
yield {"pool": pool}
so it's ```py
async with create_pool(...) as pool:
group = anyio.create_task_group()
group.start_soon(???) # What goes in here?
yield {"pool": pool}
You pass an async def to run in the background
You also need to call async with on the create_task_group
oh alright
wait why does this have to be in the pool again?
forgive me for asking
Will if you cancel your TaskGroup you don't want to cancel your pool cleanup
ah i see
ok got it
@asynccontextmanager
async def lifespan(app: FastAPI):
async with create_pool("entries/index.sql") as pool:
async with anyio.create_task_group() as group:
group.start_soon(delete_in_background())
yield {"pool": pool}
the delete_in_background is the async def i wanna run in the background
You pass an async function, not that weird object that async functions return
not that weird object that async functions return
a Coroutine?
You pass an async function
so no brackets then
Yeah not that, you should never create a coroutine without immediately awaiting it
But in this case we want concurrency so you pass the function

And the TaskGroup will call the function and await it
how do i stop and/or restart the task
So pass the second half of a memory object stream into your function and async for it
what
i didnt get any of that
"second half of a memory object stream" 
Start with creating a memory object stream
send, receive = anyio.create_memory_object_stream[None]()
with send, receive:
...
Smallest possible
alright
I think the default is the smallest possible

what next?
Show what you have
@asynccontextmanager
async def lifespan(app: FastAPI):
async with create_pool("entries/index.sql") as pool:
async with create_task_group() as group:
sender, receiver = create_memory_object_stream[None]()
with sender, receiver:
... # What goes in here?
group.start_soon(delete_in_background, pool)
yield {"pool": pool}
You need to pass your receive end to your delete_in_background
You can use functools.partial for kwargs
And you'll want to yield your send end
And whenever you call send_nowait(None) on the send end it will wake up the task
Or it will raise an exception if it's already in the process of waking
You'll just need to catch this
ok back
soooo ```py
@asynccontextmanager
async def lifespan(app: FastAPI):
async with create_pool("entries/index.sql") as pool:
async with create_task_group() as group:
sender, receiver = create_memory_object_streamNone
with sender, receiver:
group.start_soon(delete_in_background, pool, receiver)
yield {"pool": pool}
then this? ```py
@asynccontextmanager
async def lifespan(app: FastAPI):
async with create_pool("entries/index.sql") as pool:
async with create_task_group() as group:
sender, receiver = create_memory_object_streamNone
with sender, receiver:
group.start_soon(delete_in_background, pool, receiver)
yield {"pool": pool, "sender": sender}
I can't work out if you have the right things indented under the correct things, but that looks close enough
I'm on mobile and it messes with pastes
do you want me to pastebin it?
Does it work?
here's the snippet btw
havent even written delete_in_background yet 
Oh right well do that
how does it look? ```py
async def delete_in_background(pool: Pool, receiver: MemoryObjectReceiveStream[None]):
async with pool.acquire() as conn:
# Get when the next paste will expire
req = await conn.execute(
"""
SELECT id FROM expiries
ORDER BY expiry_timestamp
LIMIT 1
"""
)
row = await req.fetchone()
if not row:
# No more records to listen out for
receiver.close()
await sleep_until(row["delete_at"])
async with pool.acquire() as conn:
await conn.execute("DELETE FROM expiries WHERE id = ?", row["id"])
sleep_until is a custom function i made that gets the duration in seconds between two timestamps and sends that. it works with both datetimes and integer timestamps
@overload
async def sleep_until(datetime: dt, /) -> None:
"Sleep until a given `datetime`."
@overload
async def sleep_until(timestamp: int, /) -> None:
"Sleep until a given timestamp."
async def sleep_until(_dt_or_ts: dt | int, /) -> None:
if isinstance(_dt_or_ts, dt):
time_asleep = _dt_or_ts.timestamp() - dt.now().timestamp()
elif isinstance(_dt_or_ts, int):
time_asleep = _dt_or_ts - dt.now().timestamp()
else:
raise TypeError("given argument is not an integer or datetime object.")
if time_asleep <= 0:
return
await sleep(time_asleep)
You don't need to close the receiver
how do i "stop" the loop then?
You async for the receiver
what
It will wait until you call sender.send_nowait(None)
and where do i put that
In delete_in_background
around the whole of it?
Yeah
async def delete_in_background(pool: Pool, receiver: MemoryObjectReceiveStream[None]):
async for _ in receiver:
async with pool.acquire() as conn:
# Get when the next paste will expire
req = await conn.execute(
"""
SELECT id FROM expiries
ORDER BY expiry_timestamp
LIMIT 1
"""
)
row = await req.fetchone()
if not row:
# No more records to listen out for
... # What goes here now?
await sleep_until(row["delete_at"])
async with pool.acquire() as conn:
await conn.execute("DELETE FROM expiries WHERE id = ?", row["id"])
what do i do with the receiver now?
That's it
Although if you just want to do expiry I'd probably just do it every time you add a new paste
Or retrieve a paste, check if it's expired
i wanna wait in the background until the paste has expired, then delete it
Why?
what's wrong with that idea?
it's just sleeping until then
and that's what dpy does with its framework, so 
Probably best to just start a background task in the task group for each insert then
alright no problem
So in this case you want to yield the task group
yep
is it cool if i address this in like two hours? i'm going out for a meal shortly
This channel will close and you'll need to open a new one
not a problem with me
Sounds good then
This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.