Skip to content

Commit 69823ec

Browse files
author
Review
committed
chore: switch internal call sites to canonical class names
Phase 2 left a number of tests and examples calling the deprecated ``AsyncNextcloud`` / ``AsyncNextcloudApp`` / ``AsyncTalkBot`` / ``atalk_bot_msg`` aliases that now just point at the canonical async classes. The aliases stay in place for users (and remain covered by ``tests/_install_async.py`` and ``tests/_install_only_enabled_handler_async.py`` which are the contract tests for the backward-compat surface), but internal call sites should not look like they need them. * tests/actual_tests/misc_test.py — Async{Nextcloud,NextcloudApp} -> {Nextcloud,NextcloudApp} * tests/actual_tests/talk_bot_test.py — AsyncTalkBot -> TalkBot * examples/as_client/files/{listing,upload,find,download}.py — AsyncNextcloud -> Nextcloud * examples/as_app/talk_bot{,_ai}/lib/main.py — atalk_bot_msg -> talk_bot_msg Fix the ``examples/as_app/to_gif`` ExApp, which was a real regression: every ``nc.log`` / ``nc.files.*`` / ``nc.notifications.*`` / ``nc.ui.*`` call became an unawaited coroutine after Phase 2. Convert ``convert_video_to_gif`` and ``enabled_handler`` to coroutines, await every call site, and offload the CPU/IO-heavy ``cv2`` + ``imageio`` + ``pygifsicle`` pipeline to a sync helper that runs via ``asyncio.to_thread`` so the event loop stays responsive.
1 parent 803887d commit 69823ec

9 files changed

Lines changed: 59 additions & 55 deletions

File tree

examples/as_app/talk_bot/lib/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from fastapi import BackgroundTasks, Depends, FastAPI, Response
99

1010
from nc_py_api import NextcloudApp, talk_bot
11-
from nc_py_api.ex_app import AppAPIAuthMiddleware, atalk_bot_msg, run_app, set_handlers
11+
from nc_py_api.ex_app import AppAPIAuthMiddleware, run_app, set_handlers, talk_bot_msg
1212

1313

1414
# The same stuff as for usual External Applications
@@ -69,7 +69,7 @@ def currency_talk_bot_process_request(message: talk_bot.TalkBotMessage):
6969

7070
@APP.post("/currency_talk_bot")
7171
async def currency_talk_bot(
72-
message: Annotated[talk_bot.TalkBotMessage, Depends(atalk_bot_msg)],
72+
message: Annotated[talk_bot.TalkBotMessage, Depends(talk_bot_msg)],
7373
background_tasks: BackgroundTasks,
7474
):
7575
# As during converting, we do not process converting locally, we perform this in background, in the background task.

examples/as_app/talk_bot_ai/lib/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
from nc_py_api import NextcloudApp, talk_bot
1212
from nc_py_api.ex_app import (
1313
AppAPIAuthMiddleware,
14-
atalk_bot_msg,
1514
get_model_path,
1615
run_app,
1716
set_handlers,
17+
talk_bot_msg,
1818
)
1919

2020

@@ -41,7 +41,7 @@ def ai_talk_bot_process_request(message: talk_bot.TalkBotMessage):
4141

4242
@APP.post("/ai_talk_bot")
4343
async def ai_talk_bot(
44-
message: Annotated[talk_bot.TalkBotMessage, Depends(atalk_bot_msg)],
44+
message: Annotated[talk_bot.TalkBotMessage, Depends(talk_bot_msg)],
4545
background_tasks: BackgroundTasks,
4646
):
4747
if message.object_name == "message":

examples/as_app/to_gif/lib/main.py

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Simplest example of files_dropdown_menu + notification."""
22

3+
import asyncio
34
import tempfile
45
from contextlib import asynccontextmanager
56
from os import path
@@ -26,46 +27,51 @@ async def lifespan(app: FastAPI):
2627
APP.add_middleware(AppAPIAuthMiddleware)
2728

2829

29-
def convert_video_to_gif(input_file: FsNode, nc: NextcloudApp):
30+
def _build_gif(in_path: str, out_path: str) -> None:
31+
"""Synchronous CPU/IO-heavy work — extracted so it can run via :func:`asyncio.to_thread`."""
32+
cap = cv2.VideoCapture(in_path)
33+
image_lst = []
34+
previous_frame = None
35+
skip = 0
36+
while True:
37+
skip += 1
38+
_ret, frame = cap.read()
39+
if frame is None:
40+
break
41+
if skip == 2:
42+
skip = 0
43+
continue
44+
if previous_frame is not None:
45+
diff = numpy.mean(previous_frame != frame)
46+
if diff < 0.91:
47+
continue
48+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
49+
image_lst.append(frame_rgb)
50+
previous_frame = frame
51+
if len(image_lst) > 60:
52+
break
53+
cap.release()
54+
imageio.mimsave(out_path, image_lst)
55+
optimize(out_path)
56+
57+
58+
async def convert_video_to_gif(input_file: FsNode, nc: NextcloudApp) -> None:
3059
save_path = path.splitext(input_file.user_path)[0] + ".gif"
31-
nc.log(LogLvl.WARNING, f"Processing:{input_file.user_path} -> {save_path}")
60+
await nc.log(LogLvl.WARNING, f"Processing:{input_file.user_path} -> {save_path}")
3261
try:
3362
with tempfile.NamedTemporaryFile(mode="w+b") as tmp_in:
34-
nc.files.download2stream(input_file, tmp_in)
35-
nc.log(LogLvl.WARNING, "File downloaded")
63+
await nc.files.download2stream(input_file, tmp_in)
64+
await nc.log(LogLvl.WARNING, "File downloaded")
3665
tmp_in.flush()
37-
cap = cv2.VideoCapture(tmp_in.name)
3866
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".gif") as tmp_out:
39-
image_lst = []
40-
previous_frame = None
41-
skip = 0
42-
while True:
43-
skip += 1
44-
_ret, frame = cap.read()
45-
if frame is None:
46-
break
47-
if skip == 2:
48-
skip = 0
49-
continue
50-
if previous_frame is not None:
51-
diff = numpy.mean(previous_frame != frame)
52-
if diff < 0.91:
53-
continue
54-
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
55-
image_lst.append(frame_rgb)
56-
previous_frame = frame
57-
if len(image_lst) > 60:
58-
break
59-
cap.release()
60-
imageio.mimsave(tmp_out.name, image_lst)
61-
optimize(tmp_out.name)
62-
nc.log(LogLvl.WARNING, "GIF is ready")
63-
nc.files.upload_stream(save_path, tmp_out)
64-
nc.log(LogLvl.WARNING, "Result uploaded")
65-
nc.notifications.create(f"{input_file.name} finished!", f"{save_path} is waiting for you!")
66-
except Exception as e:
67-
nc.log(LogLvl.ERROR, str(e))
68-
nc.notifications.create("Error occurred", "Error information was written to log file")
67+
await asyncio.to_thread(_build_gif, tmp_in.name, tmp_out.name)
68+
await nc.log(LogLvl.WARNING, "GIF is ready")
69+
await nc.files.upload_stream(save_path, tmp_out)
70+
await nc.log(LogLvl.WARNING, "Result uploaded")
71+
await nc.notifications.create(f"{input_file.name} finished!", f"{save_path} is waiting for you!")
72+
except Exception as e: # noqa: BLE001
73+
await nc.log(LogLvl.ERROR, str(e))
74+
await nc.notifications.create("Error occurred", "Error information was written to log file")
6975

7076

7177
@APP.post("/video_to_gif")
@@ -79,18 +85,18 @@ async def video_to_gif(
7985
return responses.Response()
8086

8187

82-
def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
88+
async def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
8389
print(f"enabled={enabled}")
8490
try:
8591
if enabled:
86-
nc.ui.files_dropdown_menu.register_ex(
92+
await nc.ui.files_dropdown_menu.register_ex(
8793
"to_gif",
8894
"To GIF",
8995
"/video_to_gif",
9096
mime="video",
9197
icon="img/icon.svg",
9298
)
93-
except Exception as e:
99+
except Exception as e: # noqa: BLE001
94100
return str(e)
95101
return ""
96102

examples/as_client/files/download.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
async def main():
1010
# run this example after ``files_upload.py`` or adjust the image file path.
11-
nc = nc_py_api.AsyncNextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
11+
nc = nc_py_api.Nextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
1212
rgb_image = await nc.files.download("RGB.png")
1313
Image.open(BytesIO(rgb_image)).show() # wrap `bytes` into BytesIO for Pillow
1414

examples/as_client/files/find.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
async def main():
77
# create Nextcloud client instance class
8-
nc = nc_py_api.AsyncNextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
8+
nc = nc_py_api.Nextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
99

1010
print("Searching for all files which names ends with `.txt`:")
1111
result = await nc.files.find(["like", "name", "%.txt"])

examples/as_client/files/listing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
async def main():
77
# create Nextcloud client instance class
8-
nc = nc_py_api.AsyncNextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
8+
nc = nc_py_api.Nextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
99

1010
async def list_dir(directory):
1111
# usual recursive traversing over directories

examples/as_client/files/upload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
async def main():
10-
nc = nc_py_api.AsyncNextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
10+
nc = nc_py_api.Nextcloud(nextcloud_url="http://nextcloud.local", nc_auth_user="admin", nc_auth_pass="admin")
1111
buf = BytesIO()
1212
Image.merge(
1313
"RGB",

tests/actual_tests/misc_test.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from niquests import PreparedRequest, Response
77

88
from nc_py_api import (
9-
AsyncNextcloud,
10-
AsyncNextcloudApp,
9+
Nextcloud,
10+
NextcloudApp,
1111
NextcloudException,
1212
ex_app,
1313
)
@@ -113,7 +113,7 @@ async def test_verify_version_async(anc_app):
113113

114114
@pytest.mark.asyncio(scope="session")
115115
async def test_init_adapter_dav_async(anc_any):
116-
new_nc = AsyncNextcloud() if isinstance(anc_any, AsyncNextcloud) else AsyncNextcloudApp()
116+
new_nc = Nextcloud() if isinstance(anc_any, Nextcloud) else NextcloudApp()
117117
new_nc._session.init_adapter_dav()
118118
old_adapter = getattr(new_nc._session, "adapter_dav", None)
119119
assert old_adapter is not None
@@ -125,17 +125,15 @@ async def test_init_adapter_dav_async(anc_any):
125125

126126
@pytest.mark.asyncio(scope="session")
127127
async def test_no_initial_connection_async(anc_any):
128-
new_nc = AsyncNextcloud() if isinstance(anc_any, AsyncNextcloud) else AsyncNextcloudApp()
128+
new_nc = Nextcloud() if isinstance(anc_any, Nextcloud) else NextcloudApp()
129129
assert not new_nc._session._capabilities
130130
_ = await new_nc.srv_version
131131
assert new_nc._session._capabilities
132132

133133

134134
@pytest.mark.asyncio(scope="session")
135135
async def test_ocs_timeout_async(anc_any):
136-
new_nc = (
137-
AsyncNextcloud(npa_timeout=0.01) if isinstance(anc_any, AsyncNextcloud) else AsyncNextcloudApp(npa_timeout=0.01)
138-
)
136+
new_nc = Nextcloud(npa_timeout=0.01) if isinstance(anc_any, Nextcloud) else NextcloudApp(npa_timeout=0.01)
139137
with pytest.raises(NextcloudException) as e:
140138
if await new_nc.weather_status.set_location(latitude=41.896655, longitude=12.488776):
141139
await new_nc.weather_status.get_forecast()
@@ -153,7 +151,7 @@ async def test_public_ocs_async(anc_any):
153151

154152
@pytest.mark.asyncio(scope="session")
155153
async def test_perform_login_async(anc_any):
156-
new_nc = AsyncNextcloud() if isinstance(anc_any, AsyncNextcloud) else AsyncNextcloudApp()
154+
new_nc = Nextcloud() if isinstance(anc_any, Nextcloud) else NextcloudApp()
157155
assert not new_nc._session._capabilities
158156
await new_nc.perform_login()
159157
assert new_nc._session._capabilities

tests/actual_tests/talk_bot_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def test_chat_bot_receive_message_async(anc_app):
5858
if await anc_app.talk.bots_available is False:
5959
pytest.skip("Need Talk bots support")
6060
niquests.delete(f"{'http'}://{environ.get('APP_HOST', '127.0.0.1')}:{environ['APP_PORT']}/reset_bot_secret")
61-
talk_bot_inst = talk_bot.AsyncTalkBot("/talk_bot_coverage", "Coverage bot", "Desc")
61+
talk_bot_inst = talk_bot.TalkBot("/talk_bot_coverage", "Coverage bot", "Desc")
6262
await talk_bot_inst.enabled_handler(True, anc_app)
6363
conversation = await anc_app.talk.create_conversation(talk.ConversationType.GROUP, "admin")
6464
try:

0 commit comments

Comments
 (0)