init commit
This commit is contained in:
commit
439c894b39
37 changed files with 18468 additions and 0 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
env/
|
||||||
|
|
||||||
|
metrics.json
|
||||||
|
keys.json
|
||||||
|
|
||||||
|
# Need to do this in addition to adding to .gitignore
|
||||||
|
# in order to fully ignore it.
|
||||||
|
# git rm -r --cached conf.yml
|
||||||
54
README.md
Normal file
54
README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Agent Framework
|
||||||
|
|
||||||
|
Fullstack framework tailored for agent workflows
|
||||||
|
|
||||||
|
Features include:
|
||||||
|
|
||||||
|
Document tools and models with new docstring protocol
|
||||||
|
|
||||||
|
doc.py, crud.py, order.py
|
||||||
|
|
||||||
|
Auto dependency injection (no enforced repo structure)
|
||||||
|
|
||||||
|
link.py, serve.py
|
||||||
|
|
||||||
|
UI template definition along side models
|
||||||
|
|
||||||
|
order.py
|
||||||
|
|
||||||
|
General comments on design:
|
||||||
|
|
||||||
|
Requests come in at serve.py
|
||||||
|
|
||||||
|
They are routed to server depending on subdomain (route.py)
|
||||||
|
|
||||||
|
Server routes are collected on boot and available globally
|
||||||
|
|
||||||
|
Repo structure is flexible which is great for dev satisfaction
|
||||||
|
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
|
#### Creates a virtual environment
|
||||||
|
```
|
||||||
|
python3 -m venv env
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Activates environment
|
||||||
|
```
|
||||||
|
source env/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install dependencies
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Run
|
||||||
|
```
|
||||||
|
cd src/
|
||||||
|
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
See it build various routes, runners, and handlers, as well as start the webserver
|
||||||
10
conf.yml
Normal file
10
conf.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
db:
|
||||||
|
user: root
|
||||||
|
database: crimata
|
||||||
|
cors:
|
||||||
|
Access-Control-Allow-Origin: "*"
|
||||||
|
Access-Control-Allow-Methods: "*"
|
||||||
|
Access-Control-Allow-Headers: "*"
|
||||||
|
port: 8080
|
||||||
|
public: "ui"
|
||||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
aiohappyeyeballs==2.6.1
|
||||||
|
aiohttp==3.12.15
|
||||||
|
aiosignal==1.4.0
|
||||||
|
attrs==25.3.0
|
||||||
|
frozenlist==1.7.0
|
||||||
|
idna==3.10
|
||||||
|
multidict==6.6.4
|
||||||
|
peewee==3.18.2
|
||||||
|
propcache==0.3.2
|
||||||
|
PyMySQL==1.1.1
|
||||||
|
PyYAML==6.0.2
|
||||||
|
yarl==1.20.1
|
||||||
40
src/api.py
Normal file
40
src/api.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ routes
|
||||||
|
|
||||||
|
The collection of routes for the API. It gets
|
||||||
|
|
||||||
|
populated after all modules are imported.
|
||||||
|
|
||||||
|
"""
|
||||||
|
routes = {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_route(func):
|
||||||
|
|
||||||
|
def wrapper(hello, *args, **kwargs):
|
||||||
|
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def make_routes():
|
||||||
|
|
||||||
|
for name, note in notes.items():
|
||||||
|
|
||||||
|
for tag in doc.read_tags(note):
|
||||||
|
|
||||||
|
if tag == "route":
|
||||||
|
|
||||||
|
func = GET_LIB_REF(name)
|
||||||
|
|
||||||
|
route = create_route(func)
|
||||||
|
|
||||||
|
routes[name] = route
|
||||||
|
|
||||||
|
print(f"Routes: {routes.keys()}")
|
||||||
55
src/card.py
Normal file
55
src/card.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝌏 Card
|
||||||
|
|
||||||
|
A credit card model \
|
||||||
|
|
||||||
|
`
|
||||||
|
[number]
|
||||||
|
|
||||||
|
[exp] [sec]
|
||||||
|
|
||||||
|
[name]
|
||||||
|
|
||||||
|
[street]
|
||||||
|
|
||||||
|
[city]
|
||||||
|
|
||||||
|
`
|
||||||
|
> name [CharField] Cardholder's name \
|
||||||
|
|
||||||
|
> exp [CharField] Expiration date in "2035-12" format \
|
||||||
|
|
||||||
|
> sec [CharField] Authorize likes strings \
|
||||||
|
|
||||||
|
> street [CharField] Billing address street \
|
||||||
|
|
||||||
|
> city [CharField] City of billing address \
|
||||||
|
|
||||||
|
> account [IntField] ID of account for the card \
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 add_card
|
||||||
|
|
||||||
|
Add a credit card to an account for payment \
|
||||||
|
|
||||||
|
> account []
|
||||||
|
|
||||||
|
> number []
|
||||||
|
|
||||||
|
> exp []
|
||||||
|
|
||||||
|
> sec []
|
||||||
|
|
||||||
|
> street []
|
||||||
|
|
||||||
|
> city []
|
||||||
|
|
||||||
|
"""
|
||||||
|
def add_card(account, number, exp, sec, street, city):
|
||||||
|
|
||||||
|
create("Card", locals())
|
||||||
91
src/charge.py
Normal file
91
src/charge.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 charge_card
|
||||||
|
|
||||||
|
Function to charge a credit card.
|
||||||
|
|
||||||
|
> card [Card]
|
||||||
|
|
||||||
|
> order [Order]
|
||||||
|
|
||||||
|
> account [Account]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def charge_card(card, order, account):
|
||||||
|
|
||||||
|
merchantAuth = apicontractsv1.merchantAuthenticationType()
|
||||||
|
|
||||||
|
merchantAuth.name = conf["merchant_username"]
|
||||||
|
|
||||||
|
merchantAuth.transactionKey = conf["merchant_key"]
|
||||||
|
|
||||||
|
creditCard = apicontractsv1.creditCardType()
|
||||||
|
|
||||||
|
creditCard.cardNumber = card.number
|
||||||
|
|
||||||
|
creditCard.expirationDate = card.exp
|
||||||
|
|
||||||
|
creditCard.cardCode = card.sec
|
||||||
|
|
||||||
|
payment = apicontractsv1.paymentType()
|
||||||
|
|
||||||
|
payment.creditCard = creditCard
|
||||||
|
|
||||||
|
orderSV1 = apicontractsv1.paymentType()
|
||||||
|
|
||||||
|
orderSV1.invoiceNumber = order.number
|
||||||
|
|
||||||
|
orderSV1.description = order.desc
|
||||||
|
|
||||||
|
address = apicontractsv1.customerAddressType()
|
||||||
|
|
||||||
|
address.firstName = card.address.firstName
|
||||||
|
|
||||||
|
address.lastName = card.address.lastName
|
||||||
|
|
||||||
|
address.address = card.address.street
|
||||||
|
|
||||||
|
address.city = card.address.city
|
||||||
|
|
||||||
|
address.state = card.address.state
|
||||||
|
|
||||||
|
address.zip = card.address.zipcode
|
||||||
|
|
||||||
|
address.country = card.address.country
|
||||||
|
|
||||||
|
customer = apicontractsv1.customerDataType()
|
||||||
|
|
||||||
|
customer.type = "individual"
|
||||||
|
|
||||||
|
customer.id = account.number
|
||||||
|
|
||||||
|
customer.email = account.email
|
||||||
|
|
||||||
|
request = apicontractsv1.transactionRequestType()
|
||||||
|
|
||||||
|
request.transactionType = "authCaptureTransaction"
|
||||||
|
|
||||||
|
request.amount = order.amount
|
||||||
|
|
||||||
|
request.payment = payment
|
||||||
|
|
||||||
|
request.order = orderSV1
|
||||||
|
|
||||||
|
response.billTo = address
|
||||||
|
|
||||||
|
request.customer = customer
|
||||||
|
|
||||||
|
createRequest = apicontractsv1.createTransactionRequest()
|
||||||
|
|
||||||
|
createRequest.merchantAuthentication = merchantAuth
|
||||||
|
|
||||||
|
createRequest.refId = "MerchantID-0001"
|
||||||
|
|
||||||
|
createRequest.transactionrequest = request
|
||||||
|
|
||||||
|
controller = createtransactioncontroller(createRequest)
|
||||||
|
|
||||||
|
controller.execute()
|
||||||
98
src/crud.py
Normal file
98
src/crud.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 create
|
||||||
|
|
||||||
|
Create a new row in the database \
|
||||||
|
|
||||||
|
> table [str] Name of the table \
|
||||||
|
|
||||||
|
> args [dict] Key value pairs for the fields \
|
||||||
|
|
||||||
|
⮑ [int] ID of the new row \
|
||||||
|
|
||||||
|
@tool @route
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create(table, args):
|
||||||
|
|
||||||
|
model = models[table]
|
||||||
|
|
||||||
|
query = model.insert(**args)
|
||||||
|
|
||||||
|
row = query.execute()
|
||||||
|
|
||||||
|
instance = select(table, [], ("id", row))[0]
|
||||||
|
|
||||||
|
ipc.emit(table, {
|
||||||
|
|
||||||
|
"method": "create",
|
||||||
|
|
||||||
|
"data": instance
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 update
|
||||||
|
|
||||||
|
Update a row in the database given a diff \
|
||||||
|
|
||||||
|
> table [str] Name of the table in the db \
|
||||||
|
|
||||||
|
> row [int] ID of the row \
|
||||||
|
|
||||||
|
> diff [dict] key value pairs of fields to update \
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update(table, row, diff):
|
||||||
|
|
||||||
|
model = models[table]
|
||||||
|
|
||||||
|
query = model.update(**diff).where(model.id == row)
|
||||||
|
|
||||||
|
query.execute()
|
||||||
|
|
||||||
|
ipc.emit(table, {
|
||||||
|
|
||||||
|
"method": "update",
|
||||||
|
|
||||||
|
"data": {
|
||||||
|
|
||||||
|
"row": row,
|
||||||
|
|
||||||
|
"diff": diff
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 remove
|
||||||
|
|
||||||
|
Remove a row in the database \
|
||||||
|
|
||||||
|
> table [str] Name of the table in the db \
|
||||||
|
|
||||||
|
> row [int] ID of the row to remove \
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete(table, row):
|
||||||
|
|
||||||
|
model = models[table]
|
||||||
|
|
||||||
|
query = model.delete().where(model.id == row)
|
||||||
|
|
||||||
|
query.execute()
|
||||||
|
|
||||||
|
ipc.emit(table, {
|
||||||
|
|
||||||
|
"method": "remove",
|
||||||
|
|
||||||
|
"data": row
|
||||||
|
|
||||||
|
})
|
||||||
25
src/db.py
Normal file
25
src/db.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
# db = peewee.MySQLDatabase(**conf["db"])
|
||||||
|
|
||||||
|
# db.connect()
|
||||||
|
|
||||||
|
db = None
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ Base
|
||||||
|
|
||||||
|
Inherit from Base to define a new model in the
|
||||||
|
|
||||||
|
database. Base is the parent component for all
|
||||||
|
|
||||||
|
database models, as it is connected to db. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
class Base(peewee.Model):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
database = db
|
||||||
128
src/doc.py
Normal file
128
src/doc.py
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def read_notes(code):
|
||||||
|
|
||||||
|
r = r'"""\n([𝑓𝌏$].*?)"""'
|
||||||
|
|
||||||
|
notes = re.findall(r, code, re.DOTALL)
|
||||||
|
|
||||||
|
return notes
|
||||||
|
|
||||||
|
|
||||||
|
def read_name(note):
|
||||||
|
|
||||||
|
r = r'[𝑓𝌏$]\s(\w+)'
|
||||||
|
|
||||||
|
match = re.search(r, note)
|
||||||
|
|
||||||
|
name = match.group(1)
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def read_about(note):
|
||||||
|
|
||||||
|
r = r'\n(.*?)\s\\'
|
||||||
|
|
||||||
|
match = re.search(r, note, re.DOTALL)
|
||||||
|
|
||||||
|
group = match.group(1)
|
||||||
|
|
||||||
|
about = re.sub(r'\n\s{4}', lambda x: "", group)
|
||||||
|
|
||||||
|
return about
|
||||||
|
|
||||||
|
|
||||||
|
def read_params(note):
|
||||||
|
|
||||||
|
r = r'>\s(\w+)\s\[(\w+)\]\s(.*?)\s\\'
|
||||||
|
|
||||||
|
captures = re.findall(r, note, re.DOTALL)
|
||||||
|
|
||||||
|
params = []
|
||||||
|
|
||||||
|
for capture in captures:
|
||||||
|
|
||||||
|
name, form, about = capture
|
||||||
|
|
||||||
|
about = re.sub(r'\n\n\s{4}', lambda x: "", about)
|
||||||
|
|
||||||
|
params.append((name, form, about))
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def read_return(note):
|
||||||
|
|
||||||
|
r = r'⮑\s(\w+)\s\[(\w+)\]\s(.*?)\s\\'
|
||||||
|
|
||||||
|
match = re.search(r, note)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
|
||||||
|
ret = match.group(1, 2, 3)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def read_tags(note):
|
||||||
|
|
||||||
|
r = r'@(\w+)'
|
||||||
|
|
||||||
|
tags = re.findall(r, note)
|
||||||
|
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def read_template(note):
|
||||||
|
|
||||||
|
r = r'𝌏\s(.*)'
|
||||||
|
|
||||||
|
match = re.search(r, note, re.DOTALL)
|
||||||
|
|
||||||
|
template = match.group(1)
|
||||||
|
|
||||||
|
return template
|
||||||
|
|
||||||
|
|
||||||
|
def render_template(template, data):
|
||||||
|
|
||||||
|
r1, r2 = r'(.*?)`(.*?)`', r'\[(\w+)\]'
|
||||||
|
|
||||||
|
match = re.search(r1, template, re.DOTALL)
|
||||||
|
|
||||||
|
res, body = match.groups()
|
||||||
|
|
||||||
|
for data in data:
|
||||||
|
|
||||||
|
chunk = re.sub(r2, lambda x: data[x], body)
|
||||||
|
|
||||||
|
res += chunk
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
# import os
|
||||||
|
|
||||||
|
# import pprint
|
||||||
|
|
||||||
|
# for file in ["crud.py"]:
|
||||||
|
|
||||||
|
# with open(file) as f:
|
||||||
|
|
||||||
|
# content = f.read()
|
||||||
|
|
||||||
|
# notes = read_notes(content)
|
||||||
|
|
||||||
|
# for note in notes:
|
||||||
|
|
||||||
|
# name = read_name(note)
|
||||||
|
|
||||||
|
# params = read_params(note)
|
||||||
|
|
||||||
|
# tags = read_tags(note)
|
||||||
|
|
||||||
|
# input(name)
|
||||||
|
|
||||||
|
# input(params)
|
||||||
41
src/emit.py
Normal file
41
src/emit.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
class Emitter:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.listeners = {}
|
||||||
|
|
||||||
|
def on(self, channel, cb):
|
||||||
|
|
||||||
|
if channel not in self.listeners:
|
||||||
|
|
||||||
|
self.listeners[channel] = set()
|
||||||
|
|
||||||
|
self.listeners[channel].add(cb)
|
||||||
|
|
||||||
|
def emit(self, channel, *args, **kwargs):
|
||||||
|
|
||||||
|
if channel in self.listeners:
|
||||||
|
|
||||||
|
for cb in self.listeners[channel]:
|
||||||
|
|
||||||
|
cb(*args, **kwargs)
|
||||||
|
|
||||||
|
def remove_listener(self, channel, cb):
|
||||||
|
|
||||||
|
if channel in self.listeners:
|
||||||
|
|
||||||
|
self.listeners[channel].remove(cb)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ ipc
|
||||||
|
|
||||||
|
Event emitter primarly used for db updates and
|
||||||
|
|
||||||
|
watchers \
|
||||||
|
|
||||||
|
"""
|
||||||
|
ipc = Emitter()
|
||||||
0
src/err.py
Normal file
0
src/err.py
Normal file
110
src/fields.py
Normal file
110
src/fields.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ EncryptedField
|
||||||
|
|
||||||
|
Parent class for other db classes that is
|
||||||
|
|
||||||
|
in charge of keeping all the stored values
|
||||||
|
|
||||||
|
encrypted and decrypting them when they
|
||||||
|
|
||||||
|
are queried. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
class EncryptedField(peewee.Field):
|
||||||
|
|
||||||
|
def on_save(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def on_load(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ UsernameField
|
||||||
|
|
||||||
|
Username field to ensure that all usernames
|
||||||
|
|
||||||
|
are at least 5 characters long and only have
|
||||||
|
|
||||||
|
letters and numbers. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
class UsernameField(peewee.Field):
|
||||||
|
|
||||||
|
def on_save(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def on_load(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ PasswordField
|
||||||
|
|
||||||
|
Field for managing passwords. Passowrds must
|
||||||
|
|
||||||
|
be at least 8 characters long with numbers,
|
||||||
|
|
||||||
|
letters, and symbols. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
class PasswordField(EncryptedField):
|
||||||
|
|
||||||
|
def on_save(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def on_load(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ EmailField
|
||||||
|
|
||||||
|
Field for storing emails. There is validation at
|
||||||
|
|
||||||
|
the character level and sending an email
|
||||||
|
|
||||||
|
verification level. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
class EmailField(peewee.Field):
|
||||||
|
|
||||||
|
def on_save(self, value):
|
||||||
|
|
||||||
|
# r = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
|
||||||
|
|
||||||
|
# if not re.match(r, value):
|
||||||
|
|
||||||
|
# raise ValidationError()
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def on_load(self, value):
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ CharField
|
||||||
|
|
||||||
|
Declaring peewee's CharField here. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
CharField = peewee.CharField
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ IntField
|
||||||
|
|
||||||
|
Declaring peewee's IntegerField here. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
IntField = peewee.IntegerField
|
||||||
45
src/file.py
Normal file
45
src/file.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
def build_path(path):
|
||||||
|
|
||||||
|
path = path.split("/")
|
||||||
|
|
||||||
|
path = os.path.join(conf["public"], *path)
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
|
||||||
|
path += "index.html"
|
||||||
|
|
||||||
|
mime = mimetypes.guess_type(path)[0]
|
||||||
|
|
||||||
|
return path, mime
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 get_ui_file
|
||||||
|
|
||||||
|
Get a file in the ui folder. Files in the ui
|
||||||
|
|
||||||
|
folder are used by the browser. \
|
||||||
|
|
||||||
|
> path [str] The relative path to the file \
|
||||||
|
|
||||||
|
⮑ [None | dict] \
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_ui_file(path):
|
||||||
|
|
||||||
|
path, mime = build_path(path)
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
|
||||||
|
with open(path, "rb") as file:
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
"mime": mime,
|
||||||
|
|
||||||
|
"content": file.read()
|
||||||
|
|
||||||
|
}
|
||||||
36
src/get.py
Normal file
36
src/get.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 select
|
||||||
|
|
||||||
|
Retreive data from the database \
|
||||||
|
|
||||||
|
> table [str] Name of the table to select from \
|
||||||
|
|
||||||
|
> columns [list] The columns to select from \
|
||||||
|
|
||||||
|
> conditions [dict] Key, values to conditions \
|
||||||
|
|
||||||
|
⮑ [list] list of instances \
|
||||||
|
|
||||||
|
@route
|
||||||
|
|
||||||
|
"""
|
||||||
|
def select(table, columns, conditions):
|
||||||
|
|
||||||
|
model = models[table]
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
|
|
||||||
|
fields.append(getattr(model, column))
|
||||||
|
|
||||||
|
query = model.select(*fields)
|
||||||
|
|
||||||
|
for field, value in conditions:
|
||||||
|
|
||||||
|
query.where(getattr(model, field) == value)
|
||||||
|
|
||||||
|
return list(query.dicts())
|
||||||
28
src/handle.py
Normal file
28
src/handle.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ handlers
|
||||||
|
|
||||||
|
Where the various handlers go. One for each
|
||||||
|
|
||||||
|
subdomain. For instance, there is a specific
|
||||||
|
|
||||||
|
handler for websocket requests. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
handlers = {}
|
||||||
|
|
||||||
|
|
||||||
|
def make_handlers():
|
||||||
|
|
||||||
|
for name, note in notes.items():
|
||||||
|
|
||||||
|
for tag in doc.read_tags(note):
|
||||||
|
|
||||||
|
if tag == "handler":
|
||||||
|
|
||||||
|
handler = GET_LIB_REF(name)
|
||||||
|
|
||||||
|
handlers[name] = handler
|
||||||
|
|
||||||
|
print(f"Handlers: {handlers.keys()}")
|
||||||
10
src/lib.py
Normal file
10
src/lib.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import doc
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def GET_LIB_REF(name):
|
||||||
|
|
||||||
|
return getattr(sys.modules[__name__], name)
|
||||||
|
|
||||||
|
notes = {}
|
||||||
|
|
||||||
|
__all__ = ["notes", "GET_LIB_REF", "doc"]
|
||||||
56
src/link.py
Normal file
56
src/link.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import os
|
||||||
|
import ast
|
||||||
|
import lib
|
||||||
|
import doc
|
||||||
|
import parse
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
queue = os.listdir()
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
|
||||||
|
file = queue.pop(0)
|
||||||
|
|
||||||
|
if file.endswith(".py"):
|
||||||
|
|
||||||
|
with open(file) as f:
|
||||||
|
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if content.startswith("from lib import *"):
|
||||||
|
|
||||||
|
tree = ast.parse(content)
|
||||||
|
|
||||||
|
checker = parse.DependencyChecker(set(lib.__all__))
|
||||||
|
|
||||||
|
checker.visit(tree)
|
||||||
|
|
||||||
|
if checker.undefined:
|
||||||
|
|
||||||
|
if file in seen:
|
||||||
|
|
||||||
|
print(f"Seen {file}, {checker.undefined}")
|
||||||
|
|
||||||
|
queue.append(file)
|
||||||
|
|
||||||
|
seen.add(file)
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Importing {file}")
|
||||||
|
|
||||||
|
module = importlib.import_module(file[:-3])
|
||||||
|
|
||||||
|
for note in doc.read_notes(content):
|
||||||
|
|
||||||
|
name = doc.read_name(note)
|
||||||
|
|
||||||
|
if hasattr(module, name):
|
||||||
|
|
||||||
|
setattr(lib, name, getattr(module, name))
|
||||||
|
|
||||||
|
lib.__all__.append(name)
|
||||||
|
|
||||||
|
lib.notes[name] = note
|
||||||
40
src/loop.py
Normal file
40
src/loop.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
runners = {}
|
||||||
|
|
||||||
|
|
||||||
|
def make_runners():
|
||||||
|
|
||||||
|
for name, note in notes.items():
|
||||||
|
|
||||||
|
if "runner" in doc.read_tags(note):
|
||||||
|
|
||||||
|
runner = GET_LIB_REF(name)
|
||||||
|
|
||||||
|
runners[name] = runner
|
||||||
|
|
||||||
|
print(f"Runners: {runners.keys()}")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_exception(task):
|
||||||
|
|
||||||
|
print(f"Task done: {task.result}")
|
||||||
|
|
||||||
|
|
||||||
|
async def start_runners():
|
||||||
|
|
||||||
|
for name, runner in runners.items():
|
||||||
|
|
||||||
|
task = asyncio.create_task(runner())
|
||||||
|
|
||||||
|
task.add_done_callback(handle_exception)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
|
||||||
|
asyncio.run(start_runners())
|
||||||
19
src/main.py
Normal file
19
src/main.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import link
|
||||||
|
|
||||||
|
from api import make_routes
|
||||||
|
|
||||||
|
from models import make_models
|
||||||
|
|
||||||
|
from handle import make_handlers
|
||||||
|
|
||||||
|
from loop import run, make_runners
|
||||||
|
|
||||||
|
make_routes()
|
||||||
|
|
||||||
|
# make_models()
|
||||||
|
|
||||||
|
make_handlers()
|
||||||
|
|
||||||
|
make_runners()
|
||||||
|
|
||||||
|
run()
|
||||||
42
src/models.py
Normal file
42
src/models.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ models
|
||||||
|
|
||||||
|
The models of the database. Each model represents
|
||||||
|
|
||||||
|
a table and is the interface to work with to
|
||||||
|
|
||||||
|
communicate with the db. \
|
||||||
|
|
||||||
|
"""
|
||||||
|
models = {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_model(table, columns):
|
||||||
|
|
||||||
|
fields = {}
|
||||||
|
|
||||||
|
for name, valid, about in columns:
|
||||||
|
|
||||||
|
fields[name] = GET_LIB_REF(valid)
|
||||||
|
|
||||||
|
return type(table, (Base,), fields)
|
||||||
|
|
||||||
|
|
||||||
|
def make_models():
|
||||||
|
|
||||||
|
for name, note in notes.items():
|
||||||
|
|
||||||
|
if note.startswith("𝌏"):
|
||||||
|
|
||||||
|
columns = doc.read_params(note)
|
||||||
|
|
||||||
|
model = create_model(name, columns)
|
||||||
|
|
||||||
|
model.create_table()
|
||||||
|
|
||||||
|
models[name] = model
|
||||||
|
|
||||||
|
print(f"Models: {models.keys()}")
|
||||||
106
src/order.py
Normal file
106
src/order.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝌏 Order
|
||||||
|
|
||||||
|
A customer order. Very basic for now, just takes in an
|
||||||
|
|
||||||
|
email and a product. The product is the ID of a
|
||||||
|
|
||||||
|
registered product in the database \
|
||||||
|
|
||||||
|
`
|
||||||
|
<
|
||||||
|
|
||||||
|
< [number] />
|
||||||
|
|
||||||
|
< [product.name] />
|
||||||
|
|
||||||
|
< [description] />
|
||||||
|
|
||||||
|
< [format(timestamp)] />
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
> number [UidField] A unique random number \
|
||||||
|
|
||||||
|
> email [str] Email for order fulfillment and tracking \
|
||||||
|
|
||||||
|
> decription [CharField] Brief description or justification \
|
||||||
|
|
||||||
|
> product [Product] Product ID from the Products model \
|
||||||
|
|
||||||
|
> timestamp [FloatField] time.time of order placement \
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 place_order
|
||||||
|
|
||||||
|
Place an order for a product. Creates an order in the
|
||||||
|
|
||||||
|
database so that the billing runner will handle it \
|
||||||
|
|
||||||
|
> email [str] An email to connect to the order \
|
||||||
|
|
||||||
|
> product [int] A product from the Products table \
|
||||||
|
|
||||||
|
"""
|
||||||
|
def place_order(email, product):
|
||||||
|
|
||||||
|
number = uuid.uuid4()
|
||||||
|
|
||||||
|
timestamp = time.time()
|
||||||
|
|
||||||
|
dt = datetime.fromtimestamp(timestamp)
|
||||||
|
|
||||||
|
formatted = dt.strftime("%b %d, %Y at %-I:%M %p")
|
||||||
|
|
||||||
|
desc = f"Order {number} placed on {formatted}"
|
||||||
|
|
||||||
|
create("Order", {
|
||||||
|
|
||||||
|
"email": email,
|
||||||
|
|
||||||
|
"product": product,
|
||||||
|
|
||||||
|
"number": number,
|
||||||
|
|
||||||
|
"timestamp": timestamp,
|
||||||
|
|
||||||
|
"description": desc
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 bill
|
||||||
|
|
||||||
|
Manage outstanding orders by attempting to
|
||||||
|
|
||||||
|
collect payments on time (referring to the
|
||||||
|
|
||||||
|
payment plans), also handling any payment
|
||||||
|
|
||||||
|
errors. \
|
||||||
|
|
||||||
|
@runner
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def bill():
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
orders = select("Orders", [], [])
|
||||||
|
|
||||||
|
for order in orders:
|
||||||
|
|
||||||
|
if order["paid"]:
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
product = select("Product", [], ("id", order["product"]))
|
||||||
196
src/parse.py
Normal file
196
src/parse.py
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
import ast
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyChecker(ast.NodeVisitor):
|
||||||
|
|
||||||
|
def __init__(self, env):
|
||||||
|
|
||||||
|
self.undefined = []
|
||||||
|
|
||||||
|
self.scopes = [set(dir(builtins))]
|
||||||
|
|
||||||
|
self.scopes.append(env)
|
||||||
|
|
||||||
|
|
||||||
|
def visit_Import(self, node):
|
||||||
|
|
||||||
|
curr_scope = self.scopes[-1]
|
||||||
|
|
||||||
|
for name in node.names:
|
||||||
|
|
||||||
|
curr_scope.add(name.name)
|
||||||
|
|
||||||
|
|
||||||
|
def visit_ImportFrom(self, node):
|
||||||
|
|
||||||
|
curr_scope = self.scopes[-1]
|
||||||
|
|
||||||
|
for name in node.names:
|
||||||
|
|
||||||
|
if name.name == "*":
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
curr_scope.add(name.name)
|
||||||
|
|
||||||
|
|
||||||
|
def visit_Assign(self, node):
|
||||||
|
|
||||||
|
curr_scope = self.scopes[-1]
|
||||||
|
|
||||||
|
for target in node.targets:
|
||||||
|
|
||||||
|
if isinstance(target, ast.Name):
|
||||||
|
|
||||||
|
curr_scope.add(target.id)
|
||||||
|
|
||||||
|
elif isinstance(target, ast.Tuple):
|
||||||
|
|
||||||
|
for name in target.elts:
|
||||||
|
|
||||||
|
curr_scope.add(name.id)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
|
||||||
|
def visit_For(self, node):
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
if isinstance(node.target, ast.Name):
|
||||||
|
|
||||||
|
new_scope.add(node.target.id)
|
||||||
|
|
||||||
|
elif isinstance(node.target, ast.Tuple):
|
||||||
|
|
||||||
|
for name in node.target.elts:
|
||||||
|
|
||||||
|
new_scope.add(name.id)
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_With(self, node):
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
for item in node.items:
|
||||||
|
|
||||||
|
name = item.optional_vars
|
||||||
|
|
||||||
|
new_scope.add(name.id)
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_While(self, node):
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_If(self, node):
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_FunctionDef(self, node):
|
||||||
|
|
||||||
|
curr_scope = self.scopes[-1]
|
||||||
|
|
||||||
|
curr_scope.add(node.name)
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
for arg in node.args.args:
|
||||||
|
|
||||||
|
new_scope.add(arg.arg)
|
||||||
|
|
||||||
|
if node.args.vararg:
|
||||||
|
|
||||||
|
new_scope.add(node.args.vararg.arg)
|
||||||
|
|
||||||
|
if node.args.kwarg:
|
||||||
|
|
||||||
|
new_scope.add(node.args.kwarg.arg)
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_AsyncFunctionDef(self, node):
|
||||||
|
|
||||||
|
self.visit_FunctionDef(node)
|
||||||
|
|
||||||
|
|
||||||
|
def visit_ClassDef(self, node):
|
||||||
|
|
||||||
|
curr_scope = self.scopes[-1]
|
||||||
|
|
||||||
|
curr_scope.add(node.name)
|
||||||
|
|
||||||
|
new_scope = set()
|
||||||
|
|
||||||
|
self.scopes.append(new_scope)
|
||||||
|
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def visit_Name(self, node):
|
||||||
|
|
||||||
|
for scope in self.scopes:
|
||||||
|
|
||||||
|
if node.id in scope:
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
self.undefined.append(node.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# import os
|
||||||
|
|
||||||
|
# import ast
|
||||||
|
|
||||||
|
# for file in ["api.py"]:
|
||||||
|
|
||||||
|
# if file.endswith(".py"):
|
||||||
|
|
||||||
|
# print(file)
|
||||||
|
|
||||||
|
# with open(file) as f:
|
||||||
|
|
||||||
|
# content = f.read()
|
||||||
|
|
||||||
|
# tree = ast.parse(content)
|
||||||
|
|
||||||
|
# checker = DependencyChecker(set())
|
||||||
|
|
||||||
|
# checker.visit(tree)
|
||||||
|
|
||||||
|
# input(checker.undefined)
|
||||||
32
src/product.py
Normal file
32
src/product.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝌏 Product
|
||||||
|
|
||||||
|
Something that Crimata is selling. A product represents
|
||||||
|
|
||||||
|
the thing the customer will get in return for their
|
||||||
|
|
||||||
|
purchase. \
|
||||||
|
|
||||||
|
`
|
||||||
|
<
|
||||||
|
|
||||||
|
<name>
|
||||||
|
|
||||||
|
<photo>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
> name [CharField] Name of the product \
|
||||||
|
|
||||||
|
> photo [PhotoField] Preview proto of the product \
|
||||||
|
|
||||||
|
> description [CharField] Brief about \
|
||||||
|
|
||||||
|
"""
|
||||||
99
src/route.py
Normal file
99
src/route.py
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
def step(path):
|
||||||
|
|
||||||
|
for part in path.strip("/").split("/"):
|
||||||
|
|
||||||
|
yield part
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 api
|
||||||
|
|
||||||
|
Handles all http(s) requests by parsing the path
|
||||||
|
|
||||||
|
to find the route, calling the route, and
|
||||||
|
|
||||||
|
wrapping the return in a response object. \
|
||||||
|
|
||||||
|
> request [web.Request] aiohttp Request \
|
||||||
|
|
||||||
|
⮑ [web.Response] aiohttp Response object \
|
||||||
|
|
||||||
|
@handler
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def api(request):
|
||||||
|
|
||||||
|
path = step(request.path)
|
||||||
|
|
||||||
|
func = routes[next(path)]
|
||||||
|
|
||||||
|
args = await request.json()
|
||||||
|
|
||||||
|
res = web.Response(headers=conf["cors"])
|
||||||
|
|
||||||
|
res.body = func(args)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 ws
|
||||||
|
|
||||||
|
Handles all websocket requests. Upgrades the request
|
||||||
|
|
||||||
|
to the websocket protocol and then calls the
|
||||||
|
|
||||||
|
handler. \
|
||||||
|
|
||||||
|
> request [web.Request] aiohttp Request object
|
||||||
|
|
||||||
|
@handler
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def ws(request):
|
||||||
|
|
||||||
|
path = step(request.path)
|
||||||
|
|
||||||
|
func = routes[next(path)]
|
||||||
|
|
||||||
|
ws = web.WebSocketResponse(protocols=("chat"))
|
||||||
|
|
||||||
|
await ws.prepare(request)
|
||||||
|
|
||||||
|
args = await ws.receive_json()
|
||||||
|
|
||||||
|
await func(ws, **args)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 _
|
||||||
|
|
||||||
|
Handles all http(s) file requests, like when the
|
||||||
|
|
||||||
|
browser requests the html for the UI. \
|
||||||
|
|
||||||
|
> request [web.Request] aiohttp request object \
|
||||||
|
|
||||||
|
⮑ [web.Response] aiohttp response object \
|
||||||
|
|
||||||
|
@handler
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def _(request):
|
||||||
|
|
||||||
|
file = get_ui_file(request.path)
|
||||||
|
|
||||||
|
if not file:
|
||||||
|
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
res = web.Response()
|
||||||
|
|
||||||
|
res.body = file["content"]
|
||||||
|
|
||||||
|
res.headers["Content-Type"] = file["mime"]
|
||||||
|
|
||||||
|
return res
|
||||||
60
src/serve.py
Normal file
60
src/serve.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
def get_subdomain(host):
|
||||||
|
|
||||||
|
res = "_"
|
||||||
|
|
||||||
|
parts = host.split(".")
|
||||||
|
|
||||||
|
if len(parts) >= 2:
|
||||||
|
|
||||||
|
res = parts[0]
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
async def handler(request):
|
||||||
|
|
||||||
|
if request.method == "OPTIONS":
|
||||||
|
|
||||||
|
return web.Response(headers=conf["cors"])
|
||||||
|
|
||||||
|
sub = get_subdomain(request.host)
|
||||||
|
|
||||||
|
func = handlers[sub]
|
||||||
|
|
||||||
|
res = await func(request)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 lift
|
||||||
|
|
||||||
|
Handles all incoming web requests. \
|
||||||
|
|
||||||
|
> port [int] The port to listen on \
|
||||||
|
|
||||||
|
@runner
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def lift():
|
||||||
|
|
||||||
|
port = conf["port"]
|
||||||
|
|
||||||
|
server = web.Server(handler)
|
||||||
|
|
||||||
|
runner = web.ServerRunner(server)
|
||||||
|
|
||||||
|
await runner.setup()
|
||||||
|
|
||||||
|
site = web.TCPSite(runner, None, port)
|
||||||
|
|
||||||
|
await site.start()
|
||||||
|
|
||||||
|
print(f"Running on port {port}!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
20
src/ui/index.html
Normal file
20
src/ui/index.html
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="./main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="./req.js"></script>
|
||||||
|
<script src="./watch.js"></script>
|
||||||
|
|
||||||
|
<!-- vue@3.4.21 -->
|
||||||
|
<script src="./vue.js"></script>
|
||||||
|
|
||||||
|
<script type="module" src="./index.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
src/ui/index.js
Normal file
10
src/ui/index.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Main from "./main.js";
|
||||||
|
|
||||||
|
|
||||||
|
console.log(Vue.version);
|
||||||
|
|
||||||
|
|
||||||
|
const app = Vue.createApp(Main);
|
||||||
|
|
||||||
|
|
||||||
|
app.mount("#root");
|
||||||
0
src/ui/main.css
Normal file
0
src/ui/main.css
Normal file
57
src/ui/main.js
Normal file
57
src/ui/main.js
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import Account from "./account.js";
|
||||||
|
|
||||||
|
const Main = {
|
||||||
|
|
||||||
|
components: {
|
||||||
|
|
||||||
|
Account
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const accounts = watch("Account");
|
||||||
|
|
||||||
|
const cards = watch("Card");
|
||||||
|
|
||||||
|
const orders = watch("Order");
|
||||||
|
|
||||||
|
const products = watch("Products");
|
||||||
|
|
||||||
|
reqBase({
|
||||||
|
|
||||||
|
path: "/create_account",
|
||||||
|
|
||||||
|
args: {
|
||||||
|
|
||||||
|
username: "gundy97",
|
||||||
|
|
||||||
|
email: "adgundersen@gmail.com"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accounts, cards, orders, products };
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
template: `
|
||||||
|
<div class="main">
|
||||||
|
|
||||||
|
<Account v-for="account in accounts" v-bind="account" />
|
||||||
|
|
||||||
|
<Card v-for="card in cards" v-bind="card" />
|
||||||
|
|
||||||
|
<Orders v-for="order in orders" v-bind="order" />
|
||||||
|
|
||||||
|
<Product v-for="product in products" v-bind="product" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default Main;
|
||||||
43
src/ui/req.js
Normal file
43
src/ui/req.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
const reqBase = async ({ path, args }) => {
|
||||||
|
|
||||||
|
const url = buildRequestURL("api", path);
|
||||||
|
|
||||||
|
let res = await fetch(url, {
|
||||||
|
|
||||||
|
method: "post",
|
||||||
|
|
||||||
|
body: JSON.stringify(args)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const wsBase = ({ path, message, cb }) => {
|
||||||
|
|
||||||
|
const url = buildRequestURL("ws", path);
|
||||||
|
|
||||||
|
const ws = new WebSocket(url, ["chat", window.session]);
|
||||||
|
|
||||||
|
ws.onopen = (e) => {
|
||||||
|
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
|
||||||
|
cb(JSON.parse(e.data));
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const buildRequestURL = (sub, path) => {
|
||||||
|
|
||||||
|
return `http://${sub}.localhost:8080${path}`;
|
||||||
|
|
||||||
|
};
|
||||||
16669
src/ui/vue.js
Normal file
16669
src/ui/vue.js
Normal file
File diff suppressed because it is too large
Load diff
63
src/ui/watch.js
Normal file
63
src/ui/watch.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
const watch = (table) => {
|
||||||
|
|
||||||
|
ref = Vue.ref([]);
|
||||||
|
|
||||||
|
wsBase({
|
||||||
|
|
||||||
|
path: "/watch",
|
||||||
|
|
||||||
|
message: {
|
||||||
|
|
||||||
|
"table": table
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
cb: (method, data) => {
|
||||||
|
|
||||||
|
if (method == "set") {
|
||||||
|
|
||||||
|
ref.value = data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "create") {
|
||||||
|
|
||||||
|
ref.value.push(data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "update") {
|
||||||
|
|
||||||
|
const instance = ref.value.find(
|
||||||
|
|
||||||
|
instance => {
|
||||||
|
|
||||||
|
return instance.id == data.id;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(instance, data.diff);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "remove") {
|
||||||
|
|
||||||
|
ref.value.remove(data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
|
||||||
|
};
|
||||||
54
src/users.py
Normal file
54
src/users.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝌏 Account
|
||||||
|
|
||||||
|
A user's account with basic profile info \
|
||||||
|
|
||||||
|
`
|
||||||
|
<
|
||||||
|
|
||||||
|
<img .avatar [photo] />
|
||||||
|
|
||||||
|
< .info
|
||||||
|
|
||||||
|
< [name] />
|
||||||
|
|
||||||
|
< [email] />
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
> username [UsernameField] Typical username behavior \
|
||||||
|
|
||||||
|
> email [EmailField] Ideally verified \
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 create_account
|
||||||
|
|
||||||
|
Create a new account in the system. Takes in basic
|
||||||
|
|
||||||
|
profile info like username, password, etc.
|
||||||
|
|
||||||
|
> username [str]
|
||||||
|
|
||||||
|
> email [str]
|
||||||
|
|
||||||
|
⮑ [int] the unique account number
|
||||||
|
|
||||||
|
@route
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def create_account(username, email):
|
||||||
|
|
||||||
|
number = uuid.uuid4()
|
||||||
|
|
||||||
|
create("Account", locals())
|
||||||
|
|
||||||
|
return number
|
||||||
20
src/util.py
Normal file
20
src/util.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
|
||||||
|
def load_file(path, options="r"):
|
||||||
|
|
||||||
|
with open(path, options) as file:
|
||||||
|
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(path):
|
||||||
|
|
||||||
|
return yaml.safe_load(load_file(path))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 conf
|
||||||
|
|
||||||
|
"""
|
||||||
|
conf = load_yaml("../conf.yml")
|
||||||
56
src/vendor.py
Normal file
56
src/vendor.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ os
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ time
|
||||||
|
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ datetime
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ yaml
|
||||||
|
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ peewee
|
||||||
|
|
||||||
|
"""
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ asyncio
|
||||||
|
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ mimetypes
|
||||||
|
|
||||||
|
"""
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ web
|
||||||
|
|
||||||
|
"""
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
$ uuid
|
||||||
|
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
29
src/watch.py
Normal file
29
src/watch.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
𝑓 watch
|
||||||
|
|
||||||
|
Get live updates on a table in the db \
|
||||||
|
|
||||||
|
> table [str] the table in the db to track \
|
||||||
|
|
||||||
|
@route
|
||||||
|
|
||||||
|
"""
|
||||||
|
async def watch(conn, table):
|
||||||
|
|
||||||
|
def cb(method, data):
|
||||||
|
|
||||||
|
asyncio.create_task(conn.send_json(locals()))
|
||||||
|
|
||||||
|
instances = select(table, [], [])
|
||||||
|
|
||||||
|
cb("set", instances)
|
||||||
|
|
||||||
|
ipc.on(table, cb)
|
||||||
|
|
||||||
|
while not conn.closed:
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
ipc.remove_listener(table, cb)
|
||||||
Loading…
Reference in a new issue