Framework for creating web applications
Project description
Cheese Framework
Version v(1.4.95) - 23.08.18.18.43
Test version v(1.4.95) - 23.08.18.18.40
TODO
- autocommit
- models and autocommits
- javascript
Source code
https://github.com/KubaBoi/CheeseFramework/tree/development
Documentation
It is same as this BUT it is formated by my own algorithm into html. So contents is fixed on the left side of screen and it is overall clearer. (In my opinion...) Check it out :wink: it was a really hard work. All source codes are in branch webServices
in directory ./mdConverter. I would be honored if it would help you. :relieved:
https://kubaboi.github.io/CheeseFramework/
Complete method and classes documentation
Contents
- Introduction
- Cheese Tools
- Build
- Project structure
- Configuration
- Annotations
- Python code
- Testing
- JavaScript
1 Introduction
Cheese Framework is open source library for creating web applications with database connection (like Spring in Java). It can save a lot of time because developer does not have to making http server or creating whole database reader. Cheese is using pydobc library for database access so it is able to connect to most of modern database engines.
:bangbang: IMPORTANT :bangbang:
Cheese Framework is using basic http python server. So DO NOT RUN outside firewall. If you are creating an application for bigger audience or you care about security use any other framework like Django and server for production like Apache or Tomcat.
Stay safe :heart:
I am using Tomcat like some kind of gate which listens at 80 public port and resends requests to Cheese Applications running under firewall and listen at closed to public ports.
1.1 Instalation
1.1.1 Downloads
First of all you need to install CheeseFramework from pypi.org
pip install CheeseFramework
And you will need some more pypi packages:
pip install pyodbc
Documentationpip install psycopg2-binary
Documentationpip install psycopg2
Documentationpip install bs4
Documentationpip install requests
Documentationpip install GitPython
Documentation
1.1.2 Creating new project
Cheese has prepared template project in git branch template. I recommend to use this tool Cheeser for generating new project.
-
Create directory for your project
-
Open command line in this directory:
python -m Cheese -g
or
python -m Cheese -G
- if
-g
than generator would remove HelloWorld files like ("HelloWorldController.py", "helloRepository.py") - I recommend to use
-G
when you are creating your first application so you can better understand how does it work.
- if
If you need help with Cheese commands use python -m Cheese -h
.
That's all now you can start your application :blush:
1.1.3 Run Cheese application
Main script of Cheese applications is located in directory <your project>/src/<your project>.py
So just run this script.
Default app port is 8000
so you can check if everything works at localhost:8000
Congratulation :clap: you are now Dairy developer :clap:
2 Cheese Tools
WIP :nail_care:
3 Build
Build is proccess which generates .metadata
for your application. Build runs at start of application (it is pretty fast) if is in debug mode.
3.1 Metadata
.metadata
is actually json
but it is coded with your key
. key
is storred in file ./secretPass
just like regular string, nothing else. Only one string nothing more.
This file should be ignored in .gitignore
so during deploy you need create it in deploy app enviroment (developing on windows -> deploy on linux server) because if you would leave it in git repository, it would lose that secret point. key
is just password for your application.
If the secretPass
file is missing Cheese will try for coding and decoding Default
as a key. But it will give you call that you should use different key
.
In .metadata
is storred everything except appSettings.json
. So adminSettings.json
, securitySettings.json
and secrets.json
.
Uncoded metadata are accessible via Metadata
class.
import json
from Cheese.cheese import CheeseBurger
from Cheese.metadata import Metadata
CheeseBurger.init()
pathToCodedFile = "some\\path\\.metadata"
key = "jauukfnd" # key to decode data
with open(pathToCodedFile, "r", encoding="utf-8") as f:
rawData = f.read() # loads data from file coded base64
codedData = Metadata.decode64(rawData) # decode base64
rawJsonData = Metadata.decode(codedData, key) # decode by your key
jsonData = json.loads(rawJsonData) # loads json from string into dictionary
with open("metadata.json", "w") as f:
f.write(json.dumps(jsonData))
Or since v(1.4.80) there is a tool for decoding metadata:
python -m Cheese -m "key"
This will generate metadata.json
file in root
directory of project.
4 Project structure
In this paragraph we will talk about directories generated by Cheeser. I should tell you that there is class ResMan
in Cheese. ResMan
mediates paths to main directories of project. More about ResMan
later.
4.1 /resources
Directory where belongs all non source code files which won't be served by server. Some kind of your own settings or whatever.
4.2 /src
Python source code directory. Cheese will be searching there for controllers
and repositories
during building your application. You do not have to follow any structure. If .py file is in /src, it WILL be found by the builder.
4.3 /web
Directory with files served by server. Cheese server automatically looks into this directory while cannot match url endpoint with controllers.
- If url endpoint is
"/"
it search for/web/index.html
- If cannot find the file than serves
/web/errors/error404.html
- If
/web/errors/error404.html
is missing than returns ERROR 500
4.4 *.json files
Those files in root of your project are configuration files.
5 Configuration
All configration is set in *.json
files in root of project.
5.1 adminSettings.json
Contains only list of credentials for admin access.
Default adminSettings.json
{
"adminUsers":[
{
"name": "admin",
"password": "admin"
}
]
}
5.2 appSettings.json
Contains app configuration.
- name - Name of your application
- version - Version of your application
- licenseCode
- can be left empty
- it will affect if there will be
Powered by Cheese Framework
watermark at the right bottom corner of html pages served by Cheese server - if you want to get rid of this watermark go on this url http://frogie.cz:6969/licence/generate?type=full%20access and from
{ "LICENSE": { "CODE": code, "ID": int, "TYPE": "full access" } }
copycode
- port - port of your app
- dbDriver - driver for database
- postgres - for postgresql database
- https://github.com/mkleehammer/pyodbc/wiki - for other databases
- other database engines does not work very well so just use postgres please
- dbHost - host of your database
- dbName - name of your database
- dbUser - user of database
- dbPassword - password of user
- dbPort - port of your database
- allowDebug - If
false
application should be deployed. Logger will log only errors"silence"=False
logs. More about logging later. - allowCommit - If
false
application won't commit anything into database even if you write commit query - allowMultiThreading - If
true
Cheese server would be able to server more request at once (I have tested that about 60 request at once is alright) - allowCORS - If
true
Cheese server would be able to practice CORS.- Learn about CORS
- BTW... without this it does not work (yet) :scream_cat:
- allowDB - If
false
Cheese won't try to connect database
5.3 securitySettings.json
In this file can be defined access to your application. There need to be 3 objects in this json authentication
, roles
and access
.
5.3.1 authentication
authentication
needs two variables inside. enabled
and types
. enabled
is a boolean and it tells to Cheese if you want to authenticate requests or not. types
is an array of some ... something... you will see.
Example of authentication
{
"authentication": {
"enabled": true,
"types": [
{
"patern": "(?P<login>\w+):(?P<password>\w+)", // regular expression for authentication header (for example "Joe:heslo12")
"validation": "select * from passwords where password = $password$ and login = $login$", // this sql will validate if validation is possible (if there is any case)
"roleId": "select role_id from users where login = $login$", // founds users role id after validation
// encoders are for decoding data from database
// in this example every instance of "password" will be
// decoded by "passKey" which is save as secret in ./secrets.js
"encoders": {
"password": "passKey"
},
// additional is list of more checks
"additional": [
// this one will check if user with $login$ exists
// and if not ( ! means negation) it will call http request
// "http://localhost:port/machines/logMachine" defined by "endpoint"
// with POST method defined by "method"
// and with POST body
// {
// "ip": $client_ip$,
// "login": $login$
// }
//
// This endpoint registers machine into database
// $client_ip$ is defined as client ip address
// same is defined $headers$ which are requests headers
{
"validation": "!select * from users where login = $login$",
"exceptions": {
"endpoint": "/machines/logMachine",
"method": "POST",
"content": {
"ip": "$client_ip$",
"login": "$login$"
}
}
},
// this example uses "raise" which will raise Unauthorized exception
// (with text defined in "raise")
// if users account with $login$ is disabled
{
"validation": "select * from users where users.login = $login$ and users.enabled = true",
"raise": "Your account is disabled :/"
},
// and last example
// you can use "exceptions" and "raise" nodes in same time
// if user exists but he is trying to connect from
// unknown machine it will raise an exception
// and calls "/emails/unknownMachine" endpoint
// which in this case would send an email to the user
{
"validation": "select * from users inner join machines on users.id = machines.user_id where users.login = $login$ and machines.ip = $client_ip$ and machines.verified = true",
"raise": "This machine is not registered or verified. Check your email so we know that's you. Your account is temporary disabled.<br>Sorry",
"exceptions": {
"endpoint": "/emails/unknownMachine",
"method": "POST",
"content": {
"headers": "$headers$",
"ip": "$client_ip$",
"login": "$login$"
}
}
}
]
}
]
}
}
5.3.2 roles
Roles are defined with value
and name
. Name of role field coresponds with value returned from database via roleId
sql request.
Example of roles
{
"roles": {
"0": { // name of field fits value of roleId from database
"value": 0,
"name": "Admin"
},
"1": {
"value": 1,
"name": "Organiser"
},
"2": {
"value": 2,
"name": "Client"
}
}
}
5.3.3 access
Access is dictionary of endpoints and their minimal role value which is need to be accesible. If you have more endpoints which starts with same prefix and have same role level you can use *
keyword and Cheese will complete them for you. And if you will use *
but there will be some endpoints with diferent level, just add them.
Example of access
{
"access": {
"/users/login": {
"minRoleId": 2
},
"/users/get": {
"minRoleId": 2
},
"/clubs/*": { // all endpoints starting with /clubs will have minimal role level 1
"minRoleId": 1
},
"/clubs/getAll": { //except for this one... this endpoint will have minimal role level 2
"minRoleId": 2
}
}
}
5.3.4 Authorization process
Ok ... this may looks little confusing. But it is actually pretty simple. This is process of authorization:
- first of all is checked if authorization is even enabled - if not -> skipping
- decode
Authorization
header from request - find if decoded header fits any of
paterns
fromtypes
patern
is regular expression- more about regular expresions https://docs.python.org/3/howto/regex.html
- validation of credentials with
validation
- sql in this part needs to return anything (not empty response)
- for example above would finall sql looks like this:
select case when exists (select * from passwords where password = 'heslo12' and login = 'Joe') then cast(1 as bit) else cast(0 as bit) end
- variables from
Authorization
header need to have same name as inpatern
and need to be wrapped in$$
characters
- if credentials (and every additional check) are valid it will try to find role id via
roleId
sqlroleId
sql just finds value from database- than use this value as key for
roles
dictionary
- endpoint validation
endpoint
will be used as key foraccess
dictionary- if
endpoint
does not exist inaccess
than it will returns foundrole
(it meansAUTHORIZED
) - if
endpoint
exists inaccess
and itsminRoleId
is equal or lower thenroleId
.value
returnsrole
(AUTHORIZED
) - if
endpoint
exists inaccess
but itsminRoleId
is greater thenroleId
.value
returnsFalse
(UNAUTHORIZED
) - if
endpoint
exists inaccess
butrole
isNone
returnsFalse
(UNAUTHORIZED
)
- if
5.4 secrets.json
Secrets are variables which you do not want to show public in git repository or whatever. You can use secrets.json
for storring any sensitive data. Those data will be coded into metadata
during build and you do not need to commit them public.
Example of storring secrets:
{
"databasePassword": "sdjfgnskjdng4",
"sizeOfMyPP": "extreme"
}
And in appSettings.json
just insert its name with $
prefix:
{
"bla": "bla",
"blabla": "blo",
"dbPass": "$databasePassword",
"ppMeter": "$sizeOfMyPP"
}
6 Annotations
Annotations are necessary for Cheese. Cheese recognize annotation that starts #@
and it needs to end with ;
. Yes it is just python comment and what?
"If it is stupid but it works, it isn't stupid"
Some Smart Guy - 1456
There is a list of all annotations:
#@controller
#@repository
#@post
#@get
#@query
#@commit
#@return
#@dbscheme
#@dbmodel
#@testclass
#@test
#@ignore
7 Python code
This will be about how to write controllers
and repositories
.
7.1 API Controllers
Controllers are classes that handle requested endpoints. I recommend to create one folder just for your controllers but it is not necessary (or use the generated one ofc).
There is documentation of CheeseController
class https://kubaboi.github.io/CheeseFramework/doc.html#5-cheesecontroller
7.1.1 Create controller
As I said, controllers are classes. So create class that inherits from CheeseController.
To let cheeser know that this class is really controller you have to anotate it with #@controller
annotation. #@controller
annotation follows part of endpoint like this:
#@controller /apiController;
class apiController(CheeseController):
7.1.2 Methods of controller
There is sctrict scheme how should method in controller looks so it can handle endpoint. Method has to be static, so add @staticmethod
annotation above method definition. Method has to be anotated. Annotation for endpoints contains HTTP method (right now only GET
and POST
) and endpoint. Method has to have 3 arguments: server
, path
, auth
.
#@post /apiEndpoint;
@staticmethod
def getFiles(server, path, auth):
Every method needs to return object created via CheeseController.createResponse()
. We will talk about CheeseController
methods later.
If your method solves content and headers itself you can return CheeseNone()
object.
7.1.3 Method arguments
Those arguments are passed by server handler.
server
is instance of server handler ( BaseHTTPRequestHandler
).
path
is string variable and it contains url
without host and port. So for url http://localhost:8000/hello/world?name=helloboi
the path
will looks like this /hello/world?name=helloboi
.
auth
is object defined during Authentication. It will looks like this:
{
// role node is defined in ./securitySettings
"role": {
"value": 0,
"name": "Admin"
},
"login": {
"login": "login",
"password": "password" // this will be probably removed
},
"userData": "tuple" // this is tuple of user's data from database (user's whole row)
}
7.1.4 Example controller
Now you know almost everything you need to know to create your own controller. There are some functions that was not described yet. Those will be of course later. Controller will handle two endpoints:
/calculator/sum
/calculator/sub
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from cheese.modules.cheeseController import CheeseController as cc
#@controller /calculator;
class CalculatorController(cc):
"""
sum two numbers in request body and return result
body looks for example like this:
{
"NUM1": 1,
"NUM2": 5
}
so result will looks like this:
{
"RESPONSE": 6
}
"""
#@post /sum;
@staticmethod
def sum(server, path, auth):
#reads arguments from body of request
args = cc.readArgs(server)
#arguments
num1 = int(args["NUM1"])
num2 = int(args["NUM2"])
result = num1 + num2
return cc.createResponse({"RESPONSE": result}, 200)
"""
substract two numbers in request path and return result
path looks for example like this:
localhost:8000/calculator/sub?num1=5&num2=3
so result will looks like this:
{
"RESPONSE": 2
}
"""
#@get /sub;
@staticmethod
def sub(server, path, auth):
#reads arguments from endpoint path
args = cc.getArgs(server)
#arguments
num1 = int(args["num1"])
num2 = int(args["num2"])
result = num1 - num2
return cc.createResponse({"RESPONSE": result}, 200)
7.2 Repositories
Repository is like access into one table of database. There are methods that communicate with database.
7.2.1 Create repository
Repositories are most complex part of Cheese Framework. They are again classes but there need to be more annotations. Also repository have to inherits from CheeseRepository
First annotation is #@repository
, so cheeser knows this is repository, followed by name of table.
Second annotation is #@dbscheme
followed by name of table's columns in brackets where first should be Primary Key
.
Last annotation is #@dbmodel
followed by name of model for the table. It is just name for your better identification of model. But it is neccessary to be there.
from cheese.modules.cheeseRepository import CheeseRepository
#@repository users;
#@dbscheme (id, user_name, age);
#@dbmodel User;
class UserRepository(CheeseRepository):
7.2.2 Methods of repository
Method scheme is again very strict. They need to be static and every method needs to have this line in it's body:
return CheeseRepository.query(argName1=arg1, argName2=arg2,...)
arg1
and arg2
are method's arguments. Those argument's names need to corespond to names in sql annotation.
There are two types of SQL query annotations query and commit.
7.2.2.1 #@query
This annotation says that we want to get data from database. It is followed by SQL query. This query can be more than one line but have to be in quotation marks and the query can ends with semicolon.
More line query:
#@query "select * from users
# where age > 20;";
With #@query
annotation is related annotation #@return
. It determines type of return. if annotation #@return
is missing it is default.
- DEFAULT - Returns raw data what get from database. Mostly it is tuple of tuples.
#@return raw;
- Returns array of models.
#@return array;
- Returns only one model. If there is more results returns first.
#@return one;
- Returns logical value.
#@return bool;
#@return bool
example:
#@query "select case when exists
# (select * from tokens t where t.token = :token)
# then cast(0 as bit)
# else cast(1 as bit) end;";
#@return bool;
- Returns numeric value.
#@return num;
#@return num
example:
#@query "select count(*) from users;";
#@return num;
7.2.2.2 #@commit
This annotation is for writing data into database.
#@commit "update files set id=:id where file_name=:file_name;";
You will need it only when you want to change Primary Key of some row because there are three prebuilded methods that you should add into your repository. Those methods does not have any annotation and accepts only models. The update and delete method search rows by Primary Key so if you want to update row's Primary Key you need to write your own SQL query. (Maybe will be depreated idk)
7.2.3 Passing arguments to SQL query
Arguments can be insert into SQL query if you marks them with :
and in return CheeseRepository.query()
name them same as in SQL query.
Example:
#@query "select * from table where id=:someId and name=:someName;";
#@return one;
@staticmethod
def findByIdAndName(id, name):
return CheeseRepository.query(someId=id, someName=name)
7.2.3.1 Passing model
If you want to pass an model it is possible.
For model Hello
with attributes id=0
, name="hello"
, greet="hello boi"
(this is prebuilded method save):
#@commit "insert into table values :someModel;";
@staticmethod
def save(model):
return CheeseRepository.query(someModel=model)
So the finall query which will be send to database looks like this:
insert into table values (0, 'first hello', 'hello boi');
And if you want to pass only some attribute of model do this:
#@query "select * from table where name=:someModel.name or greet=:someModel.greet;";
#@return array;
@staticmethod
def findBy(model):
return CheeseRepository.query(someModel=model)
Finall query will looks like this:
select (id, name, greet) from table where name='first hello' or greet='hello boi';
7.2.4 Prebuilded methods
There are some prebuilded methods for saving, updating, removing and find new id. Those methods do not need to be defined in your custom repository. They are in CheeseRepository
and are accessible from custom repositories.
findAll()
- finds all rows of table
find(primaryKey)
- finds one model by
primaryKey
- finds one model by
findWhere(**columns)
- finds all rows with
value
of columns **columns
is kwargs so dict wherekeys
are column names and itsvalues
are ... ehm values
- finds all rows with
findOneWhere(**columns)
- finds one model with
value
of columns **columns
is same as infindWhere
- finds one model with
findNewId()
- finds new ID (free one)
save(obj)
- inserts new row into database by
obj
(it is CheeseModel)
- inserts new row into database by
update(obj)
- updates values in database by id of
obj
- updates values in database by id of
delete(obj)
- deletes row from database by id of
obj
- deletes row from database by id of
exists(**columns)
- return
true
if at least one row exists **columns
is same as infindWhere
- return
model()
- returns new instance of
CheeseModel
and finds to it free id viafindNewId()
- returns new instance of
7.3 Models
Models are non-static objects which contains data from one row of database. There is class CheeseModel
. This class stores your data and is returned from repository
(if called method is annotated with #@return
annotation and values one
or array
).
Every instance of CheeseModel
has variables modelName
and scheme
.
modelName
is defined in repository with#@dbModel
annotation- it is only for you to recognize which model is which
scheme
is list of column names and is also defined in repository with#@dbScheme
annotation
You can add your own variables (that does not matter) but when model is returned from repository query call then there are defined variables by scheme
. Those variables are filled with values from database.
Example for this repository:
from cheese.modules.cheeseRepository import CheeseRepository
#@repository users;
#@dbscheme (id, user_name, age);
#@dbmodel User;
class UserRepository(CheeseRepository):
#@query "select * from users where age>:age;";
#@return array;
def getOlderThen(userAge):
return CheeseRepository.query(age=userAge)
We can do this:
users = UserRepository.getOlderThen(20) # returns array of CheeseModel with .age > 20
# prints all users older then 20
for user in users:
print(user.id, user.user_name, user.age)
# prints name of CheeseModel, in this case => User
print(users[0].modelName)
# prints database scheme, in this case => ['id', 'user_name', 'age']
print(users[0].scheme)
7.3.1 CheeseModel methods
7.3.1.1 toJson()
toJson()
method creates python dictionary from storred variables of model. But only from variables which are in database scheme.
This example uses some of models from previous example.
print(user.toJson())
Output:
{
"ID": 0,
"USER_NAME": "idk Joe",
"AGE": 69
}
7.3.1.2 toModel(jsn)
toModel(jsn)
method sets variables of model by jsn
argument. jsn
can be dictionary or any iterable structure (list, tuple, Row (this is object from pyodbc
library))
:bangbang: If you pass anything else (but iterable) than dictionary it MUST be in order by #@dbScheme
.
user.toModel([0, "Joe", 15]) # list example
print(user.toJson())
user.toModel((0, "Joe Boy", 15)) # tuple example
print(user.toJson())
user.toModel({"id": 0, "user_name": "Joe", "age": 4}) # dictionary example
print(user.toJson())
user.toModel({"id": 0, "user_name": "Joe", "age": 4, "fancy_level": 45}) # dictionary example
print(user.toJson())
Output:
{
"ID": 0,
"USER_NAME": "Joe",
"AGE": 15
}
{
"ID": 0,
"USER_NAME": "Joe Boy",
"AGE": 15
}
{
"ID": 0,
"USER_NAME": "Joe",
"AGE": 4
}
// you can see that "fancy_level" variable is not in json because it is not defined in database scheme
{
"ID": 0,
"USER_NAME": "Joe",
"AGE": 4
}
7.3.1.3 setAttrs(**attrs)
setAttrs(**attrs)
is pretty similar to toModel(jsn)
but arguments you pass can be written like kwargs:
user.setAttrs(id=1, user_name="Jimbo", age=47)
print(user.toJson())
Output:
{
"ID": 1,
"USER_NAME": "Jimbo",
"AGE": 47
}
7.3.2 Creating model
:bangbang: Do not create models by yourself :bangbang:
Always use repository method model()
. This method will automatically finds free id for your model. This is maybe not the best way to do it so I will probably change it so id will be find during save. But USE it.
:x:
model = CheeseModel(modelName, scheme)
:heavy_check_mark:
model = YourRepository.model()
8 Testing
Testing is very important part of programming. It will tell you if you fucked something up. Cheese, because of database connection (and because it is fun creating it), has it's own test system.
Tests are run during start of application after Build when application is in debug mode.
8.1 Cheese test modules
There is list of classes which your test class file should contains. Everything will be clear when you check Test examples.
8.1.1 UnitTest
from Cheese.test import UnitTest
UnitTest
is class with methods that will throw (raise) TestError
exception which signalize to Cheese test engine that this test fails because of the result is not as expected. Those methods are static
.
Methods of UnitTest
:
UnitTest.assertEqual(value, template, comment)
If value
is not same as template
the TestError
will be raised and test fails. Also comment
will be print with fail message.
UnitTest.assertTrue(value, comment)
If value
is not equal True
the TestError
will be raised and test fails.
UnitTest.assertFalse(value, comment)
If value
is not equal False
the TestError
will be raised and test fails.
8.1.2 Pointer
from Cheese.pointer import Pointer
Usage of Pointer
is approximately similar as in C/C++. But because python does not have this amazing feature I had to make my own. Pointer
is just some pointer (:D) to any variable you need. I will show you that in some Test examples .
Methods of Pointer
:
Pointer.getValue()
Return value to which Pointer
points.
Pointer.setValue(value)
Sets value to which Pointer
points. (You won't need it)
8.1.3 Mock
from Cheese.mock import Mock
Mock
is class that you can use if you need to test some method which is using some of your repository. You can substitute return
of any method with your own values and for any argument input.
Methods of Mock
:
mock = Mock(nameOfRepository)
This is constructor (initializer) of Mock
class. Every Mock
is non-static class which mocks one repository.
mock.whenReturn(nameOfMethod, value, **kwargs)
When there is called nameOfRepository.nameOfMethod()
during test and arguments are same as **kwargs
(dictionary of arguments), then value
is returned.
mock.catchArgs(pointer, nameOfArgument, nameOfMethod, **kwargs)
When there is called nameOfRepository.nameOfMethod()
during test and arguments are same as **kwargs
, then pointer.value
will be **kwargs[nameOfArgument]
.
As I said... everything will be clear at the end of paragraph in examples.
8.2 Creating test file
Like everything else tests need to be annotated with Cheese annotations. One test file should contains only one test class (it can be more but... it is probably not best practise). This test class needs to be annotated with #@testclass
annotation. This annotation can contains some description of test class:
#@testclass this is testing something;
class helloTest:
If you add #@ignore;
annotation then whole file will be ignored during testing:
#@testclass this is testing something;
#@ignore;
class helloTest:
8.3 Test method
Test methods need #@test
annotation and also it can contain a description:
#@test I am a testing method;
@staticmethod
def helloWorldTest():
Ignored test method:
#@test I am a testing method;
#@ignore;
@staticmethod
def helloWorldTest():
8.4 Test examples
For method:
#@controller /hello;
class HelloWorldController(CheeseController):
#@get /world;
@staticmethod
def helloWorld(server, path, auth):
return "hello"
Could be test like this:
#@test hello world method;
@staticmethod
def helloWorldTest():
controller = HelloWorldController()
resp = controller.helloWorld(None, None, None)
value = resp[0].decode()
httpCode = resp[1]
UnitTest.assertEqual(value, "hello", "Response was not 'hello'")
UnitTest.assertEqual(httpCode, 200, "Status code was not 200")
For method:
#@controller /hello;
class HelloWorldController(CheeseController):
#@get /world;
@staticmethod
def helloWorld(server, path, auth):
hello = HelloRepository.model()
hello.setAttrs(hello_value="Hello boi")
return CheeseController.createResponse(hello.toJson(), 200)
Could be test like this:
#@test hello world method;
@staticmethod
def helloWorldTest():
controller = HelloWorldController()
mock = Mock("HelloRepository") # mocking HelloRepository
mock.whenReturn("findNewId", 0) # because model() method is using findNewId()
resp = controller.helloWorld(None, None, None)
value = json.loads(resp[0])
httpCode = resp[1]
# expected response should looks like this
templateResponse = {
"hello_value": "Hello boi",
"id": 1 # because findNewId() adds 1 every time
}
UnitTest.assertEqual(value, templateResponse, "Response was not as expected")
UnitTest.assertEqual(httpCode, 200, "Status code was not 200")
For method:
#@controller /hello;
class HelloWorldController(CheeseController):
#@get /world;
@staticmethod
def helloWorld(server, path, auth):
hello = hr.model()
hello.setAttrs(hello_value="Hello my friend")
hr.save(hello)
allHellos = hr.findAll()
return cc.createResponse(cc.modulesToJsonArray(allHellos), 200)
Could be test like this:
#@test hello world method;
@staticmethod
def helloWorldTest():
controller = HelloWorldController()
mock = Mock("HelloRepository")
pointer = Pointer()
mock.whenReturn("findNewId", 0) # in method model() as in previous example
"""
if method save() will be called then pointer.value will be set to value of argument "obj"
"""
mock.catchArgs(pointer, "obj", "save")
"""
if findAll() will be called then array
([pointer], that one item will be value of pointer)
will be returned
"""
mock.whenReturn("findAll", [pointer])
resp = controller.helloWorld(None, None, None)
value = json.loads(resp[0])
httpCode = resp[1]
# expected response should looks like this
templateResponse = {
"hello_value": "Hello boi",
"id": 1 # because findNewId() adds 1 every time
}
UnitTest.assertEqual(value, [templateResponse], "Response was not as expected")
UnitTest.assertEqual(httpCode, 200, "Status code was not 200")
9 JavaScript
I have create some useful javascript functions. Those scripts can be used if CORS is enabled or you can download them and insert them into your project for offline use.
Some of those scripts (for example alerts.js
) needs a css
for proper functionality. You can change it but if scripts needs css
then you should add classes with same names like which are in given .css
file. (hope you understand it).
All imports:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/time.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/cookies.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/communication.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/tableBuilder.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/alerts.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/autoComplete.js"></script>
9.1 autoComplete
Easy way to create a custom combobox. Credit belongs to w3schools: https://www.w3schools.com/howto/howto_js_autocomplete.asp
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/autoComplete.js"></script>
<!-- necessary dependencies -->
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
Needs css:
<link rel="stylesheet" href="https://kubaboi.github.io/CheeseFramework/public/styles/autocompleteStyle.css">
Variables of css:
/* colors */
--light-color /* background color of main dialog */
--text-color /* text color of not chosen row */
--secondary-color /* background color of chosen row */
/* classes */
.autocomplete /* better not to be changed */
.autocomplete-items /* all items of autocomplete list */
.autocomplete-items div /* one item of autocomplete list */
.autocomplete-items div:hover
.autocomplete-active /* onclick */
Functions:
autocomplete(inp, arr)
Method generates html elements for autocomplete dialog
inp
- input html element (html object)arr
- array of options for autocomplete
9.2 cookies
Script for saving and reading cookies.
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/cookies.js"></script>
Functions:
setCookie(cname, cvalue, exdays)
Sets a cookie
cname
- string name of the cookiecvalue
- value of the cookieexdays
- float value of how long will cookie be storred (in days)
getCookie(cname)
Returns value of cookie
cname
- string name of the cookie
9.3 communication
Script that sends xmlhttp
requests. There are two global variables. You can change them anywhere in your scripts (but change will be proceede only if your scripts are imported AFTER communication
script)
debug
- default setting istrue
, change it if you do not want to print informations about requestsauthorization
- variable that will be send likeAuthorization
header in request (if empty than this header won't be created). For example:authorization="userName:password"
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/communication.js"></script>
<!-- necessary dependencies -->
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/time.js"></script>
Functions:
callEndpoint(type, url, request=null)
Returns Promise with server JSON response.
type
- HTTP method ("GET"/"POST")url
- request url in most cases only endpoint. It can be used even for external server but thanurl
needs to havehttp[s]://server/endpoint
structurerequest
- default null (it is for "GET"). For "POST" it is just JavaScript object
Usage:
async function getData() {
var response = await callEndpoint("POST", "/endpoint", someObject);
if (response.ERROR == null) {
console.log(response);
}
else {
alert(response.ERROR);
}
}
9.4 elementManager
Script for creating html elements.
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
Functions:
createElement(type, parent=null, innerHTML="", attributes=[])
Returns created element
type
- html tag ("div", "label"...)parent
- parent element (element will be append to parent if it is not null)innerHTML
- innerHTML of created elementattributes
- list of attributes ("id", "class", "onclikc"...)- one attribute is JavaScript object contaning "name" and "value"
Usage:
// function will create div and button inside that div with text "Click me :)"
// button will have class "coolButton" and onclick will print in console "hello"
function makeElement() {
var div = createElement("div");
var element = createElement("button", div, "Click me :)",
[
{"name": "onclick", "value": "console.log('hello');"},
{"name": "class", "value": "coolButton"}
]
);
}
getValueOf(id)
Return value of element with id = id
. It will parse value based on element type. For text
or datetime
it will be string. For number
it will be integer and for radio
or checkbox
it will be boolean.
id
- id of element
9.5 tableBuilder
Script for creating tables.
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/tableBuilder.js"></script>
<!-- necessary dependencies -->
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
Functions:
clearTable(table)
Sets innerHTML of table to "".
table
- html element (do not need to be table)
addRow(table, cells, rowAttributes=[])
Adds one row in the end of the table.
table
- html element (need to be table)cells
- array of cell objects- cell are javascript objects containing "text" and "attributes" which is a list
- attributes are javascript objects containing "name" and "value"
rowAttributes
- list of attributes for row element ("id", "class", "onclick"...)- attributes are javascript objects containing "name" and "value"
addHeader(table, cells, rowAttributes=[])
Adds header to the end of the table. Make sure that you are adding it like first. (If you do not want to have header in the end or in the middle of table ofc).
table
- html element (need to be table)cells
- array of cell objects- cell are javascript objects containing "text" and "attributes" which is a list
- attributes are javascript objects containing "name" and "value"
rowAttributes
- list of attributes for row element ("id", "class", "onclick"...)- attributes are javascript objects containing "name" and "value"
9.6 alerts
Alerts script allows to create custom alerts.
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/alerts.js"></script>
<!-- necessary dependencies -->
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
Need css:
<link rel="stylesheet" href="https://kubaboi.github.io/CheeseFramework/public/styles/alertStyle.css">
Variables of css:
/*
You can create your own animations, classes, colors or just change mine...
If you make something cool I would love to see it :)
*/
/* colors */
--light-color /* background color of main dialog */
--text-color /* text color */
--secondary-color /* border color */
/* classes */
.alertDiv /* better not to be changed (except for colors) */
.alertH2 /* alert title */
.alertLabel /* alert label */
.alertOKbutton /* ok button */
.alertOKbutton:hover
.alertCancelButton /* cancel button */
.alertCancelButton:hover
.divOkAlert /* alert which pop up on the left top and it is green */
.divWrongAlert /* alert which pop up on the left top and it is red */
/* animations */
@keyframes showAlert {} /* show animation for .alertDiv (slides from top) */
@keyframes hideAlert {} /* hide animation for .alertDiv (slides into top) */
@keyframes okShowAlert {} /* show animation for .divOkAlert and .divWrongAlert (slides from left) */
@keyframes okHideAlert {} /* hide animation for .divOkAlert and .divWrongAlert (slides into left) */
Functions:
showAlert(title, message, divClass, animation, closeAnimation)
Shows alert. There are not default values (it would be soooo long).
title
- title of alert (header)message
- message of alert (label)divClass
- css class name- default is
"alertDiv"
- default is
animation
- show animation- default is
{"name":"showAlert","duration":"0.5s"}
- "name" is css animation name
- "duration" is duration of animation
- default is
closeAnimation
- close animation- default is
{"name":"hideAlert","duration":"0.5s"}
- default is
showConfirm(title, message, ifOk, divClass, animation, closeAnimation)
Show confirm alert. It will be closed after user clicks at "OK"/"Close" button.
title
- title of alert (header)message
- message of alert (label)ifOk
- function done after "OK" confirmationdivClass
- css class name- default is
"alertDiv"
- default is
animation
- show animation- default is
{"name":"showAlert","duration":"0.5s"}
- default is
closeAnimation
- close animation- default is
{"name":"hideAlert","duration":"0.5s"}
- default is
Usage:
function func(v) {
console.log(v);
}
showConfirm("Really?",
"Do you really want to print hello??????!!!!",
function() {func("Hello :)");});
showTimerAlert(title, message, time, divClass, animation, closeAnimation)
Show alert which will be after time
miliseconds closed
title
- title of alert (header)message
- message of alert (label)time
- time in milisecondsdivClass
- css class name- default is
"alertDiv"
- default is
animation
- show animation- default is
{"name":"showAlert","duration":"0.5s"}
- default is
closeAnimation
- close animation- default is
{"name":"hideAlert","duration":"0.5s"}
- default is
showOkAlert(title, message, timeAlert=0)
Show ok alert (the green one on the left). If timeAlert
is 0 then the alert won't be closed automaticaly
title
- title of alert (header)message
- message of alert (label)timeAlert
- time in miliseconds (if 0 there won't be timer)
showWrongAlert(title, message, timeAlert=0)
Show wrong alert (the red one on the left). If timeAlert
is 0 then the alert won't be closed automaticaly
title
- title of alert (header)message
- message of alert (label)timeAlert
- time in miliseconds (if 0 there won't be timer)
9.7 loadPage
Script for downloading parts of web dynamically. It is prepared for CheeseApplications
. I do not even know if it is best practice. :no_good:
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/loadPage.js"></script>
<!-- necessary dependencies -->
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/elementManager.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/communication.js"></script>
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/time.js"></script>
Functions:
loadPage(startArray, doAfter=null, afterArray=[])
Parts of the web need to be inside folder webParts
and paths inside arrays does not contain full path (main/header
for header.html
inside /web/webParts/main/
)
startArray
- list of paths for part that need to be loaded before some javascript intializationdoAfter
- javascript initialization, it is a function with processess that need loaded web parts from startArrayafterArray
- list of paths for part that do need to be loaded before initialization
getHtml(name, path, parentId, attributeClass="")
Creates div with attributeClass class, innerHTML from file and will be inside parent. And returns that div.
name
- name of html file (without .html)path
- folder without "/webParts/" and withou nameparentId
- id of html object which will contain this part of webattributeClass
- class name of div
9.8 time
This script has only one function which returns actual time. It is used by communication.js
for logs timestamps.
Import:
<script src="https://kubaboi.github.io/CheeseFramework/public/scripts/time.js"></script>
Functions:
nowTime()
Return string of actual time formated as hh:mm:ss
That's all
Here is a cake :cake: but it is a lie
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file CheeseFramework-1.4.95.tar.gz
.
File metadata
- Download URL: CheeseFramework-1.4.95.tar.gz
- Upload date:
- Size: 86.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.17
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ad28b10a9e7f154aec46c613445544f57c7380a0c9c4a389f53846eb1e2501f5 |
|
MD5 | d188c7a45f83bbe07896f6e1ea84fcb3 |
|
BLAKE2b-256 | 6d9e3f50c15829ae22265d74d93df3bb76f875919e8f5b6ac76e357c9bacc1bf |
File details
Details for the file CheeseFramework-1.4.95-py3-none-any.whl
.
File metadata
- Download URL: CheeseFramework-1.4.95-py3-none-any.whl
- Upload date:
- Size: 68.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.17
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b64cc954fa2a7e66fb413b300cbbb5dba739e9e2f0dde0315e1a6b58c973ea66 |
|
MD5 | 4ad291d43fcd5a54a8ce87a0cf315488 |
|
BLAKE2b-256 | fc84412c8ddf4c888975ae2d60dc73a4632b2d3ba483e3eac17ca25cde8f90cc |