Skip to content

Commit e959776

Browse files
authored
Notes API (#137)
Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent 674d44a commit e959776

18 files changed

+413
-42
lines changed

.github/workflows/analysis-coverage.yml

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ jobs:
149149

150150
- name: Install Talk
151151
working-directory: apps/spreed
152-
run: |
153-
make dev-setup
154-
make build-js
152+
run: make dev-setup
155153

156154
- name: Enable Talk
157155
run: php occ app:enable spreed
@@ -304,9 +302,7 @@ jobs:
304302

305303
- name: Install Talk
306304
working-directory: apps/spreed
307-
run: |
308-
make dev-setup
309-
make build-js
305+
run: make dev-setup
310306

311307
- name: Enable Talk
312308
run: php occ app:enable spreed
@@ -513,6 +509,13 @@ jobs:
513509
ref: "master"
514510
path: apps/notifications
515511

512+
- name: Checkout Notes
513+
uses: actions/checkout@v4
514+
with:
515+
repository: nextcloud/notes
516+
ref: "main"
517+
path: apps/notes
518+
516519
- name: Set up & run Nextcloud
517520
env:
518521
DB_PORT: 4444
@@ -524,6 +527,7 @@ jobs:
524527
./occ config:system:set loglevel --value=0 --type=integer
525528
./occ config:system:set debug --value=true --type=boolean
526529
./occ app:enable notifications
530+
./occ app:enable notes
527531
php -S localhost:8080 &
528532
529533
- name: Checkout NcPyApi
@@ -562,9 +566,7 @@ jobs:
562566

563567
- name: Install Talk
564568
working-directory: apps/spreed
565-
run: |
566-
make dev-setup
567-
make build-js
569+
run: make dev-setup
568570

569571
- name: Enable Talk
570572
run: php occ app:enable spreed
@@ -692,7 +694,6 @@ jobs:
692694
working-directory: apps/spreed
693695
run: |
694696
make dev-setup
695-
make build-js
696697
697698
- name: Enable Talk
698699
run: php occ app:enable spreed
@@ -775,6 +776,13 @@ jobs:
775776
ref: ${{ matrix.nextcloud }}
776777
path: apps/activity
777778

779+
- name: Checkout Notes
780+
uses: actions/checkout@v4
781+
with:
782+
repository: nextcloud/notes
783+
ref: "main"
784+
path: apps/notes
785+
778786
- name: Set up & run Nextcloud
779787
env:
780788
DB_PORT: 4444
@@ -787,6 +795,7 @@ jobs:
787795
./occ config:system:set debug --value=true --type=boolean
788796
./occ app:enable notifications
789797
./occ app:enable activity
798+
./occ app:enable notes
790799
php -S localhost:8080 &
791800
792801
- name: Checkout NcPyApi
@@ -830,9 +839,7 @@ jobs:
830839

831840
- name: Install Talk
832841
working-directory: apps/spreed
833-
run: |
834-
make dev-setup
835-
make build-js
842+
run: make dev-setup
836843

837844
- name: Enable Talk
838845
run: php occ app:enable spreed

CHANGELOG.md

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

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

5-
## [0.3.1 - 2023-09-30]
5+
## [0.3.1 - 2023-10-0x]
66

77
### Added
88

9-
- CalendarAPI with the help of [caldav](https://pypi.org/project/caldav/) package.
9+
- CalendarAPI with the help of [caldav](https://pypi.org/project/caldav/) package. #136
10+
- [NotesAPI](https://github.com/nextcloud/notes) #137
1011

1112
## [0.3.0 - 2023-09-28]
1213

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ Python library that provides a robust and well-documented API that allows develo
3232
| Shares ||||
3333
| Users & Groups ||||
3434
| User & Weather status ||||
35-
| Weather status ||||
35+
| Other APIs*** ||||
3636
| Talk Bot API* | N/A |||
3737
| Text Processing* | N/A |||
3838
| SpeechToText* | N/A |||
3939

4040
&ast;_available only for NextcloudApp_<br>
41+
&ast;&ast;&ast;_Activity, Notes_
4142

4243
### Differences between the Nextcloud and NextcloudApp classes
4344

benchmarks/aa_overhead_dav_download.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def measure_download_1mb(nc_obj: Union[Nextcloud, NextcloudApp]) -> [Any, float]
2020
start_time = perf_counter()
2121
for _ in range(ITERS):
2222
nc_obj.files.download(small_file_name)
23-
nc_obj._session.init_adapter(restart=not CACHE_SESS) # noqa
23+
nc_obj._session.init_adapter_dav(restart=not CACHE_SESS) # noqa
2424
end_time = perf_counter()
2525
nc_obj.files.delete(small_file_name, not_fail=True)
2626
return __result, round((end_time - start_time) / ITERS, 3)

benchmarks/aa_overhead_dav_download_stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def measure_download_100mb(nc_obj: Union[Nextcloud, NextcloudApp]) -> [Any, floa
2424
for _ in range(ITERS):
2525
medium_file.seek(0)
2626
nc_obj.files.download2stream(medium_file_name, medium_file)
27-
nc_obj._session.init_adapter(restart=not CACHE_SESS) # noqa
27+
nc_obj._session.init_adapter_dav(restart=not CACHE_SESS) # noqa
2828
end_time = perf_counter()
2929
nc_obj.files.delete(medium_file_name, not_fail=True)
3030
return __result, round((end_time - start_time) / ITERS, 3)

benchmarks/aa_overhead_dav_upload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def measure_upload_1mb(nc_obj: Union[Nextcloud, NextcloudApp]) -> [Any, float]:
1919
start_time = perf_counter()
2020
for _ in range(ITERS):
2121
nc_obj.files.upload(small_file_name, small_file)
22-
nc_obj._session.init_adapter(restart=not CACHE_SESS) # noqa
22+
nc_obj._session.init_adapter_dav(restart=not CACHE_SESS) # noqa
2323
end_time = perf_counter()
2424
nc_obj.files.delete(small_file_name, not_fail=True)
2525
return __result, round((end_time - start_time) / ITERS, 3)

benchmarks/aa_overhead_dav_upload_stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def measure_upload_100mb(nc_obj: Union[Nextcloud, NextcloudApp]) -> [Any, float]
2222
for _ in range(ITERS):
2323
medium_file.seek(0)
2424
nc_obj.files.upload_stream(medium_file_name, medium_file)
25-
nc_obj._session.init_adapter(restart=not CACHE_SESS) # noqa
25+
nc_obj._session.init_adapter_dav(restart=not CACHE_SESS) # noqa
2626
end_time = perf_counter()
2727
nc_obj.files.delete(medium_file_name, not_fail=True)
2828
return __result, round((end_time - start_time) / ITERS, 3)

docs/reference/Notes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.. py:currentmodule:: nc_py_api.notes
2+
3+
Notes API
4+
=========
5+
6+
.. autoclass:: nc_py_api.notes.Note
7+
:members:
8+
9+
.. autoclass:: nc_py_api.notes.NotesSettings
10+
:members:
11+
12+
.. autoclass:: nc_py_api.notes._NotesAPI
13+
:members:

docs/reference/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ Reference
77
Nextcloud
88
ExApp
99
Apps
10-
ActivityApp
1110
Files/index.rst
1211
Users/index.rst
1312
Exceptions
1413
Talk
1514
TalkBot
1615
Calendar
16+
ActivityApp
17+
Notes
1718
Session

nc_py_api/_session.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Session represents one connection to Nextcloud. All related stuff for these live here."""
2-
2+
import typing
33
from abc import ABC, abstractmethod
44
from base64 import b64encode
55
from collections.abc import Iterator
@@ -132,6 +132,7 @@ def __init__(self, **kwargs):
132132

133133
class NcSessionBasic(ABC):
134134
adapter: Client
135+
adapter_dav: Client
135136
cfg: BasicConfig
136137
user: str
137138
custom_headers: dict
@@ -145,11 +146,14 @@ def __init__(self, **kwargs):
145146
self.custom_headers = kwargs.get("headers", {})
146147
self.limits = Limits(max_keepalive_connections=20, max_connections=20, keepalive_expiry=60.0)
147148
self.init_adapter()
149+
self.init_adapter_dav()
148150
self.response_headers = HttpxHeaders()
149151

150152
def __del__(self):
151153
if hasattr(self, "adapter") and self.adapter:
152154
self.adapter.close()
155+
if hasattr(self, "adapter_dav") and self.adapter_dav:
156+
self.adapter_dav.close()
153157

154158
def get_stream(self, path: str, params: Optional[dict] = None, **kwargs) -> Iterator[Response]:
155159
headers = kwargs.pop("headers", {})
@@ -164,6 +168,24 @@ def _get_stream(self, path_params: str, headers: dict, **kwargs) -> Iterator[Res
164168
"GET", f"{self.cfg.endpoint}{path_params}", headers=headers, timeout=timeout, **kwargs
165169
)
166170

171+
def request_json(
172+
self,
173+
method: str,
174+
path: str,
175+
params: Optional[dict] = None,
176+
data: Optional[Union[bytes, str]] = None,
177+
json: Optional[Union[dict, list]] = None,
178+
**kwargs,
179+
) -> dict:
180+
method = method.upper()
181+
if params is None:
182+
params = {}
183+
params.update({"format": "json"})
184+
headers = kwargs.pop("headers", {})
185+
data_bytes = self.__data_to_bytes(headers, data, json)
186+
r = self._ocs(method, f"{quote(path)}?{urlencode(params, True)}", headers, data_bytes, not_parse=True)
187+
return loads(r.text) if r.status_code != 304 else {}
188+
167189
def ocs(
168190
self,
169191
method: str,
@@ -178,12 +200,7 @@ def ocs(
178200
params = {}
179201
params.update({"format": "json"})
180202
headers = kwargs.pop("headers", {})
181-
data_bytes = None
182-
if data is not None:
183-
data_bytes = data.encode("UTF-8") if isinstance(data, str) else data
184-
elif json is not None:
185-
headers.update({"Content-Type": "application/json"})
186-
data_bytes = dumps(json).encode("utf-8")
203+
data_bytes = self.__data_to_bytes(headers, data, json)
187204
return self._ocs(method, f"{quote(path)}?{urlencode(params, True)}", headers, data=data_bytes, **kwargs)
188205

189206
def _ocs(self, method: str, path_params: str, headers: dict, data: Optional[bytes], **kwargs):
@@ -234,12 +251,7 @@ def dav(
234251
**kwargs,
235252
) -> Response:
236253
headers = kwargs.pop("headers", {})
237-
data_bytes = None
238-
if data is not None:
239-
data_bytes = data.encode("UTF-8") if isinstance(data, str) else data
240-
elif json is not None:
241-
headers.update({"Content-Type": "application/json"})
242-
data_bytes = dumps(json).encode("utf-8")
254+
data_bytes = self.__data_to_bytes(headers, data, json)
243255
return self._dav(
244256
method,
245257
quote(self.cfg.dav_url_suffix + path) if isinstance(path, str) else path,
@@ -258,9 +270,9 @@ def dav_stream(
258270
return self._dav_stream(method, quote(self.cfg.dav_url_suffix + path), headers, data_bytes, **kwargs)
259271

260272
def _dav(self, method: str, path: str, headers: dict, data: Optional[bytes], **kwargs) -> Response:
261-
self.init_adapter()
273+
self.init_adapter_dav()
262274
timeout = kwargs.pop("timeout", self.cfg.options.timeout_dav)
263-
result = self.adapter.request(
275+
result = self.adapter_dav.request(
264276
method,
265277
self.cfg.endpoint + path if isinstance(path, str) else str(path),
266278
headers=headers,
@@ -272,9 +284,9 @@ def _dav(self, method: str, path: str, headers: dict, data: Optional[bytes], **k
272284
return result
273285

274286
def _dav_stream(self, method: str, path: str, headers: dict, data: Optional[bytes], **kwargs) -> Iterator[Response]:
275-
self.init_adapter()
287+
self.init_adapter_dav()
276288
timeout = kwargs.pop("timeout", self.cfg.options.timeout_dav)
277-
return self.adapter.stream(
289+
return self.adapter_dav.stream(
278290
method, self.cfg.endpoint + path, headers=headers, content=data, timeout=timeout, **kwargs
279291
)
280292

@@ -290,6 +302,16 @@ def init_adapter(self, restart=False) -> None:
290302
self.adapter.cookies.set("XDEBUG_SESSION", options.XDEBUG_SESSION)
291303
self._capabilities = {}
292304

305+
def init_adapter_dav(self, restart=False) -> None:
306+
if getattr(self, "adapter_dav", None) is None or restart:
307+
if restart and hasattr(self, "adapter"):
308+
self.adapter.close()
309+
self.adapter_dav = self._create_adapter()
310+
if self.custom_headers:
311+
self.adapter_dav.headers.update(self.custom_headers)
312+
if options.XDEBUG_SESSION:
313+
self.adapter_dav.cookies.set("XDEBUG_SESSION", options.XDEBUG_SESSION)
314+
293315
@abstractmethod
294316
def _create_adapter(self) -> Client:
295317
pass # pragma: no cover
@@ -321,6 +343,17 @@ def ae_url(self) -> str:
321343
"""Return base url for the App Ecosystem endpoints."""
322344
return "/ocs/v1.php/apps/app_api/api/v1"
323345

346+
@staticmethod
347+
def __data_to_bytes(
348+
headers: dict, data: Optional[Union[bytes, str]] = None, json: Optional[Union[dict, list]] = None
349+
) -> typing.Optional[bytes]:
350+
if data is not None:
351+
return data.encode("UTF-8") if isinstance(data, str) else data
352+
if json is not None:
353+
headers.update({"Content-Type": "application/json"})
354+
return dumps(json).encode("utf-8")
355+
return None
356+
324357

325358
class NcSession(NcSessionBasic):
326359
cfg: Config

0 commit comments

Comments
 (0)