The Python3 Posh Framework
A Python3 minimal framework to getting tasks done.
The Python3 Posh framework is a web framework to getting tasks done quickly. A 7 Python files framework and mycgi.py utilizing the pip package multipart.
Basically, once Apache2.4 and mod_cgi, mod_cgid are installed and ready to go, a Python3 Posh framework can get
you up and running in minutes complete with a database connected. There are 3 simple files that are at the core
of a Python3 Posh website on the client/Internet-side. The server-side has 4 simple files also, to 1. create cookie sessions, to 2. connect to a database, and 3. to process the cookie, and 4. to handle errors.
Finally, I will also use a custom class, mycgi.py, that works specifically with Python3 Apache2 cgi to allow to upload files because the cgi.py module is removed after Python3 version 3.13.
client/Internet side
These 3 files are to be located on the include_web_root variable path, for example, '/var/example/www/html/Example.com'
1. index page
2. init page
3. page or pages of content (this can be one page or many pages depending on your project)
Let's begin...
1. The index page
I will use Example.com as the domain name to get us started.
Therefore, your index is recommended to be called the following.
index_example_com.py
And create the page with the following code:
1. The index page
filename: index_example_com.py
#!/usr/bin/python3
import os
import sys
sys.stdout.reconfigure(encoding='utf-8')
from html import escape
include_server_side = '/var/example/www'
include_web_root = '/var/example/www/html/Example.com'
with open(f'''{include_server_side}/db_example_pg.py''') as f: exec(f.read())
cursor_content = database_content.cursor(cursor_factory = psycopg2.extras.RealDictCursor) # postgresql
cursor_data = database_data.cursor(cursor_factory = psycopg2.extras.RealDictCursor) # postgresql
# #cursor_content = database_content.cursor(pymysql.cursors.DictCursor) # mysql
# #cursor_data = database_data.cursor(pymysql.cursors.DictCursorr) # mysql
with open(f'''{include_server_side}/cookie_session_auth.py''') as f: exec(f.read())
session_cookie_value, session = session_cookie(session)
with open(f'''{include_server_side}/process_cookie.py''') as f: exec(f.read())
process_cookie_session()
with open(f'''{include_server_side}/mycgi.py''') as f: exec(f.read()) # forms, files
mycgi = MyCGI(os.environ)
mycgi.FieldStorage()
with open(f'''{include_web_root}/pages/display404.py''') as f: exec(f.read())
with open(f'''{include_server_side}/error_handler.py''') as f: exec(f.read())
sys.excepthook = exception_handler
with open(f'''{include_web_root}/example_init.py''') as f: exec(f.read())
mapper = Mapper(os.environ)
if __name__ == '__main__':
html = ''
print( session_cookie_value )
print("Content-Type:text/html;charset=utf-8;")
print()
if mapper.display404:
print(show404(''))
exit()
int_pages = ['login.py', 'sign_up.py']
if mapper.page in int_pages:
html += mapper.connect(mapper.page, True)
else:
html += mapper.connect('example.py', True)
print(html, end="")
2. The init page
filename: example_init.py
#!/usr/bin/python3
import os
import sys
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from html import escape
import urllib
MAX_ARGS = 4
class Mapper:
WEBSITE_DIRECTORY = ''
BIZ = {}
display404 = False
page = ''
def __init__(self, e):
self.WEBSITE_DIRECTORY = os.path.dirname(e["SCRIPT_FILENAME"])
v = []
v = e["REQUEST_URI"].split('/')
if e["REQUEST_URI"][-1] == '/': # make trailing slash optional
v.pop(len(v) - 1)
if v[0] == '': # first slash empty string, remove
v.pop(0)
v_len = len(v) # how many items, trailing / optional
if v_len >= 1:
self.page = v[0]
found_page = self.page.find('?')
if found_page != -1:
self.page = self.page[:found_page]
else:
self.page = 'example'
if v_len == 1: # /userName_companyName
self.BIZ['userName_companyName'] = v[0]
elif v_len == 2: # /product_name/product_id
self.BIZ['product_name'] = v[0]
self.BIZ['product_id'] = v[1]
elif v_len == 3: # /company/product_name/product-id/
self.BIZ['company'] = v[0]
self.BIZ['product_name'] = v[1]
self.BIZ['product_id'] = v[2]
elif v_len == MAX_ARGS: # /company/product_name/product-id/us/
self.BIZ['company'] = v[0]
self.BIZ['product_name'] = v[1]
self.BIZ['product_id'] = v[2]
self.BIZ['country'] = v[3]
elif v_len > MAX_ARGS:
pass
#self.display404 = True
if self.page == '':
self.page = 'example'
def connect(self, file, buffer_store=False):
if buffer_store:
with StringIO() as buf, redirect_stdout(buf), redirect_stderr(buf):
logger.addHandler(logging.StreamHandler(stream=buf))
with open(f'''{self.WEBSITE_DIRECTORY}/{file}''') as f:
exec(f.read())
return buf.getvalue()
else:
with open(f'''{self.WEBSITE_DIRECTORY}/{file}''') as f:
exec(f.read())
3. The actual page of content (can be several pages)
filename: example.py
#!/usr/bin/python3
def example(mapper):
html = ''
html += f'''
Example.com
Example.com
'''
if 'userName_companyName' in mapper.BIZ:
html += f'''BIZ['userName_companyName']: {mapper.BIZ['userName_companyName']} <br>'''
return html
html = example(mapper)
print(html, end="")
Server-side 4 simple files:
These 4 files are to be located on the include_server_side variable path, for example, '/var/example/www'
1. db_example_pg.py # to connect to a database
2. cookie_session_auth.py # to create a cookie (for a session)
3. process_cookie.py # to process the cookie to create a session
4. error_handler.py # to handle any error
1. The database connect file
filename: db_example_pg.py
#!/usr/bin/python3
import psycopg2
import psycopg2.extras
HOST = '127.0.0.1'
USERNAME = '' # sometimes this may be 'postgres'
PASSWORD = '' # some hard to guess password
DATABASE = 'example_content'
database_content = psycopg2.connect(f'''dbname={DATABASE} user={USERNAME} password={PASSWORD} host={HOST}''')
DATABASE = 'example_data'
database_data = psycopg2.connect(f'''dbname={DATABASE} user={USERNAME} password={PASSWORD} host={HOST}''')
# #mysql
# #import pymysql
# #database_content = pymysql.connect(HOST,USERNAME,PASSWORD,DATABASE)
# #database_data = pymysql.connect(HOST,USERNAME,PASSWORD,DATABASE)
2. The cookie create file
filename: cookie_session_auth.py
psuedo code
from http import cookies
write cookie method, function
create a Session class
utilize the cookies.SimpleCookie()
simple_cookie.output() # output with a generated a unique cookie session id
read cookie method, function
email me at: opensource3 at yahoo com
3. The cookie process file
filename: process_cookie.py
psudeo code
process cookie session method, function
connect to a db and some code
email me at: opensource3 at yahoo com
4. The error handle file
filename: error_handler.py
#!/usr/bin/python3
import logging
from datetime import datetime
log_filename = f'''{include_server_side}/tmp_errors/''' + datetime.now().strftime("%Y-%m-%d %H-%M-%S")
log_formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s')
logger = logging.getLogger()
fh = logging.FileHandler(filename=log_filename, mode='w', delay=True)
fh.setFormatter(log_formatter)
logger.addHandler(fh)
def exception_handler(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
# Let the system handle things like CTRL+C
sys.__excepthook__(*args)
logger.error('Exception: ', exc_info=(exc_type, exc_value, exc_traceback))
if os.path.exists(log_filename):
print(show404(''))
mycgi.py is utilized to replace the removed cgi.py module starting with Python 3.13
install the pip package multipart from https://github.com/defnull/multipart
pip install multipart
Note:
modify multipart.py line 15 to include MultiDict as so:
__all__ = ["MultiDict", "MultipartError", "MultipartParser", "MultipartPart", "parse_form_data"]
filename: mycgi.py
#!/usr/bin/python3
# The MIT License
# Copyright 2023 BestInternetSearch.com Inc.
# 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.
import os
import sys
from html import escape
import urllib
from multipart import MultipartParser, MultiDict, parse_options_header
forms, files = MultiDict(), MultiDict()
class MyCGI():
_os_environ = []
_escape = True
_data = {}
def __init__(self, os_environ, escape=True):
self._os_environ = os_environ
self._escape = escape
def FieldStorage(self):
e = self._os_environ
if 'REQUEST_METHOD' in e:
if e['REQUEST_METHOD'] == 'GET':
self._data = urllib.parse.parse_qs(e['QUERY_STRING'])
elif e['REQUEST_METHOD'] == 'POST':
if 'CONTENT_TYPE' in e:
content_type, options = parse_options_header(e['CONTENT_TYPE'])
if 'multipart/form-data' in e['CONTENT_TYPE']:
if 'boundary' in e['CONTENT_TYPE']:
boundary = options.get("boundary", "")
if 'CONTENT_LENGTH' in e:
content_length = int(os.environ.get("CONTENT_LENGTH", "-1"))
stream = sys.stdin.buffer
for part in MultipartParser(stream, boundary, content_length):
if part.filename or not part.is_buffered():
files[part.name] = part
if 'application/x-www-form-urlencoded' in e['CONTENT_TYPE']:
self._data = urllib.parse.parse_qs(str(sys.stdin.read()), keep_blank_values=True)
def getvalue(self, arg_key, default=''):
if arg_key in self._data:
value = self._data[arg_key]
if isinstance(value, list):
if self._escape == True:
return escape(self._data[arg_key][0])
else:
self._data[arg_key][0]
else:
if self._escape == True:
return escape(value)
else:
return value
else:
return default
Usage of mycgi.py
include_server_side = '/var/awesome_website/www'
with open(f'''{{include_server_side}}/mycgi.py''') as f: exec(f.read())
mycgi = MyCGI(os.environ)
mycgi.FieldStorage()
user = mycgi.getvalue('user')
Uploading files example #1 POST statement:
curl -F "text=default" -F "filename=@/home/computer/Desktop/aa.png" -F "filename=@/home/computer/Desktop/bb.png" http://example_website.com
filenames = files.getall('filename')
for x, file in enumerate(filenames):
file.save_as(f"""/home/computer/Desktop/file{x}.png""")
Posh Framework Download
Finally, download the .1 version posh_project.zip. It includes a sample website, some sample files of login, sign_up, admin and an index page. Including authenticating of pages with a postgresql database, etc.
And, Posh Project is created so that all files go to the index_example.py file. The only files that the url path goes directly to the files are the ajax_scripts, images, and videos. This is done with the following apache2.4 code:
<Directory "/var/example/www/html/Example.com">
Options +ExecCGI -Indexes
AddHandler cgi-script .py
AddHandler default-handler .jpg .png .gif .css .js .ico
AllowOverride None
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/ajax_scripts/ # direct url access to all the ajax_scripts back end files
RewriteRule ^/ajax_scripts/(.*)$ ajax_scripts/$1 [L]
RewriteCond %{REQUEST_URI} ^(.*).py # the pages all redirect to one controller file
RewriteRule (.*)$ index_example.py [L]
RewriteCond %{REQUEST_URI} ^(.*) # this is for any other file, including js, css, jpg, png, etc.
RewriteRule (.*)$ $1 [L]
</Directory>