Formatting.

This commit is contained in:
retoor 2025-01-24 23:35:44 +01:00
parent 18b76ebd5e
commit 9b93403a93
28 changed files with 342 additions and 246 deletions

View File

@ -1,9 +1,10 @@
import pathlib import pathlib
from types import SimpleNamespace
from aiohttp import web from aiohttp import web
from app.app import Application as BaseApplication from app.app import Application as BaseApplication
from snek.docs.app import Application as DocsApplication from snek.docs.app import Application as DocsApplication
from app.cache import time_cache_async
from snek.mapper import get_mappers from snek.mapper import get_mappers
from snek.service import get_services from snek.service import get_services
from snek.system import http 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 import RegisterView
from snek.view.register_form import RegisterFormView from snek.view.register_form import RegisterFormView
from snek.view.web import WebView from snek.view.web import WebView
from types import SimpleNamespace
class Application(BaseApplication): class Application(BaseApplication):
@ -60,7 +60,10 @@ class Application(BaseApplication):
self.router.add_get("/http-get", self.handle_http_get) self.router.add_get("/http-get", self.handle_http_get)
self.router.add_get("/http-photo", self.handle_http_photo) 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): async def handle_test(self, request):
@ -80,7 +83,6 @@ class Application(BaseApplication):
body=path.read_bytes(), headers={"Content-Type": "image/png"} 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): async def render_template(self, template, request, context=None):
return await super().render_template(template, request, context) return await super().render_template(template, request, context)

View File

@ -1,4 +1,5 @@
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
class LoginForm(Form): class LoginForm(Form):
@ -11,14 +12,16 @@ class LoginForm(Form):
max_length=20, max_length=20,
regex=r"^[a-zA-Z0-9_]+$", regex=r"^[a-zA-Z0-9_]+$",
place_holder="Username", 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( action = FormButtonElement(
name="action", name="action", value="submit", text="Login", type="button"
value="submit",
text="Login",
type="button"
) )

View File

@ -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): class UsernameField(FormInputElement):
@ -9,6 +10,7 @@ class UsernameField(FormInputElement):
result.append("Username is not available.") result.append("Username is not available.")
return result return result
class RegisterForm(Form): class RegisterForm(Form):
title = HTMLElement(tag="h1", text="Register") title = HTMLElement(tag="h1", text="Register")
@ -20,21 +22,23 @@ class RegisterForm(Form):
max_length=20, max_length=20,
regex=r"^[a-zA-Z0-9_]+$", regex=r"^[a-zA-Z0-9_]+$",
place_holder="Username", place_holder="Username",
type="text" type="text",
) )
email = FormInputElement( email = FormInputElement(
name="email", name="email",
required=False, 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-.]+$",
place_holder="Email address", 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( action = FormButtonElement(
name="action", name="action", value="submit", text="Register", type="button"
value="submit",
text="Register",
type="button"
) )

View File

@ -1,12 +1,12 @@
import functools import functools
from snek.mapper.user import UserMapper from snek.mapper.user import UserMapper
@functools.cache @functools.cache
def get_mappers(app=None): def get_mappers(app=None):
return dict( return {"user": UserMapper(app=app)}
user=UserMapper(app=app)
)
def get_mapper(name, app=None): def get_mapper(name, app=None):
return get_mappers(app=app)[name] return get_mappers(app=app)[name]

View File

@ -1,5 +1,6 @@
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
class UserMapper(BaseMapper): class UserMapper(BaseMapper):
table_name = "user" table_name = "user"

View File

@ -1,12 +1,12 @@
from snek.model.user import UserModel
import functools import functools
from snek.model.user import UserModel
@functools.cache @functools.cache
def get_models(): def get_models():
return dict( return {"user": UserModel}
user=UserModel
)
def get_model(name): def get_model(name):
return get_models()[name] return get_models()[name]

View File

@ -1,5 +1,6 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField
class UserModel(BaseModel): class UserModel(BaseModel):
username = ModelField( username = ModelField(
@ -12,8 +13,6 @@ class UserModel(BaseModel):
email = ModelField( email = ModelField(
name="email", name="email",
required=False, 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,}")

View File

@ -1,12 +1,13 @@
from snek.service.user import UserService
import functools import functools
from snek.service.user import UserService
@functools.cache @functools.cache
def get_services(app): def get_services(app):
return dict( return {"user": UserService(app=app)}
user = UserService(app=app)
)
def get_service(name, app=None): def get_service(name, app=None):
return get_services(app=app)[name] return get_services(app=app)[name]

View File

@ -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): class UserService(BaseService):
mapper_name = "user" mapper_name = "user"
@ -14,4 +15,3 @@ class UserService(BaseService):
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create user: {model.errors}.") raise Exception(f"Failed to create user: {model.errors}.")

View File

@ -1,8 +1,8 @@
import functools import functools
cache = functools.cache cache = functools.cache
def async_cache(func): def async_cache(func):
cache = {} cache = {}

View File

@ -26,8 +26,19 @@
from snek.system import model from snek.system import model
class HTMLElement(model.ModelField): 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.tag = tag
self.text = text self.text = text
self.id = id self.id = id
@ -37,16 +48,18 @@ class HTMLElement(model.ModelField):
async def to_json(self): async def to_json(self):
result = await super().to_json() result = await super().to_json()
result['text'] = self.text result["text"] = self.text
result['id'] = self.id result["id"] = self.id
result['html'] = self.html result["html"] = self.html
result['class_name'] = self.class_name result["class_name"] = self.class_name
result['tag'] = self.tag result["tag"] = self.tag
return result return result
class FormElement(HTMLElement): class FormElement(HTMLElement):
pass pass
class FormInputElement(FormElement): class FormInputElement(FormElement):
def __init__(self, type="text", place_holder=None, *args, **kwargs): def __init__(self, type="text", place_holder=None, *args, **kwargs):
super().__init__(tag="input", *args, **kwargs) super().__init__(tag="input", *args, **kwargs)
@ -59,23 +72,25 @@ class FormInputElement(FormElement):
data["type"] = self.type data["type"] = self.type
return data return data
class FormButtonElement(FormElement): class FormButtonElement(FormElement):
def __init__(self, tag="button", *args, **kwargs): def __init__(self, tag="button", *args, **kwargs):
super().__init__(tag=tag, *args, **kwargs) super().__init__(tag=tag, *args, **kwargs)
class Form(model.BaseModel): class Form(model.BaseModel):
@property @property
def html_elements(self): def html_elements(self):
return [element for element in self.fields if isinstance(element, HTMLElement)] return [element for element in self.fields if isinstance(element, HTMLElement)]
def set_user_data(self, data): 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): async def to_json(self, encode=False):
elements = await super().to_json() elements = await super().to_json()
html_elements = {} html_elements = {}
for element in elements.keys(): 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 # is_valid is async get property so we can't do getattr on it
continue continue
field = getattr(self, element) field = getattr(self, element)
@ -85,8 +100,12 @@ class Form(model.BaseModel):
except KeyError: except KeyError:
pass pass
is_valid = all(field['is_valid'] for field in html_elements.values()) 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) return {
"fields": html_elements,
"is_valid": is_valid,
"errors": await self.errors,
}
@property @property
async def errors(self): async def errors(self):

View File

@ -24,17 +24,17 @@
# SOFTWARE. # SOFTWARE.
from aiohttp import web import asyncio
import aiohttp
from app.cache import time_cache_async
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pathlib import pathlib
import uuid import uuid
import imgkit
import asyncio
import zlib 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): async def crc32(data):
try: try:
@ -43,6 +43,7 @@ async def crc32(data):
pass pass
return "crc32" + str(zlib.crc32(data)) return "crc32" + str(zlib.crc32(data))
async def get_file(name, suffix=".cache"): async def get_file(name, suffix=".cache"):
name = await crc32(name) name = await crc32(name)
path = pathlib.Path(".").joinpath("cache") path = pathlib.Path(".").joinpath("cache")
@ -50,11 +51,13 @@ async def get_file(name, suffix=".cache"):
path.mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
return path.joinpath(name + suffix) return path.joinpath(name + suffix)
async def public_touch(name=None): async def public_touch(name=None):
path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name) path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name)
path.open("wb").close() path.open("wb").close()
return path return path
async def create_site_photo(url): async def create_site_photo(url):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
if not url.startswith("https"): if not url.startswith("https"):
@ -71,21 +74,23 @@ async def create_site_photo(url):
return await loop.run_in_executor(None, make_photo) return await loop.run_in_executor(None, make_photo)
async def repair_links(base_url, html_content): async def repair_links(base_url, html_content):
soup = BeautifulSoup(html_content, "html.parser") soup = BeautifulSoup(html_content, "html.parser")
for tag in soup.find_all(['a', 'img', 'link']): for tag in soup.find_all(["a", "img", "link"]):
if tag.has_attr('href') and not tag['href'].startswith("http"): if tag.has_attr("href") and not tag["href"].startswith("http"):
tag['href'] = urljoin(base_url, tag['href']) tag["href"] = urljoin(base_url, tag["href"])
if tag.has_attr('src') and not tag['src'].startswith("http"): if tag.has_attr("src") and not tag["src"].startswith("http"):
tag['src'] = urljoin(base_url, tag['src']) tag["src"] = urljoin(base_url, tag["src"])
return soup.prettify() return soup.prettify()
async def is_html_content(content: bytes): async def is_html_content(content: bytes):
try: try:
content = content.decode(errors='ignore') content = content.decode(errors="ignore")
except: except:
pass pass
marks = ['<html', '<img', '<p', '<span', '<div'] marks = ["<html", "<img", "<p", "<span", "<div"]
try: try:
content = content.lower() content = content.lower()
for mark in marks: for mark in marks:
@ -95,6 +100,7 @@ async def is_html_content(content: bytes):
print(ex) print(ex)
return False return False
@time_cache_async(120) @time_cache_async(120)
async def get(url): async def get(url):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:

View File

@ -1,9 +1,8 @@
DEFAULT_LIMIT = 30 DEFAULT_LIMIT = 30
import typing import typing
from snek.system.model import BaseModel from snek.system.model import BaseModel
import types
class BaseMapper: class BaseMapper:
@ -29,8 +28,8 @@ class BaseMapper:
async def get(self, uid: str = None, **kwargs) -> BaseModel: async def get(self, uid: str = None, **kwargs) -> BaseModel:
if uid: if uid:
kwargs['uid'] = uid kwargs["uid"] = uid
model = self.new() self.new()
record = self.table.find_one(**kwargs) 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)
@ -42,9 +41,9 @@ class BaseMapper:
async def save(self, model: BaseModel) -> bool: async def save(self, model: BaseModel) -> bool:
record = await model.record record = await model.record
if not record.get('uid'): if not record.get("uid"):
raise Exception(f"Attempt to save without uid: {record}.") 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: async def find(self, **kwargs) -> typing.AsyncGenerator:
if not kwargs.get("_limit"): if not kwargs.get("_limit"):

View File

@ -1,29 +1,29 @@
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2 # Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
from types import SimpleNamespace 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 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): class MarkdownRenderer(HTMLRenderer):
_allow_harmful_protocols = True _allow_harmful_protocols = True
def __init__(self, app, template): def __init__(self, app, template):
self.template = template self.template = template
self.app = app self.app = app
self.env = self.app.jinja2_env self.env = self.app.jinja2_env
formatter = html.HtmlFormatter() formatter = html.HtmlFormatter()
self.env.globals['highlight_styles'] = formatter.get_style_defs() self.env.globals["highlight_styles"] = formatter.get_style_defs()
def _escape(self, str): def _escape(self, str):
return str ##escape(str) return str ##escape(str)
def block_code(self, code, lang=None, info=None): def block_code(self, code, lang=None, info=None):
if not lang: if not lang:
lang = info lang = info
@ -33,6 +33,7 @@ class MarkdownRenderer(HTMLRenderer):
lexer = get_lexer_by_name(lang, stripall=True) lexer = get_lexer_by_name(lang, stripall=True)
formatter = html.HtmlFormatter(lineseparator="<br>") formatter = html.HtmlFormatter(lineseparator="<br>")
return highlight(code, lexer, formatter) return highlight(code, lexer, formatter)
def render(self): def render(self):
markdown_string = self.app.template_path.joinpath(self.template).read_text() markdown_string = self.app.template_path.joinpath(self.template).read_text()
renderer = MarkdownRenderer(self.app, self.template) renderer = MarkdownRenderer(self.app, self.template)
@ -40,23 +41,25 @@ class MarkdownRenderer(HTMLRenderer):
return markdown(markdown_string) return markdown(markdown_string)
def render_markdown_sync(app, markdown_string): def render_markdown_sync(app, markdown_string):
renderer = MarkdownRenderer(app, None) renderer = MarkdownRenderer(app, None)
markdown = Markdown(renderer=renderer) markdown = Markdown(renderer=renderer)
return markdown(markdown_string) return markdown(markdown_string)
@time_cache_async(120) @time_cache_async(120)
async def render_markdown(app, markdown_string): 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.ext import Extension
from jinja2.nodes import Const from jinja2.nodes import Const
# Source: https://ron.sh/how-to-write-a-jinja2-extension/ # Source: https://ron.sh/how-to-write-a-jinja2-extension/
class MarkdownExtension(Extension): class MarkdownExtension(Extension):
tags = {'markdown'} tags = {"markdown"}
def __init__(self, environment): def __init__(self, environment):
self.app = SimpleNamespace(jinja2_env=environment) self.app = SimpleNamespace(jinja2_env=environment)
@ -64,13 +67,15 @@ class MarkdownExtension(Extension):
def parse(self, parser): def parse(self, parser):
line_number = next(parser.stream).lineno line_number = next(parser.stream).lineno
md_file = [Const('')] md_file = [Const("")]
body = '' body = ""
try: try:
md_file = [parser.parse_expression()] md_file = [parser.parse_expression()]
except TemplateSyntaxError: except TemplateSyntaxError:
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) body = parser.parse_statements(["name:endmarkdown"], drop_needle=True)
return nodes.CallBlock(self.call_method('_to_html', md_file), [], [], body).set_lineno(line_number) return nodes.CallBlock(
self.call_method("_to_html", md_file), [], [], body
).set_lineno(line_number)
def _to_html(self, md_file, caller): def _to_html(self, md_file, caller):
return render_markdown_sync(self.app, caller()) return render_markdown_sync(self.app, caller())

View File

@ -8,12 +8,14 @@
from aiohttp import web from aiohttp import web
@web.middleware @web.middleware
async def no_cors_middleware(request, handler): async def no_cors_middleware(request, handler):
response = await handler(request) response = await handler(request)
response.headers.pop("Access-Control-Allow-Origin", None) response.headers.pop("Access-Control-Allow-Origin", None)
return response return response
@web.middleware @web.middleware
async def cors_allow_middleware(request, handler): async def cors_allow_middleware(request, handler):
response = await handler(request) response = await handler(request)
@ -22,12 +24,15 @@ async def cors_allow_middleware(request, handler):
response.headers["Access-Control-Allow-Headers"] = "*" response.headers["Access-Control-Allow-Headers"] = "*"
return response return response
@web.middleware @web.middleware
async def cors_middleware(request, handler): async def cors_middleware(request, handler):
if request.method == "OPTIONS": if request.method == "OPTIONS":
response = web.Response() response = web.Response()
response.headers["Access-Control-Allow-Origin"] = "*" 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"] = "*" response.headers["Access-Control-Allow-Headers"] = "*"
return response return response

View File

@ -25,12 +25,12 @@
# SOFTWARE. # SOFTWARE.
import copy
import json
import re import re
import uuid import uuid
import json
from datetime import datetime, timezone
from collections import OrderedDict 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}$" 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(): for key, value in kwargs.items():
setattr(func, key, value) setattr(func, key, value)
return func return func
return decorator 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): 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: class Validator:
@ -70,7 +79,21 @@ class Validator:
def custom_validation(self): def custom_validation(self):
return True 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 self.index = Validator._index
Validator._index += 1 Validator._index += 1
self.app = app self.app = app
@ -103,9 +126,13 @@ class Validator:
if self.max_num is not None and self.value > self.max_num: if self.max_num is not None and self.value > self.max_num:
error_list.append(f"Field should be maximal {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: 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: 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): if self.regex and self.value and not re.match(self.regex, self.value):
error_list.append("Invalid value.") error_list.append("Invalid value.")
if self.kind and not isinstance(self.value, self.kind): if self.kind and not isinstance(self.value, self.kind):
@ -141,7 +168,7 @@ class Validator:
"help_text": self.help_text, "help_text": self.help_text,
"errors": errors, "errors": errors,
"is_valid": is_valid, "is_valid": is_valid,
"index": self.index "index": self.index,
} }
@ -156,7 +183,7 @@ class ModelField(Validator):
async def to_json(self): async def to_json(self):
result = await super().to_json() result = await super().to_json()
result['name'] = self.name result["name"] = self.name
return result return result
@ -193,9 +220,18 @@ class UUIDField(ModelField):
class BaseModel: class BaseModel:
uid = UUIDField(name="uid", required=True) uid = UUIDField(name="uid", required=True)
created_at = CreatedField(name="created_at", required=True, regex=TIMESTAMP_REGEX, place_holder="Created at") created_at = CreatedField(
updated_at = UpdatedField(name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at") name="created_at",
deleted_at = DeletedField(name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted 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): async def from_record(cls, record, mapper):
@ -233,10 +269,12 @@ class BaseModel:
if isinstance(obj, Validator): if isinstance(obj, Validator):
self.__dict__[key] = copy.deepcopy(obj) 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] = self.__dict__[key]
self.fields[key].model = self self.fields[key].model = self
self.fields[key].app = kwargs.get('app') self.fields[key].app = kwargs.get("app")
def __setitem__(self, key, value): def __setitem__(self, key, value):
obj = self.__dict__.get(key) obj = self.__dict__.get(key)
@ -254,17 +292,14 @@ class BaseModel:
field = self.fields.get(key) field = self.fields.get(key)
if not field: if not field:
continue continue
if value.get('name'): if value.get("name"):
value = value.get('value') value = value.get("value")
field.value = value field.value = value
@property @property
async def is_valid(self): async def is_valid(self):
return all([await field.is_valid for field in self.fields.values()]) return all([await field.is_valid for field in self.fields.values()])
def __getitem__(self, key): def __getitem__(self, key):
obj = self.__dict__.get(key) obj = self.__dict__.get(key)
if isinstance(obj, Validator): if isinstance(obj, Validator):
@ -282,20 +317,22 @@ class BaseModel:
obj = await self.to_json() obj = await self.to_json()
record = {} record = {}
for key, value in obj.items(): 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 continue
if getattr(self, key).save: if getattr(self, key).save:
record[key] = value.get('value') record[key] = value.get("value")
return record return record
async def to_json(self, encode=False): async def to_json(self, encode=False):
model_data = OrderedDict({ model_data = OrderedDict(
{
"uid": self.uid.value, "uid": self.uid.value,
"created_at": self.created_at.value, "created_at": self.created_at.value,
"updated_at": self.updated_at.value, "updated_at": self.updated_at.value,
"deleted_at": self.deleted_at.value, "deleted_at": self.deleted_at.value,
"is_valid": await self.is_valid "is_valid": await self.is_valid,
}) }
)
for key, value in self.fields.items(): for key, value in self.fields.items():
if key == "record": if key == "record":

View File

@ -2,6 +2,7 @@ import hashlib
DEFAULT_SALT = b"snekker-de-snek-" DEFAULT_SALT = b"snekker-de-snek-"
async def hash(data, salt=DEFAULT_SALT): async def hash(data, salt=DEFAULT_SALT):
try: try:
data = data.encode(errors="ignore") data = data.encode(errors="ignore")
@ -16,5 +17,6 @@ async def hash(data,salt=DEFAULT_SALT):
obj = hashlib.sha256(salted) obj = hashlib.sha256(salted)
return obj.hexdigest() return obj.hexdigest()
async def verify(string: str, hashed: str): async def verify(string: str, hashed: str):
return await hash(string) == hashed return await hash(string) == hashed

View File

@ -1,9 +1,7 @@
from snek.mapper import get_mapper from snek.mapper import get_mapper
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
class BaseService: class BaseService:
@ -32,7 +30,6 @@ class BaseService:
# if model.is_valid: You Know why not # 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): async def find(self, **kwargs):
return await self.mapper.find(**kwargs) return await self.mapper.find(**kwargs)

View File

@ -2,6 +2,7 @@ from aiohttp import web
from snek.system.markdown import render_markdown from snek.system.markdown import render_markdown
class BaseView(web.View): class BaseView(web.View):
@property @property
@ -17,10 +18,15 @@ class BaseView(web.View):
async def render_template(self, template_name, context=None): async def render_template(self, template_name, context=None):
if template_name.endswith(".md"): 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()) body = await render_markdown(self.app, response.body.decode())
return web.Response(body=body, content_type="text/html") return web.Response(body=body, content_type="text/html")
return await self.request.app.render_template(template_name, self.request,context) return await self.request.app.render_template(
template_name, self.request, context
)
class BaseFormView(BaseView): class BaseFormView(BaseView):
@ -34,12 +40,12 @@ class BaseFormView(BaseView):
async def post(self): async def post(self):
form = self.form(app=self.app) form = self.form(app=self.app)
post = await self.request.json() post = await self.request.json()
form.set_user_data(post['form']) form.set_user_data(post["form"])
result = await form.to_json() result = await form.to_json()
if post.get('action') == 'validate': if post.get("action") == "validate":
# Pass # Pass
pass pass
if post.get('action') == 'submit' and result['is_valid']: if post.get("action") == "submit" and result["is_valid"]:
await self.submit(form) await self.submit(form)
return await self.json_response(result) return await self.json_response(result)

View File

@ -9,8 +9,7 @@ Currently only some details about the internal API are available.
# of the snek.system.security module. # of the snek.system.security module.
new_user_object = await app.service.user.register( new_user_object = await app.service.user.register(
username="retoor", username="retoor", password="retoorded"
password="retoorded"
) )
``` ```
@ -23,13 +22,14 @@ var1 = security.encrypt("data")
var2 = security.encrypt(b"data") var2 = security.encrypt(b"data")
# Is correct: # Is correct:
assert(var1 == var2) assert var1 == var2
``` ```
## How to create a basic HTML / Markdown view ## How to create a basic HTML / Markdown view
```python ```python
from snek.system.view import BaseView from snek.system.view import BaseView
class IndexView(BaseView): class IndexView(BaseView):
async def get(self): async def get(self):
@ -40,8 +40,9 @@ class IndexView(BaseView):
``` ```
## How to create a FormView ## How to create a FormView
```python ```python
from snek.system.view import BaseFormView
from snek.form.register import RegisterForm from snek.form.register import RegisterForm
from snek.system.view import BaseFormView
class RegisterFormView(BaseFormView): class RegisterFormView(BaseFormView):

View File

@ -1,5 +1,3 @@
from snek.system.view import BaseView from snek.system.view import BaseView
@ -8,6 +6,7 @@ class AboutHTMLView(BaseView):
async def get(self): async def get(self):
return await self.render_template("about.html") return await self.render_template("about.html")
class AboutMDView(BaseView): class AboutMDView(BaseView):
async def get(self): async def get(self):

View File

@ -1,6 +1,3 @@
from snek.system.view import BaseView from snek.system.view import BaseView
@ -9,6 +6,7 @@ class DocsHTMLView(BaseView):
async def get(self): async def get(self):
return await self.render_template("docs.html") return await self.render_template("docs.html")
class DocsMDView(BaseView): class DocsMDView(BaseView):
async def get(self): async def get(self):

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class IndexView(BaseView): class IndexView(BaseView):
async def get(self): async def get(self):

View File

@ -1,13 +1,18 @@
from snek.form.register import RegisterForm from snek.form.register import RegisterForm
from snek.system.view import BaseView from snek.system.view import BaseView
class LoginView(BaseView): class LoginView(BaseView):
async def get(self): 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): async def post(self):
form = RegisterForm() form = RegisterForm()
form.set_user_data(await self.request.post()) form.set_user_data(await self.request.post())
print(form.is_valid()) 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()})

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseFormView
from snek.form.login import LoginForm from snek.form.login import LoginForm
from snek.system.view import BaseFormView
class LoginFormView(BaseFormView): class LoginFormView(BaseFormView):
form = LoginForm form = LoginForm

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class RegisterView(BaseView): class RegisterView(BaseView):
async def get(self): async def get(self):

View File

@ -1,9 +1,12 @@
from snek.form.register import RegisterForm from snek.form.register import RegisterForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class RegisterFormView(BaseFormView): class RegisterFormView(BaseFormView):
form = RegisterForm form = RegisterForm
async def submit(self, form): async def submit(self, form):
result = await self.app.services.user.register(form.email.value,form.username.value,form.password.value) result = await self.app.services.user.register(
form.email.value, form.username.value, form.password.value
)
print("SUBMITTED:", result) print("SUBMITTED:", result)

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class WebView(BaseView): class WebView(BaseView):
async def get(self): async def get(self):