web mvc framework for python
Project description
Hallo
Table of Contents
web mvc framework for python
Requirements
- Python:
- CPython: 3.6 or newer
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 and run project
cd your-project
hallo install
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
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 Hallo-1.0.2.tar.gz
.
File metadata
- Download URL: Hallo-1.0.2.tar.gz
- Upload date:
- Size: 31.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | aaf6964ac41a27fa64253d2294c5a7ae4fcb09a93e05bc493bb30ced6999750d |
|
MD5 | c43ba972027261a3d2d7dedef8089c08 |
|
BLAKE2b-256 | 36dd14969521e71668a216f31bd315fe7309a7aff466752d0956dddc8a42bef2 |
File details
Details for the file Hallo-1.0.2-py3-none-any.whl
.
File metadata
- Download URL: Hallo-1.0.2-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.9.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 385c38bc120c953d01144eefcb8f25dea58c858937e0184517f262257849f4ee |
|
MD5 | 34db9f9389208c3ff37592980c5d3630 |
|
BLAKE2b-256 | 2396440a7a7d0e5742c7e039399c8445a898f10cb4369844364b529316f87740 |