Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3af72d4
async Nextcloud (pre-1)
bigcat88 Dec 13, 2023
811c479
async Nextcloud (pre-2)
bigcat88 Dec 13, 2023
48a604c
async Nextcloud (pre-3)
bigcat88 Dec 13, 2023
4cb12b9
added async tests for logs and apps
bigcat88 Dec 13, 2023
0e0a88e
removed code incompatible with async
bigcat88 Dec 13, 2023
009638c
added weather_status and theming async testes
bigcat88 Dec 13, 2023
2e09176
test_get_set_location: test with nc_any
bigcat88 Dec 13, 2023
a6a5737
added tests for async user_status
bigcat88 Dec 13, 2023
a17c07c
added async users_groups tests
bigcat88 Dec 13, 2023
cb06251
added async users tests
bigcat88 Dec 13, 2023
254bbaa
added async Notifications API + tests
bigcat88 Dec 13, 2023
8ef24d4
added async Preferences API + tests
bigcat88 Dec 13, 2023
cbb873b
removed code incompatible with async (2)
bigcat88 Dec 14, 2023
e1b7920
added test for `nc_app.set_user`
bigcat88 Dec 14, 2023
0640894
added async Notes API + tests
bigcat88 Dec 14, 2023
ea82ebe
added password_confirmation async test
bigcat88 Dec 14, 2023
33c3318
added async UI API + tests
bigcat88 Dec 14, 2023
ba783dd
added async Files(and Shares) API + tests
bigcat88 Dec 14, 2023
657bfa1
added async tests for ex_app_enable/ex_app_disable
bigcat88 Dec 14, 2023
420a9af
added async Talk API and tests
bigcat88 Dec 14, 2023
3dc1d79
fixed Talk bot coverage test
bigcat88 Dec 14, 2023
f35418c
added tests for `aset_handlers`
bigcat88 Dec 14, 2023
fddadb5
rework of the `set_handlers` function
bigcat88 Dec 15, 2023
f489640
fixed talk_bot tests (1)
bigcat88 Dec 15, 2023
636264d
fixed talk_bot tests (2)
bigcat88 Dec 15, 2023
55493ae
added test: request to NC -> Timeout
bigcat88 Dec 15, 2023
2fe5d10
updated changelog and readme
bigcat88 Dec 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions .github/workflows/analysis-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -598,15 +598,32 @@ jobs:
- name: Enable Talk
run: php occ app:enable spreed

- name: Generate coverage report
- name: Generate coverage report (1)
working-directory: nc_py_api
run: |
coverage run --data-file=.coverage.talk_bot tests/_talk_bot.py &
coverage run --data-file=.coverage.talk_bot tests/_talk_bot_async.py &
echo $! > /tmp/_talk_bot.pid
coverage run --data-file=.coverage.ci -m pytest
kill -15 $(cat /tmp/_talk_bot.pid)
timeout 3m tail --pid=$(cat /tmp/_talk_bot.pid) -f /dev/null
coverage run --data-file=.coverage.at_the_end -m pytest tests/_tests_at_the_end.py

- name: Uninstall NcPyApi
run: |
php occ app_api:app:unregister "$APP_ID" --silent
php occ app_api:daemon:unregister manual_install

- name: Generate coverage report (2)
working-directory: nc_py_api
run: |
coverage run --data-file=.coverage.ci_install_models tests/_install_init_handler_models.py &
echo $! > /tmp/_install_models.pid
python3 tests/_install_wait.py http://127.0.0.1:$APP_PORT/heartbeat "\"status\":\"ok\"" 15 0.5
cd ..
sh nc_py_api/scripts/ci_register.sh "$APP_ID" "$APP_VERSION" "$APP_SECRET" "localhost" "$APP_PORT"
kill -15 $(cat /tmp/_install_models.pid)
timeout 3m tail --pid=$(cat /tmp/_install_models.pid) -f /dev/null
cd nc_py_api
coverage combine && coverage xml && coverage html

- name: HTML coverage to artifacts
Expand Down Expand Up @@ -757,21 +774,38 @@ jobs:
- name: Enable Talk
run: php occ app:enable spreed

- name: Generate coverage report
- name: Generate coverage report (1)
working-directory: nc_py_api
run: |
coverage run --data-file=.coverage.talk_bot tests/_talk_bot.py &
coverage run --data-file=.coverage.talk_bot tests/_talk_bot_async.py &
echo $! > /tmp/_talk_bot.pid
coverage run --data-file=.coverage.ci -m pytest
kill -15 $(cat /tmp/_talk_bot.pid)
timeout 3m tail --pid=$(cat /tmp/_talk_bot.pid) -f /dev/null
coverage run --data-file=.coverage.at_the_end -m pytest tests/_tests_at_the_end.py
coverage combine && coverage xml && coverage html
env:
NPA_TIMEOUT: None
NPA_TIMEOUT_DAV: None
NPA_NC_CERT: False

- name: Uninstall NcPyApi
run: |
php occ app_api:app:unregister "$APP_ID" --silent
php occ app_api:daemon:unregister manual_install

- name: Generate coverage report (2)
working-directory: nc_py_api
run: |
coverage run --data-file=.coverage.ci_install_models tests/_install_init_handler_models.py &
echo $! > /tmp/_install_models.pid
python3 tests/_install_wait.py http://127.0.0.1:$APP_PORT/heartbeat "\"status\":\"ok\"" 15 0.5
cd ..
sh nc_py_api/scripts/ci_register.sh "$APP_ID" "$APP_VERSION" "$APP_SECRET" "localhost" "$APP_PORT"
kill -15 $(cat /tmp/_install_models.pid)
timeout 3m tail --pid=$(cat /tmp/_install_models.pid) -f /dev/null
cd nc_py_api
coverage combine && coverage xml && coverage html

- name: HTML coverage to artifacts
uses: actions/upload-artifact@v3
with:
Expand Down Expand Up @@ -865,7 +899,7 @@ jobs:

- name: Install NcPyApi
working-directory: nc_py_api
run: python3 -m pip -v install . pytest coverage pillow
run: python3 -m pip -v install . pytest pytest-asyncio coverage pillow

- name: Talk Branch Main
if: ${{ startsWith(matrix.nextcloud, 'master') }}
Expand Down
31 changes: 31 additions & 0 deletions .run/aregister_nc_py_api (27).run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="aregister_nc_py_api (27)" type="PythonConfigurationType" factoryName="Python">
<module name="nc_py_api" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="APP_ID" value="nc_py_api" />
<env name="APP_PORT" value="9009" />
<env name="APP_SECRET" value="12345" />
<env name="APP_VERSION" value="1.0.0" />
<env name="NEXTCLOUD_URL" value="http://stable27.local/index.php" />
<env name="APP_HOST" value="0.0.0.0" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/_install_async.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
31 changes: 31 additions & 0 deletions .run/aregister_nc_py_api (28).run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="aregister_nc_py_api (28)" type="PythonConfigurationType" factoryName="Python">
<module name="nc_py_api" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="APP_ID" value="nc_py_api" />
<env name="APP_PORT" value="9009" />
<env name="APP_SECRET" value="12345" />
<env name="APP_VERSION" value="1.0.0" />
<env name="NEXTCLOUD_URL" value="http://stable28.local/index.php" />
<env name="APP_HOST" value="0.0.0.0" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/_install_async.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
31 changes: 31 additions & 0 deletions .run/aregister_nc_py_api (last).run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="aregister_nc_py_api (last)" type="PythonConfigurationType" factoryName="Python">
<module name="nc_py_api" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="APP_ID" value="nc_py_api" />
<env name="APP_PORT" value="9009" />
<env name="APP_SECRET" value="12345" />
<env name="APP_VERSION" value="1.0.0" />
<env name="NEXTCLOUD_URL" value="http://nextcloud.local" />
<env name="APP_HOST" value="0.0.0.0" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/_install_async.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ All notable changes to this project will be documented in this file.

### Added

- set_handlers: `enabled_handler`, `heartbeat_handler` now can be async(Coroutines). #175
- implemented `AsyncNextcloud` and `AsyncNextcloudApp` classes. #181

### Changed

- set_handlers: `enabled_handler`, `heartbeat_handler`, `init_handler` now can be async(Coroutines). #175 #181
- drop Python 3.9 support. #180
- internal code refactoring and clean-up #177

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Python library that provides a robust and well-documented API that allows develo
* **Reliable**: Minimum number of incompatible changes.
* **Robust**: All code is covered with tests as much as possible.
* **Easy**: Designed to be easy to use with excellent documentation.
* **Sync+Async**: Provides both sync and async APIs.

### Capabilities
| **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 |
Expand Down
2 changes: 1 addition & 1 deletion nc_py_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
from ._version import __version__
from .files import FilePermissions, FsNode
from .files.sharing import ShareType
from .nextcloud import Nextcloud, NextcloudApp
from .nextcloud import AsyncNextcloud, AsyncNextcloudApp, Nextcloud, NextcloudApp
26 changes: 25 additions & 1 deletion nc_py_api/_preferences.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Nextcloud API for working with classics app's storage with user's context (table oc_preferences)."""

from ._misc import check_capabilities, require_capabilities
from ._session import NcSessionBasic
from ._session import AsyncNcSessionBasic, NcSessionBasic


class PreferencesAPI:
Expand All @@ -26,3 +26,27 @@ def delete(self, app_name: str, key: str) -> None:
"""Removes a key and its value for a specific application."""
require_capabilities("provisioning_api", self._session.capabilities)
self._session.ocs("DELETE", f"{self._ep_base}/{app_name}/{key}")


class AsyncPreferencesAPI:
"""Async API for setting/removing configuration values of applications that support it."""

_ep_base: str = "/ocs/v1.php/apps/provisioning_api/api/v1/config/users"

def __init__(self, session: AsyncNcSessionBasic):
self._session = session

@property
async def available(self) -> bool:
"""Returns True if the Nextcloud instance supports this feature, False otherwise."""
return not check_capabilities("provisioning_api", await self._session.capabilities)

async def set_value(self, app_name: str, key: str, value: str) -> None:
"""Sets the value for the key for the specific application."""
require_capabilities("provisioning_api", await self._session.capabilities)
await self._session.ocs("POST", f"{self._ep_base}/{app_name}/{key}", params={"configValue": value})

async def delete(self, app_name: str, key: str) -> None:
"""Removes a key and its value for a specific application."""
require_capabilities("provisioning_api", await self._session.capabilities)
await self._session.ocs("DELETE", f"{self._ep_base}/{app_name}/{key}")
80 changes: 79 additions & 1 deletion nc_py_api/_preferences_ex.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ._exceptions import NextcloudExceptionNotFound
from ._misc import require_capabilities
from ._session import NcSessionBasic
from ._session import AsyncNcSessionBasic, NcSessionBasic


@dataclasses.dataclass
Expand Down Expand Up @@ -62,6 +62,49 @@ def delete(self, keys: str | list[str], not_fail=True) -> None:
raise e from None


class _AsyncBasicAppCfgPref:
_url_suffix: str

def __init__(self, session: AsyncNcSessionBasic):
self._session = session

async def get_value(self, key: str, default=None) -> str | None:
"""Returns the value of the key, if found, or the specified default value."""
if not key:
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_api", await self._session.capabilities)
r = await self.get_values([key])
if r:
return r[0].value
return default

async def get_values(self, keys: list[str]) -> list[CfgRecord]:
"""Returns the :py:class:`CfgRecord` for each founded key."""
if not keys:
return []
if not all(keys):
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_api", await self._session.capabilities)
data = {"configKeys": keys}
results = await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}/get-values", json=data)
return [CfgRecord(i) for i in results]

async def delete(self, keys: str | list[str], not_fail=True) -> None:
"""Deletes config/preference entries by the provided keys."""
if isinstance(keys, str):
keys = [keys]
if not keys:
return
if not all(keys):
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_api", await self._session.capabilities)
try:
await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._url_suffix}", json={"configKeys": keys})
except NextcloudExceptionNotFound as e:
if not not_fail:
raise e from None


class PreferencesExAPI(_BasicAppCfgPref):
"""User specific preferences API."""

Expand All @@ -76,6 +119,20 @@ def set_value(self, key: str, value: str) -> None:
self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)


class AsyncPreferencesExAPI(_AsyncBasicAppCfgPref):
"""User specific preferences API."""

_url_suffix = "ex-app/preference"

async def set_value(self, key: str, value: str) -> None:
"""Sets a value for a key."""
if not key:
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_api", await self._session.capabilities)
params = {"configKey": key, "configValue": value}
await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)


class AppConfigExAPI(_BasicAppCfgPref):
"""Non-user(App) specific preferences API."""

Expand All @@ -95,3 +152,24 @@ def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None
if sensitive is not None:
params["sensitive"] = sensitive
self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)


class AsyncAppConfigExAPI(_AsyncBasicAppCfgPref):
"""Non-user(App) specific preferences API."""

_url_suffix = "ex-app/config"

async def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None:
"""Sets a value and if specified the sensitive flag for a key.

.. note:: A sensitive flag ensures key values are truncated in Nextcloud logs.
Default for new records is ``False`` when sensitive is *unspecified*, if changes existing record and
sensitive is *unspecified* it will not change the existing `sensitive` flag.
"""
if not key:
raise ValueError("`key` parameter can not be empty")
require_capabilities("app_api", await self._session.capabilities)
params: dict = {"configKey": key, "configValue": value}
if sensitive is not None:
params["sensitive"] = sensitive
await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
Loading