diff --git a/src/snek/app.py b/src/snek/app.py index 78c0e2b..ab19f42 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -1,9 +1,10 @@ import pathlib +from types import SimpleNamespace from aiohttp import web from app.app import Application as BaseApplication + from snek.docs.app import Application as DocsApplication -from app.cache import time_cache_async from snek.mapper import get_mappers from snek.service import get_services from snek.system import http @@ -17,7 +18,6 @@ from snek.view.login_form import LoginFormView from snek.view.register import RegisterView from snek.view.register_form import RegisterFormView from snek.view.web import WebView -from types import SimpleNamespace class Application(BaseApplication): @@ -51,7 +51,7 @@ class Application(BaseApplication): self.router.add_view("/about.md", AboutMDView) self.router.add_view("/docs.html", DocsHTMLView) self.router.add_view("/docs.md", DocsMDView) - + self.router.add_view("/web.html", WebView) self.router.add_view("/login.html", LoginView) self.router.add_view("/login.json", LoginFormView) @@ -60,7 +60,10 @@ class Application(BaseApplication): self.router.add_get("/http-get", self.handle_http_get) self.router.add_get("/http-photo", self.handle_http_photo) - self.add_subapp("/docs", DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs"))) + self.add_subapp( + "/docs", + DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs")), + ) async def handle_test(self, request): @@ -79,9 +82,8 @@ class Application(BaseApplication): return web.Response( body=path.read_bytes(), headers={"Content-Type": "image/png"} ) - - - #@time_cache_async(60) + + # @time_cache_async(60) async def render_template(self, template, request, context=None): return await super().render_template(template, request, context) diff --git a/src/snek/form/login.py b/src/snek/form/login.py index 0d97d41..3d6d9a7 100644 --- a/src/snek/form/login.py +++ b/src/snek/form/login.py @@ -1,24 +1,27 @@ -from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement +from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement + class LoginForm(Form): title = HTMLElement(tag="h1", text="Login") username = FormInputElement( - name="username", + name="username", required=True, min_length=2, max_length=20, regex=r"^[a-zA-Z0-9_]+$", place_holder="Username", - type="text" + type="text", + ) + password = FormInputElement( + name="password", + required=True, + regex=r"^[a-zA-Z0-9_.+-]{6,}", + type="password", + place_holder="Password", ) - password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password") action = FormButtonElement( - name="action", - value="submit", - text="Login", - type="button" + name="action", value="submit", text="Login", type="button" ) - diff --git a/src/snek/form/register.py b/src/snek/form/register.py index 4252bf1..1384b8f 100644 --- a/src/snek/form/register.py +++ b/src/snek/form/register.py @@ -1,4 +1,5 @@ -from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement +from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement + class UsernameField(FormInputElement): @@ -9,32 +10,35 @@ class UsernameField(FormInputElement): result.append("Username is not available.") return result + class RegisterForm(Form): title = HTMLElement(tag="h1", text="Register") username = UsernameField( - name="username", + name="username", required=True, min_length=2, max_length=20, regex=r"^[a-zA-Z0-9_]+$", place_holder="Username", - type="text" + type="text", ) email = FormInputElement( name="email", required=False, regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", place_holder="Email address", - type="email" + type="email", + ) + password = FormInputElement( + name="password", + required=True, + regex=r"^[a-zA-Z0-9_.+-]{6,}", + type="password", + place_holder="Password", ) - password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password") action = FormButtonElement( - name="action", - value="submit", - text="Register", - type="button" + name="action", value="submit", text="Register", type="button" ) - diff --git a/src/snek/mapper/__init__.py b/src/snek/mapper/__init__.py index dc9e047..2b9b79f 100644 --- a/src/snek/mapper/__init__.py +++ b/src/snek/mapper/__init__.py @@ -1,12 +1,12 @@ -import functools +import functools + from snek.mapper.user import UserMapper -@functools.cache -def get_mappers(app=None): - return dict( - user=UserMapper(app=app) - ) +@functools.cache +def get_mappers(app=None): + return {"user": UserMapper(app=app)} + def get_mapper(name, app=None): - return get_mappers(app=app)[name] \ No newline at end of file + return get_mappers(app=app)[name] diff --git a/src/snek/mapper/user.py b/src/snek/mapper/user.py index 642028c..c388abc 100644 --- a/src/snek/mapper/user.py +++ b/src/snek/mapper/user.py @@ -1,6 +1,7 @@ -from snek.system.mapper import BaseMapper from snek.model.user import UserModel +from snek.system.mapper import BaseMapper + class UserMapper(BaseMapper): table_name = "user" - model_class = UserModel \ No newline at end of file + model_class = UserModel diff --git a/src/snek/model/__init__.py b/src/snek/model/__init__.py index 52af21a..081ae15 100644 --- a/src/snek/model/__init__.py +++ b/src/snek/model/__init__.py @@ -1,12 +1,12 @@ -from snek.model.user import UserModel -import functools +import functools + +from snek.model.user import UserModel + @functools.cache def get_models(): - return dict( - user=UserModel + return {"user": UserModel} - ) def get_model(name): return get_models()[name] diff --git a/src/snek/model/user.py b/src/snek/model/user.py index 254b6c9..adb236b 100644 --- a/src/snek/model/user.py +++ b/src/snek/model/user.py @@ -1,9 +1,10 @@ -from snek.system.model import BaseModel,ModelField +from snek.system.model import BaseModel, ModelField + class UserModel(BaseModel): - + username = ModelField( - name="username", + name="username", required=True, min_length=2, max_length=20, @@ -12,8 +13,6 @@ class UserModel(BaseModel): email = ModelField( name="email", required=False, - regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" + regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", ) - password = ModelField(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}") - - + password = ModelField(name="password", required=True, regex=r"^[a-zA-Z0-9_.+-]{6,}") diff --git a/src/snek/service/__init__.py b/src/snek/service/__init__.py index 4038f70..60fec76 100644 --- a/src/snek/service/__init__.py +++ b/src/snek/service/__init__.py @@ -1,12 +1,13 @@ -from snek.service.user import UserService -import functools +import functools + +from snek.service.user import UserService + @functools.cache def get_services(app): - return dict( - user = UserService(app=app) + return {"user": UserService(app=app)} + - ) def get_service(name, app=None): - return get_services(app=app)[name] \ No newline at end of file + return get_services(app=app)[name] diff --git a/src/snek/service/user.py b/src/snek/service/user.py index a2b0cb1..5124640 100644 --- a/src/snek/service/user.py +++ b/src/snek/service/user.py @@ -1,5 +1,6 @@ -from snek.system.service import BaseService -from snek.system import security +from snek.system import security +from snek.system.service import BaseService + class UserService(BaseService): mapper_name = "user" @@ -12,6 +13,5 @@ class UserService(BaseService): model.username = username model.password = await security.hash(password) if await self.save(model): - return model + return model raise Exception(f"Failed to create user: {model.errors}.") - \ No newline at end of file diff --git a/src/snek/system/cache.py b/src/snek/system/cache.py index 2992803..5e275d9 100644 --- a/src/snek/system/cache.py +++ b/src/snek/system/cache.py @@ -1,8 +1,8 @@ - -import functools +import functools cache = functools.cache + def async_cache(func): cache = {} @@ -14,4 +14,4 @@ def async_cache(func): cache[args] = result return result - return wrapper \ No newline at end of file + return wrapper diff --git a/src/snek/system/form.py b/src/snek/system/form.py index 82091b6..f4cf2d3 100644 --- a/src/snek/system/form.py +++ b/src/snek/system/form.py @@ -5,17 +5,17 @@ # This code uses the `snek.system.model` library for managing model fields. # MIT License -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,8 +26,19 @@ from snek.system import model + class HTMLElement(model.ModelField): - def __init__(self, id=None, tag="div", name=None, html=None, class_name=None, text=None, *args, **kwargs): + def __init__( + self, + id=None, + tag="div", + name=None, + html=None, + class_name=None, + text=None, + *args, + **kwargs, + ): self.tag = tag self.text = text self.id = id @@ -37,16 +48,18 @@ class HTMLElement(model.ModelField): async def to_json(self): result = await super().to_json() - result['text'] = self.text - result['id'] = self.id - result['html'] = self.html - result['class_name'] = self.class_name - result['tag'] = self.tag + result["text"] = self.text + result["id"] = self.id + result["html"] = self.html + result["class_name"] = self.class_name + result["tag"] = self.tag return result + class FormElement(HTMLElement): pass + class FormInputElement(FormElement): def __init__(self, type="text", place_holder=None, *args, **kwargs): super().__init__(tag="input", *args, **kwargs) @@ -59,25 +72,27 @@ class FormInputElement(FormElement): data["type"] = self.type return data + class FormButtonElement(FormElement): def __init__(self, tag="button", *args, **kwargs): super().__init__(tag=tag, *args, **kwargs) + class Form(model.BaseModel): @property def html_elements(self): return [element for element in self.fields if isinstance(element, HTMLElement)] def set_user_data(self, data): - return super().set_user_data(data.get('fields')) + return super().set_user_data(data.get("fields")) async def to_json(self, encode=False): elements = await super().to_json() html_elements = {} for element in elements.keys(): - if element == 'is_valid': + if element == "is_valid": # is_valid is async get property so we can't do getattr on it - continue + continue field = getattr(self, element) if isinstance(field, HTMLElement): try: @@ -85,8 +100,12 @@ class Form(model.BaseModel): except KeyError: pass - is_valid = all(field['is_valid'] for field in html_elements.values()) - return dict(fields=html_elements, is_valid=is_valid, errors=await self.errors) + is_valid = all(field["is_valid"] for field in html_elements.values()) + return { + "fields": html_elements, + "is_valid": is_valid, + "errors": await self.errors, + } @property async def errors(self): @@ -98,4 +117,4 @@ class Form(model.BaseModel): @property async def is_valid(self): # This is not good, but timebox to resolve issue exceeded. - return False \ No newline at end of file + return False diff --git a/src/snek/system/http.py b/src/snek/system/http.py index b5e8b4f..cd8a9b1 100644 --- a/src/snek/system/http.py +++ b/src/snek/system/http.py @@ -11,10 +11,10 @@ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,17 +24,17 @@ # SOFTWARE. -from aiohttp import web -import aiohttp -from app.cache import time_cache_async -from bs4 import BeautifulSoup -from urllib.parse import urljoin +import asyncio import pathlib import uuid -import imgkit -import asyncio import zlib -import io +from urllib.parse import urljoin + +import aiohttp +import imgkit +from app.cache import time_cache_async +from bs4 import BeautifulSoup + async def crc32(data): try: @@ -43,6 +43,7 @@ async def crc32(data): pass return "crc32" + str(zlib.crc32(data)) + async def get_file(name, suffix=".cache"): name = await crc32(name) path = pathlib.Path(".").joinpath("cache") @@ -50,17 +51,19 @@ async def get_file(name, suffix=".cache"): path.mkdir(parents=True, exist_ok=True) return path.joinpath(name + suffix) + async def public_touch(name=None): path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name) path.open("wb").close() return path + async def create_site_photo(url): loop = asyncio.get_event_loop() if not url.startswith("https"): url = "https://" + url output_path = await get_file("site-screenshot-" + url, ".png") - + if output_path.exists(): return output_path output_path.touch() @@ -71,21 +74,23 @@ async def create_site_photo(url): return await loop.run_in_executor(None, make_photo) + async def repair_links(base_url, html_content): soup = BeautifulSoup(html_content, "html.parser") - for tag in soup.find_all(['a', 'img', 'link']): - if tag.has_attr('href') and not tag['href'].startswith("http"): - tag['href'] = urljoin(base_url, tag['href']) - if tag.has_attr('src') and not tag['src'].startswith("http"): - tag['src'] = urljoin(base_url, tag['src']) + for tag in soup.find_all(["a", "img", "link"]): + if tag.has_attr("href") and not tag["href"].startswith("http"): + tag["href"] = urljoin(base_url, tag["href"]) + if tag.has_attr("src") and not tag["src"].startswith("http"): + tag["src"] = urljoin(base_url, tag["src"]) return soup.prettify() + async def is_html_content(content: bytes): try: - content = content.decode(errors='ignore') + content = content.decode(errors="ignore") except: pass - marks = [' BaseModel: + async def get(self, uid: str = None, **kwargs) -> BaseModel: if uid: - kwargs['uid'] = uid - model = self.new() + kwargs["uid"] = uid + self.new() record = self.table.find_one(**kwargs) - return await self.model_class.from_record(mapper=self,record=record) + return await self.model_class.from_record(mapper=self, record=record) async def exists(self, **kwargs): return self.table.exists(**kwargs) @@ -40,19 +39,19 @@ class BaseMapper: async def count(self, **kwargs) -> int: return self.table.count(**kwargs) - async def save(self, model:BaseModel) -> bool: + async def save(self, model: BaseModel) -> bool: record = await model.record - if not record.get('uid'): + if not record.get("uid"): raise Exception(f"Attempt to save without uid: {record}.") - return self.table.upsert(record,['uid']) + return self.table.upsert(record, ["uid"]) async def find(self, **kwargs) -> typing.AsyncGenerator: if not kwargs.get("_limit"): kwargs["_limit"] = self.default_limit for record in self.table.find(**kwargs): - yield await self.model_class.from_record(mapper=self,record=record) - - async def delete(self, kwargs=None)-> int: + yield await self.model_class.from_record(mapper=self, record=record) + + async def delete(self, kwargs=None) -> int: if not kwargs or not isinstance(kwargs, dict): raise Exception("Can't execute delete with no filter.") return self.table.delete(**kwargs) diff --git a/src/snek/system/markdown.py b/src/snek/system/markdown.py index fcffe40..23d0656 100644 --- a/src/snek/system/markdown.py +++ b/src/snek/system/markdown.py @@ -1,62 +1,65 @@ - # Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2 from types import SimpleNamespace -from mistune import escape -from mistune import Markdown -from mistune import HTMLRenderer -from pygments import highlight -from pygments.lexers import get_lexer_by_name -from pygments.formatters import html -from pygments.styles import get_style_by_name -import functools + from app.cache import time_cache_async +from mistune import HTMLRenderer, Markdown +from pygments import highlight +from pygments.formatters import html +from pygments.lexers import get_lexer_by_name + class MarkdownRenderer(HTMLRenderer): _allow_harmful_protocols = True + def __init__(self, app, template): - self.template = template - - self.app = app - self.env = self.app.jinja2_env - formatter = html.HtmlFormatter() - self.env.globals['highlight_styles'] = formatter.get_style_defs() - def _escape(self,str): - return str ##escape(str) - def block_code(self, code, lang=None,info=None): + self.template = template + + self.app = app + self.env = self.app.jinja2_env + formatter = html.HtmlFormatter() + self.env.globals["highlight_styles"] = formatter.get_style_defs() + + def _escape(self, str): + return str ##escape(str) + + def block_code(self, code, lang=None, info=None): if not lang: lang = info if not lang: return f"
{code}
" - #return '\n
%s
\n' % escape(code) + # return '\n
%s
\n' % escape(code) lexer = get_lexer_by_name(lang, stripall=True) formatter = html.HtmlFormatter(lineseparator="
") return highlight(code, lexer, formatter) + def render(self): markdown_string = self.app.template_path.joinpath(self.template).read_text() - renderer = MarkdownRenderer(self.app,self.template) + renderer = MarkdownRenderer(self.app, self.template) markdown = Markdown(renderer=renderer) return markdown(markdown_string) - def render_markdown_sync(app, markdown_string): - renderer = MarkdownRenderer(app,None) + renderer = MarkdownRenderer(app, None) markdown = Markdown(renderer=renderer) return markdown(markdown_string) + @time_cache_async(120) async def render_markdown(app, markdown_string): - return render_markdown_sync(app,markdown_string) + return render_markdown_sync(app, markdown_string) -from jinja2 import nodes, TemplateSyntaxError + +from jinja2 import TemplateSyntaxError, nodes from jinja2.ext import Extension from jinja2.nodes import Const + # Source: https://ron.sh/how-to-write-a-jinja2-extension/ class MarkdownExtension(Extension): - tags = {'markdown'} + tags = {"markdown"} def __init__(self, environment): self.app = SimpleNamespace(jinja2_env=environment) @@ -64,13 +67,15 @@ class MarkdownExtension(Extension): def parse(self, parser): line_number = next(parser.stream).lineno - md_file = [Const('')] - body = '' + md_file = [Const("")] + body = "" try: md_file = [parser.parse_expression()] except TemplateSyntaxError: - body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) - return nodes.CallBlock(self.call_method('_to_html', md_file), [], [], body).set_lineno(line_number) + body = parser.parse_statements(["name:endmarkdown"], drop_needle=True) + return nodes.CallBlock( + self.call_method("_to_html", md_file), [], [], body + ).set_lineno(line_number) def _to_html(self, md_file, caller): - return render_markdown_sync(self.app,caller()) \ No newline at end of file + return render_markdown_sync(self.app, caller()) diff --git a/src/snek/system/middleware.py b/src/snek/system/middleware.py index 7fe457f..69fe378 100644 --- a/src/snek/system/middleware.py +++ b/src/snek/system/middleware.py @@ -8,12 +8,14 @@ from aiohttp import web + @web.middleware async def no_cors_middleware(request, handler): response = await handler(request) response.headers.pop("Access-Control-Allow-Origin", None) return response + @web.middleware async def cors_allow_middleware(request, handler): response = await handler(request) @@ -22,12 +24,15 @@ async def cors_allow_middleware(request, handler): response.headers["Access-Control-Allow-Headers"] = "*" return response + @web.middleware async def cors_middleware(request, handler): if request.method == "OPTIONS": response = web.Response() response.headers["Access-Control-Allow-Origin"] = "*" - response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS" + response.headers["Access-Control-Allow-Methods"] = ( + "GET, POST, PUT, DELETE, OPTIONS" + ) response.headers["Access-Control-Allow-Headers"] = "*" return response @@ -35,4 +40,4 @@ async def cors_middleware(request, handler): response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS" response.headers["Access-Control-Allow-Headers"] = "*" - return response \ No newline at end of file + return response diff --git a/src/snek/system/model.py b/src/snek/system/model.py index 0d700ff..b41f4ba 100644 --- a/src/snek/system/model.py +++ b/src/snek/system/model.py @@ -25,12 +25,12 @@ # SOFTWARE. +import copy +import json import re import uuid -import json -from datetime import datetime, timezone from collections import OrderedDict -import copy +from datetime import datetime, timezone TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$" @@ -44,12 +44,21 @@ def add_attrs(**kwargs): for key, value in kwargs.items(): setattr(func, key, value) return func + return decorator -def validate_attrs(required=False, min_length=None, max_length=None, regex=None, **kwargs): +def validate_attrs( + required=False, min_length=None, max_length=None, regex=None, **kwargs +): def decorator(func): - return add_attrs(required=required, min_length=min_length, max_length=max_length, regex=regex, **kwargs)(func) + return add_attrs( + required=required, + min_length=min_length, + max_length=max_length, + regex=regex, + **kwargs, + )(func) class Validator: @@ -70,7 +79,21 @@ class Validator: def custom_validation(self): return True - def __init__(self, required=False, min_num=None, max_num=None, min_length=None, max_length=None, regex=None, value=None, kind=None, help_text=None, app=None, model=None, **kwargs): + def __init__( + self, + required=False, + min_num=None, + max_num=None, + min_length=None, + max_length=None, + regex=None, + value=None, + kind=None, + help_text=None, + app=None, + model=None, + **kwargs, + ): self.index = Validator._index Validator._index += 1 self.app = app @@ -103,9 +126,13 @@ class Validator: if self.max_num is not None and self.value > self.max_num: error_list.append(f"Field should be maximal {self.max_num}.") if self.min_length is not None and len(self.value) < self.min_length: - error_list.append(f"Field should be minimal {self.min_length} characters long.") + error_list.append( + f"Field should be minimal {self.min_length} characters long." + ) if self.max_length is not None and len(self.value) > self.max_length: - error_list.append(f"Field should be maximal {self.max_length} characters long.") + error_list.append( + f"Field should be maximal {self.max_length} characters long." + ) if self.regex and self.value and not re.match(self.regex, self.value): error_list.append("Invalid value.") if self.kind and not isinstance(self.value, self.kind): @@ -141,7 +168,7 @@ class Validator: "help_text": self.help_text, "errors": errors, "is_valid": is_valid, - "index": self.index + "index": self.index, } @@ -156,7 +183,7 @@ class ModelField(Validator): async def to_json(self): result = await super().to_json() - result['name'] = self.name + result["name"] = self.name return result @@ -193,30 +220,39 @@ class UUIDField(ModelField): class BaseModel: uid = UUIDField(name="uid", required=True) - created_at = CreatedField(name="created_at", required=True, regex=TIMESTAMP_REGEX, place_holder="Created at") - updated_at = UpdatedField(name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at") - deleted_at = DeletedField(name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at") + created_at = CreatedField( + name="created_at", + required=True, + regex=TIMESTAMP_REGEX, + place_holder="Created at", + ) + updated_at = UpdatedField( + name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at" + ) + deleted_at = DeletedField( + name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at" + ) - @classmethod + @classmethod async def from_record(cls, record, mapper): model = cls.__new__() - model.mapper = mapper + model.mapper = mapper model.record = record return model - @property + @property def mapper(self): - return self._mapper + return self._mapper - @mapper.setter + @mapper.setter def mapper(self, value): - self._mapper = value + self._mapper = value - @property + @property def record(self): return {field.name: field.value for field in self.fields} - - @record.setter + + @record.setter def record(self, value): for key, value in self._record.items(): field = self.fields.get(key) @@ -233,10 +269,12 @@ class BaseModel: if isinstance(obj, Validator): self.__dict__[key] = copy.deepcopy(obj) - self.__dict__[key].value = kwargs.pop(key, self.__dict__[key].initial_value) + self.__dict__[key].value = kwargs.pop( + key, self.__dict__[key].initial_value + ) self.fields[key] = self.__dict__[key] self.fields[key].model = self - self.fields[key].app = kwargs.get('app') + self.fields[key].app = kwargs.get("app") def __setitem__(self, key, value): obj = self.__dict__.get(key) @@ -254,16 +292,13 @@ class BaseModel: field = self.fields.get(key) if not field: continue - if value.get('name'): - value = value.get('value') + if value.get("name"): + value = value.get("value") field.value = value - - @property async def is_valid(self): return all([await field.is_valid for field in self.fields.values()]) - def __getitem__(self, key): obj = self.__dict__.get(key) @@ -282,20 +317,22 @@ class BaseModel: obj = await self.to_json() record = {} for key, value in obj.items(): - if not isinstance(value, dict) or not 'value' in value: + if not isinstance(value, dict) or "value" not in value: continue if getattr(self, key).save: - record[key] = value.get('value') + record[key] = value.get("value") return record async def to_json(self, encode=False): - model_data = OrderedDict({ - "uid": self.uid.value, - "created_at": self.created_at.value, - "updated_at": self.updated_at.value, - "deleted_at": self.deleted_at.value, - "is_valid": await self.is_valid - }) + model_data = OrderedDict( + { + "uid": self.uid.value, + "created_at": self.created_at.value, + "updated_at": self.updated_at.value, + "deleted_at": self.deleted_at.value, + "is_valid": await self.is_valid, + } + ) for key, value in self.fields.items(): if key == "record": @@ -325,4 +362,4 @@ class FormElement(ModelField): data = await super().to_json() data["name"] = self.name data["place_holder"] = self.place_holder - return data \ No newline at end of file + return data diff --git a/src/snek/system/security.py b/src/snek/system/security.py index b319f54..5449c50 100644 --- a/src/snek/system/security.py +++ b/src/snek/system/security.py @@ -1,12 +1,13 @@ -import hashlib +import hashlib DEFAULT_SALT = b"snekker-de-snek-" -async def hash(data,salt=DEFAULT_SALT): + +async def hash(data, salt=DEFAULT_SALT): try: data = data.encode(errors="ignore") except AttributeError: - pass + pass try: salt = salt.encode(errors="ignore") except AttributeError: @@ -16,5 +17,6 @@ async def hash(data,salt=DEFAULT_SALT): obj = hashlib.sha256(salted) return obj.hexdigest() -async def verify(string:str, hashed:str): - return await hash(string) == hashed + +async def verify(string: str, hashed: str): + return await hash(string) == hashed diff --git a/src/snek/system/service.py b/src/snek/system/service.py index d970b5f..1f9d601 100644 --- a/src/snek/system/service.py +++ b/src/snek/system/service.py @@ -1,24 +1,22 @@ - - - from snek.mapper import get_mapper -from snek.system.mapper import BaseMapper from snek.model.user import UserModel +from snek.system.mapper import BaseMapper + class BaseService: - mapper_name:BaseMapper = None + mapper_name: BaseMapper = None def __init__(self, app): - self.app = app + self.app = app if self.mapper_name: self.mapper = get_mapper(self.mapper_name, app=self.app) else: - self.mapper = None + self.mapper = None async def exists(self, **kwargs): return await self.count(**kwargs) > 0 - + async def count(self, **kwargs): return await self.mapper.count(**kwargs) @@ -27,14 +25,13 @@ class BaseService: async def get(self, **kwargs): return await self.mapper.get(**kwargs) - - async def save(self, model:UserModel): + + async def save(self, model: UserModel): # if model.is_valid: You Know why not - return await self.mapper.save(model) and True - - + return await self.mapper.save(model) and True + async def find(self, **kwargs): return await self.mapper.find(**kwargs) - + async def delete(self, **kwargs): - return await self.mapper.delete(**kwargs) \ No newline at end of file + return await self.mapper.delete(**kwargs) diff --git a/src/snek/system/view.py b/src/snek/system/view.py index 3b53f33..1cf5329 100644 --- a/src/snek/system/view.py +++ b/src/snek/system/view.py @@ -1,13 +1,14 @@ from aiohttp import web -from snek.system.markdown import render_markdown +from snek.system.markdown import render_markdown + class BaseView(web.View): - - @property + + @property def app(self): return self.request.app - + @property def db(self): return self.app.db @@ -17,31 +18,36 @@ class BaseView(web.View): async def render_template(self, template_name, context=None): if template_name.endswith(".md"): - response = await self.request.app.render_template(template_name,self.request,context) + response = await self.request.app.render_template( + template_name, self.request, context + ) body = await render_markdown(self.app, response.body.decode()) - return web.Response(body=body,content_type="text/html") - return await self.request.app.render_template(template_name, self.request,context) - + return web.Response(body=body, content_type="text/html") + return await self.request.app.render_template( + template_name, self.request, context + ) + + class BaseFormView(BaseView): - form = None + form = None async def get(self): form = self.form(app=self.app) - + return await self.json_response(await form.to_json()) - + async def post(self): form = self.form(app=self.app) post = await self.request.json() - form.set_user_data(post['form']) + form.set_user_data(post["form"]) result = await form.to_json() - if post.get('action') == 'validate': + if post.get("action") == "validate": # Pass pass - if post.get('action') == 'submit' and result['is_valid']: + if post.get("action") == "submit" and result["is_valid"]: await self.submit(form) - return await self.json_response(result) + return await self.json_response(result) - async def submit(self,model=None): + async def submit(self, model=None): print("Submit sucess") diff --git a/src/snek/templates/docs.md b/src/snek/templates/docs.md index 2cf60cb..a42b7d5 100644 --- a/src/snek/templates/docs.md +++ b/src/snek/templates/docs.md @@ -9,8 +9,7 @@ Currently only some details about the internal API are available. # of the snek.system.security module. new_user_object = await app.service.user.register( - username="retoor", - password="retoorded" + username="retoor", password="retoorded" ) ``` @@ -23,15 +22,16 @@ var1 = security.encrypt("data") var2 = security.encrypt(b"data") # Is correct: -assert(var1 == var2) +assert var1 == var2 ``` ## How to create a basic HTML / Markdown view ```python -from snek.system.view import BaseView +from snek.system.view import BaseView + class IndexView(BaseView): - + async def get(self): # The render function supports markdown. # It will render with syntax highlighting. @@ -40,11 +40,12 @@ class IndexView(BaseView): ``` ## How to create a FormView ```python -from snek.system.view import BaseFormView from snek.form.register import RegisterForm +from snek.system.view import BaseFormView + class RegisterFormView(BaseFormView): - + form = RegisterForm ``` ## How to register a class view diff --git a/src/snek/view/about.py b/src/snek/view/about.py index 593d5a9..762fc8e 100644 --- a/src/snek/view/about.py +++ b/src/snek/view/about.py @@ -1,5 +1,3 @@ - - from snek.system.view import BaseView @@ -7,8 +5,9 @@ class AboutHTMLView(BaseView): async def get(self): return await self.render_template("about.html") - + + class AboutMDView(BaseView): async def get(self): - return await self.render_template("about.md") \ No newline at end of file + return await self.render_template("about.md") diff --git a/src/snek/view/docs.py b/src/snek/view/docs.py index e69d754..519a0eb 100644 --- a/src/snek/view/docs.py +++ b/src/snek/view/docs.py @@ -1,6 +1,3 @@ - - - from snek.system.view import BaseView @@ -8,8 +5,9 @@ class DocsHTMLView(BaseView): async def get(self): return await self.render_template("docs.html") - + + class DocsMDView(BaseView): async def get(self): - return await self.render_template("docs.md") \ No newline at end of file + return await self.render_template("docs.md") diff --git a/src/snek/view/index.py b/src/snek/view/index.py index c7861fa..bd91dc8 100644 --- a/src/snek/view/index.py +++ b/src/snek/view/index.py @@ -1,5 +1,6 @@ from snek.system.view import BaseView + class IndexView(BaseView): async def get(self): diff --git a/src/snek/view/login.py b/src/snek/view/login.py index ffedc79..6566df9 100644 --- a/src/snek/view/login.py +++ b/src/snek/view/login.py @@ -1,13 +1,18 @@ from snek.form.register import RegisterForm from snek.system.view import BaseView + class LoginView(BaseView): async def get(self): - return await self.render_template("login.html") #web.json_response({"form": RegisterForm().to_json()}) - + return await self.render_template( + "login.html" + ) # web.json_response({"form": RegisterForm().to_json()}) + async def post(self): form = RegisterForm() form.set_user_data(await self.request.post()) print(form.is_valid()) - return await self.render_template("login.html", self.request) #web.json_response({"form": RegisterForm().to_json()}) + return await self.render_template( + "login.html", self.request + ) # web.json_response({"form": RegisterForm().to_json()}) diff --git a/src/snek/view/login_form.py b/src/snek/view/login_form.py index e9b6eac..576ddc6 100644 --- a/src/snek/view/login_form.py +++ b/src/snek/view/login_form.py @@ -1,5 +1,6 @@ -from snek.system.view import BaseFormView from snek.form.login import LoginForm +from snek.system.view import BaseFormView + class LoginFormView(BaseFormView): - form = LoginForm \ No newline at end of file + form = LoginForm diff --git a/src/snek/view/register.py b/src/snek/view/register.py index e3b3038..1186959 100644 --- a/src/snek/view/register.py +++ b/src/snek/view/register.py @@ -1,6 +1,7 @@ from snek.system.view import BaseView + class RegisterView(BaseView): async def get(self): - return await self.render_template("register.html") \ No newline at end of file + return await self.render_template("register.html") diff --git a/src/snek/view/register_form.py b/src/snek/view/register_form.py index 8cb6567..4c30169 100644 --- a/src/snek/view/register_form.py +++ b/src/snek/view/register_form.py @@ -1,9 +1,12 @@ from snek.form.register import RegisterForm from snek.system.view import BaseFormView + class RegisterFormView(BaseFormView): form = RegisterForm async def submit(self, form): - result = await self.app.services.user.register(form.email.value,form.username.value,form.password.value) - print("SUBMITTED:",result) \ No newline at end of file + result = await self.app.services.user.register( + form.email.value, form.username.value, form.password.value + ) + print("SUBMITTED:", result) diff --git a/src/snek/view/web.py b/src/snek/view/web.py index b06563a..d42fcec 100644 --- a/src/snek/view/web.py +++ b/src/snek/view/web.py @@ -1,6 +1,7 @@ from snek.system.view import BaseView + class WebView(BaseView): async def get(self): - return await self.render_template("web.html") \ No newline at end of file + return await self.render_template("web.html")