An attempt to learn how frameworks like Django or Flask work internally by trying to implement my own version. WARNING - this is by no means a production ready code and is not even supposed to be used in production. Changes might be applied to the codebase without any notice, so use cautiously
New in 0.2.0: we've refactored all of our code into the OOP, so the code created for the earlier version of the framedork will not work. Please refactor your application according to the new documentation
To start using framedork.py, simply clone the repository to your local machine with git clone https://github.com/RedmanPlus/framedork.py.git
. You can use all of the features by importing main file framedork.py
into your main Python file like this:
from src.framedork import Framedork
from src.preprocessors.response import Response
app = Framedork()
Here you create an instance of a framedork, that will handle all of the routing and connections from now on
To define a page, write a simple function using a @app.register
decorator like this:
@app.register('/', ['GET'])
def index(request):
return Response(["index.html", {}])
Page function accepts a request from a browser (a dictionary of all possible parameters from a request), and returns a Response object that accepts a relative adress of a template file and a dictionary with parameters that need to be added to the page. You can inspect a note on tempating later.
You will need to specify all allowed methods for the page. If a page is called by a method, that is not declarated in a register
decorator, framedork will send a '405 Method not allowed' error.
To access URL parameters, add the variable that will hold the value of a parameter to the function input like so:
def index(request, id=1):
We recomend to set some default values to the parameter, so your program won't crash if a parameter is not present. You can use the value of your URL parameters in an inner logic of your page function.
If you wish to use a POST-requests with your page, you can access all of the POST-body parameters with request["BODY"]
.
request["BODY"]
is a dictionary, containing all of the parameters, sent by a POST-request, alteady parsed and splitted by framedork. If you know in advance, what parameters are being sent, then you can simply access all the values by
param_value = request["BODY"]["name_of_param"]
or if you wish to make code cleaner:
body = request["BODY"]
param_value = body["name_of_param"]
A request["BODY"]
field is present in all request by default, even in cases if the GET requests was sent. By default it is set to an empty list, so before accessing all of the POST values you need to check if the body is empty.
POST-request parameters can be used in inner logic of your program or for getting values into the database.
All three methods generate a request['BODY']
dictionary, that you can accept. To differenciate between the methods inside your framedork function, we recomend to use match request['METHOD']:
structure, or if you're using an older version of Python - if request['METHOD'] == 'GET'
structure.
It looks something like this:
@app.register('/foo/', ['GET', 'POST', 'PUT', 'DELETE'])
def foo(request, id=1):
match request['METHOD']:
case 'GET': # A simple GET request, just returns
return Response(['test2.html', {'value': str(id)}]) # a page with values from parameters
case 'POST':
values = request['BODY'] # POST takes values from the request
animal.add(values) # and uses them to create a new record
animal.save() # in the database
return Response(['test2.html', {'value': str(id)}])
case 'PUT':
values = request['BODY'] # PUT request at first gets an entry
check = animal.filter({'id': id})# that needs to be checked, then
print(check) # updates it with the data from a
for key, value in values.items(): # PUT requet and updates the data
try:
check[0][key] = value
except KeyError:
continue
animal.add(check[0])
animal.save()
return Response(['test2.html', {'value': str(id)}])
case 'DELETE': # DELETE takes data from your request
values = request['BODY'] # and deletes all entries that satisfy
animal.delete(values) # the condition
return Response(['test2.html', {'value': str(id)}])
All of the methods can be used to create a more interractive backend or to make an API.
Framedork uses custom html preprocessor akin to Jinja2, but at the current moment has much less functionality, so it is more like abridged Jinja2.
To insert a value into a page, construct your html file like so:
<h1>{value}</h1>
Now, in your page function, add a dictionary, where keys have the same name as the labels you put in your html file. For example, if you're using URL parameters in your page, you can write a function like this:
def index(request, id=1): # id is a hypothetic parameter from http://127.0.0.1:8080/?id=1
return 'test.html', {'value': str(id)}
The value of the dictionary will then replace a label in your html page. Similarly you can use values sent in a POST request, you'll just need to wrap them in a dictionary with names, that will correspond with the ones in your html.
Please also note that the amount of labels and passed values must be exactly the same at best (or not less than the labels in your html file at worst), to avoid errors and crashes.
To extend your html file by a template, create a template file like 'base.html'. Base file must contain an extendable block like so:
{% block content %}
Similarly, the file that you will pass from your function should look something like so:
{% extends 'base.html' %}
{% block content %}
{% endblock %}
Now all of the html content placed between opening and ending of the block will be inserted into a template, and this template will be shipped to the browser.
Please note that the base template filename is a path relaive to the extendable file, and preferably they both should be located in the same folder.
DORM is a custom ORM integrated into framedork. It can be used for connecting your web app with external database, so you can use this data in your web pages. DORM is currently work-in-progress, so any changes may apply.
DORM only supports PostgreSQL connections for now
First of all, import DORM into your main file like so:
from src.dorm import Model, StringField, IntField, FloatField, DateField
Now create a model of your SQL table:
animal = Model(table='animal', fields=[
StringField(null=False, max_len=20, name='name'),
StringField(null=False, max_len=30, name='species'),
IntField(name='life_expectancy')
])
Model accepts two parameters - a name of a relative SQL table and a list of fields it will contain. Please note that your model can reflect an existing SQL table in your database - it will not fail and will just use this table and all it's data, but your model must reflect all of the columns 100% or it will crash. So on current stage of development we recomend to use DORM models with existing tables only in case these tables have only already implemented fields.
Currently DORM can only create 4 field types - String, Integer, Float and date. All of these fields have a null
parameter, that will define on initiation, if a field can be nullable. By default it is set to True
, and you don't have to add it into the definition.
To register a model, call a register_model()
function:
framedork.register_model([animal])
register_model
accepts a list of all models registered by you. It will check, if accociated tables exist in a database, and if not created - will create ones.
To use DORM models in your framedork functions, add global
instruction in a beguining of the file like so:
@app.register('/animal/', ['GET'])
def animal_page(request, name='Hyena'):
global animal
Currently you can only get values from a database and create new entries.
To get values from a database, use get()
and filter()
methods. A get()
methods returns all entries into a database in a list of dictionaries, where every dictionary is a separate entry, and every field in a dictionary represents a value with a key of the column name. filter()
does the same, but it accepts a dictionary of search parameters. Every key in a dictionary must reflect a name of a column in your SQL table.
Now you can use your fetched data in your inner logic or simply send it to the browser with templating.
To set values for your database, first of all use add()
method of your model. add()
method accepts a dictionary of all values, that you wish to write. Note that keys of the dictionary must reflect your SQL table column names, and all NOT NULL fields must be filled.
Then you can use save()
method and it will authomatically save your data in a database.
It looks something like so:
vals = {'name': 'Hyena', 'species': 'Hyaenidae', 'life_expectancy': 30}
animal.add(vals1)
animal.save()
To update values in your database, you can also use add()
and save()
combination. save()
method checks if the entry by this id already exists in your database and either updates it or creates a new one.
For example, you fetch some information from your database. Then you apply some transformation to this data. Now you can add()
it back to the model and then save()
it. It looks like so:
animals = animal.get()
one = animals[0]
one['name'] = 'Qute Hyena'
animal.add(one)
animal.save()
We recomend to apply any changes directly to the data you fetched to avoid any errors.
To delete a value from your database, use delete()
method. It accepts a dictionary, where every key represents a name of the field. The value represents a value of this particular column in the database, that will be deleted.
It looks like this:
animal.delete({'name': 'Hyena'})
Note that delete()
method may delete multiple entries in your database if your dictionary is not very specific or there is multiple entries that contain the value you specified to be deleted.
Settings in your framedork application are used for specifiing what host and port to connect to, and what are the database credentials. You can set your settings in definition of a Settings
class entity, that you'll then pass to the Framedork
entity upon it's initialising. It looks like this:
settings = Settings(
port=8000,
db="postgres",
db_conn=DB_CONNECTION,
deploy="wsgi"
)
app = Framedork(settings)
All of the arguments to the Settings
class are optional and Settings
are optional for the Framedork
instanse themselves, but you can define these fields:
- host: an IP adress from which the server will send the data.
- port: the port that will be used for connection.
- conns: the amount of consequent connections that are avaliable simultaneously.
- conn_size: the amount of bytes allowed to be recieved once at a time by the server from the client.
- db: a database you will use. Currently accepts only 'psql' as a parameter.
- db_conn: All of the necessary credential information to connect with the database. Please make sure that all of the credentials are correct.
- deploy: will define if your app is ran locally or by a WSGI web server.
You can specify what kind of response will the function send to the browser. To make your function send a json string, specify the content type in your @register
decorator:
@app.register('/api/', ['GET', 'POST'])
def api(request):
return Response([{'hi': 'you\'re qute'}])
Here your function returns a Response
object that accepts only the list with a dictionary in it. Now framedork will send it to the browser or to any endpoint that will call your website at this address.
Also, since DORM get()
and filter()
methods return a list of dictionaries with entries to the database, you can simply fetch all the data you need and straight up send the response to the client like so:
@app.register('/api/', ['GET', 'POST'])
def api(request, id=1):
response = animal.filter({'id': id})
if response == []:
return {'error': 'nothing found'}
return Response([response[0]])
This way you can create a series of different endpoints that will communicate with the database, resulting in a full framedork backend.
To run your website, you need to call a run()
method of your framedork
instance at the end of the file. Your run()
method must accept all of the functions you've written not called so they can be registered by the framework. You can do it like so:
app.run(index)
Then simply run your file with
python main.py
or
main.py
if you're using a !# at the beguining of a file.
The web server will be automatically started on 8080 port, where you can access it.
Please note that currently you cannot shutdown your server from inside the program or terminal. You can try to use Ctrl-C, but it can result with sockets being left open. We recomend currently to stop the python process from your machine's controll panell.
To run framedork.py with a web server, you'll need to change your file a little. Change your settings' local
paramether to the wsgi
. Now change your app.run()
method at the end of the file to:
wsgi = app.run(index)
Register all of your functions in a run()
method. It will return an object, that we will then call with a Gunicorn.
Now upload all of your work onto the server with Gunicorn installed on it and in a project directory call:
gunicorn --workers=2 main:app
where
main
is a name of your filewsgi
is the name of the object returned by yourrun()
function
Now your app will be hosted on a 127.0.0.1:8000, but you can specify the IP address.