From 998667c8398b5a6699c5b359c3812de88e6e424c Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sat, 23 Jan 2021 23:56:07 +0100 Subject: [PATCH] Support extras and stay slim on initial install See https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/7. --- CHANGELOG.md | 1 + README.md | 9 ++++++++- poetry.lock | 26 +++++++++++++++----------- pyproject.toml | 10 +++++++--- xbotlib.py | 32 ++++++++++++++++++++++++++++---- 5 files changed, 59 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72f9df..ff85c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Document `return` statement semantics ([#6](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/6)) - Make file system the first-class storage option ([#3](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/3)) - Note support for open formats when serving the web ([#5](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/5)) +- Support extras for optional dependencies ([#7](https://git.vvvvvvaria.org/decentral1se/xbotlib/issues/7)) # xbotlib 0.14.1 (2021-01-21) diff --git a/README.md b/README.md index fc3925b..efaac9c 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,8 @@ $ cat mybot.json For more advanced use cases, `xbotlib` also supports [Redis](https://redis.io/) as a storage back-end. You'll need to configure this (e.g. `--storage redis`) as the default uses the filesystem approach mentioned above. The same `self.db` -will then be passed as a Redis connection object. +will then be passed as a Redis connection object. You will also need to install +additional dependencies using `pip install xbotlib[redis]`. ### Loading Plugins @@ -294,6 +295,12 @@ See [here](https://slixmpp.readthedocs.io/xeps.html) for the list of supported p ### Serving HTTP +Firstly, you'll need to install additional dependencies. + +```bash +$ pip install xbotlib[web] +``` + Your bot will run a web server if you configure it to do so. Use the `--serve` option on the command-line, the `serve = True` configuration option or the `XBOT_SERVE=True` environment variable. diff --git a/poetry.lock b/poetry.lock index 283456c..f61e22f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,7 +15,7 @@ name = "aiohttp" version = "3.7.3" description = "Async http client/server framework (asyncio)" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] @@ -43,7 +43,7 @@ name = "async-timeout" version = "3.0.1" description = "Timeout context manager for asyncio programs" category = "main" -optional = false +optional = true python-versions = ">=3.5.3" [[package]] @@ -96,7 +96,7 @@ name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -137,7 +137,7 @@ name = "idna" version = "3.1" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" -optional = false +optional = true python-versions = ">=3.4" [[package]] @@ -145,7 +145,7 @@ name = "idna-ssl" version = "1.1.0" description = "Patch ssl.match_hostname for Unicode(idna) domains support" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -185,7 +185,7 @@ name = "jinja2" version = "2.11.2" description = "A very fast and expressive template engine." category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] @@ -199,7 +199,7 @@ name = "markupsafe" version = "1.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" -optional = false +optional = true python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] @@ -215,7 +215,7 @@ name = "multidict" version = "5.1.0" description = "multidict implementation" category = "main" -optional = false +optional = true python-versions = ">=3.6" [[package]] @@ -288,7 +288,7 @@ name = "redis" version = "3.5.3" description = "Python client for Redis key-value store" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] @@ -356,7 +356,7 @@ name = "yarl" version = "1.6.3" description = "Yet another URL library" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] @@ -376,10 +376,14 @@ python-versions = ">=3.6" 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)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +[extras] +redis = ["redis"] +web = ["aiohttp", "Jinja2"] + [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "0578ed3f31fcb6ea0b170b1afe2950a047bcd3eae5c0200982f2fd61abcb9307" +content-hash = "4d10b4f1e9f193be431e27f13f861f033a4c59e89258be4b40399b54f574c691" [metadata.files] aiodns = [ diff --git a/pyproject.toml b/pyproject.toml index e1ba321..e1f535d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,15 +17,19 @@ keywords = [] python = "^3.6" slixmpp = "^1.6.0" humanize = "^3.2.0" -redis = "^3.5.3" -Jinja2 = "^2.11.2" -aiohttp = "^3.7.3" +redis = {version = "^3.5.3", optional = true} +aiohttp = {version = "^3.7.3", optional = true} +Jinja2 = {version = "^2.11.2", optional = true} [tool.poetry.dev-dependencies] black = "^19.10b0" flake8 = "^3.8.3" isort = "^5.0.2" +[tool.poetry.extras] +redis = ["redis"] +web = ["aiohttp", "jinja2"] + [tool.black] line-length = 80 target-version = ["py38"] diff --git a/xbotlib.py b/xbotlib.py index 8a9d933..062c6b0 100644 --- a/xbotlib.py +++ b/xbotlib.py @@ -14,10 +14,7 @@ from os.path import exists from pathlib import Path from sys import exit, stdout -from aiohttp.web import Application, Response, get, run_app from humanize import naturaldelta -from jinja2 import Environment, FileSystemLoader -from redis import Redis from slixmpp import ClientXMPP @@ -434,8 +431,8 @@ class Bot(ClientXMPP): self.rooms = rooms self.no_auto_join = no_auto_join self.port = port - self.template = self.load_template(template) self.serve = serve + self.template = self.load_template(template) if self.serve else None self.storage = storage self.storage_file = Path(storage_file).absolute() @@ -444,6 +441,13 @@ class Bot(ClientXMPP): if not exists(Path(template).absolute()): return None + try: + from jinja2 import Environment, FileSystemLoader + except ModuleNotFoundError: + print("Missing required dependency: jinja2") + print("Have you tried `pip install xbotlib[web]`") + exit(1) + try: loader = FileSystemLoader(searchpath="./") env = Environment(loader=loader) @@ -578,12 +582,18 @@ class Bot(ClientXMPP): exit(1) else: try: + from redis import Redis + self.db = Redis.from_url(self.redis_url, decode_responses=True) return self.log.info("Successfully connected to Redis storage") except ValueError as exception: message = f"Failed to connect to Redis storage: {exception}" self.log.info(message) exit(1) + except ModuleNotFoundError: + print("missing required dependency using Redis") + print("Have you tried `pip install xbotlib[redis]`") + exit(1) def run(self): """Run the bot.""" @@ -628,6 +638,13 @@ class Bot(ClientXMPP): async def default_serve(self, request): """Default placeholder text for HTML serving.""" + try: + from aiohttp.web import Response + except ModuleNotFoundError: + print("Missing required dependency: aiohttp") + print("Have you tried `pip install xbotlib[web]`") + exit(1) + return Response(text=f"{self.nick} is alive and well") def reply(self, text, to=None, room=None): @@ -673,4 +690,11 @@ class Bot(ClientXMPP): def respond(self, response, content_type="text/html"): """Send this response back with the web server.""" + try: + from aiohttp.web import Response + except ModuleNotFoundError: + print("Missing required dependency: aiohttp") + print("Have you tried `pip install xbotlib[web]`") + exit(1) + return Response(text=response, content_type=content_type)