Skip to content

KubaBoi/CheeseFramework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Cheese Framework

Release Build

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 ๐Ÿ˜‰ 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. ๐Ÿ˜Œ

https://kubaboi.github.io/CheeseFramework/

Complete method and classes documentation

Contents

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.

โ€ผ๏ธ IMPORTANT โ€ผ๏ธ

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 โค๏ธ

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:

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.

  1. Create directory for your project

  2. 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 you need help with Cheese commands use python -m Cheese -h.

That's all now you can start your application ๐Ÿ˜Š

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 ๐Ÿ‘ you are now Dairy developer ๐Ÿ‘

2 Cheese Tools

WIP ๐Ÿ’…

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" } } copy code
  • port - port of your app
  • dbDriver - driver for database
  • 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.
  • 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&lt;login&gt;\w+):(?P&lt;password&gt;\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 from types
  • 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 in patern and need to be wrapped in $$ characters
  • if credentials (and every additional check) are valid it will try to find role id via roleId sql
    • roleId sql just finds value from database
    • than use this value as key for roles dictionary
  • endpoint validation
    • endpoint will be used as key for access dictionary
      • if endpoint does not exist in access than it will returns found role (it means AUTHORIZED)
      • if endpoint exists in access and its minRoleId is equal or lower then roleId . value returns role (AUTHORIZED)
      • if endpoint exists in access but its minRoleId is greater then roleId . value returns False (UNAUTHORIZED)
      • if endpoint exists in access but role is None returns False (UNAUTHORIZED)

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
  • findWhere(**columns)
    • finds all rows with value of columns
    • **columns is kwargs so dict where keys are column names and its values are ... ehm values
  • findOneWhere(**columns)
    • finds one model with value of columns
    • **columns is same as in findWhere
  • findNewId()
    • finds new ID (free one)
  • save(obj)
    • inserts new row into database by obj (it is CheeseModel)
  • update(obj)
    • updates values in database by id of obj
  • delete(obj)
    • deletes row from database by id of obj
  • exists(**columns)
    • return true if at least one row exists
    • **columns is same as in findWhere
  • model()
    • returns new instance of CheeseModel and finds to it free id via findNewId()

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))

โ€ผ๏ธ 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

โ€ผ๏ธ Do not create models by yourself โ€ผ๏ธ

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.

โŒ

model = CheeseModel(modelName, scheme)

โœ”๏ธ

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 cookie
  • cvalue - value of the cookie
  • exdays - 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 is true, change it if you do not want to print informations about requests
  • authorization - variable that will be send like Authorization 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 than url needs to have http[s]://server/endpoint structure
  • request - 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 element
  • attributes - 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"
  • animation - show animation
    • default is {"name":"showAlert","duration":"0.5s"}
    • "name" is css animation name
    • "duration" is duration of animation
  • closeAnimation - close animation
    • default is {"name":"hideAlert","duration":"0.5s"}

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" confirmation
  • divClass - css class name
    • default is "alertDiv"
  • animation - show animation
    • default is {"name":"showAlert","duration":"0.5s"}
  • closeAnimation - close animation
    • default is {"name":"hideAlert","duration":"0.5s"}

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 miliseconds
  • divClass - css class name
    • default is "alertDiv"
  • animation - show animation
    • default is {"name":"showAlert","duration":"0.5s"}
  • closeAnimation - close animation
    • default is {"name":"hideAlert","duration":"0.5s"}

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. ๐Ÿ™…

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 intialization
  • doAfter - javascript initialization, it is a function with processess that need loaded web parts from startArray
  • afterArray - 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 name
  • parentId - id of html object which will contain this part of web
  • attributeClass - 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 ๐Ÿฐ but it is a lie

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages