use bottle version as main

This commit is contained in:
Dita Aji Pratama 2024-06-06 00:32:46 +07:00
commit de1b5918b2
39 changed files with 96 additions and 454 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "templates/plain"]
path = templates/plain
url = https://gitea.ditaajipratama.net/aji/costapy-template-plain.git

129
README.md
View File

@ -1,6 +1,5 @@
# CostaPy # CostaPy
Python Web Framework. Build with CherryPy and Mako. Python Web Framework. Build with Bottle and Mako.
Forked from [Pytavia](https://github.com/sidonesia/pytavia). But now it have a huge difference in the structure.
## License ## License
@ -24,12 +23,9 @@ along with this program. If not, see https://www.gnu.org/licenses/.
## Requirement & Installation ## Requirement & Installation
You need this libraries to use CostaPy: You need this libraries to use CostaPy:
- cherrypy - bottle
- cherrypy-cors - gunicorn
- mako - mako
- mysql-connector
- bcrypt
- pyjwt[crypto]
You can install it with run this command You can install it with run this command
@ -39,138 +35,35 @@ Here is the completed command
sudo apt-get install -y python3-pip sudo apt-get install -y python3-pip
pip install --upgrade pip pip install --upgrade pip
pip install cherrypy pip install bottle
pip install cherrypy-cors pip install gunicorn
pip install mako pip install mako
pip install mysql-connector
pip install bcrypt
pip install pyjwt[crypto]
## Usage ## Usage
Use this command to start the web service Use this command to start the web service
python<ver> costa.py <ip_address> <port> <service_name> python3 costa.py
For an example like this
python3 costa.py localhost 80 My_Service
You can use nohup too and running it in the background like this You can use nohup too and running it in the background like this
nohup python3 costa.py localhost 80 My_Service & nohup python3 costa.py &
## Configuration ## Configuration
### Server (config/server.py)
tools.sessions.on </br>
Default: True </br>
Description: Enable sessions </br>
engine.autoreload.on </br>
Default: False </br>
Description: Auto Reload when source code change. Don't use it in production. </br>
request.show_tracebacks </br>
Default: False </br>
Description: Show traceback for debugging in development purposes. </br>
### Global Variable (config/globalvar.py) ### Global Variable (config/globalvar.py)
`directory.py` is the place for storing your Global Variable. `globalvar.py` is the place for storing your Global Variable.
GV_base_url </br> `baseurl` </br>
Is the variable for your base URL (without `/` in the end). Is the variable for your base URL (without `/` in the end).
GV_title </br> `title` </br>
Is the variable for your web title. Is the variable for your web title.
### Directory (config/directory.py) ### Directory (config/directory.py)
`directory.py` is the place for storing your path. It is useful to calling the path more efficiently. there is 2 method that you can store your path. store it in variable for templating configuration, and store it as object for routing the url. `directory.py` is the place for storing your path. It is useful to calling the path more efficiently.
This is example that use for templating
html_user = "static/pages-user"
template_user = "static/template-user"
And this is example that use for routing the url
dirconfig = {
'/' :
{
'tools.sessions.on' : True ,
'tools.staticdir.root' : os.path.abspath(os.getcwd()) ,
},
'/your_dir' :
{
'tools.staticdir.on' : True ,
'tools.staticdir.dir' : './static/your-dir' ,
},
}
### Templating (config/template.py)
Templating is useful when you had more than 1 website template for difference use case. For an example, when you had user and admin in the use case, the website for user have a navbar and footer, and the website for admin have a navbar and sidebar.
Before you create a template, make sure your `directory` configuration is ready for storing templates and pages. For an example:
html_user = "static/pages-user"
template_user = "static/template-user"
To create the template, you need to insert this code in `def __init__(self)`
self.html_pages_user = html.main.get_html(directory.html_user)
self.html_template_user = html.main.get_html(directory.template_user)
if you had admin template, you just need to add the code. for the example like this
self.html_pages_user = html.main.get_html(directory.html_user)
self.html_template_user = html.main.get_html(directory.template_user)
self.html_pages_admin = html.main.get_html(directory.html_admin)
self.html_template_admin = html.main.get_html(directory.template_admin)
and then you need create function for each of your template in main class like this
def user(self, page):
params_list = {
"template" : self.html_template_user ["user.html" ] ,
"topnav" : self.html_template_user ["user-topnav.html" ] ,
"container" : self.html_pages_user [page+".html" ]
}
return params_list
### Database (config/database.py)
This is the sample template for configure it
db_default = {
'host' : 'localhost',
'user' : 'root',
'password' : '',
'database' : 'your_db',
'autocommit' : True,
}
You also can make more than 1 database configuration like this
db_default = {
'host' : 'localhost',
'user' : 'root',
'password' : '',
'database' : 'your_db',
'autocommit' : True,
}
db_other = {
'host' : 'localhost',
'user' : 'root',
'password' : '',
'database' : 'other_db',
'autocommit' : True,
}
## Handling the modules ## Handling the modules

View File

0
config/database.py Executable file → Normal file
View File

View File

@ -1,30 +1,13 @@
import os from core import template
from core import templatestaticdir
# pages directory
page = { page = {
'public' :'page/public' , 'public' :'pages/public'
'error' :'page/error' # Non-template
} }
# public staticdir static = [
dirconfig = {
'/' :
{ {
'tools.sessions.on' : True , "route" :"/css/<filepath:re:.*\.(css|sass|css.map)>",
'tools.staticdir.root' : os.path.abspath(os.getcwd()) , "root" :"./static/css"
},
'/css' :
{
'tools.staticdir.on' : True ,
'tools.staticdir.dir' : './static/css' ,
},
'/js' :
{
'tools.staticdir.on' : True ,
'tools.staticdir.dir' : './static/js' ,
},
} }
]
# template staticdir: dirconfig dirtemplate template.add(static, "templates")
templatestaticdir.add(dirconfig, "templates")

14
config/globalvar.py Executable file → Normal file
View File

@ -1,24 +1,28 @@
baseurl = "http://localhost:81"
title = "CostaPy" title = "CostaPy"
menu = { menu = {
"public": { "public": {
"topnav": [ "navbar": [
{ {
"name":"Home", "name":"Home",
"target":"_self",
"href":"/", "href":"/",
"roles":["guest"] "roles":["guest"]
}, },
{ {
"name":"About", "name":"About",
"href":"#", "target":"_self",
"href":"/about",
"roles":["guest"] "roles":["guest"]
}, },
{ {
"name":"CostaPy Website", "name":"Docs",
"href":"https://costapy.ditaajipratama.com", "target":"_blank",
"href":"https://costapy.ditaajipratama.net",
"roles":["guest"] "roles":["guest"]
} }
] ]
} }
} }
copyright = "Copyright (C) 2022 Dita Aji Pratama"

View File

@ -1,20 +1,16 @@
from config import directory host = "localhost"
port = 15001
reloader = False
debug = False
server = 'gunicorn' # default = 'wsgiref'
update = { # cors
'server.socket_host' : "hostname" , # session
'server.socket_port' : "port" ,
'cors.expose.on' : True , # error page 403
'tools.sessions.on' : True , # error page 404
# error page 500
'engine.autoreload.on' : False , # max_request_body_size = 800 * 1024 * 1024 , # Multiply for 800MB result
'request.show_tracebacks' : False , # socket timeout = 60
# response timeout = 3600
'error_page.403' : f'{directory.page["error"]}/403.html' ,
'error_page.404' : f'{directory.page["error"]}/404.html' ,
'error_page.500' : f'{directory.page["error"]}/500.html' ,
'server.max_request_body_size' : 800 * 1024 * 1024 , # 800MB; Default 100MB
'server.socket_timeout' : 60 , # Default 10s
'response.timeout' : 3600 , # Default 300s
}

View File

9
core/staticdir.py Normal file
View File

@ -0,0 +1,9 @@
from bottle import Bottle, get, static_file
from config import directory
app = Bottle()
for items in directory.static:
@app.get(items['route'])
def static_items(filepath):
return static_file(filepath, root=items['root'])

View File

@ -1,9 +1,8 @@
import os import os
def add(dirconfig, template_directory): def add(dirconfig, template_directory):
template_directory = "templates"
template_list = [d for d in os.listdir(template_directory) if os.path.isdir(os.path.join(template_directory, d))] template_list = [d for d in os.listdir(template_directory) if os.path.isdir(os.path.join(template_directory, d))]
for template_name in template_list: for template_name in template_list:
template_module = __import__(f"{template_directory}.{template_name}.main", fromlist=["static"]) template_module = __import__(f"{template_directory}.{template_name}.main", fromlist=["static"])
for static in getattr(template_module, "static", []): for static in getattr(template_module, "static", []):
dirconfig[ static['name'] ] = static['value'] dirconfig.append(static)

View File

@ -1,40 +0,0 @@
import os
import cherrypy
def main(file, rename, directory):
try:
os.makedirs(directory, exist_ok=True)
except OSError as error:
print(error)
try:
upload_path = directory
upload_filename = file.filename
upload_rename = rename
upload_file = os.path.normpath(os.path.join(upload_path, upload_rename))
upload_size = 0
print("UPLOADING CORE: Directory: "+directory+"/"+rename)
if (os.path.isfile(directory+"/"+rename)):
print("UPLOADING CORE: Is exists! Removing!")
try:
os.remove(directory+"/"+rename)
except Exception as e:
print(f"UPLOADING CORE: removing failed: {e}")
else:
print("UPLOADING CORE: Is not exists! Removing skipped!")
with open(upload_file, 'wb') as upload_result:
while True:
data = file.file.read(8192)
if not data:
break
upload_result.write(data)
upload_size += len(data)
print("UPLOAD PATH: " + str(upload_path))
print("UPLOAD FILENAME: " + str(upload_filename))
print("UPLOAD RENAME: " + str(upload_rename))
print("UPLOAD FILE: " + str(upload_file))
print("UPLOAD SIZE: " + str(upload_size))
except Exception as e:
print(f"ERROR CORE UPLOADING: {e}")

View File

@ -6,27 +6,22 @@
# You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. # You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
import sys import sys
import cherrypy from bottle import Bottle, run
import cherrypy_cors
import handler import handler
from core import staticdir
from config import server from config import server
from config import directory
if __name__ == '__main__': app = Bottle()
dirconfig = directory.dirconfig app.merge(handler.app)
update = server.update app.merge(staticdir.app)
if len(sys.argv) >= 3: run(app,
host = server.host,
update["server.socket_host"] = sys.argv[1] port = server.port,
update["server.socket_port"] = int(sys.argv[2]) reloader = server.reloader,
server = server.server,
cherrypy_cors.install() debug = server.debug
cherrypy.config.update ( update ) )
cherrypy.quickstart ( handler.handler(), config = dirconfig )
else:
print ("Usage : python<ver> costa.py <ip_address> <port> <service_name>")
print ("Example : python3 costa.py localhost 81 CostaPySample")

23
handler.py Executable file → Normal file
View File

@ -5,20 +5,19 @@
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. # You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
import cherrypy from bottle import Bottle, route
import json from config import directory
import config.directory as directory
import templates.bare.main as bare
import templates.plain.main as template_public
import modules.public.home as public_home import modules.public.home as public_home
@cherrypy.tools.accept(media="application/json") app = Bottle()
class handler():
def index(self, **kwargs): @app.route('/')
kwargs["mako"] = { def index():
"website" : bare.main(directory.page["public"], "home") params = {
"mako":{
"website" : template_public.main(directory.page["public"], "home")
} }
return public_home.main().html(kwargs) }
index.exposed = True return public_home.main().html(params)

View File

@ -1,8 +1,5 @@
sudo apt-get install -y python3-pip sudo apt-get install -y python3-pip
pip install --upgrade pip pip install --upgrade pip
pip install cherrypy pip install bottle
pip install cherrypy-cors pip install gunicorn
pip install mako pip install mako
pip install mysql-connector
pip install bcrypt
pip install pyjwt[crypto]

View File

View File

@ -1,5 +1,5 @@
from mako.template import Template from mako.template import Template
import config.globalvar as globalvar from config import globalvar
class main: class main:
@ -7,21 +7,18 @@ class main:
pass pass
def html(self, params): def html(self, params):
return Template(params["mako"]["website"]['template']).render( return Template(params["mako"]["website"]['index']).render(
title = globalvar.title, title = globalvar.title,
baseurl = globalvar.baseurl, header = "Welcome to CostaPy",
topnav = Template(params["mako"]["website"]['topnav']).render( navbar = Template(params["mako"]["website"]['navbar']).render(
title = globalvar.title, menu = globalvar.menu['public']['navbar'],
baseurl = globalvar.baseurl,
menu = globalvar.menu['public']['topnav'],
user_roles = ["guest"], user_roles = ["guest"],
active_page = "Home" active_page = "Home"
), ),
footer = Template(params["mako"]["website"]['footer']).render( footer = Template(params["mako"]["website"]['footer']).render(
copyright = "Dita Aji Pratama", copyright = globalvar.copyright,
), ),
container = Template(params["mako"]["website"]['container']).render( container = Template(params["mako"]["website"]['container']).render(
baseurl = globalvar.baseurl, greeting = f"Welcome to your new web application! This placeholder page is here to let you know that your web framework is successfully set up and ready to go. Now, it's time to start building your project. Dive into the documentation to explore the features and capabilities at your disposal."
greeting = f"Hello world, welcome to {globalvar.title}"
) )
) )

View File

@ -1 +0,0 @@
403 Forbidden

View File

@ -1 +0,0 @@
404 Not found

View File

@ -1 +0,0 @@
500 Internal server error

View File

@ -1,9 +0,0 @@
<div class="container-fluid my-3">
<div class="row">
<div class="col-lg-12">
<h1>Welcome</h1>
<h3>This is your first pages</h3>
<p>${greeting}</p>
</div>
</div>
</div>

1
pages/public/home.html Normal file
View File

@ -0,0 +1 @@
<p>${greeting}</p>

View File

View File

@ -1,13 +0,0 @@
import cherrypy
import cherrypy_cors
import json
def body_json():
result = None
if cherrypy.request.method == 'OPTIONS':
cherrypy_cors.preflight(allowed_methods=['GET', 'POST'])
if cherrypy.request.method == 'POST':
cherrypy.serving.response.headers['Content-Type'] = 'application/json'
body_request = cherrypy.request.body.read()
result = json.loads(body_request.decode())
return result

View File

@ -1,13 +0,0 @@
import datetime
def prcss(loc, msg):
print(f"[loggorilla][{datetime.datetime.now()}][\033[32mprcss\033[39m][\033[95m{loc}\033[39m] {msg}")
def accss(loc, msg):
print(f"[loggorilla][{datetime.datetime.now()}][\033[36maccss\033[39m][\033[95m{loc}\033[39m] {msg}")
def fyinf(loc, msg):
print(f"[loggorilla][{datetime.datetime.now()}][\033[93mfyinf\033[39m][\033[95m{loc}\033[39m] {msg}")
def error(loc, msg):
print(f"[loggorilla][{datetime.datetime.now()}][\033[31merror\033[39m][\033[95m{loc}\033[39m] {msg}")

View File

@ -1,23 +0,0 @@
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
def smtp(config):
msg = MIMEMultipart('alternative')
msg['Subject' ] = config['subject' ]
msg['From' ] = config['from' ]
msg['To' ] = config['to' ]
part1 = MIMEText(config['text'], 'plain')
part2 = MIMEText(config['html'], 'html' )
msg.attach(part1)
msg.attach(part2)
smtp_server = smtplib.SMTP(config['server']['host'], config['server']['port'])
smtp_server.ehlo()
smtp_server.starttls()
smtp_server.login( config['login']['email'], config['login']['password'] )
smtp_server.sendmail('&&&&&&', config['to'], msg.as_string() )
smtp_server.quit()

View File

@ -1,23 +0,0 @@
from cryptography.hazmat.primitives import serialization
import jwt
def encode(payload, id_rsa, passphrase):
private_key = open(id_rsa, 'r').read()
key = serialization.load_ssh_private_key(private_key.encode(), password=passphrase)
token = jwt.encode(
payload = payload,
key = key,
algorithm = 'RS256'
)
return token
def decode(token, id_rsa):
public_key = open(id_rsa, 'r').read()
key = serialization.load_ssh_public_key(public_key.encode())
header = jwt.get_unverified_header(token)
payload = jwt.decode(
jwt = token,
key = key,
algorithms = [header['alg'], ]
)
return payload

View File

@ -1,3 +0,0 @@
<footer class="mt-auto bg-dark text-light p-3 text-center">
© 2022 ${copyright}
</footer>

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>${title}</title>
<script src="${baseurl}/bare/lib/jquery/jquery-3.7.0.min.js"></script>
<link href="${baseurl}/bare/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<script src="${baseurl}/bare/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="${baseurl}/bare/css/style.css">
</head>
<body class="d-flex flex-column" style="min-height:100vh;">
${topnav}
<div class="mb-5">
${container}
</div>
${footer}
</body>
</html>

View File

@ -1,17 +0,0 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="${baseurl}">${title}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
% for item in menu:
% if any(role in item['roles'] for role in user_roles):
<li class="nav-item ${'active' if item['name'] == active_page else ''}">
<a class="nav-link" href="${item['href']}">${item['name']}</a>
</li>
% endif
% endfor
</ul>
</div>
</nav>

View File

@ -1,30 +0,0 @@
from core import html
static = [
{
'name':'/bare/lib',
'value':{
'tools.staticdir.on' : True ,
'tools.staticdir.dir' : './templates/bare/static/lib' ,
}
},
{
'name':'/bare/css',
'value':{
'tools.staticdir.on' : True ,
'tools.staticdir.dir' : './templates/bare/static/css' ,
}
}
]
def main(dir, page):
html_template = html.main.get_html("templates/bare/html")
html_page = html.main.get_html(dir)
params_list = {
"template" : html_template ["template.html" ] ,
"topnav" : html_template ["topnav.html" ] ,
"footer" : html_template ["footer.html" ] ,
"container" : html_page [ page+".html" ]
}
return params_list

View File

@ -1 +0,0 @@
/* your style here */

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2011-2020 Twitter, Inc.
Copyright (c) 2011-2020 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
templates/plain Submodule

@ -0,0 +1 @@
Subproject commit adcbeee71da67d013627b67855ce3351b3a847b7