Skip to content

Commit df8f252

Browse files
authored
v0.5.0 (#153)
Changes proposed in this pull request: * Adjusted AI example to use automatic model download feature * Adjusted Makefiles of all examples * Fixed missing default icon for `files_dropdown_menu` API * Updated README.md --------- Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent 84de0f2 commit df8f252

34 files changed

+206
-376
lines changed

.run/Skeleton.run.xml renamed to .run/Skeleton (28).run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<component name="ProjectRunConfigurationManager">
2-
<configuration default="false" name="Skeleton" type="PythonConfigurationType" factoryName="Python">
2+
<configuration default="false" name="Skeleton (28)" type="PythonConfigurationType" factoryName="Python">
33
<module name="nc_py_api" />
44
<option name="INTERPRETER_OPTIONS" value="" />
55
<option name="PARENT_ENVS" value="true" />
66
<envs>
7+
<env name="PYTHONUNBUFFERED" value="1" />
78
<env name="APP_ID" value="skeleton" />
89
<env name="APP_PORT" value="9030" />
910
<env name="APP_SECRET" value="12345" />
1011
<env name="APP_VERSION" value="1.0.0" />
1112
<env name="NEXTCLOUD_URL" value="http://nextcloud.local/index.php" />
12-
<env name="PYTHONUNBUFFERED" value="1" />
1313
</envs>
1414
<option name="SDK_HOME" value="" />
1515
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />

.run/TalkBot.run.xml renamed to .run/TalkBot (28).run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<component name="ProjectRunConfigurationManager">
2-
<configuration default="false" name="TalkBot" type="PythonConfigurationType" factoryName="Python">
2+
<configuration default="false" name="TalkBot (28)" type="PythonConfigurationType" factoryName="Python">
33
<module name="nc_py_api" />
44
<option name="INTERPRETER_OPTIONS" value="" />
55
<option name="PARENT_ENVS" value="true" />
66
<envs>
7+
<env name="PYTHONUNBUFFERED" value="1" />
78
<env name="APP_ID" value="talk_bot" />
89
<env name="APP_PORT" value="9032" />
910
<env name="APP_SECRET" value="12345" />
1011
<env name="APP_VERSION" value="1.0.0" />
1112
<env name="NEXTCLOUD_URL" value="http://nextcloud.local/index.php" />
12-
<env name="PYTHONUNBUFFERED" value="1" />
1313
</envs>
1414
<option name="SDK_HOME" value="" />
1515
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />

.run/TalkBotMulti.run.xml

Lines changed: 0 additions & 29 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [0.4.1 - 2023-10-17]
5+
## [0.5.0 - 2023-10-23]
66

77
### Added
88

9-
- Support for the new AppAPI endpoint `/init` and automatically downloading models from `huggingface`. #151
9+
- Support for the new `/init` AppAPI endpoint and the ability to automatically load models from `huggingface`. #151
1010

1111
### Changed
1212

1313
- All examples were adjusted to changes in AppAPI.
14+
- The examples now use FastAPIs `lifespan` instead of the deprecated `on_event`.
1415

1516
## [0.4.0 - 2023-10-15]
1617

README.md

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ as long as it doesn't involve calls that require user password verification.
5757

5858
**NextcloudApp** avalaible only from Nextcloud 27.1.2 and greater version with installed **AppAPI**.
5959

60+
### Nextcloud skeleton app in Python
61+
62+
```python3
63+
from contextlib import asynccontextmanager
64+
65+
from fastapi import FastAPI
66+
67+
from nc_py_api import NextcloudApp
68+
from nc_py_api.ex_app import LogLvl, run_app, set_handlers
69+
70+
71+
@asynccontextmanager
72+
async def lifespan(_app: FastAPI):
73+
set_handlers(APP, enabled_handler)
74+
yield
75+
76+
77+
APP = FastAPI(lifespan=lifespan)
78+
79+
80+
def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
81+
if enabled:
82+
nc.log(LogLvl.WARNING, "Hello from nc_py_api.")
83+
else:
84+
nc.log(LogLvl.WARNING, "Bye bye from nc_py_api.")
85+
return ""
86+
87+
88+
if __name__ == "__main__":
89+
run_app("main:APP", log_level="trace")
90+
```
91+
6092
### Support
6193

6294
You can support us in several ways:
@@ -81,18 +113,3 @@ You can support us in several ways:
81113
- [Issues](https://github.com/cloud-py-api/nc_py_api/issues)
82114
- [Setting up dev environment](https://cloud-py-api.github.io/nc_py_api/DevSetup.html)
83115
- [Changelog](https://github.com/cloud-py-api/nc_py_api/blob/main/CHANGELOG.md)
84-
85-
### Motivation
86-
87-
_Python's language, elegant and clear,_<br>
88-
_Weaves logic's threads without fear,_<br>
89-
_And in the sky, where clouds take form,_<br>
90-
_Nextcloud emerges, a digital norm._<br>
91-
92-
_Together they stand, a duo bright,_<br>
93-
_Python and Nextcloud, day and night,_<br>
94-
_In a digital dance, they guide and sail,_<br>
95-
_Shaping tomorrow, where new ideas prevail._<br>
96-
97-
#### **Know that we are always here to support and assist you on your journey.**
98-
### P.S: **_Good luck, and we hope you have fun!_**

docs/NextcloudApp.rst

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ Skeleton
1212

1313
What's going on in the skeleton?
1414

15-
First, it's important to understand that an external application acts more like a microservice, with its endpoints being called by Nextcloud.
15+
In `FastAPI lifespan <https://fastapi.tiangolo.com/advanced/events/?h=lifespan#lifespan>`_ we call the ``set_handlers`` function to further process the application installation logic.
1616

17-
Therefore, when the application receives a request at the endpoint ``/enable``,
18-
it should register all its functionalities in the cloud and wait for requests from Nextcloud.
17+
Since this is a simple skeleton application, we only define the ``/enable`` endpoint.
1918

20-
.. note:: This doesn't apply to system applications, which will be covered in the next chapter.
19+
When the application receives a request at the endpoint ``/enable``,
20+
it should register all its functionalities in the cloud and wait for requests from Nextcloud.
2121

2222
So, calling:
2323

@@ -54,7 +54,7 @@ Here they are:
5454

5555
* APP_ID - ID of the application.
5656
* APP_PORT - Port on which application listen for the requests from the Nextcloud.
57-
* APP_SECRET - Secret for ``hmac`` signature generation.
57+
* APP_SECRET - Shared secret between Nextcloud and Application.
5858
* APP_VERSION - Version of the application.
5959
* AA_VERSION - Version of the AppAPI.
6060
* NEXTCLOUD_URL - URL at which the application can access the Nextcloud API.
@@ -67,7 +67,7 @@ After launching your application, execute the following command in the Nextcloud
6767
6868
php occ app_api:app:register YOUR_APP_ID manual_install --json-info \
6969
"{\"appid\":\"YOUR_APP_ID\",\"name\":\"YOUR_APP_DISPLAY_NAME\",\"daemon_config_name\":\"manual_install\",\"version\":\"YOU_APP_VERSION\",\"secret\":\"YOUR_APP_SECRET\",\"host\":\"host.docker.internal\",\"scopes\":{\"required\":[2, 10, 11],\"optional\":[30, 31, 32, 33]},\"port\":SELECTED_PORT,\"protocol\":\"http\",\"system_app\":0}" \
70-
-e --force-scopes
70+
--force-scopes
7171
7272
You can see how **nc_py_api** registers in ``scripts/dev_register.sh``.
7373

@@ -79,8 +79,7 @@ Examples for such Makefiles can be found in this repository:
7979
`ToGif <https://github.com/cloud-py-api/nc_py_api/blob/main/examples/as_app/to_gif/Makefile>`_ ,
8080
`nc_py_api <https://github.com/cloud-py-api/nc_py_api/blob/main/scripts/dev_register.sh>`_
8181

82-
During the execution of `php occ app_api:app:register`, the **enabled_handler** will be called,
83-
as we pass the flag ``-e``, meaning ``enable after registration``.
82+
During the execution of `php occ app_api:app:register`, the **enabled_handler** will be called
8483

8584
This is likely all you need to start debugging and developing an application for Nextcloud.
8685

docs/NextcloudTalkBotTransformers.rst

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Requirements
3737
We opt for the latest version of the Transformers library.
3838
Because the example was developed on a Mac, we ended up using Torchvision.
3939

40-
`If you're working solely with Nvidia, you're free to use TensorFlow instead of PyTorch.`
40+
`You're free to use TensorFlow instead of PyTorch.`
4141

4242
Next, we integrate the latest version of `nc_py_api` to minimize code redundancy and focus on the application's logic.
4343

@@ -52,63 +52,20 @@ We specify the model name globally so that we can easily change the model name i
5252

5353
**When Should We Download the Language Model?**
5454

55-
Although the example uses the smallest model available, weighing in at 300 megabytes, it's common knowledge that larger language models can be substantially bigger.
56-
Downloading such models should not begin when a processing request is already received.
55+
To make process of initializing applications more robust, separate logic was introduced, with an ``/init`` endpoint.
5756

58-
So we have two options:
59-
60-
* Heartbeat
61-
* enabled_handler
62-
63-
This can't be accomplished in the **app on/off handler** as Nextcloud expects an immediate response regarding the app's operability.
64-
65-
Thus, we place the model downloading within the Heartbeat:
66-
67-
.. code-block::
68-
69-
# Thread that performs model download.
70-
def download_models():
71-
pipeline("text2text-generation", model=MODEL_NAME) # this will download model
72-
73-
74-
def heartbeat_handler() -> str:
75-
global MODEL_INIT_THREAD
76-
print("heartbeat_handler: called") # for debug
77-
# if it is the first heartbeat, then start background thread to download a model
78-
if MODEL_INIT_THREAD is None:
79-
MODEL_INIT_THREAD = Thread(target=download_models)
80-
MODEL_INIT_THREAD.start()
81-
print("heartbeat_handler: started initialization thread") # for debug
82-
# if thread is finished then we will have "ok" in response, and AppAPI will consider that program is ready.
83-
r = "init" if MODEL_INIT_THREAD.is_alive() else "ok"
84-
print(f"heartbeat_handler: result={r}") # for debug
85-
return r
86-
87-
88-
@APP.on_event("startup")
89-
def initialization():
90-
# Provide our custom **heartbeat_handler** to set_handlers
91-
set_handlers(APP, enabled_handler, heartbeat_handler)
92-
93-
94-
.. note:: While this may not be the most ideal way to download models, it remains a viable method.
95-
In the future, a more efficient wrapper for model downloading is planned to make the process even more convenient.
96-
97-
Model Storage
98-
"""""""""""""
99-
100-
By default, models will be downloaded to a directory that's removed when updating the app.
101-
To persistently store the models even after updates, add the following line to your code:
57+
This library also provides an additional functionality over this endpoint for easy downloading of models from the `huggingface <https://huggingface.co>`_.
10258

10359
.. code-block::
10460
105-
from nc_py_api.ex_app import persist_transformers_cache # noqa # isort:skip
61+
@asynccontextmanager
62+
async def lifespan(_app: FastAPI):
63+
set_handlers(APP, enabled_handler, models_to_fetch=[MODEL_NAME])
64+
yield
10665
107-
This will set ``TRANSFORMERS_CACHE`` environment variable to point to the application persistent storage.
108-
Import of this **must be** on top before importing any code that perform the import of the ``transformers`` library.
66+
This will automatically download models specified in ``models_to_fetch`` parameter to the application persistent storage.
10967

110-
And that is all, ``transformers`` will automatically download all
111-
models you use to the **Application Persistent Storage** and AppAPI will keep it between updates.
68+
If you want write your own logic, you can always pass your own defined ``init_handler`` callback to ``set_handlers``.
11269

11370
Working with Language Models
11471
""""""""""""""""""""""""""""
@@ -122,13 +79,16 @@ Finally, we arrive at the core aspect of the application, where we interact with
12279
r = re.search(r"@ai\s(.*)", message.object_content["message"], re.IGNORECASE)
12380
if r is None:
12481
return
125-
model = pipeline("text2text-generation", model=MODEL_NAME)
82+
model = pipeline(
83+
"text2text-generation",
84+
model=snapshot_download(MODEL_NAME, local_files_only=True, cache_dir=persistent_storage()),
85+
)
12686
# Pass all text after "@ai" we to the Language model.
12787
response_text = model(r.group(1), max_length=64, do_sample=True)[0]["generated_text"]
12888
AI_BOT.send_message(response_text, message)
12989
13090
131-
Simply put, the AI logic is just two lines of code when using Transformers, which is incredibly efficient and cool.
91+
Simply put, AI logic is a few lines of code when using Transformers, which is incredibly efficient and cool.
13292

13393
Messages from the AI model are then sent back to Talk Chat as you would expect from a typical chatbot.
13494

examples/as_app/skeleton/Makefile

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ build-push:
2424
docker login ghcr.io
2525
docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/cloud-py-api/skeleton:latest .
2626

27-
.PHONY: deploy
28-
deploy:
27+
.PHONY: deploy28
28+
deploy28:
2929
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:deploy skeleton docker_dev \
3030
--info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/skeleton/appinfo/info.xml
3131

32+
.PHONY: deploy27
33+
deploy27:
34+
docker exec master-stable27-1 sudo -u www-data php occ app_api:app:deploy skeleton docker_dev \
35+
--info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/skeleton/appinfo/info.xml
36+
3237
.PHONY: run28
3338
run28:
3439
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister skeleton --silent || true
@@ -41,9 +46,16 @@ run27:
4146
docker exec master-stable27-1 sudo -u www-data php occ app_api:app:register skeleton docker_dev --force-scopes \
4247
--info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/skeleton/appinfo/info.xml
4348

44-
.PHONY: manual_register
45-
manual_register:
49+
.PHONY: register28
50+
register28:
4651
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister skeleton --silent || true
4752
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register skeleton manual_install --json-info \
4853
"{\"appid\":\"skeleton\",\"name\":\"App Skeleton\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"port\":9030,\"scopes\":{\"required\":[],\"optional\":[]},\"protocol\":\"http\",\"system_app\":0}" \
4954
--force-scopes
55+
56+
.PHONY: register27
57+
register27:
58+
docker exec master-stable27-1 sudo -u www-data php occ app_api:app:unregister skeleton --silent || true
59+
docker exec master-stable27-1 sudo -u www-data php occ app_api:app:register skeleton manual_install --json-info \
60+
"{\"appid\":\"skeleton\",\"name\":\"App Skeleton\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"port\":9030,\"scopes\":{\"required\":[],\"optional\":[]},\"protocol\":\"http\",\"system_app\":0}" \
61+
--force-scopes
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
nc_py_api[app]>=0.4.1
1+
nc_py_api[app]>=0.5.0

examples/as_app/skeleton/src/main.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
"""
2-
Simplest example.
3-
"""
1+
"""Simplest example."""
2+
from contextlib import asynccontextmanager
43

54
from fastapi import FastAPI
65

76
from nc_py_api import NextcloudApp
87
from nc_py_api.ex_app import LogLvl, run_app, set_handlers
98

10-
APP = FastAPI()
9+
10+
@asynccontextmanager
11+
async def lifespan(_app: FastAPI):
12+
set_handlers(APP, enabled_handler)
13+
yield
14+
15+
16+
APP = FastAPI(lifespan=lifespan)
1117

1218

1319
def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
@@ -22,13 +28,6 @@ def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
2228
return ""
2329

2430

25-
# Of course, you can use `FastAPI lifespan: <https://fastapi.tiangolo.com/advanced/events/#lifespan>` instead of this.
26-
# The only requirement for the application is to define `/enabled` and `/heartbeat` handlers.
27-
@APP.on_event("startup")
28-
def initialization():
29-
set_handlers(APP, enabled_handler)
30-
31-
3231
if __name__ == "__main__":
3332
# Wrapper around `uvicorn.run`.
3433
# You are free to call it directly, with just using the `APP_HOST` and `APP_PORT` variables from the environment.

0 commit comments

Comments
 (0)