Skip to main content

web mvc framework for python

Project description

Hallo

Table of Contents

web mvc framework for python

Requirements

Installation

Package is uploaded on PyPI.

You can install it with pip:

$python3 pip install hallo

Documentation

中文文档.

Example

show version

hallo --version

create project

hallo create your-project

install requirements

cd your-project
pip install -r requirements.txt

run project

python main.py

create first controller

file: module/hello.py

from app.module.base import BaseController

class HelloController(BaseController):

    def world(self):
        return 'hello world'

url: http://127.0.0.1/hello/world

create json controller

file: module/json.py

from app.module.base import BaseController

class JsonController(BaseController):

    def this_is_ok(self):
        return self.ok('this is ok')

    def this_is_error(self):
        return self.error('this is error')

url: http://127.0.0.1/json/this-is-json

{
    "code": 0,
    "data": "this is ok",
}

url: http://127.0.0.1/json/this-is-error

{
    "code": 1,
    "data": "this is error",
}

create html controller

file: module/html.py

from app.module.base import BaseController

class HtmlController(BaseController):
    
    def index(self):
        return self.render('html/index.html')

url: http://127.0.0.1/html/index

<p>hello world!</p>

model

file: models/user.py

from pymyorm.model import Model

class User(Model):
    tablename = 'user'

run table/model to reflect all model from database

python console.py table/model

config

development

class DevelopmentConfig(Config):
    ENV = 'development'

testing

class TestingConfig(Config):
    ENV = 'testing'

production

class ProductionConfig(Config):
    ENV = 'production'

host

class Config(object):
    HOST = '127.0.0.1'

port

class Config(object):
    PORT = 80

server name

class Config(object):
    # domain
    SERVER_NAME = 'hallo.com'

session

class Config(object):
    # session
    SECRET_KEY = ''
    PERMANENT_SESSION_LIFETIME = timedelta(hours=1)

file upload

class Config(object):
    # file upload
    MAX_CONTENT_LENGTH = 8 * 1024 * 1024  # 8M

mysql

class Config(object):
    # db: mysql / pgsql
    DB_POOL_SIZE = 1
    DB_CONF = dict(
        source='mysql',
        host='127.0.0.1',
        port=3306,
        user='root',
        password='password',
        database='hallo',
        charset='utf8'
    )

redis

class Config(object):
    # redis
    REDIS_URL = 'redis://127.0.0.1:6379/0'

memcache

class Config(object):
    # cache
    CACHE_CONF = [
        '127.0.0.1:11211'
    ]

oss

class Config(object):
    # oss
    OSS_CONF = dict(
        endpoint='',
        bucket='',
        access_key_id='',
        access_key_secret=''
    )

routing

1、auto routing

http://127.0.0.1/<module>/<controller>/<action>

<module>: the directory or subdirectory under module

<controller>: the controller

<action>: the function of controller

2、user defined routing

file: app/router.py

router = Router(app=app)
router.add('/hello/<name>', 'hello/hi')

file: module/hello.py

from app.module.base import BaseController
class HelloController(BaseController):

    def hi(self, name):
        return f'hi, {name}'

url: http://127.0.0.1/hello/jack

hi, jack

3、subdomain

file: config.py

class Config(object):
    SERVER_NAME = 'hallo.com'

file: app/router.py

admin = Router(app=app, subdomain='admin', module='admin')

HTTP request

1、get

file: module/http.py

from app.module.base import BaseController

class HttpController(BaseController):
    
    def info(self):
        name = self.get('name')
        age = self.get('age')
        
        return self.ok(dict(
            name=name,
            age=age
        ))

url: http://127.0.0.1/http/info?name=jack&age=18

{
    "name": "jack",
    "age": 18
}

2、post

file: module/http.py

from app.module.base import BaseController

class HttpController(BaseController):

    def save(self):
        name = self.post('name')
        age = self.post('age')
        
        return self.ok(dict(
            name=name,
            age=age
        ))

url: http://127.0.0.1/http/save

{
    "name": "lucy",
    "age": 18
}

3、header

file: module/header.py

from app.module.base import BaseController

class HeaderController(BaseController):

    def token(self):
        token = self.header('Token')
        return self.ok(token)

url: http://127.0.0.1/header/token

{
    "code": 0,
    "data": {
        "token": "123456"
    }
}

4、file

file: module/file/upload.py

from app.module.base import BaseController

class FileController(BaseController):

    def upload(self):
        file = self.file('file')

url: http://127.0.0.1/file/upload

mysql

file: sql/user.sql

create table if not exists `user` (
    `id` int unsigned not null auto_increment,
    `username` varchar(32) not null default '',
    `phone` varchar(16) not null default '',
    `money` decimal(10,2) not null default 0,
    `gender` tinyint unsigned not null default 0,
    `password` varchar(128) not null default '',
    `time` timestamp not null default current_timestamp,
    primary key(`id`),
    unique key `idx_username` (`username`),
    key `idx_phone` (`phone`),
    key `idx_time` (`time`)
) engine=InnoDB default charset=utf8mb4;

1、add user

file: module/user.py

from app.module.base import BaseController
from app.models.user import User

class UserController(BaseController):

    def add(self):
        username = self.post('username')
        phone = self.post('phone')
        money = self.post('money')
        gender = self.post('gender')

        model = User()
        model.username = username
        model.phone = phone
        model.money = money
        model.gender = gender
        model.save()

        return self.ok()

url: http://127.0.0.1/user/add

{
  "code": 0,
  "data": "ok"
}

2、edit user

file: module/user.py

from app.module.base import BaseController
from app.models.user import User

class UserController(BaseController):

    def edit(self):
        id = self.post('id')
        username = self.post('username')
        phone = self.post('phone')
        money = self.post('money')
        gender = self.post('gender')

        model = User.find().where(id=id).one()
        if not model:
            return self.error('user not exists')
        model.username = username
        model.phone = phone
        model.money = money
        model.gender = gender
        model.save()

        return self.ok()

url: http://127.0.0.1/user/edit

{
  "code": 0,
  "data": "ok"
}

3、delete user

file: module/user.py

from app.module.base import BaseController
from app.models.user import User

class UserController(BaseController):

    def delete(self):
        id = self.post('id')
        User.find().where(id=id).delete()
        return self.ok()

url: http://127.0.0.1/user/delete

{
  "code": 0,
  "data": "ok"
}

4、list user

file: module/user.py

from app.module.base import BaseController
from app.models.user import User

class UserController(BaseController):

    def list(self):
        self.init_page()
        model = User.find()
        total = model.count()
        all = model.offset(self.offset).limit(self.limit).all(raw=True)
        return self.resp_page(all, total)

url: http://127.0.0.1/user/list

redis

1、set

file: module/redis.py

from app.module.base import BaseController

class RedisController(BaseController):

    def mem_set(self):
        try:
            key = self.get('key')
            val = self.get('val')
            self.redis.set(name=key, value=val, ex=3600)
            return self.ok()
        except Exception as e:
            return self.error(str(e))

url: http://127.0.0.1/redis/mem-set?key=name&val=jack

2、get

file: module/redis.py

from app.module.base import BaseController

class RedisController(BaseController):

    def mem_get(self):
        try:
            key = self.get('key')
            val = self.redis.get(key)
            if isinstance(val, bytes):
                return self.ok(val.decode('UTF-8'))
            else:
                return self.error()
        except Exception as e:
            return self.error(str(e))

url: http://127.0.0.1/redis/mem-get?key=name

memcache

1、set

file: module/cache.py

from app.module.base import BaseController

class CacheController(BaseController):

    def mem_set(self):
        key = self.get('key')
        val = self.get('val')
        if self.cache.set(key, val):
            return self.ok()
        else:
            return self.error()

url: http://127.0.0.1/cache/mem-set?key=name&val=lucy

2、get

file: module/cache.py

from app.module.base import BaseController

class CacheController(BaseController):

    def mem_get(self):
        key = self.get('key')
        val = self.cache.get(key)
        if val:
            return self.ok(val)
        else:
            return self.error()

url: http://127.0.0.1/cache/mem-get?key=name

file upload

1、local

file: module/file.py

from app.module.base import BaseController
from app.helper.file import File

class FileController(BaseController):

    def upload(self):
        file = self.file('file')
        f = File()
        path = f.upload(file)

        resp = dict()
        resp['path'] = path
        return self.ok(resp)

url: http://127.0.0.1/file/upload

{
    "code": 0,
    "data": {
        "path": "static/upload/txt/9e/4e/9b/77/9e4e9b7754f8d4a26fc93663d2dae4d6.txt"
    }
}

2、oss

file: module/oss.py

from app.module.base import BaseController
from app.helper.oss import Oss

class OssController(BaseController):

    def upload(self):
        file = self.file('file')
        oss = Oss()
        key = oss.upload(file)

        resp = dict()
        resp['key'] = key
        resp['url'] = oss.url(key)
        resp['sign_url'] = oss.sign_url(key=key, expires=3600)
        return self.ok(resp)

url: http://127.0.0.1/oss/upload

template

file: module/html.py

from app.module.base import BaseController

class HtmlController(BaseController):

    def index(self):
        tv = dict()
        tv['title'] = 'hello world!'
        return self.render('html/index.html', tv)

url: http://127.0.0.1/html/index

console

1、builtin command: table

file: console/table.py

show all tables

python console.py table/show

create all tables

python console.py table/build

reflect all tables

python console.py table/model

2、builtin command: secret

file: console/secret.py

generate a random secret key

python console.py secret/key

3、user defined command: test

file: console/test.py

from app.console.base import BaseCommand

class TestCommand(BaseCommand):

    def hello(self):
        print('hello')

run test command

python console.py test/hello

log

1、app log

file: log/app.log

2022-07-04 22:12:13 INFO [base.py:28]: flask app init
2022-07-04 22:12:13 INFO [connection_pool.py:36]: put connection into pool
2022-07-04 22:12:13 INFO [_internal.py:224]:  * Running on http://127.0.0.1:80 (Press CTRL+C to quit)

2、console log

file: log/console.log

2022-07-04 22:14:33 WARNING [table.py:27]: table user exists

log formatter

file: helper/functions.py

def create_log(log_file):
    dictConfig({
        'version': 1,
        'formatters': {
            'default': {
                'format': '%(asctime)s %(levelname)s [%(filename)s:%(lineno)s]: %(message)s',
                'datefmt': '%Y-%m-%d %H:%M:%S'
            }
        },
        'handlers': {
            'wsgi': {
                'class': 'logging.StreamHandler',
                'stream': 'ext://flask.logging.wsgi_errors_stream',
                'formatter': 'default'
            },
            'message': {
                'class': 'logging.handlers.RotatingFileHandler',
                'formatter': 'default',
                'filename': log_file,
                'maxBytes': 1024 * 1024,
                'backupCount': 10,
                'encoding': 'utf-8'
            }
        },
        'root': {
            'level': 'INFO',
            'handlers': ['wsgi', 'message']
        }
    })

decorator

file: helper/decorator.py

1、time_cost

def time_cost(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        resp = func(*args, **kwargs)
        end = time.time()
        cost = round(end - start, 3)
        logging.info(f'{request.path} time cost={cost}s')
        return resp
    return wrapper

2、retry

def retry(num=1, seconds=0):
    def outer(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            for i in range(0, num):
                try:
                    resp = func(*args, **kwargs)
                    if resp['code'] == 0:
                        return resp
                    if seconds > 0:
                        time.sleep(seconds)
                except Exception as e:
                    logging.error(str(e))
            return error(f'retry {num} times and failed')
        return inner
    return outer

3、check_login

def check_login(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        userid = session.get('userid')
        if not userid:
            return error('login is required', 401)
        else:
            return func(*args, **kwargs)
    return wrapper

4、check_token

def check_token(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        token = request.headers.get('Token')
        if not token:
            return error('token is missing', 401)
        if not app.config['redis'].get(token):
            return error('token is expired', 401)
        else:
            return func(*args, **kwargs)
    return wrapper

Login

1、login by session

file: module/session/user.py

from app.module.base import BaseController
from flask import session
from app.models.user import User
from app.helper.decorator import check_login

class UserController(BaseController):

    def login(self):
        username = self.post('username')
        password = self.post('password')
        user = User.find().where(username=username).one()
        if not user:
            return self.error('user not exists')
        if user.password != password:
            return self.error('wrong password')
        session['userid'] = user.id
        return self.ok('login success')

2、logout by session

file: module/session/user.py

from app.module.base import BaseController
from flask import session
from app.models.user import User
from app.helper.decorator import check_login

class UserController(BaseController):

    @check_login
    def logout(self):
        session.pop('userid')
        return self.ok('logout success')

3、login by token

file: module/token/user.py

from app.module.base import BaseController
from app.module.user import User
from app.helper.decorator import check_token

class UserController(BaseController):

    def login(self):
        username = self.post('username')
        password = self.post('password')
        user = User.find().where(username=username).one()
        if not user:
            return self.error('user not exists')
        if user.password != password:
            return self.error('wrong password')
        token = user.login()
        resp = dict()
        resp['token'] = token
        return self.ok(resp)

4、logout by token

file: module/token/user.py

from app.module.base import BaseController
from app.module.user import User
from app.helper.decorator import check_token

class UserController(BaseController):

    @check_token
    def logout(self):
        self.user.logout()
        return self.ok('logout success')

deploy to production

cd your-project
hallo install
./install.sh

start project

./supervisor/start.sh

stop project

./supervisor/stop.sh

restart project

./supervisor/restart.sh

shutdown project

./supervisor/shutdown.sh

Resource

License

Hallo is released under the MIT License. See LICENSE for more information.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Hallo-0.2.6.tar.gz (31.8 kB view details)

Uploaded Source

Built Distribution

Hallo-0.2.6-py3-none-any.whl (34.8 kB view details)

Uploaded Python 3

File details

Details for the file Hallo-0.2.6.tar.gz.

File metadata

  • Download URL: Hallo-0.2.6.tar.gz
  • Upload date:
  • Size: 31.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.7

File hashes

Hashes for Hallo-0.2.6.tar.gz
Algorithm Hash digest
SHA256 4e018d70ac6cab80775c37157d79156e06bc63806c703411331f36c26a8069f8
MD5 fd9a23fe67741176793292ccd47db011
BLAKE2b-256 2e03d4d506a823c54fdb75a137e50e696edc4115e7566286aac8a9f0bfed6720

See more details on using hashes here.

Provenance

File details

Details for the file Hallo-0.2.6-py3-none-any.whl.

File metadata

  • Download URL: Hallo-0.2.6-py3-none-any.whl
  • Upload date:
  • Size: 34.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.7

File hashes

Hashes for Hallo-0.2.6-py3-none-any.whl
Algorithm Hash digest
SHA256 459437e23f9cf7afec6773cf34ecb07de191167a3a3190c455ba488b5be2809d
MD5 c07768cb1aeb42712f1646d92e9994c1
BLAKE2b-256 48f3fe34178a3e90c09d77f13f7e2fc593d15b1154d17ab6810d718bd891ae3d

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page