Compare commits

..

19 Commits

Author SHA1 Message Date
a458788841 Added device info and bot prefix
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 41s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m58s
Tests / Build and Test (3.13) (pull_request) Successful in 1m21s
2025-01-04 18:01:49 +01:00
b76f727263 Replaced MatrixBot.config with MatrixBot.bot_config because .config is already used by smbl
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m52s
Tests / Build and Test (3.11) (pull_request) Successful in 1m18s
Tests / Build and Test (3.12) (pull_request) Successful in 1m22s
Tests / Build and Test (3.13) (pull_request) Successful in 1m23s
2025-01-04 17:49:44 +01:00
kku
5a244f603d TEST: Publishing Action
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 40s
Tests / Build and Test (3.11) (pull_request) Successful in 1m18s
Tests / Build and Test (3.12) (pull_request) Successful in 1m28s
Tests / Build and Test (3.13) (pull_request) Successful in 1m24s
2025-01-02 13:51:36 +01:00
kku
9bc4d0348d WIP: Matrix support (with SMBL) 2025-01-01 22:35:17 +01:00
kku
ae54bd5cce Bump version to 4.0.2
All checks were successful
Analysis / SonarCloud (push) Successful in 42s
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m22s
Tests / Build and Test (3.13) (pull_request) Successful in 1m23s
2024-12-31 11:16:16 +01:00
kku
9ce251d733 Added a quick README for examples (belongs to #60)
All checks were successful
Analysis / SonarCloud (push) Successful in 41s
2024-12-31 11:10:06 +01:00
kku
5dd873d683 Closes #61
All checks were successful
Analysis / SonarCloud (push) Successful in 1m1s
2024-12-31 11:07:24 +01:00
b47bcbe513 Update dependency mypy to v1.14.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 35s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m37s
Tests / Build and Test (3.13) (pull_request) Successful in 1m22s
Analysis / SonarCloud (push) Successful in 47s
2024-12-30 19:17:40 +02:00
kku
bbbec75f91 Fixed naming conventions
All checks were successful
Analysis / SonarCloud (push) Successful in 44s
2024-12-29 19:27:42 +01:00
kku
94553b602e Fixed imports in examples 2024-12-29 16:27:58 +01:00
kku
3cdd6da506 Added typing_extensions to the dependencies
All checks were successful
Analysis / SonarCloud (push) Successful in 58s
Analysis / SonarCloud (pull_request) Successful in 37s
Tests / Build and Test (3.11) (pull_request) Successful in 1m21s
Tests / Build and Test (3.12) (pull_request) Successful in 1m27s
Tests / Build and Test (3.13) (pull_request) Successful in 1m49s
2024-12-29 16:06:45 +01:00
kku
d24e94b57e Tests are now for 3.11+
All checks were successful
Analysis / SonarCloud (push) Successful in 44s
2024-12-27 18:33:51 +01:00
95584c0e63 Slight documentation improvements
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
2024-12-27 00:37:54 +01:00
a13ef83e82 Merge pull request '4.0.0' (#168) from overhaul-v4 into dev
Some checks failed
Analysis / SonarCloud (push) Successful in 40s
Analysis / SonarCloud (pull_request) Successful in 52s
Tests / Build and Test (3.10) (pull_request) Failing after 58s
Tests / Build and Test (3.11) (pull_request) Successful in 1m5s
Tests / Build and Test (3.12) (pull_request) Successful in 1m16s
Tests / Build and Test (3.9) (pull_request) Failing after 58s
Reviewed-on: #168
2024-12-26 19:44:56 +02:00
0ce4ddcf7c Fixed two broken references
Some checks failed
Analysis / SonarCloud (pull_request) Successful in 42s
Tests / Build and Test (3.10) (pull_request) Failing after 57s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.9) (pull_request) Has been cancelled
2024-12-26 18:37:55 +01:00
aa2c778a6a Removed legacy usage of Union[] 2024-12-26 18:36:57 +01:00
a47a508ecf Update dependency pylint to v3.3.3
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 34s
Tests / Build and Test (3.10) (pull_request) Successful in 1m2s
Tests / Build and Test (3.11) (pull_request) Successful in 1m1s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.9) (pull_request) Successful in 1m3s
Analysis / SonarCloud (push) Successful in 39s
2024-12-24 05:27:30 +02:00
bdd649bdbe Update dependency types-aiofiles to v24.1.0.20241221
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.9) (pull_request) Successful in 1m6s
Analysis / SonarCloud (push) Successful in 44s
2024-12-21 05:18:48 +02:00
e6d9beec81 Update dependency mypy to v1.14.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 35s
Tests / Build and Test (3.10) (pull_request) Successful in 3m26s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.9) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 46s
2024-12-20 17:47:59 +02:00
29 changed files with 377 additions and 170 deletions

View File

@@ -0,0 +1,90 @@
name: Upload Python Package
on:
release:
types: [ published ]
permissions:
contents: read
jobs:
release-build:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Build release distributions
run: |
python -m pip install build
python -m build
- name: Upload distributions
uses: christopherhx/gitea-upload-artifact@v4
with:
name: release-dists
path: dist/
gitea-publish:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
needs:
- release-build
permissions:
id-token: write
environment:
name: gitea
url: https://git.end-play.xyz/profitroll/-/packages/pypi/libbot
env:
GITHUB_WORKFLOW_REF: ${{ gitea.workflow_ref }}
INPUT_REPOSITORY_URL: https://git.end-play.xyz/api/packages/profitroll/pypi
steps:
- name: Retrieve release distributions
uses: christopherhx/gitea-download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_GITEA_API_TOKEN }}
repository-url: https://git.end-play.xyz/api/packages/profitroll/pypi
pypi-publish:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
needs:
- release-build
permissions:
id-token: write
environment:
name: pypi
env:
GITHUB_WORKFLOW_REF: ${{ gitea.workflow_ref }}
steps:
- name: Retrieve release distributions
uses: christopherhx/gitea-download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PYPI_API_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
container: catthehacker/ubuntu:act-latest container: catthehacker/ubuntu:act-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: [ "3.11", "3.12", "3.13" ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -36,19 +36,20 @@ pip install libbot[pycord,speed]
### Pyrogram ### Pyrogram
```python ```python
from libbot.pyrogram import PyroClient import sys
from libbot.pyrogram.classes import PyroClient
def main(): def main():
client = PyroClient(scheduler=scheduler) client: PyroClient = PyroClient()
try: try:
client.run() client.run()
except KeyboardInterrupt: except KeyboardInterrupt:
print("Shutting down...") print("Shutting down...")
finally: finally:
if client.scheduler is not None: sys.exit()
client.scheduler.shutdown()
exit()
if __name__ == "__main__": if __name__ == "__main__":
@@ -58,29 +59,33 @@ if __name__ == "__main__":
### Pycord ### Pycord
```python ```python
import asyncio
from asyncio import AbstractEventLoop
from discord import Intents from discord import Intents
from libbot import sync from libbot.utils import config_get
from libbot.pycord import PycordBot from libbot.pycord.classes import PycordBot
async def main(): async def main():
intents = Intents.default() intents: Intents = Intents.default()
bot = PycordBot(intents=intents) bot: PycordBot = PycordBot(intents=intents)
bot.load_extension("cogs") bot.load_extension("cogs")
try: try:
await bot.start(sync.config_get("bot_token", "bot")) await bot.start(config_get("bot_token", "bot"))
except KeyboardInterrupt: except KeyboardInterrupt:
logger.warning("Shutting down...") print("Shutting down...")
await bot.close() await bot.close()
if __name__ == "__main__": if __name__ == "__main__":
loop = asyncio.get_event_loop() loop: AbstractEventLoop = asyncio.get_event_loop()
loop.run_until_complete(main()) loop.run_until_complete(main())
``` ```
## Config examples ## Config examples
For bot config examples please check the examples directory. Without a valid config file, the bot won't start at all, so you need to make sure the correct config file is used. For bot config examples please check the examples directory. Without a valid config file, the bot won't start at all, so
you need to make sure the correct config file is used.

4
examples/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Examples
If you're looking for Pyrogram usage examples, please take a look at
the [PyrogramBotBase](https://git.end-play.xyz/profitroll/PyrogramBotBase) repository.

View File

@@ -28,6 +28,7 @@ dependencies = { file = "requirements/_.txt" }
[tool.setuptools.dynamic.optional-dependencies] [tool.setuptools.dynamic.optional-dependencies]
dev = { file = "requirements/dev.txt" } dev = { file = "requirements/dev.txt" }
matrix = { file = "requirements/matrix.txt" }
pycord = { file = "requirements/pycord.txt" } pycord = { file = "requirements/pycord.txt" }
pyrogram = { file = "requirements/pyrogram.txt" } pyrogram = { file = "requirements/pyrogram.txt" }
speed = { file = "requirements/speed.txt" } speed = { file = "requirements/speed.txt" }

View File

@@ -1 +1,2 @@
aiofiles>=23.0.0 aiofiles>=23.0.0
typing-extensions~=4.12.2

View File

@@ -1,11 +1,12 @@
black==24.10.0 black==24.10.0
build==1.2.2.post1 build==1.2.2.post1
isort==5.13.2 isort==5.13.2
mypy==1.13.0 mypy==1.14.1
pylint==3.3.2 pylint==3.3.3
pytest-asyncio==0.25.0 pytest-asyncio==0.25.0
pytest-cov==6.0.0 pytest-cov==6.0.0
pytest==8.3.4 pytest==8.3.4
tox==4.23.2 tox==4.23.2
types-aiofiles==24.1.0.20240626 twine==6.0.1
types-aiofiles==24.1.0.20241221
types-ujson==5.10.0.20240515 types-ujson==5.10.0.20240515

1
requirements/matrix.txt Normal file
View File

@@ -0,0 +1 @@
simplematrixbotlib~=2.12.1

View File

@@ -1,4 +1,4 @@
__version__ = "4.0.0" __version__ = "4.0.2"
__license__ = "GPL3" __license__ = "GPL3"
__author__ = "Profitroll" __author__ = "Profitroll"

View File

@@ -1,15 +1,15 @@
from typing import Any, List, Optional, Union from typing import Any, List, Optional
class ConfigKeyError(Exception): class ConfigKeyError(Exception):
"""Raised when config key is not found. """Raised when config key is not found.
### Attributes: ### Attributes:
* key (`Union[str, List[str]]`): Missing config key. * key (`str | List[str]`): Missing config key.
""" """
def __init__(self, key: Union[str, List[str]]) -> None: def __init__(self, key: str | List[str]) -> None:
self.key: Union[str, List[str]] = key self.key: str | List[str] = key
super().__init__( super().__init__(
f"Config key {'.'.join(key) if isinstance(key, list) else key} is missing. Please set in your config file." f"Config key {'.'.join(key) if isinstance(key, list) else key} is missing. Please set in your config file."
) )
@@ -22,12 +22,12 @@ class ConfigValueError(Exception):
"""Raised when config key's value is invalid. """Raised when config key's value is invalid.
### Attributes: ### Attributes:
* key (`Union[str, List[str]]`): Invalid config key. * key (`str | List[str]`): Invalid config key.
* value (`Optional[Any]`): Key's correct value. * value (`Optional[Any]`): Key's correct value.
""" """
def __init__(self, key: Union[str, List[str]], value: Optional[Any] = None) -> None: def __init__(self, key: str | List[str], value: Optional[Any] = None) -> None:
self.key: Union[str, List[str]] = key self.key: str | List[str] = key
self.value: Optional[Any] = value self.value: Optional[Any] = value
super().__init__( super().__init__(
f"Config key {'.'.join(key) if isinstance(key, list) else key} has invalid value. {f'Must be {value}. ' if value else ''}Please set in your config file." f"Config key {'.'.join(key) if isinstance(key, list) else key} has invalid value. {f'Must be {value}. ' if value else ''}Please set in your config file."

View File

@@ -1,13 +1,13 @@
from os import listdir, PathLike from os import listdir, PathLike
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Union, List from typing import Any, Dict, List
from ..utils.config import config_get from ..utils.config import config_get
from ..utils.json import json_read from ..utils.json import json_read
from ..utils.syncs import asyncable from ..utils.syncs import asyncable
def _get_valid_locales(locales_root: Union[str, PathLike[str]]) -> List[str]: def _get_valid_locales(locales_root: str | PathLike[str]) -> List[str]:
return [".".join(entry.split(".")[:-1]) for entry in listdir(locales_root)] return [".".join(entry.split(".")[:-1]) for entry in listdir(locales_root)]
@@ -15,19 +15,19 @@ def _get_valid_locales(locales_root: Union[str, PathLike[str]]) -> List[str]:
def _( def _(
key: str, key: str,
*args: str, *args: str,
locale: Union[str, None] = "en", locale: str | None = "en",
locales_root: Union[str, Path] = Path("locale"), locales_root: str | Path = Path("locale"),
) -> Any: ) -> Any:
"""Get value of locale string """Get value of locale string
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locale (`Union[str, None]`): Locale to looked up in. Defaults to `"en"`. * locale (`str | None`): Locale to looked up in. Defaults to `"en"`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list` * `Any`: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`
""" """
if locale is None: if locale is None:
locale: str = config_get("locale") locale: str = config_get("locale")
@@ -55,19 +55,19 @@ def _(
async def _( async def _(
key: str, key: str,
*args: str, *args: str,
locale: Union[str, None] = "en", locale: str | None = "en",
locales_root: Union[str, Path] = Path("locale"), locales_root: str | Path = Path("locale"),
) -> Any: ) -> Any:
"""Get value of locale string """Get value of locale string
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locale (`Union[str, None]`): Locale to looked up in. Defaults to `"en"`. * locale (`str | None`): Locale to looked up in. Defaults to `"en"`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list` * `Any`: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`
""" """
locale: str = config_get("locale") if locale is None else locale locale: str = config_get("locale") if locale is None else locale
@@ -93,19 +93,19 @@ async def _(
@asyncable @asyncable
def in_all_locales(key: str, *args: str, locales_root: Union[str, Path] = Path("locale")) -> list: def in_all_locales(key: str, *args: str, locales_root: str | Path = Path("locale")) -> List[Any]:
"""Get value of the provided key and path in all available locales """Get value of the provided key and path in all available locales
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `list`: List of values in all locales * `List[Any]`: List of values in all locales
""" """
output: List[str] = [] output: List[Any] = []
for locale in _get_valid_locales(locales_root): for locale in _get_valid_locales(locales_root):
try: try:
@@ -127,19 +127,19 @@ def in_all_locales(key: str, *args: str, locales_root: Union[str, Path] = Path("
@in_all_locales.asynchronous @in_all_locales.asynchronous
async def in_all_locales(key: str, *args: str, locales_root: Union[str, Path] = Path("locale")) -> list: async def in_all_locales(key: str, *args: str, locales_root: str | Path = Path("locale")) -> List[Any]:
"""Get value of the provided key and path in all available locales """Get value of the provided key and path in all available locales
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `list`: List of values in all locales * `List[Any]`: List of values in all locales
""" """
output = [] output: List[Any] = []
for locale in _get_valid_locales(locales_root): for locale in _get_valid_locales(locales_root):
try: try:
@@ -162,17 +162,17 @@ async def in_all_locales(key: str, *args: str, locales_root: Union[str, Path] =
@asyncable @asyncable
def in_every_locale( def in_every_locale(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale") key: str, *args: str, locales_root: str | Path = Path("locale")
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag """Get value of the provided key and path in every available locale with locale tag
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value * `Dict[str, Any]`: Locale is a key, and it's value from locale file is a value
""" """
output: Dict[str, Any] = {} output: Dict[str, Any] = {}
@@ -198,17 +198,17 @@ def in_every_locale(
@in_every_locale.asynchronous @in_every_locale.asynchronous
async def in_every_locale( async def in_every_locale(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale") key: str, *args: str, locales_root: str | Path = Path("locale")
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag """Get value of the provided key and path in every available locale with locale tag
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`. * locales_root (`str | Path`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns: ### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value * `Dict[str, Any]`: Locale is a key, and it's value from locale file is a value
""" """
output: Dict[str, Any] = {} output: Dict[str, Any] = {}

View File

@@ -1,6 +1,6 @@
from os import listdir from os import listdir
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Union, List from typing import Any, Dict, List
from ...utils.config import config_get from ...utils.config import config_get
from ...utils.json import json_read from ...utils.json import json_read
@@ -11,8 +11,8 @@ class BotLocale:
def __init__( def __init__(
self, self,
default_locale: Union[str, None] = "en", default_locale: str | None = "en",
locales_root: Union[str, Path] = Path("locale"), locales_root: str | Path = Path("locale"),
) -> None: ) -> None:
if isinstance(locales_root, str): if isinstance(locales_root, str):
locales_root = Path(locales_root) locales_root = Path(locales_root)
@@ -29,16 +29,16 @@ class BotLocale:
for locale in valid_locales: for locale in valid_locales:
self.locales[locale] = json_read(Path(f"{locales_root}/{locale}.json")) self.locales[locale] = json_read(Path(f"{locales_root}/{locale}.json"))
def _(self, key: str, *args: str, locale: Union[str, None] = None) -> Any: def _(self, key: str, *args: str, locale: str | None = None) -> Any:
"""Get value of locale string """Get value of locale string
### Args: ### Args:
* key (`str`): The last key of the locale's keys path * key (`str`): The last key of the locale's keys path
* *args (`list`): Path to key like: `dict[args][key]` * *args (`str`): Path to key like: `dict[args][key]`
* locale (`Union[str, None]`, *optional*): Locale to looked up in. Defaults to config's `"locale"` value * locale (`str | None`, *optional*): Locale to looked up in. Defaults to config's `"locale"` value
### Returns: ### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list` * `Any`: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`
""" """
if locale is None: if locale is None:
locale: str = self.default locale: str = self.default
@@ -63,17 +63,17 @@ class BotLocale:
except KeyError: except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"' return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
def in_all_locales(self, key: str, *args: str) -> list: def in_all_locales(self, key: str, *args: str) -> List[Any]:
"""Get value of the provided key and path in all available locales """Get value of the provided key and path in all available locales
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
### Returns: ### Returns:
* `list`: List of values in all locales * `List[Any]`: List of values in all locales
""" """
output: List[str] = [] output: List[Any] = []
for name, locale in self.locales.items(): for name, locale in self.locales.items():
try: try:
@@ -98,10 +98,10 @@ class BotLocale:
### Args: ### Args:
* key (`str`): The last key of the locale's keys path. * key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`. * *args (`str`): Path to key like: `dict[args][key]`.
### Returns: ### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value * `Dict[str, Any]`: Locale is a key, and it's value from locale file is a value
""" """
output: Dict[str, Any] = {} output: Dict[str, Any] = {}

View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[matrix] is not installed

View File

@@ -0,0 +1 @@
from .bot import MatrixBot

View File

@@ -0,0 +1,80 @@
import logging
from logging import Logger
from pathlib import Path
from typing import Dict, Any
from typing_extensions import override
from ... import __version__ as __libbot_version__
from ...i18n.classes import BotLocale
from ...utils import json_read
try:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.schedulers.background import BackgroundScheduler
from simplematrixbotlib import Bot, Creds, Config
except ImportError as exc:
raise ImportError("You need to install libbot[matrix] in order to use this class.") from exc
logger: Logger = logging.getLogger(__name__)
class MatrixBot(Bot):
@override
def __init__(
self,
config: Dict[str, Any] | None = None,
config_path: str | Path = Path("config.json"),
locales_root: str | Path | None = None,
scheduler: AsyncIOScheduler | BackgroundScheduler | None = None,
smbl_creds: Creds = None,
smbl_config: Config = None,
):
self.bot_config: Dict[str, Any] = config if config is not None else json_read(config_path)
super().__init__(
creds=(
smbl_creds
if smbl_creds is not None
else Creds(
homeserver=self.bot_config["bot"]["homeserver"],
username=self.bot_config["bot"]["username"],
password=self.bot_config["bot"]["password"],
device_name=(
f"LibBotUniversal v{__libbot_version__}"
if "device_name" not in self.bot_config["bot"]
else self.bot_config["bot"]["device_name"]
),
)
),
config=smbl_config,
)
self.bot_prefix: str = (
"!" if "prefix" not in self.bot_config["bot"] else self.bot_config["bot"]["prefix"]
)
self.bot_locale: BotLocale = BotLocale(
default_locale=self.bot_config["locale"],
locales_root=(Path("locale") if locales_root is None else locales_root),
)
self.default_locale: str = self.bot_locale.default
self.locales: Dict[str, Any] = self.bot_locale.locales
self._ = self.bot_locale._
self.in_all_locales = self.bot_locale.in_all_locales
self.in_every_locale = self.bot_locale.in_every_locale
self.scheduler: AsyncIOScheduler | BackgroundScheduler | None = scheduler
@override
def run(
self, scheduler_start: bool = True, scheduler_shutdown: bool = True, scheduler_wait: bool = True
) -> None:
if self.scheduler is not None and scheduler_start:
self.scheduler.start()
super().run()
if self.scheduler is not None and scheduler_shutdown:
self.scheduler.shutdown(scheduler_wait)

View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[pycord] is not installed

View File

@@ -1,6 +1,6 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Union from typing import Any, Dict
from typing_extensions import override from typing_extensions import override
@@ -26,10 +26,10 @@ class PycordBot(Bot):
def __init__( def __init__(
self, self,
*args, *args,
config: Union[Dict[str, Any], None] = None, config: Dict[str, Any] | None = None,
config_path: Union[str, Path] = Path("config.json"), config_path: str | Path = Path("config.json"),
locales_root: Union[str, Path, None] = None, locales_root: str | Path | None = None,
scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = None, scheduler: AsyncIOScheduler | BackgroundScheduler | None = None,
**kwargs, **kwargs,
): ):
if config is None: if config is None:
@@ -56,7 +56,7 @@ class PycordBot(Bot):
self.in_all_locales = self.bot_locale.in_all_locales self.in_all_locales = self.bot_locale.in_all_locales
self.in_every_locale = self.bot_locale.in_every_locale self.in_every_locale = self.bot_locale.in_every_locale
self.scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = scheduler self.scheduler: AsyncIOScheduler | BackgroundScheduler | None = scheduler
@override @override
async def start(self, token: str, reconnect: bool = True, scheduler_start: bool = True) -> None: async def start(self, token: str, reconnect: bool = True, scheduler_start: bool = True) -> None:

View File

@@ -0,0 +1 @@
from .color import color_from_hex, hex_from_color

View File

@@ -0,0 +1,35 @@
from discord import Colour
def _int_from_hex(hex_string: str) -> int:
try:
return int(hex_string, base=16)
except Exception as exc:
raise ValueError("Input string must be a valid HEX code.") from exc
def _hex_from_int(color_int: int) -> str:
if not 0 <= color_int <= 0xFFFFFF:
raise ValueError("Color's value must be in the range 0 to 0xFFFFFF.")
return f"#{color_int:06x}"
def color_from_hex(hex_string: str) -> Colour:
"""Convert valid hexadecimal string to discord.Colour.
:param hex_string: Hexadecimal string to convert into Colour object
:type hex_string: str
:return: Colour object
"""
return Colour(_int_from_hex(hex_string))
def hex_from_color(color: Colour) -> str:
"""Convert discord.Colour to hexadecimal string.
:param color: Colour object to convert into the string
:type color: Colour
:return: Hexadecimal string in #XXXXXX format
"""
return _hex_from_int(color.value)

View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[pyrogram] is not installed

View File

@@ -5,7 +5,7 @@ from datetime import datetime, timedelta
from os import cpu_count, getpid from os import cpu_count, getpid
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import Any, Dict, List, Union from typing import Any, Dict, List
from typing_extensions import override from typing_extensions import override
@@ -48,22 +48,22 @@ class PyroClient(Client):
def __init__( def __init__(
self, self,
name: str = "bot_client", name: str = "bot_client",
owner: Union[int, None] = None, owner: int | None = None,
config: Union[Dict[str, Any], None] = None, config: Dict[str, Any] | None = None,
config_path: Union[str, Path] = Path("config.json"), config_path: str | Path = Path("config.json"),
api_id: Union[int, None] = None, api_id: int | None = None,
api_hash: Union[str, None] = None, api_hash: str | None = None,
bot_token: Union[str, None] = None, bot_token: str | None = None,
workers: int = min(32, cpu_count() + 4), workers: int = min(32, cpu_count() + 4),
locales_root: Union[str, Path, None] = None, locales_root: str | Path | None = None,
plugins_root: str = "plugins", plugins_root: str = "plugins",
plugins_exclude: Union[List[str], None] = None, plugins_exclude: List[str] | None = None,
sleep_threshold: int = 120, sleep_threshold: int = 120,
max_concurrent_transmissions: int = 1, max_concurrent_transmissions: int = 1,
commands_source: Union[Dict[str, dict], None] = None, commands_source: Dict[str, dict] | None = None,
scoped_commands: Union[bool, None] = None, scoped_commands: bool | None = None,
i18n_bot_info: bool = False, i18n_bot_info: bool = False,
scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = None, scheduler: AsyncIOScheduler | BackgroundScheduler | None = None,
**kwargs, **kwargs,
): ):
if config is None: if config is None:
@@ -113,7 +113,7 @@ class PyroClient(Client):
self.in_all_locales = self.bot_locale.in_all_locales self.in_all_locales = self.bot_locale.in_all_locales
self.in_every_locale = self.bot_locale.in_every_locale self.in_every_locale = self.bot_locale.in_every_locale
self.scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = scheduler self.scheduler: AsyncIOScheduler | BackgroundScheduler | None = scheduler
self.scopes_placeholders: Dict[str, int] = {"owner": self.owner} self.scopes_placeholders: Dict[str, int] = {"owner": self.owner}
@@ -173,7 +173,7 @@ class PyroClient(Client):
) )
logger.info( logger.info(
"Bot's info for the locale %s has been updated", "Bot's info for the locale %s has been updated",
self.code, code,
) )
except KeyError: except KeyError:
logger.warning( logger.warning(
@@ -238,7 +238,7 @@ class PyroClient(Client):
except SystemExit as exc: except SystemExit as exc:
raise SystemExit("Bot has been shut down, this is not an application error!") from exc raise SystemExit("Bot has been shut down, this is not an application error!") from exc
async def collect_commands(self) -> Union[List[CommandSet], None]: async def collect_commands(self) -> List[CommandSet] | None:
"""Gather list of the bot's commands """Gather list of the bot's commands
### Returns: ### Returns:
@@ -309,7 +309,9 @@ class PyroClient(Client):
# This part here looks into the handlers and looks for commands # This part here looks into the handlers and looks for commands
# in it, if there are any. Then adds them to self.commands # in it, if there are any. Then adds them to self.commands
for handler in self.dispatcher.groups[0]: for handler in self.dispatcher.groups[0]:
if isinstance(handler, MessageHandler): if isinstance(handler, MessageHandler) and (
hasattr(handler.filters, "base") or hasattr(handler.filters, "other")
):
for entry in [handler.filters.base, handler.filters.other]: for entry in [handler.filters.base, handler.filters.other]:
if hasattr(entry, "commands"): if hasattr(entry, "commands"):
for command in entry.commands: for command in entry.commands:
@@ -338,7 +340,7 @@ class PyroClient(Client):
command, command,
) )
async def register_commands(self, command_sets: Union[List[CommandSet], None] = None) -> None: async def register_commands(self, command_sets: List[CommandSet] | None = None) -> None:
"""Register commands stored in bot's 'commands' attribute""" """Register commands stored in bot's 'commands' attribute"""
if command_sets is None: if command_sets is None:
@@ -365,7 +367,7 @@ class PyroClient(Client):
language_code=command_set.language_code, language_code=command_set.language_code,
) )
async def remove_commands(self, command_sets: Union[List[CommandSet], None] = None) -> None: async def remove_commands(self, command_sets: List[CommandSet] | None = None) -> None:
"""Remove commands stored in bot's 'commands' attribute""" """Remove commands stored in bot's 'commands' attribute"""
if command_sets is None: if command_sets is None:

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import List, Union from typing import List
try: try:
from pyrogram.types import ( from pyrogram.types import (
@@ -13,9 +13,7 @@ try:
BotCommandScopeDefault, BotCommandScopeDefault,
) )
except ImportError as exc: except ImportError as exc:
raise ImportError( raise ImportError("You need to install libbot[pyrogram] in order to use this class.") from exc
"You need to install libbot[pyrogram] in order to use this class."
) from exc
@dataclass @dataclass
@@ -23,13 +21,13 @@ class CommandSet:
"""Command stored in PyroClient's 'commands' attribute""" """Command stored in PyroClient's 'commands' attribute"""
commands: List[BotCommand] commands: List[BotCommand]
scope: Union[ scope: (
BotCommandScopeDefault, BotCommandScopeDefault
BotCommandScopeAllPrivateChats, | BotCommandScopeAllPrivateChats
BotCommandScopeAllGroupChats, | BotCommandScopeAllGroupChats
BotCommandScopeAllChatAdministrators, | BotCommandScopeAllChatAdministrators
BotCommandScopeChat, | BotCommandScopeChat
BotCommandScopeChatAdministrators, | BotCommandScopeChatAdministrators
BotCommandScopeChatMember, | BotCommandScopeChatMember
] = BotCommandScopeDefault ) = BotCommandScopeDefault
language_code: str = "" language_code: str = ""

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, Union, Dict from typing import Any, Dict
from ..json import json_read, json_write from ..json import json_read, json_write
from ..misc import nested_delete, nested_set from ..misc import nested_delete, nested_set
@@ -14,14 +14,14 @@ DEFAULT_CONFIG_LOCATION: str = "config.json"
@asyncable @asyncable
def config_get(key: str, *path: str, config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION) -> Any: def config_get(key: str, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> Any:
"""Get a value of the config key by its path provided """Get a value of the config key by its path provided
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"` For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`
### Args: ### Args:
* key (`str`): Key that contains the value * key (`str`): Key that contains the value
* *path (`str`): Path to the key that contains the value * *path (`str`): Path to the key that contains the value
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Returns: ### Returns:
* `Any`: Key's value * `Any`: Key's value
@@ -53,14 +53,14 @@ def config_get(key: str, *path: str, config_file: Union[str, Path] = DEFAULT_CON
@config_get.asynchronous @config_get.asynchronous
async def config_get(key: str, *path: str, config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION) -> Any: async def config_get(key: str, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> Any:
"""Get a value of the config key by its path provided """Get a value of the config key by its path provided
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"` For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`
### Args: ### Args:
* key (`str`): Key that contains the value * key (`str`): Key that contains the value
* *path (`str`): Path to the key that contains the value * *path (`str`): Path to the key that contains the value
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Returns: ### Returns:
* `Any`: Key's value * `Any`: Key's value
@@ -92,16 +92,14 @@ async def config_get(key: str, *path: str, config_file: Union[str, Path] = DEFAU
@asyncable @asyncable
def config_set( def config_set(key: str, value: Any, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> None:
key: str, value: Any, *path: str, config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION
) -> None:
"""Set config's key by its path to the value """Set config's key by its path to the value
### Args: ### Args:
* key (`str`): Key that leads to the value * key (`str`): Key that leads to the value
* value (`Any`): Any JSON serializable data * value (`Any`): Any JSON serializable data
* *path (`str`): Path to the key of the target * *path (`str`): Path to the key of the target
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Raises: ### Raises:
* `KeyError`: Key is not found under path provided * `KeyError`: Key is not found under path provided
@@ -111,7 +109,7 @@ def config_set(
@config_set.asynchronous @config_set.asynchronous
async def config_set( async def config_set(
key: str, value: Any, *path: str, config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION key: str, value: Any, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION
) -> None: ) -> None:
"""Set config's key by its path to the value """Set config's key by its path to the value
@@ -119,7 +117,7 @@ async def config_set(
* key (`str`): Key that leads to the value * key (`str`): Key that leads to the value
* value (`Any`): Any JSON serializable data * value (`Any`): Any JSON serializable data
* *path (`str`): Path to the key of the target * *path (`str`): Path to the key of the target
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Raises: ### Raises:
* `KeyError`: Key is not found under path provided * `KeyError`: Key is not found under path provided
@@ -132,7 +130,7 @@ def config_delete(
key: str, key: str,
*path: str, *path: str,
missing_ok: bool = False, missing_ok: bool = False,
config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION, config_file: str | Path = DEFAULT_CONFIG_LOCATION,
) -> None: ) -> None:
"""Set config's key by its path """Set config's key by its path
@@ -140,7 +138,7 @@ def config_delete(
* key (`str`): Key to delete * key (`str`): Key to delete
* *path (`str`): Path to the key of the target * *path (`str`): Path to the key of the target
* missing_ok (`bool`): Do not raise an exception if the key is missing. Defaults to `False` * missing_ok (`bool`): Do not raise an exception if the key is missing. Defaults to `False`
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Raises: ### Raises:
* `KeyError`: Key is not found under path provided and `missing_ok` is `False` * `KeyError`: Key is not found under path provided and `missing_ok` is `False`
@@ -161,7 +159,7 @@ async def config_delete(
key: str, key: str,
*path: str, *path: str,
missing_ok: bool = False, missing_ok: bool = False,
config_file: Union[str, Path] = DEFAULT_CONFIG_LOCATION, config_file: str | Path = DEFAULT_CONFIG_LOCATION,
) -> None: ) -> None:
"""Set config's key by its path """Set config's key by its path
@@ -169,7 +167,7 @@ async def config_delete(
* key (`str`): Key to delete * key (`str`): Key to delete
* *path (`str`): Path to the key of the target * *path (`str`): Path to the key of the target
* missing_ok (`bool`): Do not raise an exception if the key is missing. Defaults to `False` * missing_ok (`bool`): Do not raise an exception if the key is missing. Defaults to `False`
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"` * config_file (`str | Path`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Raises: ### Raises:
* `KeyError`: Key is not found under path provided and `missing_ok` is `False` * `KeyError`: Key is not found under path provided and `missing_ok` is `False`

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, Union from typing import Any
import aiofiles import aiofiles
@@ -13,11 +13,11 @@ except ImportError:
@asyncable @asyncable
def json_read(path: Union[str, Path]) -> Any: def json_read(path: str | Path) -> Any:
"""Read contents of a JSON file """Read contents of a JSON file
### Args: ### Args:
* path (`Union[str, Path]`): Path-like object or path as a string * path (`str | Path`): Path-like object or path as a string
### Returns: ### Returns:
* `Any`: File contents * `Any`: File contents
@@ -29,11 +29,11 @@ def json_read(path: Union[str, Path]) -> Any:
@json_read.asynchronous @json_read.asynchronous
async def json_read(path: Union[str, Path]) -> Any: async def json_read(path: str | Path) -> Any:
"""Read contents of a JSON file """Read contents of a JSON file
### Args: ### Args:
* path (`Union[str, Path]`): Path-like object or path as a string * path (`str | Path`): Path-like object or path as a string
### Returns: ### Returns:
* `Any`: File contents * `Any`: File contents
@@ -45,12 +45,12 @@ async def json_read(path: Union[str, Path]) -> Any:
@asyncable @asyncable
def json_write(data: Any, path: Union[str, Path]) -> None: def json_write(data: Any, path: str | Path) -> None:
"""Write contents to a JSON file """Write contents to a JSON file
### Args: ### Args:
* data (`Any`): Contents to write. Must be a JSON serializable * data (`Any`): Contents to write. Must be a JSON serializable
* path (`Union[str, Path]`): Path-like object or path as a string of a destination * path (`str | Path`): Path-like object or path as a string of a destination
""" """
with open(str(path), mode="w", encoding="utf-8") as f: with open(str(path), mode="w", encoding="utf-8") as f:
f.write( f.write(
@@ -61,12 +61,12 @@ def json_write(data: Any, path: Union[str, Path]) -> None:
@json_write.asynchronous @json_write.asynchronous
async def json_write(data: Any, path: Union[str, Path]) -> None: async def json_write(data: Any, path: str | Path) -> None:
"""Write contents to a JSON file """Write contents to a JSON file
### Args: ### Args:
* data (`Any`): Contents to write. Must be a JSON serializable * data (`Any`): Contents to write. Must be a JSON serializable
* path (`Union[str, Path]`): Path-like object or path as a string of a destination * path (`str | Path`): Path-like object or path as a string of a destination
""" """
async with aiofiles.open(str(path), mode="w", encoding="utf-8") as f: async with aiofiles.open(str(path), mode="w", encoding="utf-8") as f:
await f.write( await f.write(

View File

@@ -1,8 +1,6 @@
from pathlib import Path from typing import Any, List
from typing import Any, List, Union
import pytest import pytest
from libbot.i18n import BotLocale from libbot.i18n import BotLocale
@@ -20,14 +18,12 @@ from libbot.i18n import BotLocale
def test_bot_locale_get( def test_bot_locale_get(
key: str, key: str,
args: List[str], args: List[str],
locale: Union[str, None], locale: str | None,
expected: Any, expected: Any,
bot_locale: BotLocale, bot_locale: BotLocale,
): ):
assert ( assert (
bot_locale._(key, *args, locale=locale) bot_locale._(key, *args, locale=locale) if locale is not None else bot_locale._(key, *args)
if locale is not None
else bot_locale._(key, *args)
) == expected ) == expected
@@ -39,9 +35,7 @@ def test_bot_locale_get(
("nested", ["callbacks", "default"], ["sure", "авжеж"]), ("nested", ["callbacks", "default"], ["sure", "авжеж"]),
], ],
) )
def test_i18n_in_all_locales( def test_i18n_in_all_locales(key: str, args: List[str], expected: Any, bot_locale: BotLocale):
key: str, args: List[str], expected: Any, bot_locale: BotLocale
):
assert (bot_locale.in_all_locales(key, *args)) == expected assert (bot_locale.in_all_locales(key, *args)) == expected
@@ -53,7 +47,5 @@ def test_i18n_in_all_locales(
("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}), ("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}),
], ],
) )
def test_i18n_in_every_locale( def test_i18n_in_every_locale(key: str, args: List[str], expected: Any, bot_locale: BotLocale):
key: str, args: List[str], expected: Any, bot_locale: BotLocale
):
assert (bot_locale.in_every_locale(key, *args)) == expected assert (bot_locale.in_every_locale(key, *args)) == expected

View File

@@ -1,8 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import Any, List, Union from typing import Any, List
import pytest import pytest
from libbot import i18n from libbot import i18n
@@ -21,7 +20,7 @@ from libbot import i18n
async def test_i18n_get( async def test_i18n_get(
key: str, key: str,
args: List[str], args: List[str],
locale: Union[str, None], locale: str | None,
expected: Any, expected: Any,
location_locale: Path, location_locale: Path,
): ):
@@ -41,12 +40,8 @@ async def test_i18n_get(
("nested", ["callbacks", "default"], ["sure", "авжеж"]), ("nested", ["callbacks", "default"], ["sure", "авжеж"]),
], ],
) )
async def test_i18n_in_all_locales( async def test_i18n_in_all_locales(key: str, args: List[str], expected: Any, location_locale: Path):
key: str, args: List[str], expected: Any, location_locale: Path assert (await i18n.in_all_locales(key, *args, locales_root=location_locale)) == expected
):
assert (
await i18n.in_all_locales(key, *args, locales_root=location_locale)
) == expected
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -58,9 +53,5 @@ async def test_i18n_in_all_locales(
("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}), ("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}),
], ],
) )
async def test_i18n_in_every_locale( async def test_i18n_in_every_locale(key: str, args: List[str], expected: Any, location_locale: Path):
key: str, args: List[str], expected: Any, location_locale: Path assert (await i18n.in_every_locale(key, *args, locales_root=location_locale)) == expected
):
assert (
await i18n.in_every_locale(key, *args, locales_root=location_locale)
) == expected

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, List, Union from typing import Any, List
import pytest import pytest
from libbot.i18n import _, in_all_locales, in_every_locale from libbot.i18n import _, in_all_locales, in_every_locale
@@ -19,7 +19,7 @@ from libbot.i18n import _, in_all_locales, in_every_locale
def test_i18n_get( def test_i18n_get(
key: str, key: str,
args: List[str], args: List[str],
locale: Union[str, None], locale: str | None,
expected: Any, expected: Any,
location_locale: Path, location_locale: Path,
): ):

View File

@@ -4,7 +4,7 @@ except ImportError:
from json import dumps, JSONDecodeError from json import dumps, JSONDecodeError
from pathlib import Path from pathlib import Path
from typing import Any, Union from typing import Any
import pytest import pytest
from libbot.utils import json_read, json_write from libbot.utils import json_read, json_write
@@ -24,7 +24,7 @@ from libbot.utils import json_read, json_write
("tests/data/empty.json", {}), ("tests/data/empty.json", {}),
], ],
) )
async def test_json_read_valid(path: Union[str, Path], expected: Any): async def test_json_read_valid(path: str | Path, expected: Any):
assert await json_read(path) == expected assert await json_read(path) == expected
@@ -36,7 +36,7 @@ async def test_json_read_valid(path: Union[str, Path], expected: Any):
("tests/data/nonexistent.json", FileNotFoundError), ("tests/data/nonexistent.json", FileNotFoundError),
], ],
) )
async def test_json_read_invalid(path: Union[str, Path], expected: Any): async def test_json_read_invalid(path: str | Path, expected: Any):
with pytest.raises(expected): with pytest.raises(expected):
await json_read(path) await json_read(path)
@@ -55,7 +55,7 @@ async def test_json_read_invalid(path: Union[str, Path], expected: Any):
({}, "tests/data/empty.json"), ({}, "tests/data/empty.json"),
], ],
) )
async def test_json_write(data: Any, path: Union[str, Path]): async def test_json_write(data: Any, path: str | Path):
await json_write(data, path) await json_write(data, path)
assert Path(path).is_file() assert Path(path).is_file()

View File

@@ -4,7 +4,7 @@ except ImportError:
from json import dumps, JSONDecodeError from json import dumps, JSONDecodeError
from pathlib import Path from pathlib import Path
from typing import Any, Union from typing import Any
import pytest import pytest
from libbot.utils import json_read, json_write from libbot.utils import json_read, json_write
@@ -23,7 +23,7 @@ from libbot.utils import json_read, json_write
("tests/data/empty.json", {}), ("tests/data/empty.json", {}),
], ],
) )
def test_json_read_valid(path: Union[str, Path], expected: Any): def test_json_read_valid(path: str | Path, expected: Any):
assert json_read(path) == expected assert json_read(path) == expected
@@ -34,7 +34,7 @@ def test_json_read_valid(path: Union[str, Path], expected: Any):
("tests/data/nonexistent.json", FileNotFoundError), ("tests/data/nonexistent.json", FileNotFoundError),
], ],
) )
def test_json_read_invalid(path: Union[str, Path], expected: Any): def test_json_read_invalid(path: str | Path, expected: Any):
with pytest.raises(expected): with pytest.raises(expected):
assert json_read(path) == expected assert json_read(path) == expected
@@ -52,7 +52,7 @@ def test_json_read_invalid(path: Union[str, Path], expected: Any):
({}, "tests/data/empty.json"), ({}, "tests/data/empty.json"),
], ],
) )
def test_json_write(data: Any, path: Union[str, Path]): def test_json_write(data: Any, path: str | Path):
json_write(data, path) json_write(data, path)
assert Path(path).is_file() assert Path(path).is_file()