Compare commits

...

10 Commits

6 changed files with 78 additions and 44 deletions

View File

@ -1,12 +1,16 @@
# xbotlib x.x.x (UNRELEASED)
# xbotlib 0.16.0 (2021-01-??)
# xbotlib 0.16.0 (2021-02-02)
- Fix logging of exceptions and increase info for stack traces ([#2](https://github.com/decentral1se/xbotlib/issues/2))
- Format JSON to human readable when saving ([#8](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/8))
- Fix room formatting when storing in configuration files ([#10](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/10))
- `--storage-file` goes away and is replaced by the `--output` option ([#9](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/9))
- New internal runtime data store available in `.xbotlib/data.json` ([#11](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/11))
- Add documentation for invitations management ([#11](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/11))
- Fix `del` usage for `SimpleStorage` ([#5](https://github.com/decentral1se/xbotlib/issues/5))
- Always log tracebacks for errors ([#6](https://github.com/decentral1se/xbotlib/issues/6))
- Use latest Slixmpp (1.7) version
# xbotlib 0.15.2 (2021-01-24)

View File

@ -258,6 +258,7 @@ easily template and generate HTML. The web server is provided by
The default template search path is `index.html.j2` in the current working
directory. This can be configured through the usual configuration entrypoints.
It is possible to use the template without the built-in server too!
Here's a small example that renders a random ASCII letter and uses a stylesheet.
@ -412,11 +413,11 @@ you want to chat or just invite your bots for testing.
## More Examples
See more examples on [git.vvvvvvaria.org](https://git.vvvvvvaria.org/explore/repos?q=xbotlib&topic=1).
See more examples on [git.vvvvvvaria.org](https://git.vvvvvvaria.org/varia/bots).
## Deploy your bots
See [bots.varia.zone](https://bots.varia.zone/).
> Documentation coming **soon**.
## Roadmap

40
poetry.lock generated
View File

@ -153,7 +153,7 @@ idna = ">=2.0"
[[package]]
name = "importlib-metadata"
version = "3.3.0"
version = "3.4.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
@ -164,8 +164,8 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "isort"
@ -182,7 +182,7 @@ colors = ["colorama (>=0.4.3,<0.5.0)"]
[[package]]
name = "jinja2"
version = "2.11.2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "main"
optional = true
@ -304,7 +304,7 @@ python-versions = "*"
[[package]]
name = "slixmpp"
version = "1.6.0"
version = "1.7.0"
description = "Slixmpp is an elegant Python library for XMPP (aka Jabber)."
category = "main"
optional = false
@ -479,6 +479,7 @@ cffi = [
{file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"},
{file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"},
{file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"},
{file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"},
{file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"},
@ -507,16 +508,16 @@ idna-ssl = [
{file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"},
]
importlib-metadata = [
{file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"},
{file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"},
{file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
{file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
]
isort = [
{file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"},
{file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"},
]
jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
]
markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
@ -537,20 +538,39 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mccabe = [
@ -721,7 +741,7 @@ regex = [
{file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
]
slixmpp = [
{file = "slixmpp-1.6.0.tar.gz", hash = "sha256:8a3799856068d67dccb233b91309e511911ab612c57ab4ef53398c2d83a75549"},
{file = "slixmpp-1.7.0.tar.gz", hash = "sha256:7f2eec44a4bb8a1e099a3396fe7a0bf2a275c5656940b6c16ce6c79ccc13dc17"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},

View File

@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
[tool.poetry]
name = "xbotlib"
version = "0.15.2"
version = "0.16.0"
description = "XMPP bots for humans"
authors = ["decentral1se <lukewm@riseup.net>"]
maintainers = ["decentral1se <lukewm@riseup.net>"]

View File

@ -1,7 +1,6 @@
"""Unit tests for xbotlib module."""
from logging import getLogger
from pathlib import Path
from pytest import fixture
@ -101,3 +100,10 @@ def test_config(config):
assert config.serve
assert config.storage == "file"
assert config.output == "."
def test_simple_message_delete(tmp_db_path):
db = SimpleDatabase(tmp_db_path, log)
db["foo"] = "bar"
del db["foo"]
assert "foo" not in db

View File

@ -13,7 +13,6 @@ from os import environ, mkdir
from os.path import exists
from pathlib import Path
from sys import exit, stdout
from traceback import print_exc
from humanize import naturaldelta
from slixmpp import ClientXMPP
@ -44,7 +43,7 @@ class SimpleDatabase(dict):
self.update(loads(handle.read()))
except Exception as exception:
message = f"Loading file storage failed: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
def _dumps(self):
@ -54,17 +53,17 @@ class SimpleDatabase(dict):
handle.write(dumps(self, indent=2, sort_keys=True))
except Exception as exception:
message = f"Saving file storage failed: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
def __setitem__(self, key, val):
"""Write data to the database."""
dict.__setitem__(self, key, val)
super().__setitem__(key, val)
self._dumps()
def __delitem__(self, key):
"""Remove data from the database."""
dict.__delitem__(key)
super().__delitem__(key)
self._dumps()
def update(self, *args, **kwargs):
@ -112,7 +111,8 @@ class SimpleMessage:
filtered = list(filter(None, split))
return filtered[-1].strip()
except Exception as exception:
self.log.error(f"Couldn't parse {body}: {exception}")
message = f"Couldn't parse {body}: {exception}"
self.log.error(message, exc_info=exception)
return None
@property
@ -490,11 +490,7 @@ class Bot(ClientXMPP):
self.no_auto_join = no_auto_join
self.port = port
self.serve_web = serve_web
self.template = None
if self.serve_web:
self.template = self.load_template(template)
self.template = self.load_template(template)
self.storage = storage
self.output = Path(output).absolute()
@ -505,10 +501,11 @@ class Bot(ClientXMPP):
try:
from jinja2 import Environment, FileSystemLoader
except ModuleNotFoundError:
except ModuleNotFoundError as exception:
self.log.error(
"Missing required dependency jinja2, ",
"have you tried `pip install xbotlib[web]`",
exc_info=exception,
)
exit(1)
@ -516,8 +513,9 @@ class Bot(ClientXMPP):
loader = FileSystemLoader(searchpath="./")
env = Environment(loader=loader)
return env.get_template(template)
except Exception:
self.log.error(f"Unable to load {template}")
except Exception as exception:
message = f"Unable to load {template}"
self.log.error(message, exc_info=exception)
exit(1)
def register_xmpp_event_handlers(self):
@ -550,8 +548,8 @@ class Bot(ClientXMPP):
try:
self.direct(message)
except Exception as exception:
self.log.error(f"Bot.direct threw exception: {exception}")
print_exc(file=stdout)
message = f"Bot.direct threw exception: {exception}"
self.log.error(message, exc_info=exception)
if self.storage == "file":
self.db._dumps()
@ -575,7 +573,8 @@ class Bot(ClientXMPP):
with open(abspath, "rb") as handle:
contents = handle.read()
except Exception as exception:
self.log.error(f"Failed to load avatar: {exception}")
message = f"Failed to load avatar: {exception}"
self.log.error(message, exc_info=exception)
return
id = self.plugin["xep_0084"].generate_id(contents)
@ -641,8 +640,8 @@ class Bot(ClientXMPP):
try:
self.group(message)
except Exception as exception:
self.log.error(f"Bot.group threw exception: {exception}")
print_exc(file=stdout)
message = f"Bot.group threw exception: {exception}"
self.log.error(message, exc_info=exception)
if self.storage == "file":
self.db._dumps()
@ -665,7 +664,7 @@ class Bot(ClientXMPP):
self.log.info(f"Loaded {plugin}")
except Exception as exception:
message = f"Loading additional plugins failed: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
def init_storage(self):
"""Initialise the storage back-end."""
@ -677,7 +676,7 @@ class Bot(ClientXMPP):
self.log.info("Successfully loaded file storage")
except Exception as exception:
message = f"Failed to load {file_storage_path}: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
else:
try:
@ -687,12 +686,13 @@ class Bot(ClientXMPP):
return self.log.info("Successfully connected to Redis storage")
except ValueError as exception:
message = f"Failed to connect to Redis storage: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
except ModuleNotFoundError:
except ModuleNotFoundError as exception:
self.log.error(
"Missing required dependency using Redis, ",
"have you tried `pip install xbotlib[redis]`",
exc_info=exception,
)
exit(1)
@ -705,7 +705,7 @@ class Bot(ClientXMPP):
self.log.info("Successfully initialised internal directory")
except Exception as exception:
message = f"Failed to create {internal_dir}: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
try:
@ -714,7 +714,7 @@ class Bot(ClientXMPP):
self.log.info("Successfully loaded internal storage")
except Exception as exception:
message = f"Failed to load {internal_storage_path}: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
exit(1)
if "invited" not in self._data:
@ -735,7 +735,7 @@ class Bot(ClientXMPP):
self.log.info("Finished running setup")
except Exception as exception:
message = f"Bot.setup failed: {exception}"
self.log.error(message)
self.log.error(message, exc_info=exception)
self.process(forever=False)
except (KeyboardInterrupt, RuntimeError):
@ -745,10 +745,11 @@ class Bot(ClientXMPP):
"""Run the web server."""
try:
from aiohttp.web import Application, get, run_app
except ModuleNotFoundError:
except ModuleNotFoundError as exception:
self.log.error(
"Missing required dependency aiohttp, ",
"have you tried `pip install xbotlib[web]`",
exc_info=exception,
)
exit(1)
@ -772,10 +773,11 @@ class Bot(ClientXMPP):
"""Default placeholder text for HTML serving."""
try:
from aiohttp.web import Response
except ModuleNotFoundError:
except ModuleNotFoundError as exception:
self.log.error(
"Missing required dependency aiohttp, ",
"have you tried `pip install xbotlib[web]`",
exc_info=exception,
)
exit(1)
@ -826,10 +828,11 @@ class Bot(ClientXMPP):
"""Send this response back with the web server."""
try:
from aiohttp.web import Response
except ModuleNotFoundError:
except ModuleNotFoundError as exception:
self.log.error(
"Missing required dependency aiohttp, ",
"have you tried `pip install xbotlib[web]`",
exc_info=exception,
)
exit(1)