Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/setup #61

Merged
merged 50 commits into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f40c679
[add] examplesディレクトリを追加
Aug 31, 2020
a714d28
Merge remote-tracking branch 'origin/develop' into feature/samples
Aug 31, 2020
94248ad
[add] 雛形を追加
Aug 31, 2020
68d9b91
fix
takuan517 Aug 31, 2020
8490a03
Merge branch 'feature/samples' of github.com:mitama-org/mitama into f…
takuan517 Aug 31, 2020
baedeb3
add skelton
takuan517 Aug 31, 2020
448e384
fix
takuan517 Aug 31, 2020
79c09a3
[add] サーバーの初期化部分、ユーザー登録部分の作り込み
Sep 4, 2020
b46ca53
add mkapp command
takuan517 Sep 4, 2020
3d7c36d
[update]
Sep 4, 2020
51805ef
Merge branch 'develop' into feature/setup
Sep 4, 2020
2dc2c3e
[add]ユーザー、グループのルーティングを設定
Sep 5, 2020
06d0e6d
[update] UserとGroupを一部抽象化/listメソッドの追加
Sep 6, 2020
ff1c1ca
[update] ミドルウェアを追加
Sep 6, 2020
4c4631c
[update]
Sep 6, 2020
be24ad8
Merge remote-tracking branch 'origin/frature/template' into frature/t…
Sep 6, 2020
95c7e17
[merge]
Sep 6, 2020
4919de7
[update] ミドルウェアの仕様を変更
Sep 7, 2020
85f1774
[add] ログイン状態の管理ミドルウェアの追加
Sep 7, 2020
a22cf1a
[add] rails風のインターフェースをつくりますた
Sep 17, 2020
439941e
[update] ルーティングの仕様を変更
Sep 18, 2020
5a12dfe
[add] メソッドを追加
Sep 18, 2020
a0b76c1
[update] いつか非同期サーバー本体も自前で作ってしまいたい。そう思わざるを得ないほどにAio-httpの不可触部分に頼り切ってしま…
Sep 19, 2020
0e2f119
[add] ユーザー・グループの作成、更新、削除にHookをつける機能を追加
Sep 19, 2020
e3567ac
[update] スタイリング他
Sep 19, 2020
1e42fee
[fix] setup.py
Sep 19, 2020
5d93d17
[update] スタイリング。人生初の岐阜県にて
Sep 20, 2020
b737980
[update] 設定画面、アプリ画面を実装
Sep 21, 2020
15e2d71
[update] アプリ設定を追加
Sep 21, 2020
4283f21
[update] サーバー実装を変更
Sep 24, 2020
5d93bba
[fix] 不具合を修正
Sep 26, 2020
6bc1256
[update] 権限モデルを追加
Sep 26, 2020
04396e9
[update] パーミッションの機能を追加
Sep 27, 2020
edc8446
[update] テストを追加
Sep 27, 2020
f390b41
[update] テストの問題を解決
Sep 27, 2020
b9ffae4
[rm] いらないテスト削除
Sep 27, 2020
3d2c44f
[update] パーミッション機能が進化した
Sep 27, 2020
ea6b31e
[update] かんせー
Sep 28, 2020
5691fe9
[fix] テスト通らないの修正
Sep 28, 2020
34258a6
[fix] 余計な出力けした
Sep 28, 2020
d001f7f
[rm] テスト用ファイルを削除
Sep 28, 2020
88845b4
[update] Docstringを追加
Sep 29, 2020
18e9ad0
[update] 削除時のHookを追加
Sep 29, 2020
d53fbd2
[fix] フックの不具合を修正
Sep 29, 2020
4acaf81
[fix] パーミッションとか修正
Sep 30, 2020
2ca01aa
[fix] アプリ初期化時の不具合を修正
Sep 30, 2020
6c43361
[update] モデルからデータベース用の型を直接取れるようにした
Sep 30, 2020
251b681
[fix] テストを修正
Sep 30, 2020
0ce69f1
[fix]
Sep 30, 2020
18563fa
[fix]
Sep 30, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
__pycache__/
node_modules/
.cache
*.egg-info/
*.egg
.installed.cfg
db.sqlite3
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
test-run:
cd tests/interface; mitama run

watch-css:
npm run watch-css

build-css:
npm run build-css
96 changes: 95 additions & 1 deletion mitama/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,97 @@
from jinja2 import *
from .app import App
from .meta import BaseMetadata
from .router import Router
from .builder import Builder
from .registry import AppRegistry
from mitama.http import Request, Response
from pathlib import Path
from mimetypes import add_type, guess_type
from abc import ABCMeta, abstractmethod
import os

add_type('application/json', '.map')

class Controller():
'''MVCのControllerの基底クラス

メソッドを記述すると、それをリクエスト処理に利用できる。
ルーティング時にメソッドを特に指定しない場合はhandleメソッドが実行される。
非同期関数として定義すること。非同期なので、別プロセスを中継するとかもやりやすい(かもしれない)。
:param app: Controllerを起動するAppのインスタンスの参照
:param view: Controllerが利用するJinja2のEnvironmentインスタンス
'''
app = None
view = None
async def handle(self, request: Request):
'''リクエストハンドラ

ルーティング時に特にメソッド名を指定しない場合にはこのメソッドが起動する。
独自にメソッドを定義する場合にも、このメソッドと同じインターフェースを実装しなければならない。
:param request: mitama.http.Requestのインスタンス
:return: mitama.http.Responseのインスタンス
'''
pass

class Middleware(metaclass = ABCMeta):
'''Requestを加工するMiddlewareの抽象クラス

processメソッドによって受け取ったRequestを変更し、handler(次のMiddleware、またはControllerのメソッド)に受け渡す。
:param app: Middlewareを起動するAppのインスタンスの参照
:param view: Middlewareが利用するJinja2のEnvironmentインスタンス
'''
app = None
view = None
@abstractmethod
async def process(self, request: Request, handler):
'''Middlewareのメイン処理

Middlewareは必ずこのメソッドを実装しなければならない。
:param request: mitama.http.Requestのインスタンス
:param handler: requestを引数に受け取る関数(Middleware.process、またはControllerのリクエストハンドラ)
'''
pass

class StaticFileController(Controller):
'''静的ファイルを配信するController

デフォルトではアプリのパッケージ内の :file:`static/` の中身を配信する。
'''
def __init__(self, *paths):
'''初期化処理

初期化するとき、引数にディレクトリのパスを列挙すると、そのディレクトリが配信される。
何も指定されなかった場合、アプリ内の :file:`static/` が配信される。
:param *paths: 配信するディレクトリ
'''
super().__init__()
self.paths = list(paths)
def __connected__(self):
app_mod_dir = Path(os.path.dirname(__file__))
self.view = Environment(
enable_async=True,
loader = FileSystemLoader([
self.app.project_dir,
app_mod_dir / 'templates',
app_mod_dir / '../http/templates'
])
)
if len(self.paths) == 0:
self.paths.append(self.app.install_dir / 'static')
async def handle(self, req: Request):
for path in self.paths:
filename = path / req.params['path']
if filename.is_file():
mime = guess_type(str(filename)) or 'application/octet-stream'
with open(filename) as f:
return Response(body = f.read(), headers={
'content-type': mime[0]
})
for path in self.paths:
filename = path / '404.html'
if filename.is_file():
with open(filename) as f:
return await Response(text = f.read(), status = 404, headers = {
'content-type': 'text/html'
})
template = self.view.get_template('404.html')
return await Response.render(template, req, status = 404)
116 changes: 108 additions & 8 deletions mitama/app/app.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,114 @@
#!/usr/bin/python
from aiohttp import web
from yarl import URL
from jinja2 import Environment, FileSystemLoader
from pathlib import Path
import magic
import os
from base64 import b64encode
from mitama.app.noimage import load_noimage_app
from mitama.hook import HookRegistry
from mitama.http import Request, Response

def dataurl(blob):
f = magic.Magic(mime = True, uncompress = True)
mime = f.from_buffer(blob)
return 'data:'+mime+';base64,'+b64encode(blob).decode()

class App:
def __init__(self, meta):
self.app = web.Application(
middlewares = [
web.normalize_path_middleware(append_slash = True)
]
)
self.meta = meta
self.name = meta.name
template_dir = 'templates'
instances = list()
description = ""
name = ""
@property
def icon(self):
return load_noimage_app()
def __init__(self, **kwargs):
self.app = web.Application(client_max_size = kwargs["client_max_size"] if "client_max_size" in kwargs else 100*1024*1024)
self.screen_name = kwargs['name']
self.path = kwargs['path']
self.project_dir = Path(kwargs['project_dir']) if 'project_dir' in kwargs else None
self.project_root_dir = Path(kwargs['project_root_dir']) if 'project_dir' in kwargs else None
self.install_dir = Path(kwargs['install_dir']) if 'project_dir' in kwargs else Path(os.path.dirname(__file__)) / '../http/'
for instance in self.instances:
instance.app = self
instance.view = self.view
if hasattr(instance, '__connected__'):
instance.__connected__()
hook_registry = HookRegistry()
if hasattr(self, 'create_user'):
hook_registry.add_create_user_hook(self.create_user)
if hasattr(self, 'create_group'):
hook_registry.add_create_group_hook(self.create_group)
if hasattr(self, 'update_user'):
hook_registry.add_update_user_hook(self.update_user)
if hasattr(self, 'update_group'):
hook_registry.add_update_group_hook(self.update_group)
if hasattr(self, 'delete_user'):
hook_registry.add_delete_user_hook(self.delete_user)
if hasattr(self, 'delete_group'):
hook_registry.add_delete_group_hook(self.delete_group)
async def handle(request):
if not isinstance(request, Request):
request = Request.from_request(request)
result = await self.router.match(request)
if result:
request, handle = result
return await handle(request)
else:
return await self.error(request, 404)
self.app.router.add_route('*', '/{tail:.*}', handle)
def __getattr__(self, name):
return getattr(self.app, name)
def set_middleware(self, middlewares):
self.app.middlewares.extend(middlewares)
def convert_fullurl(self, req, url):
scheme= req.scheme
hostname = req.host
path = self.path
if path[0] != '/':
path = '/' + path
if path[-1] == '/':
path = path[0:-2]
if url[0] != '/':
url = '/' + url
return scheme + "://" + hostname + path + url
def convert_url(self, url):
path = self.path
if path[0] != '/':
path = '/' + path
if path[-1] == '/':
path = path[0:-2]
if url[0] != '/':
url = '/' + url
return path + url
def revert_url(self, url):
path = self.path
if path[0] != '/':
path = '/' + path
if path[-1] == '/':
path = path[0:-2]
url = str(url.path)
url = URL(url[len(path):])
return url
@property
def view(self):
self._view= Environment(
enable_async = True,
loader = FileSystemLoader(self.install_dir / self.template_dir)
)
def filter_user(arg):
return [user for user in arg if user.__class__.__name__ == "User"]
def filter_group(arg):
return [group for group in arg if group.__class__.__name__ == "Group"]
self._view.filters["user"] = filter_user
self._view.filters["group"] = filter_group
self._view.globals.update(
url = self.convert_url,
fullurl = self.convert_fullurl,
dataurl = dataurl
)
return self._view
async def error(self, request, code):
template = self.view.get_template(str(code) + '.html')
return await Response.render(template, request)
26 changes: 26 additions & 0 deletions mitama/app/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import inspect
import os

class Builder(object):
'''Appにメタ情報を入力して生成するビルダー

プロジェクトディレクトリのパスやインストール先、プロジェクト内のアプリ用ディレクトリのパスをAppに設定し、インスタンスを返却します。
特にこれを弄るケースは想定していませんが、独自の挙動を付けたかったら継承して作っても良いかもしれません。
アプリのパッケージ直下のAppBuilderが起動されるので、:file:`__init__.py` に :samp:`class AppBuilder(Builder)` を定義してください。
'''
app = None
def __init__(self):
self.data = {}
pass
def set_path(self, path):
self.data['path'] = path
def set_name(self, name):
self.data['name'] = name
def set_project_dir(self, path):
self.data['project_dir'] = path
def set_project_root_dir(self, path):
self.data['project_root_dir'] = path
def build(self):
install_dir = os.path.dirname(inspect.getfile(self.__class__))
self.data['install_dir'] = install_dir
return self.app(**self.data)
6 changes: 0 additions & 6 deletions mitama/app/meta.py

This file was deleted.

92 changes: 92 additions & 0 deletions mitama/app/method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from mitama.app.router import Route

def view(path, handler):
'''GET, POSTのルーティング先を指定

ブラウザで見れる一般的なルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['GET', 'POST'], path, handler)

def any(path, handler):
'''メソッドを考慮しないルーティング先を指定

メソッドに関係なくマッチするルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTION', 'HEAD'], path, handler)

def post(path, handler):
'''POSTのルーティング先を指定

POSTメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['POST'], path, handler)

def patch(path, handler):
'''PATCHのルーティング先を指定

PATCHメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['PATCH'], path, handler)

def head(path, handler):
'''HEADのルーティング先を指定

HEADメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['HEAD'], path, handler)

def option(path, handler):
'''OPTIONのルーティング先を指定

OPTIONメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['OPTION'], path, handler)

def put(path, handler):
'''PUTのルーティング先を指定

PUTメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['PUT'], path, handler)

def get(path, handler):
'''GETのルーティング先を指定

GETメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['GET'], path, handler)

def delete(path, handler):
'''DELETEのルーティング先を指定

DELETEメソッドのルーティング先を作成します。
:param path: マッチするパス
:param handler: リクエストハンドラ
:return: Routeインスタンス
'''
return Route(['DELETE'], path, handler)

21 changes: 21 additions & 0 deletions mitama/app/middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from mitama.http import Response
from mitama.auth import AuthorizationError, check_jwt
from mitama.app import Middleware
import urllib

class SessionMiddleware(Middleware):
'''ログイン判定ミドルウェア

ログインしていないユーザーがアクセスした場合、/login?redirect_to=<URL>にリダイレクトします。
'''
async def process(self, request, handler):
sess = await request.session()
try:
if 'jwt_token' in sess:
request.user = check_jwt(sess['jwt_token'])
else:
return Response.redirect('/login?redirect_to='+urllib.parse.quote(str(request.url), safe=''))
except Exception as err:
return Response.redirect('/login?redirect_to='+urllib.parse.quote(str(request.url), safe=''))
return await handler(request)

Loading