diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8177a91..01e25c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: python:3.9-alpine +image: python:3.10-alpine variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" diff --git a/litecord/blueprints/channel/messages.py b/litecord/blueprints/channel/messages.py index ee1b6ba..c1a621b 100644 --- a/litecord/blueprints/channel/messages.py +++ b/litecord/blueprints/channel/messages.py @@ -29,7 +29,8 @@ from litecord.errors import MessageNotFound, Forbidden from litecord.enums import MessageType, ChannelType, GUILD_CHANS from litecord.schemas import validate, MESSAGE_CREATE -from litecord.utils import pg_set_json, query_tuple_from_args, extract_limit +from litecord.utils import query_tuple_from_args, extract_limit +from litecord.json import pg_set_json from litecord.permissions import get_permissions from litecord.embed.sanitizer import fill_embed diff --git a/litecord/blueprints/webhooks.py b/litecord/blueprints/webhooks.py index 9c903da..cc5d4ab 100644 --- a/litecord/blueprints/webhooks.py +++ b/litecord/blueprints/webhooks.py @@ -52,7 +52,7 @@ from litecord.common.messages import ( from litecord.embed.sanitizer import fill_embed, fetch_mediaproxy_img from litecord.embed.messages import process_url_embed, is_media_url from litecord.embed.schemas import EmbedURL -from litecord.utils import pg_set_json +from litecord.json import pg_set_json from litecord.enums import MessageType from litecord.images import STATIC_IMAGE_MIMES diff --git a/litecord/gateway/encoding.py b/litecord/gateway/encoding.py index a2001d8..dc187b3 100644 --- a/litecord/gateway/encoding.py +++ b/litecord/gateway/encoding.py @@ -20,7 +20,7 @@ along with this program. If not, see . import json import earl -from litecord.utils import LitecordJSONEncoder +from litecord.json import LitecordJSONEncoder def encode_json(payload) -> str: diff --git a/litecord/json.py b/litecord/json.py new file mode 100644 index 0000000..e6fccbf --- /dev/null +++ b/litecord/json.py @@ -0,0 +1,69 @@ +""" + +Litecord +Copyright (C) 2018-2021 Luna Mendes and Litecord Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +import json +from typing import Any +from decimal import Decimal +from uuid import UUID +from dataclasses import asdict, is_dataclass + +import quart.json.provider + + +class LitecordJSONEncoder(json.JSONEncoder): + """Custom JSON encoder for Litecord. Useful for json.dumps""" + + def default(self, value: Any): + if isinstance(value, (Decimal, UUID)): + return str(value) + + if is_dataclass(value): + return asdict(value) + + if hasattr(value, "to_json"): + return value.to_json + + return super().default(self, value) + + +class LitecordJSONProvider(quart.json.provider.DefaultJSONProvider): + """Custom JSON provider for Quart.""" + + def __init__(self, *args, **kwargs): + self.encoder = LitecordJSONEncoder(**kwargs) + + def default(self, value: Any): + self.encoder.default(value) + + +async def pg_set_json(con): + """Set JSON and JSONB codecs for an asyncpg connection.""" + await con.set_type_codec( + "json", + encoder=lambda v: json.dumps(v, cls=LitecordJSONEncoder), + decoder=json.loads, + schema="pg_catalog", + ) + + await con.set_type_codec( + "jsonb", + encoder=lambda v: json.dumps(v, cls=LitecordJSONEncoder), + decoder=json.loads, + schema="pg_catalog", + ) diff --git a/litecord/storage.py b/litecord/storage.py index a4cec8b..3f9b77d 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -33,7 +33,7 @@ from litecord.blueprints.channel.reactions import ( from litecord.blueprints.user.billing import PLAN_ID_TO_TYPE from litecord.types import timestamp_ -from litecord.utils import pg_set_json +from litecord.json import pg_set_json log = Logger(__name__) diff --git a/litecord/utils.py b/litecord/utils.py index 028f7e6..f153d85 100644 --- a/litecord/utils.py +++ b/litecord/utils.py @@ -18,14 +18,12 @@ along with this program. If not, see . """ import asyncio -import json import secrets import datetime import re from typing import Any, Iterable, Optional, Sequence, List, Dict, Union from logbook import Logger -from quart.json import JSONEncoder from quart import current_app as app from litecord.common.messages import message_view @@ -156,35 +154,6 @@ def mmh3(inp_str: str, seed: int = 0): return _u(h1) >> 0 -class LitecordJSONEncoder(JSONEncoder): - """Custom JSON encoder for Litecord.""" - - def default(self, value: Any): - """By default, this will try to get the to_json attribute of a given - value being JSON encoded.""" - try: - return value.to_json - except AttributeError: - return super().default(value) - - -async def pg_set_json(con): - """Set JSON and JSONB codecs for an asyncpg connection.""" - await con.set_type_codec( - "json", - encoder=lambda v: json.dumps(v, cls=LitecordJSONEncoder), - decoder=json.loads, - schema="pg_catalog", - ) - - await con.set_type_codec( - "jsonb", - encoder=lambda v: json.dumps(v, cls=LitecordJSONEncoder), - decoder=json.loads, - schema="pg_catalog", - ) - - def yield_chunks(input_list: Sequence[Any], chunk_size: int): """Yield successive n-sized chunks from l. diff --git a/pyproject.toml b/pyproject.toml index 31ce245..4f996c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,19 +7,19 @@ license = "GPLv3-only" [tool.poetry.dependencies] python = "^3.9" -bcrypt = "^3.2.0" -itsdangerous = "^1.1.0" -asyncpg = "^0.24.0" -websockets = "^10.0" +bcrypt = "^3.2.2" +itsdangerous = "^2.1.2" +asyncpg = "^0.26.0" +websockets = "^10.3" Earl-ETF = "^2.1.2" logbook = "^1.5.3" Cerberus = "^1.3.4" -quart = {git = "https://gitlab.com/pgjones/quart", rev = "c1ac142c6c51709765045f830b242950099b2295"} -pillow = "^8.3.2" -aiohttp = "^3.7.4" -zstandard = "^0.15.2" +quart = "^0.18.0" +pillow = "^9.2.0" +aiohttp = "^3.8.1" +zstandard = "^0.18.0" winter = {git = "https://gitlab.com/elixire/winter"} -wsproto = "^1.0.0" +wsproto = "^1.1.0" diff --git a/run.py b/run.py index df6fb03..0f22d69 100644 --- a/run.py +++ b/run.py @@ -105,7 +105,7 @@ from litecord.pubsub.lazy_guild import LazyGuildManager from litecord.gateway.gateway import websocket_handler -from litecord.utils import LitecordJSONEncoder +from litecord.json import LitecordJSONProvider # == HACKY PATCH == # this MUST be removed once Hypercorn gets py3.10 support. @@ -135,7 +135,7 @@ def make_app(): logging.getLogger("websockets").setLevel(logbook.INFO) # use our custom json encoder for custom data types - app.json_encoder = LitecordJSONEncoder + app.json_provider_class = LitecordJSONProvider return app diff --git a/tox.ini b/tox.ini index ed53a2f..43fde9a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,22 @@ [tox] -envlist = py3.9 +envlist = py3.10 isolated_build = true [testenv] ignore_errors = true deps = - pytest==6.2.5 - pytest-asyncio==0.15.1 - pytest-cov==2.12.1 - flake8==3.9.2 - black==21.6b0 - mypy==0.910 + pytest==7.1.2 + pytest-asyncio==0.19.0 + pytest-cov==3.0.0 + flake8==5.0.4 + black==22.6.0 + mypy==0.971 pytest-instafail==0.4.2 commands = python3 ./manage.py migrate black --check litecord run.py tests manage flake8 litecord run.py tests manage - pytest {posargs:tests} + pytest --asyncio-mode=auto {posargs:tests} [flake8] max-line-length = 88