From c2cc428abe58bbf50215b8727151406d6acd13e4 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Thu, 26 Oct 2023 22:38:31 +0200 Subject: [PATCH] lint --- .flake8 | 6 +- .pre-commit-config.yaml | 5 - ai/preprocess.py | 21 ++- alembic/env.py | 9 +- alembic/versions/fe943b098418_init_alembic.py | 6 +- auth/authenticate.py | 31 +++-- auth/credentials.py | 3 +- auth/email.py | 16 +-- auth/identity.py | 15 +-- auth/jwtcodec.py | 21 ++- auth/oauth.py | 5 +- auth/tokenstorage.py | 5 +- base/orm.py | 8 +- base/redis.py | 4 +- main.py | 27 ++-- migration/__init__.py | 36 ++--- migration/bson2json.py | 9 +- migration/export.py | 9 +- migration/extract.py | 125 +++++++++--------- migration/html2text/__init__.py | 16 +-- migration/html2text/cli.py | 4 +- migration/html2text/utils.py | 4 +- migration/tables/comments.py | 20 ++- migration/tables/content_items.py | 51 ++++--- migration/tables/remarks.py | 22 +-- migration/tables/users.py | 15 +-- orm/collection.py | 6 +- orm/community.py | 8 +- orm/notification.py | 4 +- orm/rbac.py | 27 ++-- orm/reaction.py | 4 +- orm/shout.py | 17 ++- orm/topic.py | 6 +- orm/user.py | 11 +- requirements-dev.txt | 1 + requirements.txt | 3 - resolvers/__init__.py | 35 ----- resolvers/auth.py | 35 +++-- resolvers/create/editor.py | 27 ++-- resolvers/create/migrate.py | 20 +-- resolvers/inbox/chats.py | 16 +-- resolvers/inbox/load.py | 32 ++--- resolvers/inbox/messages.py | 29 ++-- resolvers/inbox/search.py | 14 +- resolvers/notifications.py | 11 +- resolvers/upload.py | 32 ++--- resolvers/zine/following.py | 42 +++--- resolvers/zine/load.py | 60 +++++---- resolvers/zine/profile.py | 35 +++-- resolvers/zine/reactions.py | 59 +++++---- resolvers/zine/topics.py | 17 ++- server.py | 62 +++++---- services/following.py | 8 +- services/main.py | 8 +- .../notifications/notification_service.py | 26 ++-- services/notifications/sse.py | 6 +- services/search.py | 15 ++- services/stat/viewed.py | 28 ++-- services/zine/gittask.py | 6 +- settings.py | 2 +- setup.cfg | 7 +- setup.cfg.bak | 39 ++++++ validations/auth.py | 3 +- validations/inbox.py | 3 +- 64 files changed, 631 insertions(+), 626 deletions(-) delete mode 100644 resolvers/__init__.py mode change 100755 => 100644 setup.cfg create mode 100644 setup.cfg.bak diff --git a/.flake8 b/.flake8 index e82de95a..523cb30f 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] -ignore = E203,W504,W191,W503 +ignore = E203 exclude = .git,__pycache__,orm/rbac.py -max-complexity = 10 -max-line-length = 108 +max-complexity = 15 +max-line-length = 100 indent-string = ' ' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c25b29a2..42569413 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,6 @@ repos: - id: check-docstring-first - id: check-json - id: check-merge-conflict - - id: check-toml - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace @@ -33,12 +32,8 @@ repos: - id: black args: - --line-length=100 - - --skip-string-normalization - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 hooks: - id: flake8 - args: - - --max-line-length=100 - - --disable=protected-access diff --git a/ai/preprocess.py b/ai/preprocess.py index 6cc5ae5a..82d06f71 100644 --- a/ai/preprocess.py +++ b/ai/preprocess.py @@ -1,29 +1,28 @@ -import re -from string import punctuation - -import nltk from bs4 import BeautifulSoup from nltk.corpus import stopwords from pymystem3 import Mystem -from transformers import BertTokenizer +from string import punctuation + +import nltk +import re nltk.download("stopwords") def get_clear_text(text): - soup = BeautifulSoup(text, 'html.parser') + soup = BeautifulSoup(text, "html.parser") # extract the plain text from the HTML document without tags - clear_text = '' + clear_text = "" for tag in soup.find_all(): - clear_text += tag.string or '' + clear_text += tag.string or "" - clear_text = re.sub(pattern='[\u202F\u00A0\n]+', repl=' ', string=clear_text) + clear_text = re.sub(pattern="[\u202F\u00A0\n]+", repl=" ", string=clear_text) # only words - clear_text = re.sub(pattern='[^A-ZА-ЯЁ -]', repl='', string=clear_text, flags=re.IGNORECASE) + clear_text = re.sub(pattern="[^A-ZА-ЯЁ -]", repl="", string=clear_text, flags=re.IGNORECASE) - clear_text = re.sub(pattern='\s+', repl=' ', string=clear_text) + clear_text = re.sub(pattern=r"\s+", repl=" ", string=clear_text) clear_text = clear_text.lower() diff --git a/alembic/env.py b/alembic/env.py index 91012c34..58e3e200 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,9 +1,8 @@ -from logging.config import fileConfig - -from sqlalchemy import engine_from_config, pool - from alembic import context +from base.orm import Base +from logging.config import fileConfig from settings import DB_URL +from sqlalchemy import engine_from_config, pool # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -17,8 +16,6 @@ config.set_section_option(config.config_ini_section, "DB_URL", DB_URL) if config.config_file_name is not None: fileConfig(config.config_file_name) -from base.orm import Base - target_metadata = [Base.metadata] # other values from the config, defined by the needs of env.py, diff --git a/alembic/versions/fe943b098418_init_alembic.py b/alembic/versions/fe943b098418_init_alembic.py index 6f62301f..52796fea 100644 --- a/alembic/versions/fe943b098418_init_alembic.py +++ b/alembic/versions/fe943b098418_init_alembic.py @@ -7,12 +7,12 @@ Create Date: 2023-08-19 01:37:57.031933 """ from typing import Sequence, Union -import sqlalchemy as sa +# import sqlalchemy as sa -from alembic import op +# from alembic import op # revision identifiers, used by Alembic. -revision: str = 'fe943b098418' +revision: str = "fe943b098418" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/auth/authenticate.py b/auth/authenticate.py index 7792766d..9e4c93fc 100644 --- a/auth/authenticate.py +++ b/auth/authenticate.py @@ -1,17 +1,15 @@ -from functools import wraps -from typing import Optional, Tuple - -from graphql.type import GraphQLResolveInfo -from sqlalchemy.orm import exc, joinedload -from starlette.authentication import AuthenticationBackend -from starlette.requests import HTTPConnection - from auth.credentials import AuthCredentials, AuthUser from auth.tokenstorage import SessionToken from base.exceptions import OperationNotAllowed from base.orm import local_session +from functools import wraps +from graphql.type import GraphQLResolveInfo from orm.user import Role, User from settings import SESSION_TOKEN_HEADER +from sqlalchemy.orm import exc, joinedload +from starlette.authentication import AuthenticationBackend +from starlette.requests import HTTPConnection +from typing import Optional, Tuple class JWTAuthenticate(AuthenticationBackend): @@ -19,16 +17,16 @@ class JWTAuthenticate(AuthenticationBackend): self, request: HTTPConnection ) -> Optional[Tuple[AuthCredentials, AuthUser]]: if SESSION_TOKEN_HEADER not in request.headers: - return AuthCredentials(scopes={}), AuthUser(user_id=None, username='') + return AuthCredentials(scopes={}), AuthUser(user_id=None, username="") token = request.headers.get(SESSION_TOKEN_HEADER) if not token: print("[auth.authenticate] no token in header %s" % SESSION_TOKEN_HEADER) return AuthCredentials(scopes={}, error_message=str("no token")), AuthUser( - user_id=None, username='' + user_id=None, username="" ) - if len(token.split('.')) > 1: + if len(token.split(".")) > 1: payload = await SessionToken.verify(token) with local_session() as session: @@ -47,20 +45,21 @@ class JWTAuthenticate(AuthenticationBackend): return ( AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True), - AuthUser(user_id=user.id, username=''), + AuthUser(user_id=user.id, username=""), ) except exc.NoResultFound: pass - return AuthCredentials(scopes={}, error_message=str('Invalid token')), AuthUser( - user_id=None, username='' + return AuthCredentials(scopes={}, error_message=str("Invalid token")), AuthUser( + user_id=None, username="" ) def login_required(func): @wraps(func) async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs): - # print('[auth.authenticate] login required for %r with info %r' % (func, info)) # debug only + # debug only + # print('[auth.authenticate] login required for %r with info %r' % (func, info)) auth: AuthCredentials = info.context["request"].auth # print(auth) if not auth or not auth.logged_in: @@ -75,7 +74,7 @@ def permission_required(resource, operation, func): @wraps(func) async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs): print( - '[auth.authenticate] permission_required for %r with info %r' % (func, info) + "[auth.authenticate] permission_required for %r with info %r" % (func, info) ) # debug only auth: AuthCredentials = info.context["request"].auth if not auth.logged_in: diff --git a/auth/credentials.py b/auth/credentials.py index 63a1d161..856c2374 100644 --- a/auth/credentials.py +++ b/auth/credentials.py @@ -1,6 +1,5 @@ -from typing import List, Optional, Text - from pydantic import BaseModel +from typing import List, Optional, Text # from base.exceptions import Unauthorized diff --git a/auth/email.py b/auth/email.py index ca8b2bc4..faa64725 100644 --- a/auth/email.py +++ b/auth/email.py @@ -1,17 +1,17 @@ -import requests - from settings import MAILGUN_API_KEY, MAILGUN_DOMAIN -api_url = "https://api.mailgun.net/v3/%s/messages" % (MAILGUN_DOMAIN or 'discours.io') -noreply = "discours.io " % (MAILGUN_DOMAIN or 'discours.io') +import requests + +api_url = "https://api.mailgun.net/v3/%s/messages" % (MAILGUN_DOMAIN or "discours.io") +noreply = "discours.io " % (MAILGUN_DOMAIN or "discours.io") lang_subject = {"ru": "Подтверждение почты", "en": "Confirm email"} async def send_auth_email(user, token, lang="ru", template="email_confirmation"): try: to = "%s <%s>" % (user.name, user.email) - if lang not in ['ru', 'en']: - lang = 'ru' + if lang not in ["ru", "en"]: + lang = "ru" subject = lang_subject.get(lang, lang_subject["en"]) template = template + "_" + lang payload = { @@ -19,9 +19,9 @@ async def send_auth_email(user, token, lang="ru", template="email_confirmation") "to": to, "subject": subject, "template": template, - "h:X-Mailgun-Variables": "{ \"token\": \"%s\" }" % token, + "h:X-Mailgun-Variables": '{ "token": "%s" }' % token, } - print('[auth.email] payload: %r' % payload) + print("[auth.email] payload: %r" % payload) # debug # print('http://localhost:3000/?modal=auth&mode=confirm-email&token=%s' % token) response = requests.post(api_url, auth=("api", MAILGUN_API_KEY), data=payload) diff --git a/auth/identity.py b/auth/identity.py index cc1bf3c8..6e89079f 100644 --- a/auth/identity.py +++ b/auth/identity.py @@ -1,16 +1,14 @@ -from binascii import hexlify -from hashlib import sha256 - -from jwt import DecodeError, ExpiredSignatureError -from passlib.hash import bcrypt -from sqlalchemy import or_ - from auth.jwtcodec import JWTCodec from auth.tokenstorage import TokenStorage # from base.exceptions import InvalidPassword, InvalidToken from base.orm import local_session +from binascii import hexlify +from hashlib import sha256 +from jwt import DecodeError, ExpiredSignatureError from orm import User +from passlib.hash import bcrypt +from sqlalchemy import or_ from validations.auth import AuthInput @@ -35,6 +33,7 @@ class Password: Verify that password hash is equal to specified hash. Hash format: $2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm + # noqa: W605 \__/\/ \____________________/\_____________________________/ | | Salt Hash | Cost @@ -84,7 +83,7 @@ class Identity: @staticmethod async def onetime(token: str) -> User: try: - print('[auth.identity] using one time token') + print("[auth.identity] using one time token") payload = JWTCodec.decode(token) if not await TokenStorage.exist(f"{payload.user_id}-{payload.username}-{token}"): # raise InvalidToken("Login token has expired, please login again") diff --git a/auth/jwtcodec.py b/auth/jwtcodec.py index 8fc12d27..870ed540 100644 --- a/auth/jwtcodec.py +++ b/auth/jwtcodec.py @@ -1,11 +1,10 @@ -from datetime import datetime, timezone - -import jwt - from base.exceptions import ExpiredToken, InvalidToken +from datetime import datetime, timezone from settings import JWT_ALGORITHM, JWT_SECRET_KEY from validations.auth import AuthInput, TokenPayload +import jwt + class JWTCodec: @staticmethod @@ -20,7 +19,7 @@ class JWTCodec: try: return jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM) except Exception as e: - print('[auth.jwtcodec] JWT encode error %r' % e) + print("[auth.jwtcodec] JWT encode error %r" % e) @staticmethod def decode(token: str, verify_exp: bool = True) -> TokenPayload: @@ -41,12 +40,12 @@ class JWTCodec: # print('[auth.jwtcodec] debug token %r' % r) return r except jwt.InvalidIssuedAtError: - print('[auth.jwtcodec] invalid issued at: %r' % payload) - raise ExpiredToken('check token issued time') + print("[auth.jwtcodec] invalid issued at: %r" % payload) + raise ExpiredToken("check token issued time") except jwt.ExpiredSignatureError: - print('[auth.jwtcodec] expired signature %r' % payload) - raise ExpiredToken('check token lifetime') + print("[auth.jwtcodec] expired signature %r" % payload) + raise ExpiredToken("check token lifetime") except jwt.InvalidTokenError: - raise InvalidToken('token is not valid') + raise InvalidToken("token is not valid") except jwt.InvalidSignatureError: - raise InvalidToken('token is not valid') + raise InvalidToken("token is not valid") diff --git a/auth/oauth.py b/auth/oauth.py index 02f56ff5..89695c72 100644 --- a/auth/oauth.py +++ b/auth/oauth.py @@ -1,9 +1,8 @@ -from authlib.integrations.starlette_client import OAuth -from starlette.responses import RedirectResponse - from auth.identity import Identity from auth.tokenstorage import TokenStorage +from authlib.integrations.starlette_client import OAuth from settings import FRONTEND_URL, OAUTH_CLIENTS +from starlette.responses import RedirectResponse oauth = OAuth() diff --git a/auth/tokenstorage.py b/auth/tokenstorage.py index b5a5bc39..79a1a9b6 100644 --- a/auth/tokenstorage.py +++ b/auth/tokenstorage.py @@ -1,7 +1,6 @@ -from datetime import datetime, timedelta, timezone - from auth.jwtcodec import JWTCodec from base.redis import redis +from datetime import datetime, timedelta, timezone from settings import ONETIME_TOKEN_LIFE_SPAN, SESSION_TOKEN_LIFE_SPAN from validations.auth import AuthInput @@ -35,7 +34,7 @@ class SessionToken: class TokenStorage: @staticmethod async def get(token_key): - print('[tokenstorage.get] ' + token_key) + print("[tokenstorage.get] " + token_key) # 2041-user@domain.zn-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyMDQxLCJ1c2VybmFtZSI6ImFudG9uLnJld2luK3Rlc3QtbG9hZGNoYXRAZ21haWwuY29tIiwiZXhwIjoxNjcxNzgwNjE2LCJpYXQiOjE2NjkxODg2MTYsImlzcyI6ImRpc2NvdXJzIn0.Nml4oV6iMjMmc6xwM7lTKEZJKBXvJFEIZ-Up1C1rITQ return await redis.execute("GET", token_key) diff --git a/base/orm.py b/base/orm.py index 02105f51..0ebb8de7 100644 --- a/base/orm.py +++ b/base/orm.py @@ -1,11 +1,9 @@ -from typing import Any, Callable, Dict, Generic, TypeVar - -from sqlalchemy import Column, Integer, create_engine +from settings import DB_URL +from sqlalchemy import Column, create_engine, Integer from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import Session from sqlalchemy.sql.schema import Table - -from settings import DB_URL +from typing import Any, Callable, Dict, Generic, TypeVar engine = create_engine(DB_URL, echo=False, pool_size=10, max_overflow=20) diff --git a/base/redis.py b/base/redis.py index d5d4babd..52a49caa 100644 --- a/base/redis.py +++ b/base/redis.py @@ -1,7 +1,5 @@ -from asyncio import sleep - from aioredis import from_url - +from asyncio import sleep from settings import REDIS_URL diff --git a/main.py b/main.py index 3f839ab5..8c4a7670 100644 --- a/main.py +++ b/main.py @@ -1,21 +1,12 @@ -import asyncio -import os -from importlib import import_module -from os.path import exists - from ariadne import load_schema_from_path, make_executable_schema from ariadne.asgi import GraphQL -from starlette.applications import Starlette -from starlette.middleware import Middleware -from starlette.middleware.authentication import AuthenticationMiddleware -from starlette.middleware.sessions import SessionMiddleware -from starlette.routing import Route - from auth.authenticate import JWTAuthenticate from auth.oauth import oauth_authorize, oauth_login from base.redis import redis from base.resolvers import resolvers +from importlib import import_module from orm import init_tables +from os.path import exists from resolvers.auth import confirm_email_handler from resolvers.upload import upload_handler from services.main import storages_init @@ -25,6 +16,14 @@ from services.stat.viewed import ViewedStorage # from services.zine.gittask import GitTask from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN, SESSION_SECRET_KEY +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.authentication import AuthenticationMiddleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.routing import Route + +import asyncio +import os import_module("resolvers") schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) # type: ignore @@ -51,7 +50,7 @@ async def start_up(): sentry_sdk.init(SENTRY_DSN) except Exception as e: - print('[sentry] init error') + print("[sentry] init error") print(e) @@ -60,7 +59,7 @@ async def dev_start_up(): await redis.connect() return else: - with open(DEV_SERVER_PID_FILE_NAME, 'w', encoding='utf-8') as f: + with open(DEV_SERVER_PID_FILE_NAME, "w", encoding="utf-8") as f: f.write(str(os.getpid())) await start_up() @@ -75,7 +74,7 @@ routes = [ Route("/oauth/{provider}", endpoint=oauth_login), Route("/oauth-authorize", endpoint=oauth_authorize), Route("/confirm/{token}", endpoint=confirm_email_handler), - Route("/upload", endpoint=upload_handler, methods=['POST']), + Route("/upload", endpoint=upload_handler, methods=["POST"]), Route("/subscribe/{user_id}", endpoint=sse_subscribe_handler), ] diff --git a/migration/__init__.py b/migration/__init__.py index 17cc5ffd..bf1ba8d8 100644 --- a/migration/__init__.py +++ b/migration/__init__.py @@ -1,18 +1,12 @@ """ cmd managed migration """ -import asyncio -import gc -import json -import sys from datetime import datetime, timezone - -import bs4 - from migration.export import export_mdx from migration.tables.comments import migrate as migrateComment from migration.tables.comments import migrate_2stage as migrateComment_2stage from migration.tables.content_items import get_shout_slug from migration.tables.content_items import migrate as migrateShout -from migration.tables.remarks import migrate as migrateRemark + +# from migration.tables.remarks import migrate as migrateRemark from migration.tables.topics import migrate as migrateTopic from migration.tables.users import migrate as migrateUser from migration.tables.users import migrate_2stage as migrateUser_2stage @@ -20,6 +14,12 @@ from migration.tables.users import post_migrate as users_post_migrate from orm import init_tables from orm.reaction import Reaction +import asyncio +import bs4 +import gc +import json +import sys + TODAY = datetime.strftime(datetime.now(tz=timezone.utc), "%Y%m%d") OLD_DATE = "2016-03-05 22:22:00.350000" @@ -111,7 +111,7 @@ async def shouts_handle(storage, args): # print main counter counter += 1 print( - '[migration] shouts_handle %d: %s @%s' + "[migration] shouts_handle %d: %s @%s" % ((counter + 1), shout_dict["slug"], author["slug"]) ) @@ -132,13 +132,13 @@ async def shouts_handle(storage, args): print("[migration] " + str(anonymous_author) + " authored by @anonymous") -async def remarks_handle(storage): - print("[migration] comments") - c = 0 - for entry_remark in storage["remarks"]["data"]: - remark = await migrateRemark(entry_remark, storage) - c += 1 - print("[migration] " + str(c) + " remarks migrated") +# async def remarks_handle(storage): +# print("[migration] comments") +# c = 0 +# for entry_remark in storage["remarks"]["data"]: +# remark = await migrateRemark(entry_remark, storage) +# c += 1 +# print("[migration] " + str(c) + " remarks migrated") async def comments_handle(storage): @@ -149,9 +149,9 @@ async def comments_handle(storage): for oldcomment in storage["reactions"]["data"]: if not oldcomment.get("deleted"): reaction = await migrateComment(oldcomment, storage) - if type(reaction) == str: + if isinstance(reaction, str): missed_shouts[reaction] = oldcomment - elif type(reaction) == Reaction: + elif isinstance(reaction, Reaction): reaction = reaction.dict() rid = reaction["id"] oid = reaction["oid"] diff --git a/migration/bson2json.py b/migration/bson2json.py index cff33b28..66507791 100644 --- a/migration/bson2json.py +++ b/migration/bson2json.py @@ -1,11 +1,10 @@ +from .utils import DateTimeEncoder + +import bson import gc import json import os -import bson - -from .utils import DateTimeEncoder - def json_tables(): print("[migration] unpack dump/discours/*.bson to migration/data/*.json") @@ -19,7 +18,7 @@ def json_tables(): "remarks": [], } for table in data.keys(): - print('[migration] bson2json for ' + table) + print("[migration] bson2json for " + table) gc.collect() lc = [] bs = open("dump/discours/" + table + ".bson", "rb").read() diff --git a/migration/export.py b/migration/export.py index 42004ee3..4105a220 100644 --- a/migration/export.py +++ b/migration/export.py @@ -1,11 +1,10 @@ -import json -import os +from .extract import extract_html, extract_media +from .utils import DateTimeEncoder from datetime import datetime, timezone import frontmatter - -from .extract import extract_html, extract_media -from .utils import DateTimeEncoder +import json +import os OLD_DATE = "2016-03-05 22:22:00.350000" EXPORT_DEST = "../discoursio-web/data/" diff --git a/migration/extract.py b/migration/extract.py index 511e68ed..eca8f8d0 100644 --- a/migration/extract.py +++ b/migration/extract.py @@ -1,9 +1,11 @@ +from bs4 import BeautifulSoup + import base64 import os import re -import uuid -from bs4 import BeautifulSoup +# import uuid + TOOLTIP_REGEX = r"(\/\/\/(.+)\/\/\/)" contentDir = os.path.join( @@ -26,40 +28,40 @@ def replace_tooltips(body): return newbody -def extract_footnotes(body, shout_dict): - parts = body.split("&&&") - lll = len(parts) - newparts = list(parts) - placed = False - if lll & 1: - if lll > 1: - i = 1 - print("[extract] found %d footnotes in body" % (lll - 1)) - for part in parts[1:]: - if i & 1: - placed = True - if 'a class="footnote-url" href=' in part: - print("[extract] footnote: " + part) - fn = 'a class="footnote-url" href="' - exxtracted_link = part.split(fn, 1)[1].split('"', 1)[0] - extracted_body = part.split(fn, 1)[1].split('>', 1)[1].split('', 1)[0] - print("[extract] footnote link: " + extracted_link) - with local_session() as session: - Reaction.create( - { - "shout": shout_dict['id'], - "kind": ReactionKind.FOOTNOTE, - "body": extracted_body, - "range": str(body.index(fn + link) - len('<')) - + ':' - + str(body.index(extracted_body) + len('')), - } - ) - newparts[i] = "ℹ️" - else: - newparts[i] = part - i += 1 - return ("".join(newparts), placed) +# def extract_footnotes(body, shout_dict): +# parts = body.split("&&&") +# lll = len(parts) +# newparts = list(parts) +# placed = False +# if lll & 1: +# if lll > 1: +# i = 1 +# print("[extract] found %d footnotes in body" % (lll - 1)) +# for part in parts[1:]: +# if i & 1: +# placed = True +# if 'a class="footnote-url" href=' in part: +# print("[extract] footnote: " + part) +# fn = 'a class="footnote-url" href="' +# # exxtracted_link = part.split(fn, 1)[1].split('"', 1)[0] +# extracted_body = part.split(fn, 1)[1].split(">", 1)[1].split("", 1)[0] +# print("[extract] footnote link: " + extracted_link) +# with local_session() as session: +# Reaction.create( +# { +# "shout": shout_dict["id"], +# "kind": ReactionKind.FOOTNOTE, +# "body": extracted_body, +# "range": str(body.index(fn + link) - len("<")) +# + ":" +# + str(body.index(extracted_body) + len("")), +# } +# ) +# newparts[i] = "ℹ️" +# else: +# newparts[i] = part +# i += 1 +# return ("".join(newparts), placed) def place_tooltips(body): @@ -228,7 +230,6 @@ di = "data:image" def extract_md_images(body, prefix): - newbody = "" body = ( body.replace("\n! [](" + di, "\n ![](" + di) .replace("\n[](" + di, "\n![](" + di) @@ -236,10 +237,10 @@ def extract_md_images(body, prefix): ) parts = body.split(di) if len(parts) > 1: - newbody = extract_dataimages(parts, prefix) + new_body = extract_dataimages(parts, prefix) else: - newbody = body - return newbody + new_body = body + return new_body def cleanup_md(body): @@ -262,28 +263,28 @@ def cleanup_md(body): return newbody -def extract_md(body, shout_dict=None): - newbody = body - if newbody: - newbody = cleanup_md(newbody) - if not newbody: - raise Exception("cleanup error") - - if shout_dict: - uid = shout_dict['id'] or uuid.uuid4() - newbody = extract_md_images(newbody, uid) - if not newbody: - raise Exception("extract_images error") - - newbody, placed = extract_footnotes(body, shout_dict) - if not newbody: - raise Exception("extract_footnotes error") - - return newbody +# def extract_md(body, shout_dict=None): +# newbody = body +# if newbody: +# newbody = cleanup_md(newbody) +# if not newbody: +# raise Exception("cleanup error") +# +# if shout_dict: +# uid = shout_dict["id"] or uuid.uuid4() +# newbody = extract_md_images(newbody, uid) +# if not newbody: +# raise Exception("extract_images error") +# +# newbody, placed = extract_footnotes(body, shout_dict) +# if not newbody: +# raise Exception("extract_footnotes error") +# +# return newbody def extract_media(entry): - '''normalized media extraction method''' + """normalized media extraction method""" # media [ { title pic url body } ]} kind = entry.get("type") if not kind: @@ -398,16 +399,14 @@ def cleanup_html(body: str) -> str: return new_body -def extract_html(entry, shout_id=None, cleanup=False): - body_orig = (entry.get("body") or "").replace('\(', '(').replace('\)', ')') +def extract_html(entry, cleanup=False): + body_orig = (entry.get("body") or "").replace(r"\(", "(").replace(r"\)", ")") if cleanup: # we do that before bs parsing to catch the invalid html body_clean = cleanup_html(body_orig) if body_clean != body_orig: print(f"[migration] html cleaned for slug {entry.get('slug', None)}") body_orig = body_clean - if shout_id: - extract_footnotes(body_orig, shout_id) body_html = str(BeautifulSoup(body_orig, features="html.parser")) if cleanup: # we do that after bs parsing because it can add dummy tags diff --git a/migration/html2text/__init__.py b/migration/html2text/__init__.py index 6b87f297..c99afc59 100644 --- a/migration/html2text/__init__.py +++ b/migration/html2text/__init__.py @@ -1,13 +1,5 @@ """html2text: Turn HTML into equivalent Markdown-structured text.""" -import html.entities -import html.parser -import re -import string -import urllib.parse as urlparse -from textwrap import wrap -from typing import Dict, List, Optional, Tuple, Union - from . import config from .elements import AnchorElement, ListElement from .typing import OutCallback @@ -26,6 +18,14 @@ from .utils import ( skipwrap, unifiable_n, ) +from textwrap import wrap +from typing import Dict, List, Optional, Tuple, Union + +import html.entities +import html.parser +import re +import string +import urllib.parse as urlparse __version__ = (2020, 1, 16) diff --git a/migration/html2text/cli.py b/migration/html2text/cli.py index 62e0738f..f6cf3c57 100644 --- a/migration/html2text/cli.py +++ b/migration/html2text/cli.py @@ -1,8 +1,8 @@ +from . import __version__, config, HTML2Text + import argparse import sys -from . import HTML2Text, __version__, config - # noinspection DuplicatedCode def main() -> None: diff --git a/migration/html2text/utils.py b/migration/html2text/utils.py index fd6a16c2..545bbd17 100644 --- a/migration/html2text/utils.py +++ b/migration/html2text/utils.py @@ -1,7 +1,7 @@ -import html.entities +from . import config from typing import Dict, List, Optional -from . import config +import html.entities unifiable_n = { html.entities.name2codepoint[k]: v for k, v in config.UNIFIABLE.items() if k != "nbsp" diff --git a/migration/tables/comments.py b/migration/tables/comments.py index 092850c8..13d2809d 100644 --- a/migration/tables/comments.py +++ b/migration/tables/comments.py @@ -1,8 +1,6 @@ -from datetime import datetime, timezone - -from dateutil.parser import parse as date_parse - from base.orm import local_session +from datetime import datetime, timezone +from dateutil.parser import parse as date_parse from migration.html2text import html2text from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutReactionsFollower @@ -30,12 +28,12 @@ def auto_followers(session, topics, reaction_dict): tf = ( session.query(TopicFollower) .where(TopicFollower.follower == reaction_dict["createdBy"]) - .filter(TopicFollower.topic == t['id']) + .filter(TopicFollower.topic == t["id"]) .first() ) if not tf: topic_following = TopicFollower.create( - follower=reaction_dict["createdBy"], topic=t['id'], auto=True + follower=reaction_dict["createdBy"], topic=t["id"], auto=True ) session.add(topic_following) @@ -57,13 +55,13 @@ def migrate_ratings(session, entry, reaction_dict): rr = Reaction.create(**re_reaction_dict) following2 = ( session.query(ShoutReactionsFollower) - .where(ShoutReactionsFollower.follower == re_reaction_dict['createdBy']) + .where(ShoutReactionsFollower.follower == re_reaction_dict["createdBy"]) .filter(ShoutReactionsFollower.shout == rr.shout) .first() ) if not following2: following2 = ShoutReactionsFollower.create( - follower=re_reaction_dict['createdBy'], shout=rr.shout, auto=True + follower=re_reaction_dict["createdBy"], shout=rr.shout, auto=True ) session.add(following2) session.add(rr) @@ -160,9 +158,9 @@ async def migrate(entry, storage): def migrate_2stage(old_comment, idmap): - if old_comment.get('body'): - new_id = idmap.get(old_comment.get('oid')) - new_id = idmap.get(old_comment.get('_id')) + if old_comment.get("body"): + new_id = idmap.get(old_comment.get("oid")) + new_id = idmap.get(old_comment.get("_id")) if new_id: new_replyto_id = None old_replyto_id = old_comment.get("replyTo") diff --git a/migration/tables/content_items.py b/migration/tables/content_items.py index 92a97c24..053a8a97 100644 --- a/migration/tables/content_items.py +++ b/migration/tables/content_items.py @@ -1,18 +1,17 @@ -import json -import re -from datetime import datetime, timezone - -from dateutil.parser import parse as date_parse -from sqlalchemy.exc import IntegrityError -from transliterate import translit - from base.orm import local_session +from datetime import datetime, timezone +from dateutil.parser import parse as date_parse from migration.extract import extract_html, extract_media from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutReactionsFollower, ShoutTopic from orm.topic import Topic, TopicFollower from orm.user import User from services.stat.viewed import ViewedStorage +from sqlalchemy.exc import IntegrityError +from transliterate import translit + +import json +import re OLD_DATE = "2016-03-05 22:22:00.350000" ts = datetime.now(tz=timezone.utc) @@ -35,7 +34,7 @@ def get_shout_slug(entry): slug = friend.get("slug", "") if slug: break - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + slug = re.sub("[^0-9a-zA-Z]+", "-", slug) return slug @@ -43,27 +42,27 @@ def create_author_from_app(app): user = None userdata = None # check if email is used - if app['email']: + if app["email"]: with local_session() as session: - user = session.query(User).where(User.email == app['email']).first() + user = session.query(User).where(User.email == app["email"]).first() if not user: # print('[migration] app %r' % app) - name = app.get('name') + name = app.get("name") if name: slug = translit(name, "ru", reversed=True).lower() - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) - print('[migration] created slug %s' % slug) + slug = re.sub("[^0-9a-zA-Z]+", "-", slug) + print("[migration] created slug %s" % slug) # check if slug is used if slug: user = session.query(User).where(User.slug == slug).first() # get slug from email if user: - slug = app['email'].split('@')[0] + slug = app["email"].split("@")[0] user = session.query(User).where(User.slug == slug).first() # one more try if user: - slug += '-author' + slug += "-author" user = session.query(User).where(User.slug == slug).first() # create user with application data @@ -81,7 +80,7 @@ def create_author_from_app(app): user = User.create(**userdata) session.add(user) session.commit() - userdata['id'] = user.id + userdata["id"] = user.id userdata = user.dict() return userdata @@ -119,14 +118,14 @@ async def get_user(entry, storage): elif user_oid: userdata = storage["users"]["by_oid"].get(user_oid) if not userdata: - print('no userdata by oid, anonymous') + print("no userdata by oid, anonymous") userdata = anondict print(app) # cleanup slug if userdata: slug = userdata.get("slug", "") if slug: - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + slug = re.sub("[^0-9a-zA-Z]+", "-", slug) userdata["slug"] = slug else: userdata = anondict @@ -160,7 +159,7 @@ async def migrate(entry, storage): } # main topic patch - r['mainTopic'] = r['topics'][0] + r["mainTopic"] = r["topics"][0] # published author auto-confirm if entry.get("published"): @@ -183,7 +182,7 @@ async def migrate(entry, storage): shout_dict["oid"] = entry.get("_id", "") shout = await create_shout(shout_dict) except IntegrityError as e: - print('[migration] create_shout integrity error', e) + print("[migration] create_shout integrity error", e) shout = await resolve_create_shout(shout_dict) except Exception as e: raise Exception(e) @@ -202,7 +201,7 @@ async def migrate(entry, storage): # shout views await ViewedStorage.increment( - shout_dict["slug"], amount=entry.get("views", 1), viewer='old-discours' + shout_dict["slug"], amount=entry.get("views", 1), viewer="old-discours" ) # del shout_dict['ratings'] @@ -240,7 +239,7 @@ async def add_topics_follower(entry, storage, user): session.add(tf) session.commit() except IntegrityError: - print('[migration.shout] hidden by topic ' + tpc.slug) + print("[migration.shout] hidden by topic " + tpc.slug) # main topic maintopic = storage["replacements"].get(topics_by_oid.get(category, {}).get("slug")) if maintopic in ttt: @@ -261,7 +260,7 @@ async def process_user(userdata, storage, oid): if not user: try: slug = userdata["slug"].lower().strip() - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + slug = re.sub("[^0-9a-zA-Z]+", "-", slug) userdata["slug"] = slug user = User.create(**userdata) session.add(user) @@ -289,9 +288,9 @@ async def resolve_create_shout(shout_dict): s = session.query(Shout).filter(Shout.slug == shout_dict["slug"]).first() bump = False if s: - if s.createdAt != shout_dict['createdAt']: + if s.createdAt != shout_dict["createdAt"]: # create new with different slug - shout_dict["slug"] += '-' + shout_dict["layout"] + shout_dict["slug"] += "-" + shout_dict["layout"] try: await create_shout(shout_dict) except IntegrityError as e: diff --git a/migration/tables/remarks.py b/migration/tables/remarks.py index 09957ed4..e133050f 100644 --- a/migration/tables/remarks.py +++ b/migration/tables/remarks.py @@ -5,24 +5,24 @@ from orm.reaction import Reaction, ReactionKind def migrate(entry, storage): - post_oid = entry['contentItem'] + post_oid = entry["contentItem"] print(post_oid) - shout_dict = storage['shouts']['by_oid'].get(post_oid) + shout_dict = storage["shouts"]["by_oid"].get(post_oid) if shout_dict: - print(shout_dict['body']) + print(shout_dict["body"]) remark = { - "shout": shout_dict['id'], - "body": extract_md(html2text(entry['body']), shout_dict), + "shout": shout_dict["id"], + "body": extract_md(html2text(entry["body"]), shout_dict), "kind": ReactionKind.REMARK, } - if entry.get('textBefore'): - remark['range'] = ( - str(shout_dict['body'].index(entry['textBefore'] or '')) - + ':' + if entry.get("textBefore"): + remark["range"] = ( + str(shout_dict["body"].index(entry["textBefore"] or "")) + + ":" + str( - shout_dict['body'].index(entry['textAfter'] or '') - + len(entry['textAfter'] or '') + shout_dict["body"].index(entry["textAfter"] or "") + + len(entry["textAfter"] or "") ) ) diff --git a/migration/tables/users.py b/migration/tables/users.py index 46f2e825..40c80f21 100644 --- a/migration/tables/users.py +++ b/migration/tables/users.py @@ -1,11 +1,10 @@ -import re - +from base.orm import local_session from bs4 import BeautifulSoup from dateutil.parser import parse +from orm.user import AuthorFollower, User, UserRating from sqlalchemy.exc import IntegrityError -from base.orm import local_session -from orm.user import AuthorFollower, User, UserRating +import re def migrate(entry): @@ -33,12 +32,12 @@ def migrate(entry): if entry.get("profile"): # slug slug = entry["profile"].get("path").lower() - slug = re.sub('[^0-9a-zA-Z]+', '-', slug).strip() + slug = re.sub("[^0-9a-zA-Z]+", "-", slug).strip() user_dict["slug"] = slug bio = ( (entry.get("profile", {"bio": ""}).get("bio") or "") - .replace('\(', '(') - .replace('\)', ')') + .replace(r"\(", "(") + .replace(r"\)", ")") ) bio_text = BeautifulSoup(bio, features="lxml").text @@ -144,7 +143,7 @@ def migrate_2stage(entry, id_map): } user_rating = UserRating.create(**user_rating_dict) - if user_rating_dict['value'] > 0: + if user_rating_dict["value"] > 0: af = AuthorFollower.create(author=user.id, follower=rater.id, auto=True) session.add(af) session.add(user_rating) diff --git a/orm/collection.py b/orm/collection.py index c9975b62..1c432727 100644 --- a/orm/collection.py +++ b/orm/collection.py @@ -1,8 +1,6 @@ -from datetime import datetime - -from sqlalchemy import Column, DateTime, ForeignKey, String - from base.orm import Base +from datetime import datetime +from sqlalchemy import Column, DateTime, ForeignKey, String class ShoutCollection(Base): diff --git a/orm/community.py b/orm/community.py index 7045e1aa..c31732a0 100644 --- a/orm/community.py +++ b/orm/community.py @@ -1,8 +1,6 @@ -from datetime import datetime - -from sqlalchemy import Column, DateTime, ForeignKey, String - from base.orm import Base, local_session +from datetime import datetime +from sqlalchemy import Column, DateTime, ForeignKey, String class CommunityFollower(Base): @@ -33,4 +31,4 @@ class Community(Base): session.add(d) session.commit() Community.default_community = d - print('[orm] default community id: %s' % d.id) + print("[orm] default community id: %s" % d.id) diff --git a/orm/notification.py b/orm/notification.py index a838ce6b..2fdc9d5d 100644 --- a/orm/notification.py +++ b/orm/notification.py @@ -1,11 +1,9 @@ +from base.orm import Base from datetime import datetime from enum import Enum as Enumeration - from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer from sqlalchemy.dialects.postgresql import JSONB -from base.orm import Base - class NotificationType(Enumeration): NEW_COMMENT = 1 diff --git a/orm/rbac.py b/orm/rbac.py index 80914949..bb7eb34b 100644 --- a/orm/rbac.py +++ b/orm/rbac.py @@ -1,9 +1,8 @@ -import warnings - +from base.orm import Base, local_session, REGISTRY from sqlalchemy import Column, ForeignKey, String, TypeDecorator, UniqueConstraint from sqlalchemy.orm import relationship -from base.orm import REGISTRY, Base, engine, local_session +import warnings # Role Based Access Control # @@ -165,14 +164,14 @@ class Permission(Base): ) -if __name__ == "__main__": - Base.metadata.create_all(engine) - ops = [ - Permission(role=1, operation=1, resource=1), - Permission(role=1, operation=2, resource=1), - Permission(role=1, operation=3, resource=1), - Permission(role=1, operation=4, resource=1), - Permission(role=2, operation=4, resource=1), - ] - global_session.add_all(ops) - global_session.commit() +# if __name__ == "__main__": +# Base.metadata.create_all(engine) +# ops = [ +# Permission(role=1, operation=1, resource=1), +# Permission(role=1, operation=2, resource=1), +# Permission(role=1, operation=3, resource=1), +# Permission(role=1, operation=4, resource=1), +# Permission(role=2, operation=4, resource=1), +# ] +# global_session.add_all(ops) +# global_session.commit() diff --git a/orm/reaction.py b/orm/reaction.py index f3680b6d..89fed9eb 100644 --- a/orm/reaction.py +++ b/orm/reaction.py @@ -1,10 +1,8 @@ +from base.orm import Base from datetime import datetime from enum import Enum as Enumeration - from sqlalchemy import Column, DateTime, Enum, ForeignKey, String -from base.orm import Base - class ReactionKind(Enumeration): AGREE = 1 # +1 diff --git a/orm/shout.py b/orm/shout.py index 0d980b8a..7a77b66c 100644 --- a/orm/shout.py +++ b/orm/shout.py @@ -1,12 +1,10 @@ -from datetime import datetime - -from sqlalchemy import JSON, Boolean, Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.orm import column_property, relationship - from base.orm import Base, local_session +from datetime import datetime from orm.reaction import Reaction from orm.topic import Topic from orm.user import User +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, JSON, String +from sqlalchemy.orm import column_property, relationship class ShoutTopic(Base): @@ -70,7 +68,7 @@ class Shout(Base): # TODO: these field should be used or modified community = Column(ForeignKey("community.id"), default=1) - lang = Column(String, nullable=False, default='ru', comment="Language") + lang = Column(String, nullable=False, default="ru", comment="Language") mainTopic = Column(ForeignKey("topic.slug"), nullable=True) visibility = Column(String, nullable=True) # owner authors community public versionOf = Column(ForeignKey("shout.id"), nullable=True) @@ -81,7 +79,12 @@ class Shout(Base): with local_session() as session: s = session.query(Shout).first() if not s: - entry = {"slug": "genesis-block", "body": "", "title": "Ничего", "lang": "ru"} + entry = { + "slug": "genesis-block", + "body": "", + "title": "Ничего", + "lang": "ru", + } s = Shout.create(**entry) session.add(s) session.commit() diff --git a/orm/topic.py b/orm/topic.py index b0d7cc01..6da93732 100644 --- a/orm/topic.py +++ b/orm/topic.py @@ -1,8 +1,6 @@ -from datetime import datetime - -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String - from base.orm import Base +from datetime import datetime +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String class TopicFollower(Base): diff --git a/orm/user.py b/orm/user.py index d10be411..d76c4627 100644 --- a/orm/user.py +++ b/orm/user.py @@ -1,11 +1,10 @@ -from datetime import datetime - -from sqlalchemy import JSON as JSONType -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - from base.orm import Base, local_session +from datetime import datetime from orm.rbac import Role +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer +from sqlalchemy import JSON as JSONType +from sqlalchemy import String +from sqlalchemy.orm import relationship class UserRating(Base): diff --git a/requirements-dev.txt b/requirements-dev.txt index b2e99a01..31fbe456 100755 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ brunette flake8 mypy pre-commit +black diff --git a/requirements.txt b/requirements.txt index edbf46ff..a919e623 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,15 +18,12 @@ transliterate~=1.10.2 requests~=2.28.1 bcrypt>=4.0.0 bson~=0.5.10 -flake8 DateTime~=4.7 asyncio~=3.4.3 python-dateutil~=2.8.2 beautifulsoup4~=4.11.1 lxml sentry-sdk>=1.14.0 -# sse_starlette -graphql-ws nltk~=3.8.1 pymystem3~=0.2.0 transformers~=4.28.1 diff --git a/resolvers/__init__.py b/resolvers/__init__.py deleted file mode 100644 index 78ae7e22..00000000 --- a/resolvers/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from resolvers.auth import ( - auth_send_link, - confirm_email, - get_current_user, - is_email_used, - login, - register_by_email, - sign_out, -) -from resolvers.create.editor import create_shout, delete_shout, update_shout -from resolvers.create.migrate import markdown_body -from resolvers.inbox.chats import create_chat, delete_chat, update_chat -from resolvers.inbox.load import load_chats, load_messages_by, load_recipients -from resolvers.inbox.messages import create_message, delete_message, mark_as_read, update_message -from resolvers.inbox.search import search_recipients -from resolvers.notifications import load_notifications -from resolvers.zine.following import follow, unfollow -from resolvers.zine.load import load_shout, load_shouts_by -from resolvers.zine.profile import get_authors_all, load_authors_by, rate_user, update_profile -from resolvers.zine.reactions import ( - create_reaction, - delete_reaction, - load_reactions_by, - reactions_follow, - reactions_unfollow, - update_reaction, -) -from resolvers.zine.topics import ( - get_topic, - topic_follow, - topic_unfollow, - topics_all, - topics_by_author, - topics_by_community, -) diff --git a/resolvers/auth.py b/resolvers/auth.py index c28898e3..3ba15d9d 100644 --- a/resolvers/auth.py +++ b/resolvers/auth.py @@ -1,13 +1,5 @@ # -*- coding: utf-8 -*- -import re -from datetime import datetime, timezone -from urllib.parse import quote_plus - -from graphql.type import GraphQLResolveInfo -from starlette.responses import RedirectResponse -from transliterate import translit - from auth.authenticate import login_required from auth.credentials import AuthCredentials from auth.email import send_auth_email @@ -23,8 +15,15 @@ from base.exceptions import ( ) from base.orm import local_session from base.resolvers import mutation, query +from datetime import datetime, timezone +from graphql.type import GraphQLResolveInfo from orm import Role, User from settings import FRONTEND_URL, SESSION_TOKEN_HEADER +from starlette.responses import RedirectResponse +from transliterate import translit +from urllib.parse import quote_plus + +import re @mutation.field("getSession") @@ -45,7 +44,7 @@ async def get_current_user(_, info): async def confirm_email(_, info, token): """confirm owning email address""" try: - print('[resolvers.auth] confirm email by token') + print("[resolvers.auth] confirm email by token") payload = JWTCodec.decode(token) user_id = payload.user_id await TokenStorage.get(f"{user_id}-{payload.username}-{token}") @@ -68,9 +67,9 @@ async def confirm_email_handler(request): token = request.path_params["token"] # one time request.session["token"] = token res = await confirm_email(None, {}, token) - print('[resolvers.auth] confirm_email request: %r' % request) + print("[resolvers.auth] confirm_email request: %r" % request) if "error" in res: - raise BaseHttpException(res['error']) + raise BaseHttpException(res["error"]) else: response = RedirectResponse(url=FRONTEND_URL) response.set_cookie("token", res["token"]) # session token @@ -87,22 +86,22 @@ def create_user(user_dict): def generate_unique_slug(src): - print('[resolvers.auth] generating slug from: ' + src) + print("[resolvers.auth] generating slug from: " + src) slug = translit(src, "ru", reversed=True).replace(".", "-").lower() - slug = re.sub('[^0-9a-zA-Z]+', '-', slug) + slug = re.sub("[^0-9a-zA-Z]+", "-", slug) if slug != src: - print('[resolvers.auth] translited name: ' + slug) + print("[resolvers.auth] translited name: " + slug) c = 1 with local_session() as session: user = session.query(User).where(User.slug == slug).first() while user: user = session.query(User).where(User.slug == slug).first() - slug = slug + '-' + str(c) + slug = slug + "-" + str(c) c += 1 if not user: unique_slug = slug - print('[resolvers.auth] ' + unique_slug) - return quote_plus(unique_slug.replace('\'', '')).replace('+', '-') + print("[resolvers.auth] " + unique_slug) + return quote_plus(unique_slug.replace("'", "")).replace("+", "-") @mutation.field("registerUser") @@ -117,7 +116,7 @@ async def register_by_email(_, _info, email: str, password: str = "", name: str slug = generate_unique_slug(name) user = session.query(User).where(User.slug == slug).first() if user: - slug = generate_unique_slug(email.split('@')[0]) + slug = generate_unique_slug(email.split("@")[0]) user_dict = { "email": email, "username": email, # will be used to store phone number or some messenger network id diff --git a/resolvers/create/editor.py b/resolvers/create/editor.py index d6db8bf6..6ec690f7 100644 --- a/resolvers/create/editor.py +++ b/resolvers/create/editor.py @@ -1,15 +1,13 @@ -from datetime import datetime, timezone - -from sqlalchemy import and_ -from sqlalchemy.orm import joinedload - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session from base.resolvers import mutation +from datetime import datetime, timezone from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.topic import Topic from resolvers.zine.reactions import reactions_follow, reactions_unfollow +from sqlalchemy import and_ +from sqlalchemy.orm import joinedload @mutation.field("createShout") @@ -18,15 +16,15 @@ async def create_shout(_, info, inp): auth: AuthCredentials = info.context["request"].auth with local_session() as session: - topics = session.query(Topic).filter(Topic.slug.in_(inp.get('topics', []))).all() + topics = session.query(Topic).filter(Topic.slug.in_(inp.get("topics", []))).all() new_shout = Shout.create( **{ "title": inp.get("title"), - "subtitle": inp.get('subtitle'), - "lead": inp.get('lead'), - "description": inp.get('description'), - "body": inp.get("body", ''), + "subtitle": inp.get("subtitle"), + "lead": inp.get("lead"), + "description": inp.get("description"), + "body": inp.get("body", ""), "layout": inp.get("layout"), "authors": inp.get("authors", []), "slug": inp.get("slug"), @@ -128,7 +126,10 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False): ] shout_topics_to_remove = session.query(ShoutTopic).filter( - and_(ShoutTopic.shout == shout.id, ShoutTopic.topic.in_(topic_to_unlink_ids)) + and_( + ShoutTopic.shout == shout.id, + ShoutTopic.topic.in_(topic_to_unlink_ids), + ) ) for shout_topic_to_remove in shout_topics_to_remove: @@ -136,13 +137,13 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False): shout_input["mainTopic"] = shout_input["mainTopic"]["slug"] - if shout_input["mainTopic"] == '': + if shout_input["mainTopic"] == "": del shout_input["mainTopic"] shout.update(shout_input) updated = True - if publish and shout.visibility == 'owner': + if publish and shout.visibility == "owner": shout.visibility = "community" shout.publishedAt = datetime.now(tz=timezone.utc) updated = True diff --git a/resolvers/create/migrate.py b/resolvers/create/migrate.py index 9e849f86..028808b1 100644 --- a/resolvers/create/migrate.py +++ b/resolvers/create/migrate.py @@ -1,10 +1,10 @@ -from base.resolvers import query -from migration.extract import extract_md -from resolvers.auth import login_required - - -@login_required -@query.field("markdownBody") -def markdown_body(_, info, body: str): - body = extract_md(body) - return body +# from base.resolvers import query +# from migration.extract import extract_md +# from resolvers.auth import login_required +# +# +# @login_required +# @query.field("markdownBody") +# def markdown_body(_, info, body: str): +# body = extract_md(body) +# return body diff --git a/resolvers/inbox/chats.py b/resolvers/inbox/chats.py index a589e870..95a31f69 100644 --- a/resolvers/inbox/chats.py +++ b/resolvers/inbox/chats.py @@ -1,13 +1,13 @@ -import json -import uuid -from datetime import datetime, timezone - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.redis import redis from base.resolvers import mutation +from datetime import datetime, timezone from validations.inbox import Chat +import json +import uuid + @mutation.field("updateChat") @login_required @@ -49,7 +49,7 @@ async def update_chat(_, info, chat_new: Chat): async def create_chat(_, info, title="", members=[]): auth: AuthCredentials = info.context["request"].auth chat = {} - print('create_chat members: %r' % members) + print("create_chat members: %r" % members) if auth.user_id not in members: members.append(int(auth.user_id)) @@ -71,8 +71,8 @@ async def create_chat(_, info, title="", members=[]): chat = await redis.execute("GET", f"chats/{c.decode('utf-8')}") if chat: chat = json.loads(chat) - if chat['title'] == "": - print('[inbox] createChat found old chat') + if chat["title"] == "": + print("[inbox] createChat found old chat") print(chat) break if chat: @@ -105,7 +105,7 @@ async def delete_chat(_, info, chat_id: str): chat = await redis.execute("GET", f"/chats/{chat_id}") if chat: chat = dict(json.loads(chat)) - if auth.user_id in chat['admins']: + if auth.user_id in chat["admins"]: await redis.execute("DEL", f"chats/{chat_id}") await redis.execute("SREM", "chats_by_user/" + str(auth.user_id), chat_id) await redis.execute("COMMIT") diff --git a/resolvers/inbox/load.py b/resolvers/inbox/load.py index 43f8a07c..54ae75d5 100644 --- a/resolvers/inbox/load.py +++ b/resolvers/inbox/load.py @@ -1,5 +1,4 @@ -import json - +from .unread import get_unread_counter from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session @@ -8,13 +7,13 @@ from base.resolvers import query from orm.user import User from resolvers.zine.profile import followed_authors -from .unread import get_unread_counter +import json # from datetime import datetime, timedelta, timezone async def load_messages(chat_id: str, limit: int = 5, offset: int = 0, ids=[]): - '''load :limit messages for :chat_id with :offset''' + """load :limit messages for :chat_id with :offset""" messages = [] message_ids = [] if ids: @@ -29,10 +28,10 @@ async def load_messages(chat_id: str, limit: int = 5, offset: int = 0, ids=[]): if message_ids: message_keys = [f"chats/{chat_id}/messages/{mid}" for mid in message_ids] messages = await redis.mget(*message_keys) - messages = [json.loads(msg.decode('utf-8')) for msg in messages] + messages = [json.loads(msg.decode("utf-8")) for msg in messages] replies = [] for m in messages: - rt = m.get('replyTo') + rt = m.get("replyTo") if rt: rt = int(rt) if rt not in message_ids: @@ -52,7 +51,7 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0): if cids: cids = list(cids)[offset : offset + limit] if not cids: - print('[inbox.load] no chats were found') + print("[inbox.load] no chats were found") cids = [] onliners = await redis.execute("SMEMBERS", "users-online") if not onliners: @@ -63,14 +62,14 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0): c = await redis.execute("GET", "chats/" + cid) if c: c = dict(json.loads(c)) - c['messages'] = await load_messages(cid, 5, 0) - c['unread'] = await get_unread_counter(cid, auth.user_id) + c["messages"] = await load_messages(cid, 5, 0) + c["unread"] = await get_unread_counter(cid, auth.user_id) with local_session() as session: - c['members'] = [] + c["members"] = [] for uid in c["users"]: a = session.query(User).where(User.id == uid).first() if a: - c['members'].append( + c["members"].append( { "id": a.id, "slug": a.slug, @@ -87,16 +86,16 @@ async def load_chats(_, info, limit: int = 50, offset: int = 0): @query.field("loadMessagesBy") @login_required async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0): - '''load :limit messages of :chat_id with :offset''' + """load :limit messages of :chat_id with :offset""" auth: AuthCredentials = info.context["request"].auth userchats = await redis.execute("SMEMBERS", "chats_by_user/" + str(auth.user_id)) - userchats = [c.decode('utf-8') for c in userchats] + userchats = [c.decode("utf-8") for c in userchats] # print('[inbox] userchats: %r' % userchats) if userchats: # print('[inbox] loading messages by...') messages = [] - by_chat = by.get('chat') + by_chat = by.get("chat") if by_chat in userchats: chat = await redis.execute("GET", f"chats/{by_chat}") # print(chat) @@ -104,7 +103,10 @@ async def load_messages_by(_, info, by, limit: int = 10, offset: int = 0): return {"messages": [], "error": "chat not exist"} # everyone's messages in filtered chat messages = await load_messages(by_chat, limit, offset) - return {"messages": sorted(list(messages), key=lambda m: m['createdAt']), "error": None} + return { + "messages": sorted(list(messages), key=lambda m: m["createdAt"]), + "error": None, + } else: return {"error": "Cannot access messages of this chat"} diff --git a/resolvers/inbox/messages.py b/resolvers/inbox/messages.py index 3d35105a..b3d2689f 100644 --- a/resolvers/inbox/messages.py +++ b/resolvers/inbox/messages.py @@ -1,16 +1,11 @@ -import asyncio -import json -from datetime import datetime, timezone -from typing import Any - -from graphql.type import GraphQLResolveInfo - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.redis import redis from base.resolvers import mutation -from services.following import Following, FollowingManager, FollowingResult -from validations.inbox import Message +from datetime import datetime, timezone +from services.following import FollowingManager, FollowingResult + +import json @mutation.field("createMessage") @@ -27,15 +22,15 @@ async def create_message(_, info, chat: str, body: str, replyTo=None): message_id = await redis.execute("GET", f"chats/{chat['id']}/next_message_id") message_id = int(message_id) new_message = { - "chatId": chat['id'], + "chatId": chat["id"], "id": message_id, "author": auth.user_id, "body": body, "createdAt": int(datetime.now(tz=timezone.utc).timestamp()), } if replyTo: - new_message['replyTo'] = replyTo - chat['updatedAt'] = new_message['createdAt'] + new_message["replyTo"] = replyTo + chat["updatedAt"] = new_message["createdAt"] await redis.execute("SET", f"chats/{chat['id']}", json.dumps(chat)) print(f"[inbox] creating message {new_message}") await redis.execute( @@ -48,8 +43,8 @@ async def create_message(_, info, chat: str, body: str, replyTo=None): for user_slug in users: await redis.execute("LPUSH", f"chats/{chat['id']}/unread/{user_slug}", str(message_id)) - result = FollowingResult("NEW", 'chat', new_message) - await FollowingManager.push('chat', result) + result = FollowingResult("NEW", "chat", new_message) + await FollowingManager.push("chat", result) return {"message": new_message, "error": None} @@ -76,8 +71,8 @@ async def update_message(_, info, chat_id: str, message_id: int, body: str): await redis.execute("SET", f"chats/{chat_id}/messages/{message_id}", json.dumps(message)) - result = FollowingResult("UPDATED", 'chat', message) - await FollowingManager.push('chat', result) + result = FollowingResult("UPDATED", "chat", message) + await FollowingManager.push("chat", result) return {"message": message, "error": None} @@ -106,7 +101,7 @@ async def delete_message(_, info, chat_id: str, message_id: int): for user_id in users: await redis.execute("LREM", f"chats/{chat_id}/unread/{user_id}", 0, str(message_id)) - result = FollowingResult("DELETED", 'chat', message) + result = FollowingResult("DELETED", "chat", message) await FollowingManager.push(result) return {} diff --git a/resolvers/inbox/search.py b/resolvers/inbox/search.py index 8a3f0c2d..510ce52c 100644 --- a/resolvers/inbox/search.py +++ b/resolvers/inbox/search.py @@ -1,14 +1,14 @@ -import json -from datetime import datetime, timedelta, timezone - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session from base.redis import redis from base.resolvers import query +from datetime import datetime, timedelta, timezone from orm.user import AuthorFollower, User from resolvers.inbox.load import load_messages +import json + @query.field("searchRecipients") @login_required @@ -59,22 +59,22 @@ async def search_user_chats(by, messages, user_id: int, limit, offset): cids.union(set(await redis.execute("SMEMBERS", "chats_by_user/" + str(user_id)))) messages = [] - by_author = by.get('author') + by_author = by.get("author") if by_author: # all author's messages cids.union(set(await redis.execute("SMEMBERS", f"chats_by_user/{by_author}"))) # author's messages in filtered chat messages.union(set(filter(lambda m: m["author"] == by_author, list(messages)))) for c in cids: - c = c.decode('utf-8') + c = c.decode("utf-8") messages = await load_messages(c, limit, offset) - body_like = by.get('body') + body_like = by.get("body") if body_like: # search in all messages in all user's chats for c in cids: # FIXME: use redis scan here - c = c.decode('utf-8') + c = c.decode("utf-8") mmm = await load_messages(c, limit, offset) for m in mmm: if body_like in m["body"]: diff --git a/resolvers/notifications.py b/resolvers/notifications.py index 98314d2e..3ece629e 100644 --- a/resolvers/notifications.py +++ b/resolvers/notifications.py @@ -1,10 +1,9 @@ -from sqlalchemy import and_, desc, select, update - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session from base.resolvers import mutation, query from orm import Notification +from sqlalchemy import and_, desc, select, update @query.field("loadNotifications") @@ -16,8 +15,8 @@ async def load_notifications(_, info, params=None): auth: AuthCredentials = info.context["request"].auth user_id = auth.user_id - limit = params.get('limit', 50) - offset = params.get('offset', 0) + limit = params.get("limit", 50) + offset = params.get("offset", 0) q = ( select(Notification) @@ -33,7 +32,7 @@ async def load_notifications(_, info, params=None): total_unread_count = ( session.query(Notification) - .where(and_(Notification.user == user_id, Notification.seen == False)) + .where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712 .count() ) @@ -74,7 +73,7 @@ async def mark_all_notifications_as_read(_, info): statement = ( update(Notification) - .where(and_(Notification.user == user_id, Notification.seen == False)) + .where(and_(Notification.user == user_id, Notification.seen == False)) # noqa: E712 .values(seen=True) ) diff --git a/resolvers/upload.py b/resolvers/upload.py index 3eee3358..9649222c 100644 --- a/resolvers/upload.py +++ b/resolvers/upload.py @@ -1,33 +1,33 @@ +from botocore.exceptions import BotoCoreError, ClientError +from starlette.responses import JSONResponse + +import boto3 import os import shutil import tempfile import uuid -import boto3 -from botocore.exceptions import BotoCoreError, ClientError -from starlette.responses import JSONResponse - -STORJ_ACCESS_KEY = os.environ.get('STORJ_ACCESS_KEY') -STORJ_SECRET_KEY = os.environ.get('STORJ_SECRET_KEY') -STORJ_END_POINT = os.environ.get('STORJ_END_POINT') -STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') -CDN_DOMAIN = os.environ.get('CDN_DOMAIN') +STORJ_ACCESS_KEY = os.environ.get("STORJ_ACCESS_KEY") +STORJ_SECRET_KEY = os.environ.get("STORJ_SECRET_KEY") +STORJ_END_POINT = os.environ.get("STORJ_END_POINT") +STORJ_BUCKET_NAME = os.environ.get("STORJ_BUCKET_NAME") +CDN_DOMAIN = os.environ.get("CDN_DOMAIN") async def upload_handler(request): form = await request.form() - file = form.get('file') + file = form.get("file") if file is None: - return JSONResponse({'error': 'No file uploaded'}, status_code=400) + return JSONResponse({"error": "No file uploaded"}, status_code=400) file_name, file_extension = os.path.splitext(file.filename) - key = 'files/' + str(uuid.uuid4()) + file_extension + key = "files/" + str(uuid.uuid4()) + file_extension # Create an S3 client with Storj configuration s3 = boto3.client( - 's3', + "s3", aws_access_key_id=STORJ_ACCESS_KEY, aws_secret_access_key=STORJ_SECRET_KEY, endpoint_url=STORJ_END_POINT, @@ -45,10 +45,10 @@ async def upload_handler(request): ExtraArgs={"ContentType": file.content_type}, ) - url = 'https://' + CDN_DOMAIN + '/' + key + url = "https://" + CDN_DOMAIN + "/" + key - return JSONResponse({'url': url, 'originalFilename': file.filename}) + return JSONResponse({"url": url, "originalFilename": file.filename}) except (BotoCoreError, ClientError) as e: print(e) - return JSONResponse({'error': 'Failed to upload file'}, status_code=500) + return JSONResponse({"error": "Failed to upload file"}, status_code=500) diff --git a/resolvers/zine/following.py b/resolvers/zine/following.py index 24935d5e..bc92371a 100644 --- a/resolvers/zine/following.py +++ b/resolvers/zine/following.py @@ -1,20 +1,12 @@ -import asyncio - -from graphql.type import GraphQLResolveInfo - from auth.authenticate import login_required from auth.credentials import AuthCredentials -from base.orm import local_session from base.resolvers import mutation -from orm.shout import ShoutReactionsFollower -from orm.topic import TopicFollower # from resolvers.community import community_follow, community_unfollow -from orm.user import AuthorFollower from resolvers.zine.profile import author_follow, author_unfollow from resolvers.zine.reactions import reactions_follow, reactions_unfollow from resolvers.zine.topics import topic_follow, topic_unfollow -from services.following import Following, FollowingManager, FollowingResult +from services.following import FollowingManager, FollowingResult @mutation.field("follow") @@ -25,20 +17,20 @@ async def follow(_, info, what, slug): try: if what == "AUTHOR": if author_follow(auth.user_id, slug): - result = FollowingResult("NEW", 'author', slug) - await FollowingManager.push('author', result) + result = FollowingResult("NEW", "author", slug) + await FollowingManager.push("author", result) elif what == "TOPIC": if topic_follow(auth.user_id, slug): - result = FollowingResult("NEW", 'topic', slug) - await FollowingManager.push('topic', result) + result = FollowingResult("NEW", "topic", slug) + await FollowingManager.push("topic", result) elif what == "COMMUNITY": if False: # TODO: use community_follow(auth.user_id, slug): - result = FollowingResult("NEW", 'community', slug) - await FollowingManager.push('community', result) + result = FollowingResult("NEW", "community", slug) + await FollowingManager.push("community", result) elif what == "REACTIONS": if reactions_follow(auth.user_id, slug): - result = FollowingResult("NEW", 'shout', slug) - await FollowingManager.push('shout', result) + result = FollowingResult("NEW", "shout", slug) + await FollowingManager.push("shout", result) except Exception as e: print(Exception(e)) return {"error": str(e)} @@ -54,20 +46,20 @@ async def unfollow(_, info, what, slug): try: if what == "AUTHOR": if author_unfollow(auth.user_id, slug): - result = FollowingResult("DELETED", 'author', slug) - await FollowingManager.push('author', result) + result = FollowingResult("DELETED", "author", slug) + await FollowingManager.push("author", result) elif what == "TOPIC": if topic_unfollow(auth.user_id, slug): - result = FollowingResult("DELETED", 'topic', slug) - await FollowingManager.push('topic', result) + result = FollowingResult("DELETED", "topic", slug) + await FollowingManager.push("topic", result) elif what == "COMMUNITY": if False: # TODO: use community_unfollow(auth.user_id, slug): - result = FollowingResult("DELETED", 'community', slug) - await FollowingManager.push('community', result) + result = FollowingResult("DELETED", "community", slug) + await FollowingManager.push("community", result) elif what == "REACTIONS": if reactions_unfollow(auth.user_id, slug): - result = FollowingResult("DELETED", 'shout', slug) - await FollowingManager.push('shout', result) + result = FollowingResult("DELETED", "shout", slug) + await FollowingManager.push("shout", result) except Exception as e: return {"error": str(e)} diff --git a/resolvers/zine/load.py b/resolvers/zine/load.py index 06f400fc..90d790ac 100644 --- a/resolvers/zine/load.py +++ b/resolvers/zine/load.py @@ -1,26 +1,24 @@ -from datetime import datetime, timedelta, timezone - -from sqlalchemy.orm import aliased, joinedload -from sqlalchemy.sql.expression import and_, asc, case, desc, func, nulls_last, select, text - from auth.authenticate import login_required from auth.credentials import AuthCredentials -from base.exceptions import ObjectNotExist, OperationNotAllowed +from base.exceptions import ObjectNotExist from base.orm import local_session from base.resolvers import query +from datetime import datetime, timedelta, timezone from orm import TopicFollower from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.user import AuthorFollower +from sqlalchemy.orm import aliased, joinedload +from sqlalchemy.sql.expression import and_, asc, case, desc, func, nulls_last, select def add_stat_columns(q): aliased_reaction = aliased(Reaction) q = q.outerjoin(aliased_reaction).add_columns( - func.sum(aliased_reaction.id).label('reacted_stat'), + func.sum(aliased_reaction.id).label("reacted_stat"), func.sum(case((aliased_reaction.kind == ReactionKind.COMMENT, 1), else_=0)).label( - 'commented_stat' + "commented_stat" ), func.sum( case( @@ -36,13 +34,13 @@ def add_stat_columns(q): (aliased_reaction.kind == ReactionKind.DISLIKE, -1), else_=0, ) - ).label('rating_stat'), + ).label("rating_stat"), func.max( case( (aliased_reaction.kind != ReactionKind.COMMENT, None), else_=aliased_reaction.createdAt, ) - ).label('last_comment'), + ).label("last_comment"), ) return q @@ -60,7 +58,7 @@ def apply_filters(q, filters, user_id=None): if filters.get("layout"): q = q.filter(Shout.layout == filters.get("layout")) - if filters.get('excludeLayout'): + if filters.get("excludeLayout"): q = q.filter(Shout.layout != filters.get("excludeLayout")) if filters.get("author"): q = q.filter(Shout.authors.any(slug=filters.get("author"))) @@ -95,9 +93,13 @@ async def load_shout(_, info, slug=None, shout_id=None): q = q.filter(Shout.deletedAt.is_(None)).group_by(Shout.id) try: - [shout, reacted_stat, commented_stat, rating_stat, last_comment] = session.execute( - q - ).first() + [ + shout, + reacted_stat, + commented_stat, + rating_stat, + last_comment, + ] = session.execute(q).first() shout.stat = { "viewed": shout.views, @@ -154,7 +156,7 @@ async def load_shouts_by(_, info, options): order_by = options.get("order_by", Shout.publishedAt) - query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by) + query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by) offset = options.get("offset", 0) limit = options.get("limit", 10) @@ -164,9 +166,13 @@ async def load_shouts_by(_, info, options): with local_session() as session: shouts_map = {} - for [shout, reacted_stat, commented_stat, rating_stat, last_comment] in session.execute( - q - ).unique(): + for [ + shout, + reacted_stat, + commented_stat, + rating_stat, + last_comment, + ] in session.execute(q).unique(): shouts.append(shout) shout.stat = { "viewed": shout.views, @@ -225,7 +231,11 @@ async def get_my_feed(_, info, options): joinedload(Shout.topics), ) .where( - and_(Shout.publishedAt.is_not(None), Shout.deletedAt.is_(None), Shout.id.in_(subquery)) + and_( + Shout.publishedAt.is_not(None), + Shout.deletedAt.is_(None), + Shout.id.in_(subquery), + ) ) ) @@ -234,7 +244,7 @@ async def get_my_feed(_, info, options): order_by = options.get("order_by", Shout.publishedAt) - query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by) + query_order_by = desc(order_by) if options.get("order_by_desc", True) else asc(order_by) offset = options.get("offset", 0) limit = options.get("limit", 10) @@ -243,9 +253,13 @@ async def get_my_feed(_, info, options): shouts = [] with local_session() as session: shouts_map = {} - for [shout, reacted_stat, commented_stat, rating_stat, last_comment] in session.execute( - q - ).unique(): + for [ + shout, + reacted_stat, + commented_stat, + rating_stat, + last_comment, + ] in session.execute(q).unique(): shouts.append(shout) shout.stat = { "viewed": shout.views, diff --git a/resolvers/zine/profile.py b/resolvers/zine/profile.py index ecdc26c7..7275226d 100644 --- a/resolvers/zine/profile.py +++ b/resolvers/zine/profile.py @@ -1,18 +1,16 @@ -from datetime import datetime, timedelta, timezone -from typing import List - -from sqlalchemy import and_, distinct, func, literal, select -from sqlalchemy.orm import aliased, joinedload - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session from base.resolvers import mutation, query +from datetime import datetime, timedelta, timezone from orm.reaction import Reaction, ReactionKind from orm.shout import ShoutAuthor, ShoutTopic from orm.topic import Topic, TopicFollower from orm.user import AuthorFollower, Role, User, UserRating, UserRole from resolvers.zine.topics import followed_by_user +from sqlalchemy import and_, distinct, func, literal, select +from sqlalchemy.orm import aliased, joinedload +from typing import List def add_author_stat_columns(q): @@ -22,24 +20,24 @@ def add_author_stat_columns(q): # user_rating_aliased = aliased(UserRating) q = q.outerjoin(shout_author_aliased).add_columns( - func.count(distinct(shout_author_aliased.shout)).label('shouts_stat') + func.count(distinct(shout_author_aliased.shout)).label("shouts_stat") ) q = q.outerjoin(author_followers, author_followers.author == User.id).add_columns( - func.count(distinct(author_followers.follower)).label('followers_stat') + func.count(distinct(author_followers.follower)).label("followers_stat") ) q = q.outerjoin(author_following, author_following.follower == User.id).add_columns( - func.count(distinct(author_following.author)).label('followings_stat') + func.count(distinct(author_following.author)).label("followings_stat") ) - q = q.add_columns(literal(0).label('rating_stat')) + q = q.add_columns(literal(0).label("rating_stat")) # FIXME # q = q.outerjoin(user_rating_aliased, user_rating_aliased.user == User.id).add_columns( # # TODO: check # func.sum(user_rating_aliased.value).label('rating_stat') # ) - q = q.add_columns(literal(0).label('commented_stat')) + q = q.add_columns(literal(0).label("commented_stat")) # q = q.outerjoin(Reaction, and_(Reaction.createdBy == User.id, Reaction.body.is_not(None))).add_columns( # func.count(distinct(Reaction.id)).label('commented_stat') # ) @@ -50,7 +48,13 @@ def add_author_stat_columns(q): def add_stat(author, stat_columns): - [shouts_stat, followers_stat, followings_stat, rating_stat, commented_stat] = stat_columns + [ + shouts_stat, + followers_stat, + followings_stat, + rating_stat, + commented_stat, + ] = stat_columns author.stat = { "shouts": shouts_stat, "followers": followers_stat, @@ -227,7 +231,12 @@ async def get_author(_, _info, slug): with local_session() as session: comments_count = ( session.query(Reaction) - .where(and_(Reaction.createdBy == author.id, Reaction.kind == ReactionKind.COMMENT)) + .where( + and_( + Reaction.createdBy == author.id, + Reaction.kind == ReactionKind.COMMENT, + ) + ) .count() ) author.stat["commented"] = comments_count diff --git a/resolvers/zine/reactions.py b/resolvers/zine/reactions.py index 0a37f6c3..680cac52 100644 --- a/resolvers/zine/reactions.py +++ b/resolvers/zine/reactions.py @@ -1,25 +1,23 @@ -from datetime import datetime, timedelta, timezone - -from sqlalchemy import and_, asc, case, desc, func, select, text -from sqlalchemy.orm import aliased - from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.exceptions import OperationNotAllowed from base.orm import local_session from base.resolvers import mutation, query +from datetime import datetime, timedelta, timezone from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutReactionsFollower from orm.user import User from services.notifications.notification_service import notification_service +from sqlalchemy import and_, asc, case, desc, func, select, text +from sqlalchemy.orm import aliased def add_reaction_stat_columns(q): aliased_reaction = aliased(Reaction) q = q.outerjoin(aliased_reaction, Reaction.id == aliased_reaction.replyTo).add_columns( - func.sum(aliased_reaction.id).label('reacted_stat'), - func.sum(case((aliased_reaction.body.is_not(None), 1), else_=0)).label('commented_stat'), + func.sum(aliased_reaction.id).label("reacted_stat"), + func.sum(case((aliased_reaction.body.is_not(None), 1), else_=0)).label("commented_stat"), func.sum( case( (aliased_reaction.kind == ReactionKind.AGREE, 1), @@ -32,7 +30,7 @@ def add_reaction_stat_columns(q): (aliased_reaction.kind == ReactionKind.DISLIKE, -1), else_=0, ) - ).label('rating_stat'), + ).label("rating_stat"), ) return q @@ -91,7 +89,7 @@ def reactions_unfollow(user_id: int, shout_id: int): def is_published_author(session, user_id): - '''checks if user has at least one publication''' + """checks if user has at least one publication""" return ( session.query(Shout) .where(Shout.authors.contains(user_id)) @@ -102,7 +100,7 @@ def is_published_author(session, user_id): def check_to_publish(session, user_id, reaction): - '''set shout to public if publicated approvers amount > 4''' + """set shout to public if publicated approvers amount > 4""" if not reaction.replyTo and reaction.kind in [ ReactionKind.ACCEPT, ReactionKind.LIKE, @@ -126,7 +124,7 @@ def check_to_publish(session, user_id, reaction): def check_to_hide(session, user_id, reaction): - '''hides any shout if 20% of reactions are negative''' + """hides any shout if 20% of reactions are negative""" if not reaction.replyTo and reaction.kind in [ ReactionKind.REJECT, ReactionKind.DISLIKE, @@ -136,7 +134,11 @@ def check_to_hide(session, user_id, reaction): approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all() rejects = 0 for r in approvers_reactions: - if r.kind in [ReactionKind.REJECT, ReactionKind.DISLIKE, ReactionKind.DISPROOF]: + if r.kind in [ + ReactionKind.REJECT, + ReactionKind.DISLIKE, + ReactionKind.DISPROOF, + ]: rejects += 1 if len(approvers_reactions) / rejects < 5: return True @@ -146,14 +148,14 @@ def check_to_hide(session, user_id, reaction): def set_published(session, shout_id): s = session.query(Shout).where(Shout.id == shout_id).first() s.publishedAt = datetime.now(tz=timezone.utc) - s.visibility = text('public') + s.visibility = text("public") session.add(s) session.commit() def set_hidden(session, shout_id): s = session.query(Shout).where(Shout.id == shout_id).first() - s.visibility = text('community') + s.visibility = text("community") session.add(s) session.commit() @@ -162,7 +164,7 @@ def set_hidden(session, shout_id): @login_required async def create_reaction(_, info, reaction): auth: AuthCredentials = info.context["request"].auth - reaction['createdBy'] = auth.user_id + reaction["createdBy"] = auth.user_id rdict = {} with local_session() as session: shout = session.query(Shout).where(Shout.id == reaction["shout"]).one() @@ -230,8 +232,8 @@ async def create_reaction(_, info, reaction): await notification_service.handle_new_reaction(r.id) rdict = r.dict() - rdict['shout'] = shout.dict() - rdict['createdBy'] = author.dict() + rdict["shout"] = shout.dict() + rdict["createdBy"] = author.dict() # self-regulation mechanics if check_to_hide(session, auth.user_id, r): @@ -244,7 +246,7 @@ async def create_reaction(_, info, reaction): except Exception as e: print(f"[resolvers.reactions] error on reactions autofollowing: {e}") - rdict['stat'] = {"commented": 0, "reacted": 0, "rating": 0} + rdict["stat"] = {"commented": 0, "reacted": 0, "rating": 0} return {"reaction": rdict} @@ -274,7 +276,11 @@ async def update_reaction(_, info, id, reaction={}): if reaction.get("range"): r.range = reaction.get("range") session.commit() - r.stat = {"commented": commented_stat, "reacted": reacted_stat, "rating": rating_stat} + r.stat = { + "commented": commented_stat, + "reacted": reacted_stat, + "rating": rating_stat, + } return {"reaction": r} @@ -338,7 +344,7 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0): if by.get("comment"): q = q.filter(func.length(Reaction.body) > 0) - if len(by.get('search', '')) > 2: + if len(by.get("search", "")) > 2: q = q.filter(Reaction.body.ilike(f'%{by["body"]}%')) if by.get("days"): @@ -346,7 +352,7 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0): q = q.filter(Reaction.createdAt > after) order_way = asc if by.get("sort", "").startswith("-") else desc - order_field = by.get("sort", "").replace('-', '') or Reaction.createdAt + order_field = by.get("sort", "").replace("-", "") or Reaction.createdAt q = q.group_by(Reaction.id, User.id, Shout.id).order_by(order_way(order_field)) @@ -357,9 +363,14 @@ async def load_reactions_by(_, _info, by, limit=50, offset=0): reactions = [] with local_session() as session: - for [reaction, user, shout, reacted_stat, commented_stat, rating_stat] in session.execute( - q - ): + for [ + reaction, + user, + shout, + reacted_stat, + commented_stat, + rating_stat, + ] in session.execute(q): reaction.createdBy = user reaction.shout = shout reaction.stat = { diff --git a/resolvers/zine/topics.py b/resolvers/zine/topics.py index 72ecf9ac..f24065cd 100644 --- a/resolvers/zine/topics.py +++ b/resolvers/zine/topics.py @@ -1,12 +1,11 @@ -from sqlalchemy import and_, distinct, func, select -from sqlalchemy.orm import aliased - from auth.authenticate import login_required from base.orm import local_session from base.resolvers import mutation, query from orm import User from orm.shout import ShoutAuthor, ShoutTopic from orm.topic import Topic, TopicFollower +from sqlalchemy import and_, distinct, func, select +from sqlalchemy.orm import aliased def add_topic_stat_columns(q): @@ -15,11 +14,11 @@ def add_topic_stat_columns(q): q = ( q.outerjoin(ShoutTopic, Topic.id == ShoutTopic.topic) - .add_columns(func.count(distinct(ShoutTopic.shout)).label('shouts_stat')) + .add_columns(func.count(distinct(ShoutTopic.shout)).label("shouts_stat")) .outerjoin(aliased_shout_author, ShoutTopic.shout == aliased_shout_author.shout) - .add_columns(func.count(distinct(aliased_shout_author.user)).label('authors_stat')) + .add_columns(func.count(distinct(aliased_shout_author.user)).label("authors_stat")) .outerjoin(aliased_topic_follower) - .add_columns(func.count(distinct(aliased_topic_follower.follower)).label('followers_stat')) + .add_columns(func.count(distinct(aliased_topic_follower.follower)).label("followers_stat")) ) q = q.group_by(Topic.id) @@ -29,7 +28,11 @@ def add_topic_stat_columns(q): def add_stat(topic, stat_columns): [shouts_stat, authors_stat, followers_stat] = stat_columns - topic.stat = {"shouts": shouts_stat, "authors": authors_stat, "followers": followers_stat} + topic.stat = { + "shouts": shouts_stat, + "authors": authors_stat, + "followers": followers_stat, + } return topic diff --git a/server.py b/server.py index 48186da0..a491c30d 100644 --- a/server.py +++ b/server.py @@ -1,45 +1,44 @@ +from settings import DEV_SERVER_PID_FILE_NAME, PORT + import os import sys - import uvicorn -from settings import DEV_SERVER_PID_FILE_NAME, PORT - def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook): print("%s: %s" % (exception_type.__name__, exception)) log_settings = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'default': { - '()': 'uvicorn.logging.DefaultFormatter', - 'fmt': '%(levelprefix)s %(message)s', - 'use_colors': None, + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "%(levelprefix)s %(message)s", + "use_colors": None, }, - 'access': { - '()': 'uvicorn.logging.AccessFormatter', - 'fmt': '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', + "access": { + "()": "uvicorn.logging.AccessFormatter", + "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', }, }, - 'handlers': { - 'default': { - 'formatter': 'default', - 'class': 'logging.StreamHandler', - 'stream': 'ext://sys.stderr', + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", }, - 'access': { - 'formatter': 'access', - 'class': 'logging.StreamHandler', - 'stream': 'ext://sys.stdout', + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", }, }, - 'loggers': { - 'uvicorn': {'handlers': ['default'], 'level': 'INFO'}, - 'uvicorn.error': {'level': 'INFO', 'handlers': ['default'], 'propagate': True}, - 'uvicorn.access': {'handlers': ['access'], 'level': 'INFO', 'propagate': False}, + "loggers": { + "uvicorn": {"handlers": ["default"], "level": "INFO"}, + "uvicorn.error": {"level": "INFO", "handlers": ["default"], "propagate": True}, + "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, }, } @@ -48,7 +47,8 @@ local_headers = [ ("Access-Control-Allow-Origin", "https://localhost:3000"), ( "Access-Control-Allow-Headers", - "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization", + "DNT,User-Agent,X-Requested-With,If-Modified-Since," + + "Cache-Control,Content-Type,Range,Authorization", ), ("Access-Control-Expose-Headers", "Content-Length,Content-Range"), ("Access-Control-Allow-Credentials", "true"), @@ -92,4 +92,10 @@ if __name__ == "__main__": json_tables() else: sys.excepthook = exception_handler - uvicorn.run("main:app", host="0.0.0.0", port=PORT, proxy_headers=True, server_header=True) + uvicorn.run( + "main:app", + host="0.0.0.0", + port=PORT, + proxy_headers=True, + server_header=True, + ) diff --git a/services/following.py b/services/following.py index 8261d696..a2be6af4 100644 --- a/services/following.py +++ b/services/following.py @@ -18,7 +18,7 @@ class Following: class FollowingManager: lock = asyncio.Lock() - data = {'author': [], 'topic': [], 'shout': [], 'chat': []} + data = {"author": [], "topic": [], "shout": [], "chat": []} @staticmethod async def register(kind, uid): @@ -34,13 +34,13 @@ class FollowingManager: async def push(kind, payload): try: async with FollowingManager.lock: - if kind == 'chat': - for chat in FollowingManager['chat']: + if kind == "chat": + for chat in FollowingManager["chat"]: if payload.message["chatId"] == chat.uid: chat.queue.put_nowait(payload) else: for entity in FollowingManager[kind]: - if payload.shout['createdBy'] == entity.uid: + if payload.shout["createdBy"] == entity.uid: entity.queue.put_nowait(payload) except Exception as e: print(Exception(e)) diff --git a/services/main.py b/services/main.py index 98fddcc1..6397a5e5 100644 --- a/services/main.py +++ b/services/main.py @@ -5,9 +5,9 @@ from services.stat.viewed import ViewedStorage async def storages_init(): with local_session() as session: - print('[main] initialize SearchService') + print("[main] initialize SearchService") await SearchService.init(session) - print('[main] SearchService initialized') - print('[main] initialize storages') + print("[main] SearchService initialized") + print("[main] initialize storages") await ViewedStorage.init() - print('[main] storages initialized') + print("[main] storages initialized") diff --git a/services/notifications/notification_service.py b/services/notifications/notification_service.py index 8467e836..ade98763 100644 --- a/services/notifications/notification_service.py +++ b/services/notifications/notification_service.py @@ -1,14 +1,13 @@ -import asyncio -import json -from datetime import datetime, timezone - -from sqlalchemy import and_ - from base.orm import local_session +from datetime import datetime, timezone from orm import Notification, Reaction, Shout, User from orm.notification import NotificationType from orm.reaction import ReactionKind from services.notifications.sse import connection_manager +from sqlalchemy import and_ + +import asyncio +import json def shout_to_shout_data(shout): @@ -16,13 +15,18 @@ def shout_to_shout_data(shout): def user_to_user_data(user): - return {"id": user.id, "name": user.name, "slug": user.slug, "userpic": user.userpic} + return { + "id": user.id, + "name": user.name, + "slug": user.slug, + "userpic": user.userpic, + } def update_prev_notification(notification, user, reaction): notification_data = json.loads(notification.data) - notification_data["users"] = [u for u in notification_data["users"] if u['id'] != user.id] + notification_data["users"] = [u for u in notification_data["users"] if u["id"] != user.id] notification_data["users"].append(user_to_user_data(user)) if notification_data["reactionIds"] is None: @@ -61,7 +65,7 @@ class NewReactionNotificator: Notification.type == NotificationType.NEW_REPLY, Notification.shout == shout.id, Notification.reaction == parent_reaction.id, - Notification.seen == False, + Notification.seen == False, # noqa: E712 ) ) .first() @@ -103,7 +107,7 @@ class NewReactionNotificator: Notification.user == shout.createdBy, Notification.type == NotificationType.NEW_COMMENT, Notification.shout == shout.id, - Notification.seen == False, + Notification.seen == False, # noqa: E712 ) ) .first() @@ -154,7 +158,7 @@ class NotificationService: try: await notificator.run() except Exception as e: - print(f'[NotificationService.worker] error: {str(e)}') + print(f"[NotificationService.worker] error: {str(e)}") notification_service = NotificationService() diff --git a/services/notifications/sse.py b/services/notifications/sse.py index 55cae575..23352344 100644 --- a/services/notifications/sse.py +++ b/services/notifications/sse.py @@ -1,9 +1,9 @@ -import asyncio -import json - from sse_starlette.sse import EventSourceResponse from starlette.requests import Request +import asyncio +import json + class ConnectionManager: def __init__(self): diff --git a/services/search.py b/services/search.py index d1748cdd..ffcd32b5 100644 --- a/services/search.py +++ b/services/search.py @@ -1,10 +1,10 @@ -import asyncio -import json - from base.redis import redis from orm.shout import Shout from resolvers.zine.load import load_shouts_by +import asyncio +import json + class SearchService: lock = asyncio.Lock() @@ -13,7 +13,7 @@ class SearchService: @staticmethod async def init(session): async with SearchService.lock: - print('[search.service] did nothing') + print("[search.service] did nothing") SearchService.cache = {} @staticmethod @@ -21,7 +21,12 @@ class SearchService: cached = await redis.execute("GET", text) if not cached: async with SearchService.lock: - options = {"title": text, "body": text, "limit": limit, "offset": offset} + options = { + "title": text, + "body": text, + "limit": limit, + "offset": offset, + } payload = await load_shouts_by(None, None, options) await redis.execute("SET", text, json.dumps(payload)) return payload diff --git a/services/stat/viewed.py b/services/stat/viewed.py index c9f9a6db..ce5070b2 100644 --- a/services/stat/viewed.py +++ b/services/stat/viewed.py @@ -1,16 +1,14 @@ -import asyncio -import time +from base.orm import local_session from datetime import datetime, timedelta, timezone +from gql import Client, gql +from gql.transport.aiohttp import AIOHTTPTransport +from orm import Topic +from orm.shout import Shout, ShoutTopic from os import environ, path from ssl import create_default_context -from gql import Client, gql -from gql.transport.aiohttp import AIOHTTPTransport -from sqlalchemy import func - -from base.orm import local_session -from orm import Topic, User -from orm.shout import Shout, ShoutTopic +import asyncio +import time load_facts = gql( """ @@ -46,7 +44,7 @@ query getDomains { } """ ) -schema_str = open(path.dirname(__file__) + '/ackee.graphql').read() +schema_str = open(path.dirname(__file__) + "/ackee.graphql").read() token = environ.get("ACKEE_TOKEN", "") @@ -54,7 +52,9 @@ def create_client(headers=None, schema=None): return Client( schema=schema, transport=AIOHTTPTransport( - url="https://ackee.discours.io/api", ssl=create_default_context(), headers=headers + url="https://ackee.discours.io/api", + ssl=create_default_context(), + headers=headers, ), ) @@ -98,7 +98,7 @@ class ViewedStorage: try: for page in self.pages: p = page["value"].split("?")[0] - slug = p.split('discours.io/')[-1] + slug = p.split("discours.io/")[-1] shouts[slug] = page["count"] for slug in shouts.keys(): await ViewedStorage.increment(slug, shouts[slug]) @@ -162,14 +162,14 @@ class ViewedStorage: self.by_topics[topic.slug][shout_slug] = self.by_shouts[shout_slug] @staticmethod - async def increment(shout_slug, amount=1, viewer='ackee'): + async def increment(shout_slug, amount=1, viewer="ackee"): """the only way to change views counter""" self = ViewedStorage async with self.lock: # TODO optimize, currenty we execute 1 DB transaction per shout with local_session() as session: shout = session.query(Shout).where(Shout.slug == shout_slug).one() - if viewer == 'old-discours': + if viewer == "old-discours": # this is needed for old db migration if shout.viewsOld == amount: print(f"viewsOld amount: {amount}") diff --git a/services/zine/gittask.py b/services/zine/gittask.py index 31e55025..6c6ce440 100644 --- a/services/zine/gittask.py +++ b/services/zine/gittask.py @@ -1,8 +1,8 @@ +from pathlib import Path +from settings import SHOUTS_REPO + import asyncio import subprocess -from pathlib import Path - -from settings import SHOUTS_REPO class GitTask: diff --git a/settings.py b/settings.py index bd096081..f3da9952 100644 --- a/settings.py +++ b/settings.py @@ -31,4 +31,4 @@ SENTRY_DSN = environ.get("SENTRY_DSN") SESSION_SECRET_KEY = environ.get("SESSION_SECRET_KEY") or "!secret" # for local development -DEV_SERVER_PID_FILE_NAME = 'dev-server.pid' +DEV_SERVER_PID_FILE_NAME = "dev-server.pid" diff --git a/setup.cfg b/setup.cfg old mode 100755 new mode 100644 index 588918a1..e3db2ef9 --- a/setup.cfg +++ b/setup.cfg @@ -9,15 +9,16 @@ force_alphabetical_sort = false [tool:brunette] # https://github.com/odwyersoftware/brunette -line-length = 120 +line-length = 100 single-quotes = false [flake8] # https://github.com/PyCQA/flake8 exclude = .git,__pycache__,.mypy_cache,.vercel -max-line-length = 120 -max-complexity = 15 +max-line-length = 100 +max-complexity = 10 select = B,C,E,F,W,T4,B9 +# FIXME # E203: Whitespace before ':' # E266: Too many leading '#' for block comment # E501: Line too long (82 > 79 characters) diff --git a/setup.cfg.bak b/setup.cfg.bak new file mode 100644 index 00000000..588918a1 --- /dev/null +++ b/setup.cfg.bak @@ -0,0 +1,39 @@ +[isort] +# https://github.com/PyCQA/isort +line_length = 120 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +force_alphabetical_sort = false + +[tool:brunette] +# https://github.com/odwyersoftware/brunette +line-length = 120 +single-quotes = false + +[flake8] +# https://github.com/PyCQA/flake8 +exclude = .git,__pycache__,.mypy_cache,.vercel +max-line-length = 120 +max-complexity = 15 +select = B,C,E,F,W,T4,B9 +# E203: Whitespace before ':' +# E266: Too many leading '#' for block comment +# E501: Line too long (82 > 79 characters) +# E722: Do not use bare except, specify exception instead +# W503: Line break occurred before a binary operator +# F403: 'from module import *' used; unable to detect undefined names +# C901: Function is too complex +ignore = E203,E266,E501,E722,W503,F403,C901 + +[mypy] +# https://github.com/python/mypy +ignore_missing_imports = true +warn_return_any = false +warn_unused_configs = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +[mypy-api.*] +ignore_errors = true diff --git a/validations/auth.py b/validations/auth.py index 73b83079..59c49bd4 100644 --- a/validations/auth.py +++ b/validations/auth.py @@ -1,6 +1,5 @@ -from typing import Optional, Text - from pydantic import BaseModel +from typing import Optional, Text class AuthInput(BaseModel): diff --git a/validations/inbox.py b/validations/inbox.py index 58645dd9..d864ed67 100644 --- a/validations/inbox.py +++ b/validations/inbox.py @@ -1,6 +1,5 @@ -from typing import List, Optional, Text - from pydantic import BaseModel +from typing import List, Optional, Text class Message(BaseModel):