Initial commit.
This commit is contained in:
commit
66f8942936
166
.gitignore
vendored
Normal file
166
.gitignore
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
.vscode
|
||||
.history
|
||||
*.db*
|
||||
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
PYTHON=./.venv/bin/python
|
||||
PIP=./.venv/bin/pip
|
||||
APP=./venv/bin/snek.serve
|
||||
PORT = 8081
|
||||
|
||||
|
||||
install:
|
||||
python3 -m venv .venv
|
||||
$(PIP) install -e .
|
||||
|
||||
run:
|
||||
$(APP) --port=$(PORT)
|
||||
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Boeh
|
||||
|
||||
## Description
|
||||
Matrix bot written in Python that says boeh everytime that Joe talks. He knows why.
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
24
setup.cfg
Normal file
24
setup.cfg
Normal file
@ -0,0 +1,24 @@
|
||||
[metadata]
|
||||
name = snek
|
||||
version = 1.0.0
|
||||
description = Snek chat server
|
||||
author = retoor
|
||||
author_email = retoor@molodetz.nl
|
||||
license = MIT
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
package_dir =
|
||||
= src
|
||||
python_requires = >=3.7
|
||||
install_requires =
|
||||
app @ git+https://retoor.molodetz.nl/retoor/app
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
snek.serve = snek.server:cli
|
20
src/snek/app.py
Normal file
20
src/snek/app.py
Normal file
@ -0,0 +1,20 @@
|
||||
from app.app import Application as BaseApplication
|
||||
from snek.forms import RegisterForm
|
||||
from aiohttp import web
|
||||
|
||||
class Application(BaseApplication):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.router.add_get("/register", self.handle_register)
|
||||
self.router.add_post("/register", self.handle_register)
|
||||
|
||||
async def handle_register(self, request):
|
||||
if request.method == "GET":
|
||||
return web.json_response({"form": RegisterForm().to_json()})
|
||||
elif request.method == "POST":
|
||||
return self.render("register.html")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Application()
|
||||
web.run_app(app,port=8081,host="0.0.0.0")
|
40
src/snek/forms.py
Normal file
40
src/snek/forms.py
Normal file
@ -0,0 +1,40 @@
|
||||
from snek import models
|
||||
|
||||
class FormElement(models.ModelField):
|
||||
|
||||
def __init__(self,html_type, place_holder=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.place_holder = place_holder
|
||||
self.html_type = html_type
|
||||
|
||||
def to_json(self):
|
||||
data = super().to_json()
|
||||
data["html_type"] = self.html_type
|
||||
data["place_holder"] = self.place_holder
|
||||
return data
|
||||
|
||||
class Form(models.BaseModel):
|
||||
pass
|
||||
|
||||
class RegisterForm(Form):
|
||||
|
||||
username = FormElement(
|
||||
name="username",
|
||||
required=True,
|
||||
min_length=2,
|
||||
max_length=20,
|
||||
regex=r"^[a-zA-Z0-9_]+$",
|
||||
place_holder="Username",
|
||||
html_type="text"
|
||||
)
|
||||
email = FormElement(
|
||||
name="email",
|
||||
required=True,
|
||||
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
|
||||
place_holder="Email address",
|
||||
html_type="email"
|
||||
)
|
||||
password = FormElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",html_type="password")
|
||||
|
||||
|
||||
|
263
src/snek/models.py
Normal file
263
src/snek/models.py
Normal file
@ -0,0 +1,263 @@
|
||||
import re
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime , timezone
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
|
||||
TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$"
|
||||
|
||||
def now():
|
||||
return str(datetime.now(timezone.utc))
|
||||
|
||||
def add_attrs(**kwargs):
|
||||
def decorator(func):
|
||||
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 decorator(func):
|
||||
return add_attrs(required=required,min_length=min_length,max_length=max_length,regex=regex,**kwargs)(func)
|
||||
|
||||
class Validator:
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self,val):
|
||||
self._value = json.loads(json.dumps(val,default=str))
|
||||
|
||||
@property
|
||||
def initial_value(self):
|
||||
return None
|
||||
|
||||
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,**kwargs):
|
||||
self.required = required
|
||||
self.min_num = min_num
|
||||
self.max_num = max_num
|
||||
self.min_length = min_length
|
||||
self.max_length = max_length
|
||||
self.regex = regex
|
||||
self._value = None
|
||||
self.value = value
|
||||
self.type = kind
|
||||
self.help_text = help_text
|
||||
self.__dict__.update(kwargs)
|
||||
@property
|
||||
def errors(self):
|
||||
error_list = []
|
||||
if self.value is None and self.required:
|
||||
error_list.append("Field is required.")
|
||||
return error_list
|
||||
|
||||
if self.value is None:
|
||||
return error_list
|
||||
|
||||
if self.type == float or self.type == int:
|
||||
if self.min_num is not None and self.value < self.min_num:
|
||||
error_list.append("Field should be minimal {}.".format(self.min_num))
|
||||
if self.max_num is not None and self.value > self.max_num:
|
||||
error_list.append("Field should be maximal {}.".format(self.max_num))
|
||||
if self.min_length is not None and len(self.value) < self.min_length:
|
||||
error_list.append("Field should be minimal {} characters long.".format(self.min_length))
|
||||
if self.max_length is not None and len(self.value) > self.max_length:
|
||||
error_list.append("Field should be maximal {} characters long.".format(self.max_length))
|
||||
if not self.regex is None and not self.value is None and not re.match(self.regex, self.value):
|
||||
error_list.append("Invalid value.".format(self.regex))
|
||||
if not self.type is None and type(self.value) != self.type:
|
||||
error_list.append("Invalid type. It is supposed to be {}.".format(self.type))
|
||||
return error_list
|
||||
|
||||
def validate(self):
|
||||
if self.errors:
|
||||
raise ValueError("\n", self.errors)
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
try:
|
||||
self.validate()
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"required": self.required,
|
||||
"min_num": self.min_num,
|
||||
"max_num": self.max_num,
|
||||
"min_length": self.min_length,
|
||||
"max_length": self.max_length,
|
||||
"regex": self.regex,
|
||||
"value": self.value,
|
||||
"type": self.type,
|
||||
"help_text": self.help_text,
|
||||
"errors": self.errors,
|
||||
"is_valid": self.is_valid
|
||||
}
|
||||
|
||||
class ModelField(Validator):
|
||||
def __init__(self,name=None,save=True, *args, **kwargs):
|
||||
self.name = name
|
||||
|
||||
self.save = save
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CreatedField(ModelField):
|
||||
|
||||
@property
|
||||
def initial_value(self):
|
||||
return now()
|
||||
|
||||
def update(self):
|
||||
if not self.value:
|
||||
self.value = now()
|
||||
|
||||
class UpdatedField(ModelField):
|
||||
|
||||
def update(self):
|
||||
self.value = now()
|
||||
|
||||
class DeletedField(ModelField):
|
||||
|
||||
def update(self):
|
||||
self.value = now()
|
||||
|
||||
class UUIDField(ModelField):
|
||||
|
||||
@property
|
||||
def initial_value(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
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")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
print(self.__dict__)
|
||||
print(dir(self.__class__))
|
||||
for key in dir(self.__class__):
|
||||
obj = getattr(self.__class__,key)
|
||||
|
||||
if isinstance(obj,Validator):
|
||||
self.__dict__[key] = copy.deepcopy(obj)
|
||||
print("JAAA")
|
||||
self.__dict__[key].value = kwargs.pop(key,self.__dict__[key].initial_value)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
obj = self.__dict__.get(key)
|
||||
if isinstance(obj,Validator):
|
||||
obj.value = value
|
||||
|
||||
def __getattr__(self, key):
|
||||
obj = self.__dict__.get(key)
|
||||
if isinstance(obj,Validator):
|
||||
print("HPAPP")
|
||||
return obj.value
|
||||
return obj
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
obj = self.__dict__.get(key)
|
||||
if isinstance(obj,Validator):
|
||||
return obj.value
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
obj = getattr(self,key)
|
||||
if isinstance(obj,Validator):
|
||||
obj.value = value
|
||||
else:
|
||||
setattr(self,key,value)
|
||||
#def __getattr__(self, key):
|
||||
# obj = self.__dict__.get(key)
|
||||
# if isinstance(obj,Validator):
|
||||
# return obj.value
|
||||
@property
|
||||
def record(self):
|
||||
obj = self.to_json()
|
||||
record = {}
|
||||
for key,value in obj.items():
|
||||
if getattr(self,key).save:
|
||||
record[key] = value.get('value')
|
||||
return record
|
||||
|
||||
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
|
||||
})
|
||||
for key,value in self.__dict__.items():
|
||||
if key == "record":
|
||||
continue
|
||||
value = self.__dict__[key]
|
||||
if hasattr(value,"value"):
|
||||
model_data[key] = value.to_json()
|
||||
if encode:
|
||||
return json.dumps(model_data,indent=2)
|
||||
return model_data
|
||||
|
||||
class FormElement(ModelField):
|
||||
|
||||
def __init__(self, place_holder=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.place_holder = place_holder
|
||||
|
||||
|
||||
|
||||
class FormElement(ModelField):
|
||||
|
||||
def __init__(self,place_holder=None, *args, **kwargs):
|
||||
self.place_holder = place_holder
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def to_json(self):
|
||||
data = super().to_json()
|
||||
data["name"] = self.name
|
||||
data["place_holder"] = self.place_holder
|
||||
return data
|
||||
|
||||
|
||||
|
||||
|
||||
class TestModel(BaseModel):
|
||||
|
||||
first_name = FormElement(name="first_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="First name")
|
||||
last_name = FormElement(name="last_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Last name")
|
||||
email = FormElement(name="email",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")
|
||||
password = FormElement(name="password",required=True,regex=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$",place_holder="Password")
|
||||
|
||||
class Form:
|
||||
username = FormElement(required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Username")
|
||||
email = FormElement(required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.place_holder = kwargs.pop("place_holder",None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
model = TestModel(first_name="John",last_name="Doe",email="n9K9p@example.com",password="Password123")
|
||||
model2 = TestModel(first_name="John",last_name="Doe",email="ddd",password="zzz")
|
||||
model.first_name = "AAA"
|
||||
print(model.first_name)
|
||||
print(model.first_name.value)
|
||||
|
||||
print(model.first_name)
|
||||
print(model.first_name.value)
|
||||
print(model.to_json(True))
|
||||
print(model2.to_json(True))
|
||||
print(model2.record)
|
Loading…
Reference in New Issue
Block a user