From 97cdfc4c1f540bb45b98a92b25f1c6e5310de5d9 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 1 May 2018 19:54:19 +0900 Subject: [PATCH 01/20] add base scaffold --- helmetica/__init__.py | 0 helmetica/cli.py | 60 +++++++++++++++++++++++ helmetica/scaffold/__init__.py | 18 +++++++ helmetica/scaffold/app.py | 87 +++++++++++++++++++++++++++++++++ helmetica/scaffold/extension.py | 61 +++++++++++++++++++++++ helmetica/scaffold/model.py | 50 +++++++++++++++++++ helmetica/scaffold/test.py | 58 ++++++++++++++++++++++ setup.py | 45 +++++++++++++++++ 8 files changed, 379 insertions(+) create mode 100644 helmetica/__init__.py create mode 100644 helmetica/cli.py create mode 100644 helmetica/scaffold/__init__.py create mode 100644 helmetica/scaffold/app.py create mode 100644 helmetica/scaffold/extension.py create mode 100644 helmetica/scaffold/model.py create mode 100644 helmetica/scaffold/test.py create mode 100644 setup.py diff --git a/helmetica/__init__.py b/helmetica/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/helmetica/cli.py b/helmetica/cli.py new file mode 100644 index 0000000..eeacd80 --- /dev/null +++ b/helmetica/cli.py @@ -0,0 +1,60 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +main.py +helmetica main script +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-24' +import os +import click +from helmetica.scaffold.app import App +from helmetica.scaffold.extension import Extension + +@click.group() +def main(): + pass + +@main.command() +@click.option('--db', default=None, help='SQLAlchemy or Mongoengine or None') +@click.option('--cache', default=None, help='Redis or Memd or None') +@click.option('--restful', default=None, help='use Flask-Restful or API based on Flask common decorator') +@click.option('--web', default='gunicorn', help='use gunicorn or others') +def init(db, cache, restful, web): + dirs = ['./app/', './test/', './config/'] + for dir in dirs: + if os.path.exists(os.path.dirname(dir)): + click.echo('[WARNING] directory {} is already exists, skip to create this directory'.format(dir)) + continue + os.makedirs(os.path.dirname(dir)) + app = App(db=db, cache=cache) + extension = Extension(db=db, cache=cache) + with open('app/__init__.py', 'w') as __init__: + __init__.write(app.create_app__init__()) + if extension.any_extension: + with open('app/extensions.py', 'w') as ext: + ext.write(extension.create_extensions()) + with open('wsgi.py', 'w') as wsgi: + wsgi.write(app.create_wsgi()) + with open('config/__init__.py', 'w') as config: + config.write(app.create_config()) + # 最初にディレクトリを作成して欲しい。 + # database を SQLAlchemy, Mongoengine, None を指定出来るようにしたい + # cache を redis, memcached, None を指定できるようにしたい + # 初回いきなり起動できるscaffold にしたい + +@main.command() +@click.argument('name', type=str, required=True) +@click.option('--version', default='v1', help='API version') +def api(name, version): + # API を勝手に作成出来るようにしたい + click.echo('create api {}/{}'.format(name, version)) + pass + +@main.command() +@click.argument('name', type=str, required=True) +def model(name): + # モデルを勝手に作成出来るようにしたい + click.echo('create model {}'.format(name)) + pass diff --git a/helmetica/scaffold/__init__.py b/helmetica/scaffold/__init__.py new file mode 100644 index 0000000..15c23d8 --- /dev/null +++ b/helmetica/scaffold/__init__.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +__init__.py +Scaffold Abstract +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-27' + + +class Scaffold(object): + def __init__(self, filepath): + self.filepath = filepath + + def write(self, source_code): + with open(self.filepath, 'w') as py: + py.write(source_code) diff --git a/helmetica/scaffold/app.py b/helmetica/scaffold/app.py new file mode 100644 index 0000000..fa9e20c --- /dev/null +++ b/helmetica/scaffold/app.py @@ -0,0 +1,87 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +app.py +scaffold create_app +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-27' +from textwrap import dedent + + +class App(object): + """ App Scaffold + """ + + def __init__(self, db=None, cache=None): + self.db = db + self.cache = cache + + def create_app__init__(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask import Flask + + def create_app(env='development') + app = Flask(__name__) + if env == 'development': + app.config.from_object('config.development.Development') + elif env == 'production': + app.config.from_object('config.production.Production') + else: + app.config.from_object('config.development.Development') + + {} + return app + """.format( + self.create_extension() + ) + return dedent(source_code) + + def create_wsgi(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + import os + from app import create_app + + app = create_app(os.getenv('FLASK_ENV', None)) + + if __name__ == '__main__': + app.run(host='0.0.0.0') + """ + return dedent(source_code) + + def create_config(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + + class Config(object): + FLASK_ENV = 'development' + """ + return dedent(source_code) + + def create_extension(self): + source_code = """\ + {db} + {cache} + """.format( + db=self.create_db(), + cache=self.create_cache(), + ).strip() + return source_code + + def create_db(self): + if self.db: + return 'db.init_app(app)' + else: + return '' + + def create_cache(self): + if self.cache: + return 'redis.init_app(app)' + else: + return '' diff --git a/helmetica/scaffold/extension.py b/helmetica/scaffold/extension.py new file mode 100644 index 0000000..e29adad --- /dev/null +++ b/helmetica/scaffold/extension.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +extension.py +scaffold create_extension +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-30' +from textwrap import dedent + + +class Extension(object): + """ Extension Scaffold + """ + + def __init__(self, db=None, cache=None): + self.db = db + self.cache = cache + + def create_extensions(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + {} + + {} + """.format( + self.create_header(), + self.create_instance(), + ) + return dedent(source_code).strip() + + def create_header(self): + import_db = '' + import_cache = '' + if self.db: + import_db = 'from flask_sqlalchemy import SQLAlchemy' + if self.cache: + import_cache = 'from flask_redis import FlaskRedis' + header = """ + {} + {} + """.format(import_db, import_cache) + return header.strip() + + def create_instance(self): + instance_db = '' + instance_cache = '' + if self.db: + instance_db = 'db = SQLAlchemy()' + if self.cache: + instance_cache = 'redis = FlaskRedis()' + instance = """ + {} + {} + """.format(instance_db, instance_cache) + return instance.strip() + + def any_extension(self): + return self.db or self.cache diff --git a/helmetica/scaffold/model.py b/helmetica/scaffold/model.py new file mode 100644 index 0000000..82f3520 --- /dev/null +++ b/helmetica/scaffold/model.py @@ -0,0 +1,50 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +model.py +scaffold model +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-27' + + +class Model(object): + """ Model Scaffold + """ + + def __init__(self, db): + self.db = db + + def create_sqlalchemy__init__(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from datetime import datetime + from sqlalchemy.ext.declarative import declared_attr + from app.extensions import db + + + class Model(db.Model): + __abstract__ = True + + id = db.Column(db.Integer, primary_key=True) + + @declared_attr + def created_at(cls): + return db.Column(db.DateTime, default=datetime.utcnow) + + @declared_attr + def updated_at(cls): + return db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + """ + return source_code + + def create_sqlalchmey_model(self): + pass + + def create_mongoengine__init__(self): + pass + + def create_mongoengine__model(self): + pass diff --git a/helmetica/scaffold/test.py b/helmetica/scaffold/test.py new file mode 100644 index 0000000..58ea172 --- /dev/null +++ b/helmetica/scaffold/test.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +test.py +Test Scaffold +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-27' + + +class Test(object): + + def create__init__(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + + import json + from contextlib import contextmanager + from unittest import TestCase + from app import create_app + + app = create_app() + + class Experiment(TestCase): + + def setUp(self): + app.testing = True + self.client = app.test_client() + self.logger = app.logger + self.app_config = app.config + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + def response_to_dict(self, response): + return json.loads(response.data) + + @contextmanager + def authenticate(self, user=None): + pass + """ + return source_code + + def create_nose_cfg(self): + source_code = """\ + [nosetests] + verbosity=2 + with-timer=1 + rednose=True + nocapture=True + with-coverage=1 + cover-package=. + """ + return source_code diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..110c79e --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +setup.py +setup script +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-04-24' +from setuptools import setup + +#with open('README.md', 'rt', encoding='utf8') as f: +# readme = f.read() + +setup( + name='Helmetica', + version='0.0.1', + description='scaffold command line interface for Flask application', + #long_description=readme, + author='Yoshiya Ito', + author_email='myon53@gmail.com', + url='https://github.com/yoshiya0503/Flask-Best-Practices.git', + license='MIT', + platforms='any', + packages=['helmetica', 'helmetica.scaffold'], + install_requires=[ + 'click>=5.1', + 'Inflector>=2.0', + ], + classifiers=[ + 'Environment :: Web Environment', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + entry_points=''' + [console_scripts] + helmetica=helmetica.cli:main + ''' +) From 1aad4aad3949d8f01e12cc4e1711ee9519c85c25 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 1 May 2018 21:51:04 +0900 Subject: [PATCH 02/20] add extension, test, docker scaffold --- helmetica/cli.py | 64 ++++++++++++++++-------- helmetica/scaffold/api.py | 76 ++++++++++++++++++++++++++++ helmetica/scaffold/app.py | 24 --------- helmetica/scaffold/config.py | 29 +++++++++++ helmetica/scaffold/docker.py | 89 +++++++++++++++++++++++++++++++++ helmetica/scaffold/extension.py | 11 ++-- helmetica/scaffold/test.py | 6 +-- helmetica/scaffold/wsgi.py | 29 +++++++++++ 8 files changed, 278 insertions(+), 50 deletions(-) create mode 100644 helmetica/scaffold/api.py create mode 100644 helmetica/scaffold/config.py create mode 100644 helmetica/scaffold/docker.py create mode 100644 helmetica/scaffold/wsgi.py diff --git a/helmetica/cli.py b/helmetica/cli.py index eeacd80..75b37a7 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -10,6 +10,11 @@ import os import click from helmetica.scaffold.app import App +from helmetica.scaffold.config import Config +from helmetica.scaffold.wsgi import WSGI +from helmetica.scaffold.api import API +from helmetica.scaffold.test import Test +from helmetica.scaffold.docker import Docker from helmetica.scaffold.extension import Extension @click.group() @@ -17,40 +22,59 @@ def main(): pass @main.command() -@click.option('--db', default=None, help='SQLAlchemy or Mongoengine or None') -@click.option('--cache', default=None, help='Redis or Memd or None') -@click.option('--restful', default=None, help='use Flask-Restful or API based on Flask common decorator') +@click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') +@click.option('--cache', default=None, type=click.Choice(['redis', 'memcached']), help='Redis or Memd or None') +@click.option('--api', default=None, help='Flask-Restful or Flask decorator') @click.option('--web', default='gunicorn', help='use gunicorn or others') -def init(db, cache, restful, web): - dirs = ['./app/', './test/', './config/'] +@click.option('--virtualization', default=None, type=click.Choice(['docker', 'vagrant']), help='container or VM') +def init(db, cache, api, web, virtualization): + dirs = ['./app/', './test/', './config/', './app/api/v1/'] for dir in dirs: if os.path.exists(os.path.dirname(dir)): click.echo('[WARNING] directory {} is already exists, skip to create this directory'.format(dir)) continue os.makedirs(os.path.dirname(dir)) + app = App(db=db, cache=cache) + wsgi = WSGI() + config = Config() + test = Test() + docker = Docker(db=db, cache=cache) extension = Extension(db=db, cache=cache) - with open('app/__init__.py', 'w') as __init__: - __init__.write(app.create_app__init__()) + api = API(api=api, name='root') + + with open('app/__init__.py', 'w') as f: + f.write(app.create_app__init__()) + with open('wsgi.py', 'w') as f: + f.write(wsgi.create_wsgi()) + with open('config/__init__.py', 'w') as f: + f.write(config.create__init__()) + with open('Dockerfile', 'w') as f: + f.write(docker.create_dockerfile()) + with open('docker-compose.yml', 'w') as f: + f.write(docker.create_docker_compose_yml()) if extension.any_extension: - with open('app/extensions.py', 'w') as ext: - ext.write(extension.create_extensions()) - with open('wsgi.py', 'w') as wsgi: - wsgi.write(app.create_wsgi()) - with open('config/__init__.py', 'w') as config: - config.write(app.create_config()) - # 最初にディレクトリを作成して欲しい。 - # database を SQLAlchemy, Mongoengine, None を指定出来るようにしたい - # cache を redis, memcached, None を指定できるようにしたい - # 初回いきなり起動できるscaffold にしたい + with open('app/extensions.py', 'w') as f: + f.write(extension.create_extensions()) + with open('test/__init__.py', 'w') as f: + f.write(test.create__init__()) + with open('nose.cfg', 'w') as f: + f.write(test.create_nose_cfg()) + with open('app/api/__init__.py', 'w') as f: + pass + with open('app/api/v1/__init__.py', 'w') as f: + f.write(api.create_restful__init__()) + with open('app/api/v1/root.py', 'w') as f: + f.write(api.create_restful()) @main.command() @click.argument('name', type=str, required=True) @click.option('--version', default='v1', help='API version') def api(name, version): - # API を勝手に作成出来るようにしたい - click.echo('create api {}/{}'.format(name, version)) - pass + path = 'app/api/{}/{}.py'.format(version, name) + api = API(name=name) + with open(path, 'w') as f: + f.write(api.create_restful()) @main.command() @click.argument('name', type=str, required=True) diff --git a/helmetica/scaffold/api.py b/helmetica/scaffold/api.py new file mode 100644 index 0000000..650f2ea --- /dev/null +++ b/helmetica/scaffold/api.py @@ -0,0 +1,76 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +api.py +scaffold create_api +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-01' +from textwrap import dedent +from inflector import Inflector + + +class API(object): + """ API Scaffold + """ + + def __init__(self, api=None, name=None): + self.api = api + self.name = name + + def create_restful__init__(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask import Blueprint + from flask_restful import Api + from app.api.v1.root import Root + + api_v1 = Blueprint('api/v1', __name__) + api = Api(api_v1) + api.add_resource(Root, '/') + """ + return dedent(source_code) + + def create_restful(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask_restful import Resource, fields, marshal_with, reqparse + + resource_field = {{ + 'id': fields.Integer, + 'created_at': fields.DateTime, + 'updated_at': fields.DateTime, + }} + + class {name}(Resource): + parser = reqparse.RequestParser() + parser.add_argument('query', type=str, help="query string") + parser.add_argument('body', type=str, help="body string") + + @marshal_with(resource_fields) + def get(self, id=None): + args = self.post_parser.parse_args() + if id is None: + return {{}}, 200 + return [], 200 + + @marshal_with(resource_fields) + def post(self): + args = self.post_parser.parse_args() + return {{}}, 201 + + @marshal_with(resource_fields) + def put(self, id=None): + args = self.post_parser.parse_args() + return {{}}, 204 + + @marshal_with(resource_fields) + def delete(self, id=None): + return {{}}, 204 + """.format( + name=Inflector().camelize(self.name) + ) + return dedent(source_code) diff --git a/helmetica/scaffold/app.py b/helmetica/scaffold/app.py index fa9e20c..7cf8beb 100644 --- a/helmetica/scaffold/app.py +++ b/helmetica/scaffold/app.py @@ -40,30 +40,6 @@ def create_app(env='development') ) return dedent(source_code) - def create_wsgi(self): - source_code = """\ - #! /usr/bin/env python3 - # -*- encoding: utf-8 -*- - import os - from app import create_app - - app = create_app(os.getenv('FLASK_ENV', None)) - - if __name__ == '__main__': - app.run(host='0.0.0.0') - """ - return dedent(source_code) - - def create_config(self): - source_code = """\ - #! /usr/bin/env python3 - # -*- encoding: utf-8 -*- - - class Config(object): - FLASK_ENV = 'development' - """ - return dedent(source_code) - def create_extension(self): source_code = """\ {db} diff --git a/helmetica/scaffold/config.py b/helmetica/scaffold/config.py new file mode 100644 index 0000000..af5e744 --- /dev/null +++ b/helmetica/scaffold/config.py @@ -0,0 +1,29 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +config.py +scaffold create_config +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-01' +from textwrap import dedent + + +class Config(object): + """ Config Scaffold + """ + + def __init__(self, db=None, cache=None): + self.db = db + self.cache = cache + + def create__init__(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + + class Config(object): + FLASK_ENV = 'development' + """ + return dedent(source_code) diff --git a/helmetica/scaffold/docker.py b/helmetica/scaffold/docker.py new file mode 100644 index 0000000..b622de4 --- /dev/null +++ b/helmetica/scaffold/docker.py @@ -0,0 +1,89 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +docker.py +scaffold create_docker +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-01' +from textwrap import dedent + + +class Docker(object): + """ Docker Scaffold + """ + + def __init__(self, db=None, cache=None): + self.db = db + self.cache = cache + + def create_dockerfile(self): + source_code = """\ + FROM python:3.6 + # -- Install Pipenv: + RUN pip install pipenv --upgrade + WORKDIR /tmp + # -- Adding Pipfiles + ADD ./Pipfile Pipfile + ADD ./Pipfile.lock Pipfile.lock + RUN pipenv install --deploy --system + """ + return dedent(source_code) + + def create_docker_compose_yml(self): + source_code = """\ + version: '3' + services: + api: + build: . + volumes: + - .:/app + working_dir: "/app" + environment: + FLASK_APP: "wsgi.py" + FLASK_DEBUG: "1" + command: "flask run --host=0.0.0.0" + links: + - redis + - db + ports: + - "5000:5000" + {db} + {cache} + """.format( + db=self.create_db(), + cache=self.create_cache(), + ) + return dedent(source_code).strip() + + def create_db(self): + source_code = '' + if self.db == 'sqlalchemy': + source_code = """db: + image: mysql:5.7 + command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci + environment: + MYSQL_DATABASE: "app_development" + MYSQL_ROOT_PASSWORD: "root" + expose: + - "3306" + """ + if self.db == 'mongoengine': + source_code = """db: + image: mongo:latest + expose: + - "27017" + command: mongod --smallfiles --logpath=/dev/null + """ + return dedent(source_code) + + def create_cache(self): + source_code = '' + if self.cache == 'redis': + source_code = """redis: + image: redis:3.2 + expose: + - "6379" + """ + return dedent(source_code) diff --git a/helmetica/scaffold/extension.py b/helmetica/scaffold/extension.py index e29adad..5f03428 100644 --- a/helmetica/scaffold/extension.py +++ b/helmetica/scaffold/extension.py @@ -34,9 +34,11 @@ def create_extensions(self): def create_header(self): import_db = '' import_cache = '' - if self.db: + if self.db == 'sqlalchemy': import_db = 'from flask_sqlalchemy import SQLAlchemy' - if self.cache: + if self.db == 'mongoengine': + import_db = 'from flask_mongoengine import MongoEngine' + if self.cache == 'redis': import_cache = 'from flask_redis import FlaskRedis' header = """ {} @@ -47,8 +49,10 @@ def create_header(self): def create_instance(self): instance_db = '' instance_cache = '' - if self.db: + if self.db == 'sqlalchemy': instance_db = 'db = SQLAlchemy()' + if self.db == 'mongoeingine': + instance_db = 'db = MongoEngine()' if self.cache: instance_cache = 'redis = FlaskRedis()' instance = """ @@ -58,4 +62,5 @@ def create_instance(self): return instance.strip() def any_extension(self): + # TODO バグってる return self.db or self.cache diff --git a/helmetica/scaffold/test.py b/helmetica/scaffold/test.py index 58ea172..02b27a4 100644 --- a/helmetica/scaffold/test.py +++ b/helmetica/scaffold/test.py @@ -7,7 +7,7 @@ __author__ = 'Yoshiya Ito ' __version__ = '1.0.0' __date__ = '2018-04-27' - +from textwrap import dedent class Test(object): @@ -43,7 +43,7 @@ def response_to_dict(self, response): def authenticate(self, user=None): pass """ - return source_code + return dedent(source_code) def create_nose_cfg(self): source_code = """\ @@ -55,4 +55,4 @@ def create_nose_cfg(self): with-coverage=1 cover-package=. """ - return source_code + return dedent(source_code) diff --git a/helmetica/scaffold/wsgi.py b/helmetica/scaffold/wsgi.py new file mode 100644 index 0000000..cc42f6b --- /dev/null +++ b/helmetica/scaffold/wsgi.py @@ -0,0 +1,29 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +wsgi.py +scaffold create_wsgi +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-01' +from textwrap import dedent + + +class WSGI(object): + """ wsgi Scaffold + """ + + def create_wsgi(self): + source_code = """\ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + import os + from app import create_app + + app = create_app(os.getenv('FLASK_ENV', None)) + + if __name__ == '__main__': + app.run(host='0.0.0.0') + """ + return dedent(source_code) From 117c50e1f90a298b17cbff792997f38366fd5b91 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Sat, 12 May 2018 22:26:00 +0900 Subject: [PATCH 03/20] add api scaffold and bug fix --- helmetica/cli.py | 24 +++++++-- helmetica/scaffold/api.py | 8 +-- helmetica/scaffold/app.py | 37 ++++++++++++-- helmetica/scaffold/config.py | 12 +++-- helmetica/scaffold/docker.py | 8 +-- helmetica/scaffold/pipfile.py | 92 +++++++++++++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 helmetica/scaffold/pipfile.py diff --git a/helmetica/cli.py b/helmetica/cli.py index 75b37a7..ee39d1a 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -16,6 +16,7 @@ from helmetica.scaffold.test import Test from helmetica.scaffold.docker import Docker from helmetica.scaffold.extension import Extension +from helmetica.scaffold.pipfile import Pipfile @click.group() def main(): @@ -24,7 +25,7 @@ def main(): @main.command() @click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') @click.option('--cache', default=None, type=click.Choice(['redis', 'memcached']), help='Redis or Memd or None') -@click.option('--api', default=None, help='Flask-Restful or Flask decorator') +@click.option('--api', default=None, type=click.Choice(['rest', 'decorator']), help='Flask-Restful or Flask decorator') @click.option('--web', default='gunicorn', help='use gunicorn or others') @click.option('--virtualization', default=None, type=click.Choice(['docker', 'vagrant']), help='container or VM') def init(db, cache, api, web, virtualization): @@ -35,7 +36,8 @@ def init(db, cache, api, web, virtualization): continue os.makedirs(os.path.dirname(dir)) - app = App(db=db, cache=cache) + app = App(db=db, cache=cache, api=api) + pipfile = Pipfile(db=db, cache=cache) wsgi = WSGI() config = Config() test = Test() @@ -43,16 +45,27 @@ def init(db, cache, api, web, virtualization): extension = Extension(db=db, cache=cache) api = API(api=api, name='root') + with open('./Pipfile', 'w') as f: + f.write(pipfile.create_pipfile()) + with open('app/__init__.py', 'w') as f: f.write(app.create_app__init__()) + with open('wsgi.py', 'w') as f: f.write(wsgi.create_wsgi()) + with open('config/__init__.py', 'w') as f: - f.write(config.create__init__()) + f.write(config.create_config(name='config', env='test')) + with open('config/development.py', 'w') as f: + f.write(config.create_config(name='development', env='development')) + with open('config/production.py', 'w') as f: + f.write(config.create_config(name='production', env='production')) + with open('Dockerfile', 'w') as f: f.write(docker.create_dockerfile()) with open('docker-compose.yml', 'w') as f: f.write(docker.create_docker_compose_yml()) + if extension.any_extension: with open('app/extensions.py', 'w') as f: f.write(extension.create_extensions()) @@ -60,10 +73,11 @@ def init(db, cache, api, web, virtualization): f.write(test.create__init__()) with open('nose.cfg', 'w') as f: f.write(test.create_nose_cfg()) + with open('app/api/__init__.py', 'w') as f: - pass - with open('app/api/v1/__init__.py', 'w') as f: f.write(api.create_restful__init__()) + with open('app/api/v1/__init__.py', 'w') as f: + pass with open('app/api/v1/root.py', 'w') as f: f.write(api.create_restful()) diff --git a/helmetica/scaffold/api.py b/helmetica/scaffold/api.py index 650f2ea..b28255f 100644 --- a/helmetica/scaffold/api.py +++ b/helmetica/scaffold/api.py @@ -39,7 +39,7 @@ def create_restful(self): # -*- encoding: utf-8 -*- from flask_restful import Resource, fields, marshal_with, reqparse - resource_field = {{ + resource_fields = {{ 'id': fields.Integer, 'created_at': fields.DateTime, 'updated_at': fields.DateTime, @@ -52,19 +52,19 @@ class {name}(Resource): @marshal_with(resource_fields) def get(self, id=None): - args = self.post_parser.parse_args() + args = self.parser.parse_args() if id is None: return {{}}, 200 return [], 200 @marshal_with(resource_fields) def post(self): - args = self.post_parser.parse_args() + args = self.parser.parse_args() return {{}}, 201 @marshal_with(resource_fields) def put(self, id=None): - args = self.post_parser.parse_args() + args = self.parser.parse_args() return {{}}, 204 @marshal_with(resource_fields) diff --git a/helmetica/scaffold/app.py b/helmetica/scaffold/app.py index 7cf8beb..c639b14 100644 --- a/helmetica/scaffold/app.py +++ b/helmetica/scaffold/app.py @@ -14,18 +14,21 @@ class App(object): """ App Scaffold """ - def __init__(self, db=None, cache=None): + def __init__(self, api=None, db=None, cache=None): self.db = db self.cache = cache + self.api = api def create_app__init__(self): source_code = """\ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from flask import Flask + {} - def create_app(env='development') + def create_app(env='development'): app = Flask(__name__) + if env == 'development': app.config.from_object('config.development.Development') elif env == 'production': @@ -34,12 +37,34 @@ def create_app(env='development') app.config.from_object('config.development.Development') {} + {} + return app """.format( - self.create_extension() + self.create_header(), + self.create_extension(), + self.create_api(), ) return dedent(source_code) + def create_header(self): + import_db = '' + import_cache = '' + import_api = '' + if self.db == 'sqlalchemy' or self.db == 'mongoengine': + import_db = 'from app.extensions import db' + if self.cache == 'redis': + import_cache = 'from app.extensions import redis' + if self.api: + import_api = 'from app.api import api_v1' + + header = """ + {} + {} + {} + """.format(import_db, import_cache, import_api) + return header.strip() + def create_extension(self): source_code = """\ {db} @@ -61,3 +86,9 @@ def create_cache(self): return 'redis.init_app(app)' else: return '' + + def create_api(self): + if self.api: + return "app.register_blueprint(api_v1, url_prefix='/api/v1')" + else: + return '' diff --git a/helmetica/scaffold/config.py b/helmetica/scaffold/config.py index af5e744..1f4e884 100644 --- a/helmetica/scaffold/config.py +++ b/helmetica/scaffold/config.py @@ -8,6 +8,7 @@ __version__ = '1.0.0' __date__ = '2018-05-01' from textwrap import dedent +from inflector import Inflector class Config(object): @@ -18,12 +19,15 @@ def __init__(self, db=None, cache=None): self.db = db self.cache = cache - def create__init__(self): + def create_config(self, name='config', env='test'): source_code = """\ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- - class Config(object): - FLASK_ENV = 'development' - """ + class {name}(object): + ENV = '{env}' + """.format( + name=Inflector().camelize(name), + env=env + ) return dedent(source_code) diff --git a/helmetica/scaffold/docker.py b/helmetica/scaffold/docker.py index b622de4..5119fc4 100644 --- a/helmetica/scaffold/docker.py +++ b/helmetica/scaffold/docker.py @@ -41,8 +41,8 @@ def create_docker_compose_yml(self): - .:/app working_dir: "/app" environment: - FLASK_APP: "wsgi.py" - FLASK_DEBUG: "1" + FLASK_APP: "wsgi.py" + FLASK_DEBUG: "1" command: "flask run --host=0.0.0.0" links: - redis @@ -64,8 +64,8 @@ def create_db(self): image: mysql:5.7 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci environment: - MYSQL_DATABASE: "app_development" - MYSQL_ROOT_PASSWORD: "root" + MYSQL_DATABASE: "app_development" + MYSQL_ROOT_PASSWORD: "root" expose: - "3306" """ diff --git a/helmetica/scaffold/pipfile.py b/helmetica/scaffold/pipfile.py new file mode 100644 index 0000000..c984575 --- /dev/null +++ b/helmetica/scaffold/pipfile.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +pipfile.py +scaffold pipfile +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-09' +from os import system +from textwrap import dedent + + +class Pipfile(object): + """ Pipfile Scaffold + """ + + def __init__(self, db=None, cache=None): + self.db = db + self.cache = cache + + def lock(self): + system('pipenv lock') + + def create_pipfile(self): + source_code = """\ + [[source]] + + url = "https://pypi.python.org/simple" + verify_ssl = true + name = "pypi" + + [dev-packages] + {nose} + + [packages] + {flask} + {db} + {cache} + + [requires] + + python_version = "3.6" + """.format( + flask=self.create_flask(), + nose=self.create_nose(), + db=self.create_db(), + cache=self.create_cache() + ) + return dedent(source_code) + + def create_flask(self): + source_code = """\ + flask = "*" + flask-restful = "*" + gunicorn = "*" + """ + return source_code.strip() + + def create_nose(self): + source_code = """\ + nose = "*" + coverage = "*" + rednose = "*" + nose-timer = "*" + factory-boy = "*" + """ + return source_code.strip() + + def create_db(self): + source_code = '' + if self.db == 'sqlalchemy': + source_code = """\ + pymysql = "*" + flask-sqlalchemy = "*" + flask-migrate = "*" + """ + if self.db == 'mongoengine': + source_code = """\ + pymongo = "*" + mongoengine = "*" + """ + return source_code.strip() + + def create_cache(self): + source_code = '' + if self.cache == 'redis': + source_code = """\ + redis = "*" + flask-redis = "*" + """ + return source_code.strip() From 1aa8cd4b51039a2d30ba905315a03f135fd26620 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 16 May 2018 21:37:47 +0900 Subject: [PATCH 04/20] add README --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 35f15cd..535091a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,41 @@ -# Flask-Best-Practices -このリポジトリはFlaskのベストプラクティス、実施的なテクニックを紹介するリポジトリです。 -以下の URL で色々確認できます。 +# Helmetica -https://github.com/yoshiya0503/Flask-Best-Practices/wiki +THIS IS NOT WEB FRAMEWORK TO REPLACE FLASK. + +Helmetica is scaffold tools, and wiki to implement better flask applications. + +When we try to build web applications by using flask web framework, there are to many patterns and practices. +This fact make it difficult to implement apps that have simple architecture. +In other words, because of too many manners, options, and patters, to implement more bigger applications is not so easy. +(but, to implement small app by using flask is extreamly easy) +Therefor, we try to implement the scaffold tools head for better architecture applications as mach as possible +based on our many experiences. + +* better and common directory structure +* scaffold to create tipycal API, model. +* select powerful packages(like SQLAlchemy Nose) + +# Installation + +``` +pip install helmetica +``` + +# Usage + +comming soon + +# Development + +This repos is too young, so we provide few useful features yet. +we will grad if you send PRs... + +# See Before Wiki (Flask Best Practices) + +Why we apply broken change? Because, before repo source code is slightly trivial, +and we believe this change will not cause negative impact to ohters. + +To create scaffold tools for flask will cause good affect the world rather than remain trivial code. +But there are no warries. Flask-best-Practices contents (wiki docs) remain here (for japanese). + +https://github.com/yoshiya0503/Helmetica/wiki From 28f4010a1de5aa352e460add71f5660fe86f7ce2 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 16 May 2018 21:41:47 +0900 Subject: [PATCH 05/20] add badge --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 535091a..0f71fbb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Helmetica +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md#pull-requests) + +[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) + +--- + THIS IS NOT WEB FRAMEWORK TO REPLACE FLASK. Helmetica is scaffold tools, and wiki to implement better flask applications. From f1eb30657db051dc1c6ca494a2306767e715a7a7 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 16 May 2018 21:42:31 +0900 Subject: [PATCH 06/20] fix space --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0f71fbb..6dd2000 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Helmetica [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md#pull-requests) - [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) --- From c301daa7d7b2451859118b115c7ba448bc10a7ab Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 16 May 2018 21:44:46 +0900 Subject: [PATCH 07/20] fix indent of README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6dd2000..ba48a69 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ When we try to build web applications by using flask web framework, there are to This fact make it difficult to implement apps that have simple architecture. In other words, because of too many manners, options, and patters, to implement more bigger applications is not so easy. (but, to implement small app by using flask is extreamly easy) + Therefor, we try to implement the scaffold tools head for better architecture applications as mach as possible based on our many experiences. From e3e6ec32da7715e394e8df3526c839be4f2c0b75 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Sat, 19 May 2018 16:00:10 +0900 Subject: [PATCH 08/20] remove redundant files --- sample_app/app/__init__.py | 21 --------------------- sample_app/app/models/__init__.py | 0 sample_app/app/models/user.py | 17 ----------------- sample_app/app/views/__init__.py | 18 ------------------ sample_app/app/views/user.py | 30 ------------------------------ sample_app/server.py | 13 ------------- test.py | 29 ----------------------------- 7 files changed, 128 deletions(-) delete mode 100644 sample_app/app/__init__.py delete mode 100644 sample_app/app/models/__init__.py delete mode 100644 sample_app/app/models/user.py delete mode 100644 sample_app/app/views/__init__.py delete mode 100644 sample_app/app/views/user.py delete mode 100644 sample_app/server.py delete mode 100644 test.py diff --git a/sample_app/app/__init__.py b/sample_app/app/__init__.py deleted file mode 100644 index 60ab885..0000000 --- a/sample_app/app/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -#! /usr/bin/env python3 -# -*- encoding: utf-8 -*- -""" -__init__.py -flask app -""" -__author__ = 'Yoshiya Ito ' -__version__ = '0.0.1' -__date__ = '30 03 2016' - -from flask import Flask -from flask.ext.mongoengine import MongoEngine -from flask.ext.restful import Api -from redis import Redis - -sample = Flask(__name__) -redis = Redis() -mongo = MongoEngine(sample) -api = Api(sample) - -import app.views diff --git a/sample_app/app/models/__init__.py b/sample_app/app/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sample_app/app/models/user.py b/sample_app/app/models/user.py deleted file mode 100644 index 0d51cc9..0000000 --- a/sample_app/app/models/user.py +++ /dev/null @@ -1,17 +0,0 @@ -#! /usr/bin/env python3 -# -*- encoding: utf-8 -*- -""" -user -User モデル -""" -__author__ = 'Yoshiya Ito ' -__version__ = '0.0.1' -__date__ = '30 03 2016' - -from app import mongo - - -class User(mongo.Document): - name = mongo.StringField() - email = mongo.StringField() - password = mongo.StringField() diff --git a/sample_app/app/views/__init__.py b/sample_app/app/views/__init__.py deleted file mode 100644 index 967aed1..0000000 --- a/sample_app/app/views/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -#! /usr/bin/env python3 -# -*- encoding: utf-8 -*- -""" -__init__.py -api マッピング -""" -__author__ = 'Yoshiya Ito ' -__version__ = '0.0.1' -__date__ = '30 03 2016' - -from app import api -from app.views.user import UserView - -api.add_resource( - UserView, - '/api/v1/users', - '/api/v1/users/' -) diff --git a/sample_app/app/views/user.py b/sample_app/app/views/user.py deleted file mode 100644 index 95bb464..0000000 --- a/sample_app/app/views/user.py +++ /dev/null @@ -1,30 +0,0 @@ -#! /usr/bin/env python3 -# -*- encoding: utf-8 -*- -""" -user.py -user api -""" -__author__ = 'Yoshiya Ito ' -__version__ = '0.0.1' -__date__ = '30 03 2016' - - -from flask.ext.restful import Resource -from app.models.user import User - - -class UserView(Resource): - - def get(self, id=None): - l = User.objects() - print(l) - return 'get' - - def post(self): - return 'post' - - def put(self, id): - return 'put' - - def delete(self, id): - return 'delete' diff --git a/sample_app/server.py b/sample_app/server.py deleted file mode 100644 index 7388651..0000000 --- a/sample_app/server.py +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/env python3 -# -*- encoding: utf-8 -*- -""" -server.py -サーバ起動スクリプト -""" -__author__ = 'Yoshiya Ito ' -__version__ = '0.0.1' -__date__ = '30 03 2016' - -from app import sample - -sample.run() diff --git a/test.py b/test.py deleted file mode 100644 index b7b89be..0000000 --- a/test.py +++ /dev/null @@ -1,29 +0,0 @@ -from flask import Flask - -app = Flask(__name__) - -@app.before_request -def logging_before_request(): - print('before request') - -@app.after_request -def logging_after_request(res): - """ レスポンスが取れる - res オブジェクトを返す必要がある - """ - print('after request') - return res - -@app.teardown_request -def logging_end_of_request(exc): - """ 例外が取れる - """ - print('end_request') - -@app.route('/') -def hello(): - print('request') - return 'Hello World!' - -if __name__ == '__main__': - app.run() From 9e8194ac949c397543d523a81bce460c3054cefa Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Sat, 19 May 2018 18:39:45 +0900 Subject: [PATCH 09/20] use stript function instead of \ --- helmetica/cli.py | 9 ++++--- helmetica/scaffold/api.py | 8 +++--- helmetica/scaffold/app.py | 47 +++++++++++++-------------------- helmetica/scaffold/config.py | 4 +-- helmetica/scaffold/docker.py | 47 ++++++++++++++++++--------------- helmetica/scaffold/extension.py | 2 +- helmetica/scaffold/model.py | 6 +++-- helmetica/scaffold/pipfile.py | 14 +++++----- helmetica/scaffold/test.py | 8 +++--- helmetica/scaffold/wsgi.py | 4 +-- 10 files changed, 74 insertions(+), 75 deletions(-) diff --git a/helmetica/cli.py b/helmetica/cli.py index ee39d1a..b112cda 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -25,7 +25,7 @@ def main(): @main.command() @click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') @click.option('--cache', default=None, type=click.Choice(['redis', 'memcached']), help='Redis or Memd or None') -@click.option('--api', default=None, type=click.Choice(['rest', 'decorator']), help='Flask-Restful or Flask decorator') +@click.option('--api', default=None, type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--web', default='gunicorn', help='use gunicorn or others') @click.option('--virtualization', default=None, type=click.Choice(['docker', 'vagrant']), help='container or VM') def init(db, cache, api, web, virtualization): @@ -47,6 +47,7 @@ def init(db, cache, api, web, virtualization): with open('./Pipfile', 'w') as f: f.write(pipfile.create_pipfile()) + pipfile.lock() with open('app/__init__.py', 'w') as f: f.write(app.create_app__init__()) @@ -83,15 +84,17 @@ def init(db, cache, api, web, virtualization): @main.command() @click.argument('name', type=str, required=True) +@click.option('--api', default=None, type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--version', default='v1', help='API version') -def api(name, version): +def api(name, api, version): path = 'app/api/{}/{}.py'.format(version, name) - api = API(name=name) + api = API(api=api, name=name) with open(path, 'w') as f: f.write(api.create_restful()) @main.command() @click.argument('name', type=str, required=True) +@click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') def model(name): # モデルを勝手に作成出来るようにしたい click.echo('create model {}'.format(name)) diff --git a/helmetica/scaffold/api.py b/helmetica/scaffold/api.py index b28255f..f3542fd 100644 --- a/helmetica/scaffold/api.py +++ b/helmetica/scaffold/api.py @@ -20,7 +20,7 @@ def __init__(self, api=None, name=None): self.name = name def create_restful__init__(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from flask import Blueprint @@ -31,10 +31,10 @@ def create_restful__init__(self): api = Api(api_v1) api.add_resource(Root, '/') """ - return dedent(source_code) + return dedent(source_code).strip() def create_restful(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from flask_restful import Resource, fields, marshal_with, reqparse @@ -73,4 +73,4 @@ def delete(self, id=None): """.format( name=Inflector().camelize(self.name) ) - return dedent(source_code) + return dedent(source_code).strip() diff --git a/helmetica/scaffold/app.py b/helmetica/scaffold/app.py index c639b14..fdd89bc 100644 --- a/helmetica/scaffold/app.py +++ b/helmetica/scaffold/app.py @@ -20,11 +20,11 @@ def __init__(self, api=None, db=None, cache=None): self.api = api def create_app__init__(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from flask import Flask - {} + {header} def create_app(env='development'): app = Flask(__name__) @@ -36,22 +36,22 @@ def create_app(env='development'): else: app.config.from_object('config.development.Development') - {} - {} + {extension} + {api} return app """.format( - self.create_header(), - self.create_extension(), - self.create_api(), + header=self.create_header(), + extension=self.create_extension(), + api=self.create_api(), ) - return dedent(source_code) + return dedent(source_code).strip() def create_header(self): import_db = '' import_cache = '' import_api = '' - if self.db == 'sqlalchemy' or self.db == 'mongoengine': + if self.db: import_db = 'from app.extensions import db' if self.cache == 'redis': import_cache = 'from app.extensions import redis' @@ -66,26 +66,17 @@ def create_header(self): return header.strip() def create_extension(self): - source_code = """\ - {db} - {cache} - """.format( - db=self.create_db(), - cache=self.create_cache(), - ).strip() - return source_code - - def create_db(self): + create_db = '' + create_cache = '' if self.db: - return 'db.init_app(app)' - else: - return '' - - def create_cache(self): - if self.cache: - return 'redis.init_app(app)' - else: - return '' + create_db = 'db.init_app(app)' + if self.cache == 'redis': + create_cache = 'redis.init_app(app)' + source_code = """ + {} + {} + """.format(create_db, create_cache) + return source_code.strip() def create_api(self): if self.api: diff --git a/helmetica/scaffold/config.py b/helmetica/scaffold/config.py index 1f4e884..3746d1c 100644 --- a/helmetica/scaffold/config.py +++ b/helmetica/scaffold/config.py @@ -20,7 +20,7 @@ def __init__(self, db=None, cache=None): self.cache = cache def create_config(self, name='config', env='test'): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- @@ -30,4 +30,4 @@ class {name}(object): name=Inflector().camelize(name), env=env ) - return dedent(source_code) + return dedent(source_code).strip() diff --git a/helmetica/scaffold/docker.py b/helmetica/scaffold/docker.py index 5119fc4..338e0f2 100644 --- a/helmetica/scaffold/docker.py +++ b/helmetica/scaffold/docker.py @@ -19,7 +19,7 @@ def __init__(self, db=None, cache=None): self.cache = cache def create_dockerfile(self): - source_code = """\ + source_code = """ FROM python:3.6 # -- Install Pipenv: RUN pip install pipenv --upgrade @@ -29,10 +29,10 @@ def create_dockerfile(self): ADD ./Pipfile.lock Pipfile.lock RUN pipenv install --deploy --system """ - return dedent(source_code) + return dedent(source_code).strip() def create_docker_compose_yml(self): - source_code = """\ + source_code = """ version: '3' services: api: @@ -60,30 +60,33 @@ def create_docker_compose_yml(self): def create_db(self): source_code = '' if self.db == 'sqlalchemy': - source_code = """db: - image: mysql:5.7 - command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci - environment: - MYSQL_DATABASE: "app_development" - MYSQL_ROOT_PASSWORD: "root" - expose: - - "3306" + source_code = """ + db: + image: mysql:5.7 + command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci + environment: + MYSQL_DATABASE: "app_development" + MYSQL_ROOT_PASSWORD: "root" + expose: + - "3306" """ if self.db == 'mongoengine': - source_code = """db: - image: mongo:latest - expose: - - "27017" - command: mongod --smallfiles --logpath=/dev/null + source_code = """ + db: + image: mongo:latest + expose: + - "27017" + command: mongod --smallfiles --logpath=/dev/null """ - return dedent(source_code) + return source_code.strip() def create_cache(self): source_code = '' if self.cache == 'redis': - source_code = """redis: - image: redis:3.2 - expose: - - "6379" + source_code = """ + redis: + image: redis:3.2 + expose: + - "6379" """ - return dedent(source_code) + return source_code.strip() diff --git a/helmetica/scaffold/extension.py b/helmetica/scaffold/extension.py index 5f03428..d4521e5 100644 --- a/helmetica/scaffold/extension.py +++ b/helmetica/scaffold/extension.py @@ -19,7 +19,7 @@ def __init__(self, db=None, cache=None): self.cache = cache def create_extensions(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- {} diff --git a/helmetica/scaffold/model.py b/helmetica/scaffold/model.py index 82f3520..436c0de 100644 --- a/helmetica/scaffold/model.py +++ b/helmetica/scaffold/model.py @@ -7,6 +7,8 @@ __author__ = 'Yoshiya Ito ' __version__ = '1.0.0' __date__ = '2018-04-27' +from textwrap import dedent +from inflector import Inflector class Model(object): @@ -17,7 +19,7 @@ def __init__(self, db): self.db = db def create_sqlalchemy__init__(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from datetime import datetime @@ -38,7 +40,7 @@ def created_at(cls): def updated_at(cls): return db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) """ - return source_code + return dedent(source_code).strip() def create_sqlalchmey_model(self): pass diff --git a/helmetica/scaffold/pipfile.py b/helmetica/scaffold/pipfile.py index c984575..fb0b6cf 100644 --- a/helmetica/scaffold/pipfile.py +++ b/helmetica/scaffold/pipfile.py @@ -23,7 +23,7 @@ def lock(self): system('pipenv lock') def create_pipfile(self): - source_code = """\ + source_code = """ [[source]] url = "https://pypi.python.org/simple" @@ -47,10 +47,10 @@ def create_pipfile(self): db=self.create_db(), cache=self.create_cache() ) - return dedent(source_code) + return dedent(source_code).strip() def create_flask(self): - source_code = """\ + source_code = """ flask = "*" flask-restful = "*" gunicorn = "*" @@ -58,7 +58,7 @@ def create_flask(self): return source_code.strip() def create_nose(self): - source_code = """\ + source_code = """ nose = "*" coverage = "*" rednose = "*" @@ -70,13 +70,13 @@ def create_nose(self): def create_db(self): source_code = '' if self.db == 'sqlalchemy': - source_code = """\ + source_code = """ pymysql = "*" flask-sqlalchemy = "*" flask-migrate = "*" """ if self.db == 'mongoengine': - source_code = """\ + source_code = """ pymongo = "*" mongoengine = "*" """ @@ -85,7 +85,7 @@ def create_db(self): def create_cache(self): source_code = '' if self.cache == 'redis': - source_code = """\ + source_code = """ redis = "*" flask-redis = "*" """ diff --git a/helmetica/scaffold/test.py b/helmetica/scaffold/test.py index 02b27a4..1e9c2ff 100644 --- a/helmetica/scaffold/test.py +++ b/helmetica/scaffold/test.py @@ -12,7 +12,7 @@ class Test(object): def create__init__(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- @@ -43,10 +43,10 @@ def response_to_dict(self, response): def authenticate(self, user=None): pass """ - return dedent(source_code) + return dedent(source_code).strip() def create_nose_cfg(self): - source_code = """\ + source_code = """ [nosetests] verbosity=2 with-timer=1 @@ -55,4 +55,4 @@ def create_nose_cfg(self): with-coverage=1 cover-package=. """ - return dedent(source_code) + return dedent(source_code).strip() diff --git a/helmetica/scaffold/wsgi.py b/helmetica/scaffold/wsgi.py index cc42f6b..0def5b4 100644 --- a/helmetica/scaffold/wsgi.py +++ b/helmetica/scaffold/wsgi.py @@ -15,7 +15,7 @@ class WSGI(object): """ def create_wsgi(self): - source_code = """\ + source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- import os @@ -26,4 +26,4 @@ def create_wsgi(self): if __name__ == '__main__': app.run(host='0.0.0.0') """ - return dedent(source_code) + return dedent(source_code).strip() From 9626d026a790f76163ffb53da5277faa5e8d0f13 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Mon, 21 May 2018 18:46:40 +0900 Subject: [PATCH 10/20] add mongoengine init model --- helmetica/scaffold/model.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/helmetica/scaffold/model.py b/helmetica/scaffold/model.py index 436c0de..3f011bc 100644 --- a/helmetica/scaffold/model.py +++ b/helmetica/scaffold/model.py @@ -18,6 +18,12 @@ class Model(object): def __init__(self, db): self.db = db + def create__init__(self): + if self.db == 'sqlalchemy': + return self.create_sqlalchemy__init__() + if self.db == 'mongoengine': + return self.create_mongoengine__init__() + def create_sqlalchemy__init__(self): source_code = """ #! /usr/bin/env python3 @@ -46,7 +52,23 @@ def create_sqlalchmey_model(self): pass def create_mongoengine__init__(self): - pass + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from datetime import datetime + from mongoengine import Document, DateTimeField + + class Model(Document): + created_at = DateTimeField() + updated_at = DateTimeField(default=datetime.datetime.now) + + def save(self, *args, **kwargs): + if not self.created_at: + self.created_at = datetime.datetime.now() + self.updated_at = datetime.datetime.now() + return super(Model, self).save(*args, **kwargs) + """ + return dedent(source_code).strip() def create_mongoengine__model(self): pass From 1235c87dd57e17f1d4f7663f0e59eeb991acc955 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 23 May 2018 22:46:18 +0900 Subject: [PATCH 11/20] fix cache -> redis --- helmetica/cli.py | 50 ++++++++++++++++++--------------- helmetica/scaffold/app.py | 22 +++++++-------- helmetica/scaffold/config.py | 29 +++++++++++++++++-- helmetica/scaffold/docker.py | 12 ++++---- helmetica/scaffold/extension.py | 24 +++++++--------- helmetica/scaffold/model.py | 33 ++++++++++++++++++++-- helmetica/scaffold/pipfile.py | 12 ++++---- helmetica/scaffold/wsgi.py | 22 +++++++++++++-- 8 files changed, 136 insertions(+), 68 deletions(-) diff --git a/helmetica/cli.py b/helmetica/cli.py index b112cda..d9aa66b 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -13,6 +13,7 @@ from helmetica.scaffold.config import Config from helmetica.scaffold.wsgi import WSGI from helmetica.scaffold.api import API +from helmetica.scaffold.model import Model from helmetica.scaffold.test import Test from helmetica.scaffold.docker import Docker from helmetica.scaffold.extension import Extension @@ -23,35 +24,35 @@ def main(): pass @main.command() +@click.option('--api', default='restful', type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') -@click.option('--cache', default=None, type=click.Choice(['redis', 'memcached']), help='Redis or Memd or None') -@click.option('--api', default=None, type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') -@click.option('--web', default='gunicorn', help='use gunicorn or others') -@click.option('--virtualization', default=None, type=click.Choice(['docker', 'vagrant']), help='container or VM') -def init(db, cache, api, web, virtualization): - dirs = ['./app/', './test/', './config/', './app/api/v1/'] +@click.option('--redis', default=False, is_flag=True, flag_value='redis', help='using Redis or None') +@click.option('--docker', default=False, is_flag=True, flag_value='docker', help='using container') +def init(api, db, redis, docker): + dirs = ['./app/', './test/', './config/', './app/api/v1/', './app/models/'] for dir in dirs: if os.path.exists(os.path.dirname(dir)): click.echo('[WARNING] directory {} is already exists, skip to create this directory'.format(dir)) continue os.makedirs(os.path.dirname(dir)) - app = App(db=db, cache=cache, api=api) - pipfile = Pipfile(db=db, cache=cache) - wsgi = WSGI() - config = Config() + app = App(db=db, redis=redis, api=api) + pipfile = Pipfile(db=db, redis=redis) + wsgi = WSGI(db=db) + config = Config(db=db, redis=redis) test = Test() - docker = Docker(db=db, cache=cache) - extension = Extension(db=db, cache=cache) + extension = Extension(db=db, redis=redis) + docker = Docker(db=db, redis=redis) api = API(api=api, name='root') + model = Model(db=db, name='root') with open('./Pipfile', 'w') as f: f.write(pipfile.create_pipfile()) - pipfile.lock() with open('app/__init__.py', 'w') as f: f.write(app.create_app__init__()) - + with open('app/extensions.py', 'w') as f: + f.write(extension.create_extensions()) with open('wsgi.py', 'w') as f: f.write(wsgi.create_wsgi()) @@ -67,9 +68,6 @@ def init(db, cache, api, web, virtualization): with open('docker-compose.yml', 'w') as f: f.write(docker.create_docker_compose_yml()) - if extension.any_extension: - with open('app/extensions.py', 'w') as f: - f.write(extension.create_extensions()) with open('test/__init__.py', 'w') as f: f.write(test.create__init__()) with open('nose.cfg', 'w') as f: @@ -82,9 +80,14 @@ def init(db, cache, api, web, virtualization): with open('app/api/v1/root.py', 'w') as f: f.write(api.create_restful()) + with open('app/models/__init__.py', 'w') as f: + f.write(model.create__init__()) + with open('app/models/root.py', 'w') as f: + f.write(model.create_model()) + @main.command() @click.argument('name', type=str, required=True) -@click.option('--api', default=None, type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') +@click.option('--api', default='restful', type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--version', default='v1', help='API version') def api(name, api, version): path = 'app/api/{}/{}.py'.format(version, name) @@ -94,8 +97,9 @@ def api(name, api, version): @main.command() @click.argument('name', type=str, required=True) -@click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') -def model(name): - # モデルを勝手に作成出来るようにしたい - click.echo('create model {}'.format(name)) - pass +@click.option('--db', default='sqlalchemy', type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') +def model(name, db): + path = 'app/models/{}.py'.format(name) + model = Model(db=db, name=name) + with open(path, 'w') as f: + f.write(model.create_model()) diff --git a/helmetica/scaffold/app.py b/helmetica/scaffold/app.py index fdd89bc..b1c00e2 100644 --- a/helmetica/scaffold/app.py +++ b/helmetica/scaffold/app.py @@ -14,10 +14,10 @@ class App(object): """ App Scaffold """ - def __init__(self, api=None, db=None, cache=None): - self.db = db - self.cache = cache + def __init__(self, api=None, db=None, redis=None): self.api = api + self.db = db + self.redis = redis def create_app__init__(self): source_code = """ @@ -49,12 +49,12 @@ def create_app(env='development'): def create_header(self): import_db = '' - import_cache = '' + import_redis = '' import_api = '' if self.db: import_db = 'from app.extensions import db' - if self.cache == 'redis': - import_cache = 'from app.extensions import redis' + if self.redis == 'redis': + import_redis = 'from app.extensions import redis' if self.api: import_api = 'from app.api import api_v1' @@ -62,20 +62,20 @@ def create_header(self): {} {} {} - """.format(import_db, import_cache, import_api) + """.format(import_db, import_redis, import_api) return header.strip() def create_extension(self): create_db = '' - create_cache = '' + create_redis = '' if self.db: create_db = 'db.init_app(app)' - if self.cache == 'redis': - create_cache = 'redis.init_app(app)' + if self.redis == 'redis': + create_redis = 'redis.init_app(app)' source_code = """ {} {} - """.format(create_db, create_cache) + """.format(create_db, create_redis) return source_code.strip() def create_api(self): diff --git a/helmetica/scaffold/config.py b/helmetica/scaffold/config.py index 3746d1c..2aed66a 100644 --- a/helmetica/scaffold/config.py +++ b/helmetica/scaffold/config.py @@ -15,9 +15,9 @@ class Config(object): """ Config Scaffold """ - def __init__(self, db=None, cache=None): + def __init__(self, db=None, redis=None): self.db = db - self.cache = cache + self.redis = redis def create_config(self, name='config', env='test'): source_code = """ @@ -26,8 +26,31 @@ def create_config(self, name='config', env='test'): class {name}(object): ENV = '{env}' + {db} + {redis} """.format( name=Inflector().camelize(name), - env=env + env=env, + db=self.create_sqlalchemy(), + redis=self.create_redis(), ) return dedent(source_code).strip() + + def create_sqlalchemy(self): + source_code = '' + if self.db == 'sqlalchemy': + source_code = """ + SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@db:3306/root?charset=utf8mb4' + SQLALCHEMY_TRACK_MODIFICATIONS = True + """ + if self.db == 'mongoengine': + source_code = """ + MONGODB_HOST = 'db' + MONGODB_PORT = 27017 + MONGODB_DB = 'root' + """ + return source_code + + def create_redis(self): + if self.redis == 'redis': + return "REDIS_URL = 'redis://:@redis:6379/0'" diff --git a/helmetica/scaffold/docker.py b/helmetica/scaffold/docker.py index 338e0f2..7a01f8f 100644 --- a/helmetica/scaffold/docker.py +++ b/helmetica/scaffold/docker.py @@ -14,9 +14,9 @@ class Docker(object): """ Docker Scaffold """ - def __init__(self, db=None, cache=None): + def __init__(self, db=None, redis=None): self.db = db - self.cache = cache + self.redis = redis def create_dockerfile(self): source_code = """ @@ -50,10 +50,10 @@ def create_docker_compose_yml(self): ports: - "5000:5000" {db} - {cache} + {redis} """.format( db=self.create_db(), - cache=self.create_cache(), + redis=self.create_redis(), ) return dedent(source_code).strip() @@ -80,9 +80,9 @@ def create_db(self): """ return source_code.strip() - def create_cache(self): + def create_redis(self): source_code = '' - if self.cache == 'redis': + if self.redis == 'redis': source_code = """ redis: image: redis:3.2 diff --git a/helmetica/scaffold/extension.py b/helmetica/scaffold/extension.py index d4521e5..d23bc5a 100644 --- a/helmetica/scaffold/extension.py +++ b/helmetica/scaffold/extension.py @@ -14,9 +14,9 @@ class Extension(object): """ Extension Scaffold """ - def __init__(self, db=None, cache=None): + def __init__(self, db=None, redis=None): self.db = db - self.cache = cache + self.redis = redis def create_extensions(self): source_code = """ @@ -33,34 +33,30 @@ def create_extensions(self): def create_header(self): import_db = '' - import_cache = '' + import_redis = '' if self.db == 'sqlalchemy': import_db = 'from flask_sqlalchemy import SQLAlchemy' if self.db == 'mongoengine': import_db = 'from flask_mongoengine import MongoEngine' - if self.cache == 'redis': - import_cache = 'from flask_redis import FlaskRedis' + if self.redis == 'redis': + import_redis = 'from flask_redis import FlaskRedis' header = """ {} {} - """.format(import_db, import_cache) + """.format(import_db, import_redis) return header.strip() def create_instance(self): instance_db = '' - instance_cache = '' + instance_redis = '' if self.db == 'sqlalchemy': instance_db = 'db = SQLAlchemy()' if self.db == 'mongoeingine': instance_db = 'db = MongoEngine()' - if self.cache: - instance_cache = 'redis = FlaskRedis()' + if self.redis: + instance_redis = 'redis = FlaskRedis()' instance = """ {} {} - """.format(instance_db, instance_cache) + """.format(instance_db, instance_redis) return instance.strip() - - def any_extension(self): - # TODO バグってる - return self.db or self.cache diff --git a/helmetica/scaffold/model.py b/helmetica/scaffold/model.py index 3f011bc..0665d41 100644 --- a/helmetica/scaffold/model.py +++ b/helmetica/scaffold/model.py @@ -15,8 +15,9 @@ class Model(object): """ Model Scaffold """ - def __init__(self, db): + def __init__(self, db=None, name=None): self.db = db + self.name = name def create__init__(self): if self.db == 'sqlalchemy': @@ -24,6 +25,12 @@ def create__init__(self): if self.db == 'mongoengine': return self.create_mongoengine__init__() + def create_model(self): + if self.db == 'sqlalchemy': + return self.create_sqlalchmey_model() + if self.db == 'mongoengine': + return self.create_mongoengine_model() + def create_sqlalchemy__init__(self): source_code = """ #! /usr/bin/env python3 @@ -49,7 +56,17 @@ def updated_at(cls): return dedent(source_code).strip() def create_sqlalchmey_model(self): - pass + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from app.models import Model + + class {name}(Model): + pass + """.format( + name=Inflector().camelize(self.name) + ) + return dedent(source_code).strip() def create_mongoengine__init__(self): source_code = """ @@ -71,4 +88,14 @@ def save(self, *args, **kwargs): return dedent(source_code).strip() def create_mongoengine__model(self): - pass + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from app.models import Model + + class {name}(Model): + pass + """.format( + name=Inflector().camelize(self.name) + ) + return dedent(source_code).strip() diff --git a/helmetica/scaffold/pipfile.py b/helmetica/scaffold/pipfile.py index fb0b6cf..1572327 100644 --- a/helmetica/scaffold/pipfile.py +++ b/helmetica/scaffold/pipfile.py @@ -15,9 +15,9 @@ class Pipfile(object): """ Pipfile Scaffold """ - def __init__(self, db=None, cache=None): + def __init__(self, db=None, redis=None): self.db = db - self.cache = cache + self.redis = redis def lock(self): system('pipenv lock') @@ -36,7 +36,7 @@ def create_pipfile(self): [packages] {flask} {db} - {cache} + {redis} [requires] @@ -45,7 +45,7 @@ def create_pipfile(self): flask=self.create_flask(), nose=self.create_nose(), db=self.create_db(), - cache=self.create_cache() + redis=self.create_redis() ) return dedent(source_code).strip() @@ -82,9 +82,9 @@ def create_db(self): """ return source_code.strip() - def create_cache(self): + def create_redis(self): source_code = '' - if self.cache == 'redis': + if self.redis == 'redis': source_code = """ redis = "*" flask-redis = "*" diff --git a/helmetica/scaffold/wsgi.py b/helmetica/scaffold/wsgi.py index 0def5b4..908bb05 100644 --- a/helmetica/scaffold/wsgi.py +++ b/helmetica/scaffold/wsgi.py @@ -14,16 +14,34 @@ class WSGI(object): """ wsgi Scaffold """ + def __init__(self, db): + self.db = db + def create_wsgi(self): source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- import os - from app import create_app + {header} app = create_app(os.getenv('FLASK_ENV', None)) + {migrate} if __name__ == '__main__': app.run(host='0.0.0.0') - """ + """.format( + header=self.create_header(), + migrate=self.create_migrate() + ) + return dedent(source_code).strip() + + def create_header(self): + if self.db == 'sqlalchemy': + return 'from app import create_app, db' + return 'from app import create_app' + + def create_migrate(self): + if self.db == 'sqlalchemy': + return 'migrate = Migrate(app, db)' + return '' From cdffc901b0b82198ea750ab1f3cd1f5f834fcce4 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Wed, 23 May 2018 22:58:04 +0900 Subject: [PATCH 12/20] fix database name --- helmetica/scaffold/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helmetica/scaffold/config.py b/helmetica/scaffold/config.py index 2aed66a..8783f97 100644 --- a/helmetica/scaffold/config.py +++ b/helmetica/scaffold/config.py @@ -40,12 +40,12 @@ def create_sqlalchemy(self): source_code = '' if self.db == 'sqlalchemy': source_code = """ - SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@db:3306/root?charset=utf8mb4' + SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@db:3306/app_development?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = True """ if self.db == 'mongoengine': source_code = """ - MONGODB_HOST = 'db' + MONGODB_HOST = 'app_development' MONGODB_PORT = 27017 MONGODB_DB = 'root' """ From 4d8cdae1dcbb438dfbe8cb3bfb7fbdb6ebd27ab5 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Thu, 24 May 2018 22:22:48 +0900 Subject: [PATCH 13/20] add method view and decorator scaffold --- helmetica/cli.py | 47 ++++++++++----- helmetica/scaffold/api.py | 100 +++++++++++++++++++++++++++++++- helmetica/scaffold/decorator.py | 50 ++++++++++++++++ methodview.py | 32 ---------- 4 files changed, 178 insertions(+), 51 deletions(-) create mode 100644 helmetica/scaffold/decorator.py delete mode 100644 methodview.py diff --git a/helmetica/cli.py b/helmetica/cli.py index d9aa66b..ef4a500 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -18,6 +18,7 @@ from helmetica.scaffold.docker import Docker from helmetica.scaffold.extension import Extension from helmetica.scaffold.pipfile import Pipfile +from helmetica.scaffold.decorator import Decorator @click.group() def main(): @@ -26,9 +27,10 @@ def main(): @main.command() @click.option('--api', default='restful', type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--db', default=None, type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') +@click.option('--decorator', default=False, is_flag=True, help='create decorator or None') @click.option('--redis', default=False, is_flag=True, flag_value='redis', help='using Redis or None') @click.option('--docker', default=False, is_flag=True, flag_value='docker', help='using container') -def init(api, db, redis, docker): +def init(api, db, decorator, redis, docker): dirs = ['./app/', './test/', './config/', './app/api/v1/', './app/models/'] for dir in dirs: if os.path.exists(os.path.dirname(dir)): @@ -42,19 +44,21 @@ def init(api, db, redis, docker): config = Config(db=db, redis=redis) test = Test() extension = Extension(db=db, redis=redis) - docker = Docker(db=db, redis=redis) api = API(api=api, name='root') - model = Model(db=db, name='root') + decorator = Decorator(name='root') with open('./Pipfile', 'w') as f: f.write(pipfile.create_pipfile()) + with open('wsgi.py', 'w') as f: + f.write(wsgi.create_wsgi()) with open('app/__init__.py', 'w') as f: f.write(app.create_app__init__()) with open('app/extensions.py', 'w') as f: f.write(extension.create_extensions()) - with open('wsgi.py', 'w') as f: - f.write(wsgi.create_wsgi()) + if decorator: + with open('app/decorators.py', 'w') as f: + f.write(decorator.create_decorators()) with open('config/__init__.py', 'w') as f: f.write(config.create_config(name='config', env='test')) @@ -63,10 +67,12 @@ def init(api, db, redis, docker): with open('config/production.py', 'w') as f: f.write(config.create_config(name='production', env='production')) - with open('Dockerfile', 'w') as f: - f.write(docker.create_dockerfile()) - with open('docker-compose.yml', 'w') as f: - f.write(docker.create_docker_compose_yml()) + if docker: + docker = Docker(db=db, redis=redis) + with open('Dockerfile', 'w') as f: + f.write(docker.create_dockerfile()) + with open('docker-compose.yml', 'w') as f: + f.write(docker.create_docker_compose_yml()) with open('test/__init__.py', 'w') as f: f.write(test.create__init__()) @@ -74,16 +80,18 @@ def init(api, db, redis, docker): f.write(test.create_nose_cfg()) with open('app/api/__init__.py', 'w') as f: - f.write(api.create_restful__init__()) + f.write(api.create__init__()) with open('app/api/v1/__init__.py', 'w') as f: pass with open('app/api/v1/root.py', 'w') as f: - f.write(api.create_restful()) + f.write(api.create_api()) - with open('app/models/__init__.py', 'w') as f: - f.write(model.create__init__()) - with open('app/models/root.py', 'w') as f: - f.write(model.create_model()) + if db: + model = Model(db=db, name='root') + with open('app/models/__init__.py', 'w') as f: + f.write(model.create__init__()) + with open('app/models/root.py', 'w') as f: + f.write(model.create_model()) @main.command() @click.argument('name', type=str, required=True) @@ -93,7 +101,7 @@ def api(name, api, version): path = 'app/api/{}/{}.py'.format(version, name) api = API(api=api, name=name) with open(path, 'w') as f: - f.write(api.create_restful()) + f.write(api.create_api()) @main.command() @click.argument('name', type=str, required=True) @@ -103,3 +111,10 @@ def model(name, db): model = Model(db=db, name=name) with open(path, 'w') as f: f.write(model.create_model()) + +@main.command() +@click.argument('name', type=str, required=True) +def decorator(name): + decorator = Decorator(name=name) + with open('app/decorators.py', 'a') as f: + f.write(decorator.create_decorator()) diff --git a/helmetica/scaffold/api.py b/helmetica/scaffold/api.py index f3542fd..ea1eb08 100644 --- a/helmetica/scaffold/api.py +++ b/helmetica/scaffold/api.py @@ -19,18 +19,39 @@ def __init__(self, api=None, name=None): self.api = api self.name = name + def create__init__(self): + if self.api == 'restful': + return self.create_restful__init__() + if self.api == 'decorator': + return '' + if self.api == 'class': + return self.create_method_view__init__() + + def create_api(self): + if self.api == 'restful': + return self.create_restful() + if self.api == 'decorator': + return self.create_decorator() + if self.api == 'class': + return self.create_method_view() + def create_restful__init__(self): + name=Inflector().camelize(self.name) source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- from flask import Blueprint from flask_restful import Api - from app.api.v1.root import Root + from app.api.v1.{name} import {Name} api_v1 = Blueprint('api/v1', __name__) api = Api(api_v1) - api.add_resource(Root, '/') - """ + api.add_resource({Name}, '/{names}/') + """.format( + name=Inflector().underscore(self.name), + names=Inflector().pluralize(self.name), + Name=Inflector().camelize(self.name) + ) return dedent(source_code).strip() def create_restful(self): @@ -46,6 +67,7 @@ def create_restful(self): }} class {name}(Resource): + parser = reqparse.RequestParser() parser.add_argument('query', type=str, help="query string") parser.add_argument('body', type=str, help="body string") @@ -74,3 +96,75 @@ def delete(self, id=None): name=Inflector().camelize(self.name) ) return dedent(source_code).strip() + + def create_method_view__init__(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask import Blueprint + from app.api.v1.{name} import {Name} + + api_v1 = Blueprint('api/v1', __name__) + api_v1.add_url_rule('/{names}', view_func={Name}.as_view('{name}'), methods=['GET', 'POST', 'PUT', 'DELETE']) + """.format( + name=Inflector().underscore(self.name), + names=Inflector().pluralize(self.name), + Name=Inflector().camelize(self.name) + ) + return dedent(source_code).strip() + + def create_decorator(self): + instance = Inflector().underscore(self.name) + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask import Blueprint + + {instance} = Blueprint('{instances}', __name__) + + @{instance}.route('{instances}/<:id>', methods=['GET']) + def get(id): + return 'GET' + + @{instance}.route('{instances}/', methods=['POST']) + def post(): + return 'POST' + + @{instance}.route('{instances}/<:id>', methods=['PUT']) + def put(id): + return 'PUT' + + @{instance}.route('{instances}/<:id>', methods=['DELETE']) + def delete(id): + return 'PUT' + + """.format( + instance=instance, + instances=Inflector().pluralize(instance) + ) + return dedent(source_code).strip() + + def create_method_view(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from flask.views import MethodView + + class {name}(MethodView): + def get(self, id=None): + if not id: return 'index' + return 'show' + + def post(self): + return 'create' + + def put(self, id): + return 'update' + + def delete(self, id): + return 'delete' + + """.format( + name=Inflector().camelize(self.name), + ) + return dedent(source_code).strip() diff --git a/helmetica/scaffold/decorator.py b/helmetica/scaffold/decorator.py new file mode 100644 index 0000000..9868ebd --- /dev/null +++ b/helmetica/scaffold/decorator.py @@ -0,0 +1,50 @@ +#! /usr/bin/env python3 +# -*- encoding: utf-8 -*- +""" +decorator.py +scaffold decorator +""" +__author__ = 'Yoshiya Ito ' +__version__ = '1.0.0' +__date__ = '2018-05-24' +from textwrap import dedent +from inflector import Inflector + + +class Decorator(object): + """ Decorator Scaffold + """ + + def __init__(self, name=None): + self.name = name + + def create_decorators(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from functools import wraps + + def {name}(func): + @wraps(func) + def wrapper(*ar, **kw): + # something hear + return func(*ar, **kw) + return wrapper + """.format( + name=Inflector().underscore(self.name) + ) + return dedent(source_code).strip() + + def create_decorator(self): + source_code = """ + + def {name}(func): + @wraps(func) + def wrapper(*ar, **kw): + # something hear + return func(*ar, **kw) + return wrapper + """.format( + name=Inflector().underscore(self.name) + ) + return dedent(source_code) diff --git a/methodview.py b/methodview.py deleted file mode 100644 index 301dee9..0000000 --- a/methodview.py +++ /dev/null @@ -1,32 +0,0 @@ -from flask.views import MethodView -from flask import Flask - -app = Flask(__name__) - -def middleware(func): - def deco(*args, **kwargs): - print('middleware') - return func(*args, **kwargs) - return deco - -class Resource(MethodView): - decorators = [middleware] - - def get(self, id=None): - if not id: return 'index' - return 'show' - - def post(self): - return 'create' - - def put(self, id): - return 'update' - - def delete(self, id): - return 'delete' - -resource_view = Resource.as_view('resource') -app.add_url_rule('/resource', view_func=resource_view, methods=['GET', 'POST']) -app.add_url_rule('/resource/', view_func=resource_view, methods=['GET', 'PUT', 'DELETE']) - -app.run() From 8162152449e8005ecf06488eb025e5786e8930c1 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Thu, 24 May 2018 22:47:44 +0900 Subject: [PATCH 14/20] add test scaffold --- helmetica/cli.py | 2 +- helmetica/scaffold/test.py | 116 ++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/helmetica/cli.py b/helmetica/cli.py index ef4a500..fe6b063 100644 --- a/helmetica/cli.py +++ b/helmetica/cli.py @@ -42,7 +42,7 @@ def init(api, db, decorator, redis, docker): pipfile = Pipfile(db=db, redis=redis) wsgi = WSGI(db=db) config = Config(db=db, redis=redis) - test = Test() + test = Test(db=db) extension = Extension(db=db, redis=redis) api = API(api=api, name='root') decorator = Decorator(name='root') diff --git a/helmetica/scaffold/test.py b/helmetica/scaffold/test.py index 1e9c2ff..b75374a 100644 --- a/helmetica/scaffold/test.py +++ b/helmetica/scaffold/test.py @@ -8,14 +8,18 @@ __version__ = '1.0.0' __date__ = '2018-04-27' from textwrap import dedent +from inflector import Inflector class Test(object): + def __init__(self, name=None, db=None): + self.db =db + self.name = name + def create__init__(self): source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- - import json from contextlib import contextmanager from unittest import TestCase @@ -28,7 +32,6 @@ class Experiment(TestCase): def setUp(self): app.testing = True self.client = app.test_client() - self.logger = app.logger self.app_config = app.config self.app_context = app.app_context() self.app_context.push() @@ -56,3 +59,112 @@ def create_nose_cfg(self): cover-package=. """ return dedent(source_code).strip() + + def create_api_test(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from test import Experiment + from test.factories.{name}_factory import {Name}Factory + from app.models.{name} import {Name} + + class {Name}APITest(Experiment): + + def setUp(self): + super().setUp() + {Name}Factory.create_batch(5) + + def test_get_list_200(self): + {name} = {Name}.query.first() + url = "/api/v1/{names}" + response = self.client.get(url, data={}) + assert response.status_code == 200 + + def test_get_200(self): + {name} = {Name}.query.first() + url = "/api/v1/{names}/{{}}".format({name}.id) + response = self.client.get(url, data={}) + assert response.status_code == 200 + + def test_post_201(self): + url = "/api/v1/{names}" + response = self.client.post(url, data={}) + assert response.status_code == 201 + + def test_put_204(self): + {name} = {Name}.query.first() + url = "/api/v1/{names}/{{}}".format({name}.id) + response = self.client.put(url, data={}) + assert response.status_code == 204 + + def test_delete_204(self): + {name} = {Name}.query.first() + url = "/api/v1/{names}/{{}}".format({name}.id) + response = self.client.delete(url, data={}) + assert response.status_code == 204 + """.format( + name=Inflector().underscore(self.name), + names=Inflector().pluralize(self.name), + Name=Inflector().camelize(self.name) + ) + return dedent(source_code).strip() + + def create_model_test(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from test import Experiment + from test.factories.{name}_factory import {Name}Factory + from app.models.{name} import {Name} + + class {Name}ModelTest(Experiment): + + def setUp(self): + {Name}Factory.create_batch(5) + + def something(self): + {name} = {Name}.query.first() + assert {name} is not None + """.format( + name=Inflector().underscore(self.name), + Name=Inflector().camelize(self.name) + ) + return dedent(source_code).strip() + + def create_sqlalchemy_factoryboy(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from factory import Sequence, SubFactory, Iterator, fuzzy + from factory.alchemy import SQLAlchemyModelFactory + from app.extensions import db + from app.models.{name} import {Name} + + class {Name}Factory(SQLAlchemyModelFactory): + class Meta: + model = {Name} + sqlalchemy_session = db.session + + id = Sequence(lambda n: n+1) + """.format( + name=self.name, + Name=Inflector().camelize(self.name), + ) + return dedent(source_code).strip() + + def create_mongoengine_factoryboy(self): + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from factory import Sequence, SubFactory, Iterator, fuzzy + from factory.mongoengine import MongoEngineFactory + from app.models.{name} import {Name} + + class {Name}Factory(MongoEngineFactory): + class Meta: + model = {Name} + """.format( + name=self.name, + Name=Inflector().camelize(self.name), + ) + return dedent(source_code).strip() From ec41a763082f847430e7afdd5e266655f9728be2 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 14:27:10 +0900 Subject: [PATCH 15/20] add circle ci --- .circleci/config.yml | 35 +++++++++++++++++++ README.md | 14 +++++--- {helmetica => hermetica}/__init__.py | 0 {helmetica => hermetica}/cli.py | 24 ++++++------- {helmetica => hermetica}/scaffold/__init__.py | 0 {helmetica => hermetica}/scaffold/api.py | 0 {helmetica => hermetica}/scaffold/app.py | 0 {helmetica => hermetica}/scaffold/config.py | 0 .../scaffold/decorator.py | 0 {helmetica => hermetica}/scaffold/docker.py | 0 .../scaffold/extension.py | 0 {helmetica => hermetica}/scaffold/model.py | 0 {helmetica => hermetica}/scaffold/pipfile.py | 0 {helmetica => hermetica}/scaffold/test.py | 10 ++++-- {helmetica => hermetica}/scaffold/wsgi.py | 0 setup.py | 10 +++--- 16 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 .circleci/config.yml rename {helmetica => hermetica}/__init__.py (100%) rename {helmetica => hermetica}/cli.py (89%) rename {helmetica => hermetica}/scaffold/__init__.py (100%) rename {helmetica => hermetica}/scaffold/api.py (100%) rename {helmetica => hermetica}/scaffold/app.py (100%) rename {helmetica => hermetica}/scaffold/config.py (100%) rename {helmetica => hermetica}/scaffold/decorator.py (100%) rename {helmetica => hermetica}/scaffold/docker.py (100%) rename {helmetica => hermetica}/scaffold/extension.py (100%) rename {helmetica => hermetica}/scaffold/model.py (100%) rename {helmetica => hermetica}/scaffold/pipfile.py (100%) rename {helmetica => hermetica}/scaffold/test.py (95%) rename {helmetica => hermetica}/scaffold/wsgi.py (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..68a2af5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,35 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/python:3.6.1 + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install wheel twine + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "requirements.txt" }} + + - run: + name: run build + command: | + . venv/bin/activate + python setup.py sdist bdist_wheel + twine upload --repository pypi dist/* diff --git a/README.md b/README.md index ba48a69..2457a87 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -# Helmetica +# Hermetica + +[![CircleCI](https://circleci.com/gh/yoshiya0503/Hermetica.svg?style=shield&circle-token=4614abf3b106e5f31f9726ebaedfcebc5c7fa859)](https://circleci.com/gh/yoshiya0503/Hermetica) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md#pull-requests) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) @@ -7,10 +9,10 @@ THIS IS NOT WEB FRAMEWORK TO REPLACE FLASK. -Helmetica is scaffold tools, and wiki to implement better flask applications. +Hermetica is scaffold tools, and wiki to implement better flask applications. When we try to build web applications by using flask web framework, there are to many patterns and practices. -This fact make it difficult to implement apps that have simple architecture. +This diversity make it difficult to implement apps that have simple architecture. In other words, because of too many manners, options, and patters, to implement more bigger applications is not so easy. (but, to implement small app by using flask is extreamly easy) @@ -23,8 +25,10 @@ based on our many experiences. # Installation +We dare to support only python 3.x, because python 2.x will eventually deprecated almost all systems, and we have to get used to python 3.x quickly. + ``` -pip install helmetica +pip install hermetica ``` # Usage @@ -44,4 +48,4 @@ and we believe this change will not cause negative impact to ohters. To create scaffold tools for flask will cause good affect the world rather than remain trivial code. But there are no warries. Flask-best-Practices contents (wiki docs) remain here (for japanese). -https://github.com/yoshiya0503/Helmetica/wiki +https://github.com/yoshiya0503/Hermetica/wiki diff --git a/helmetica/__init__.py b/hermetica/__init__.py similarity index 100% rename from helmetica/__init__.py rename to hermetica/__init__.py diff --git a/helmetica/cli.py b/hermetica/cli.py similarity index 89% rename from helmetica/cli.py rename to hermetica/cli.py index fe6b063..3899de6 100644 --- a/helmetica/cli.py +++ b/hermetica/cli.py @@ -2,23 +2,23 @@ # -*- encoding: utf-8 -*- """ main.py -helmetica main script +hermetica main script """ __author__ = 'Yoshiya Ito ' __version__ = '1.0.0' __date__ = '2018-04-24' import os import click -from helmetica.scaffold.app import App -from helmetica.scaffold.config import Config -from helmetica.scaffold.wsgi import WSGI -from helmetica.scaffold.api import API -from helmetica.scaffold.model import Model -from helmetica.scaffold.test import Test -from helmetica.scaffold.docker import Docker -from helmetica.scaffold.extension import Extension -from helmetica.scaffold.pipfile import Pipfile -from helmetica.scaffold.decorator import Decorator +from hermetica.scaffold.app import App +from hermetica.scaffold.config import Config +from hermetica.scaffold.wsgi import WSGI +from hermetica.scaffold.api import API +from hermetica.scaffold.model import Model +from hermetica.scaffold.test import Test +from hermetica.scaffold.docker import Docker +from hermetica.scaffold.extension import Extension +from hermetica.scaffold.pipfile import Pipfile +from hermetica.scaffold.decorator import Decorator @click.group() def main(): @@ -42,8 +42,8 @@ def init(api, db, decorator, redis, docker): pipfile = Pipfile(db=db, redis=redis) wsgi = WSGI(db=db) config = Config(db=db, redis=redis) - test = Test(db=db) extension = Extension(db=db, redis=redis) + test = Test(db=db, name='root') api = API(api=api, name='root') decorator = Decorator(name='root') diff --git a/helmetica/scaffold/__init__.py b/hermetica/scaffold/__init__.py similarity index 100% rename from helmetica/scaffold/__init__.py rename to hermetica/scaffold/__init__.py diff --git a/helmetica/scaffold/api.py b/hermetica/scaffold/api.py similarity index 100% rename from helmetica/scaffold/api.py rename to hermetica/scaffold/api.py diff --git a/helmetica/scaffold/app.py b/hermetica/scaffold/app.py similarity index 100% rename from helmetica/scaffold/app.py rename to hermetica/scaffold/app.py diff --git a/helmetica/scaffold/config.py b/hermetica/scaffold/config.py similarity index 100% rename from helmetica/scaffold/config.py rename to hermetica/scaffold/config.py diff --git a/helmetica/scaffold/decorator.py b/hermetica/scaffold/decorator.py similarity index 100% rename from helmetica/scaffold/decorator.py rename to hermetica/scaffold/decorator.py diff --git a/helmetica/scaffold/docker.py b/hermetica/scaffold/docker.py similarity index 100% rename from helmetica/scaffold/docker.py rename to hermetica/scaffold/docker.py diff --git a/helmetica/scaffold/extension.py b/hermetica/scaffold/extension.py similarity index 100% rename from helmetica/scaffold/extension.py rename to hermetica/scaffold/extension.py diff --git a/helmetica/scaffold/model.py b/hermetica/scaffold/model.py similarity index 100% rename from helmetica/scaffold/model.py rename to hermetica/scaffold/model.py diff --git a/helmetica/scaffold/pipfile.py b/hermetica/scaffold/pipfile.py similarity index 100% rename from helmetica/scaffold/pipfile.py rename to hermetica/scaffold/pipfile.py diff --git a/helmetica/scaffold/test.py b/hermetica/scaffold/test.py similarity index 95% rename from helmetica/scaffold/test.py rename to hermetica/scaffold/test.py index b75374a..ab19238 100644 --- a/helmetica/scaffold/test.py +++ b/hermetica/scaffold/test.py @@ -123,14 +123,20 @@ def setUp(self): {Name}Factory.create_batch(5) def something(self): - {name} = {Name}.query.first() - assert {name} is not None + pass """.format( name=Inflector().underscore(self.name), Name=Inflector().camelize(self.name) ) return dedent(source_code).strip() + def create_factoryboy(self): + if self.db == 'sqlalchemy': + return self.create_sqlalchemy_factoryboy() + if self.db == 'mongoengine': + return self.create_mongoengine_factoryboy() + return '' + def create_sqlalchemy_factoryboy(self): source_code = """ #! /usr/bin/env python3 diff --git a/helmetica/scaffold/wsgi.py b/hermetica/scaffold/wsgi.py similarity index 100% rename from helmetica/scaffold/wsgi.py rename to hermetica/scaffold/wsgi.py diff --git a/setup.py b/setup.py index 110c79e..4b83f8a 100644 --- a/setup.py +++ b/setup.py @@ -13,8 +13,8 @@ # readme = f.read() setup( - name='Helmetica', - version='0.0.1', + name='Hermetica', + version='1.0.0', description='scaffold command line interface for Flask application', #long_description=readme, author='Yoshiya Ito', @@ -22,7 +22,7 @@ url='https://github.com/yoshiya0503/Flask-Best-Practices.git', license='MIT', platforms='any', - packages=['helmetica', 'helmetica.scaffold'], + packages=['hermetica', 'hermetica.scaffold'], install_requires=[ 'click>=5.1', 'Inflector>=2.0', @@ -31,8 +31,6 @@ 'Environment :: Web Environment', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', @@ -40,6 +38,6 @@ ], entry_points=''' [console_scripts] - helmetica=helmetica.cli:main + hermetica=hermetica.cli:main ''' ) From 5114a1d665234f347e215c52c60c0fe107947f10 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 15:37:57 +0900 Subject: [PATCH 16/20] fix api scaffold --- hermetica/cli.py | 8 ++++ hermetica/scaffold/api.py | 82 +++++++++++++++++++++--------------- hermetica/scaffold/app.py | 9 ++-- hermetica/scaffold/docker.py | 24 +++++++++-- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/hermetica/cli.py b/hermetica/cli.py index 3899de6..f3f4ee5 100644 --- a/hermetica/cli.py +++ b/hermetica/cli.py @@ -31,6 +31,8 @@ def main(): @click.option('--redis', default=False, is_flag=True, flag_value='redis', help='using Redis or None') @click.option('--docker', default=False, is_flag=True, flag_value='docker', help='using container') def init(api, db, decorator, redis, docker): + """ initialize your flask app + """ dirs = ['./app/', './test/', './config/', './app/api/v1/', './app/models/'] for dir in dirs: if os.path.exists(os.path.dirname(dir)): @@ -98,6 +100,8 @@ def init(api, db, decorator, redis, docker): @click.option('--api', default='restful', type=click.Choice(['restful', 'decorator', 'class']), help='Flask-Restful or Flask decorator or methodview') @click.option('--version', default='v1', help='API version') def api(name, api, version): + """ create api + """ path = 'app/api/{}/{}.py'.format(version, name) api = API(api=api, name=name) with open(path, 'w') as f: @@ -107,6 +111,8 @@ def api(name, api, version): @click.argument('name', type=str, required=True) @click.option('--db', default='sqlalchemy', type=click.Choice(['sqlalchemy', 'mongoengine']), help='SQLAlchemy or Mongoengine or None') def model(name, db): + """ create model + """ path = 'app/models/{}.py'.format(name) model = Model(db=db, name=name) with open(path, 'w') as f: @@ -115,6 +121,8 @@ def model(name, db): @main.command() @click.argument('name', type=str, required=True) def decorator(name): + """ create decorator + """ decorator = Decorator(name=name) with open('app/decorators.py', 'a') as f: f.write(decorator.create_decorator()) diff --git a/hermetica/scaffold/api.py b/hermetica/scaffold/api.py index ea1eb08..f0c94a8 100644 --- a/hermetica/scaffold/api.py +++ b/hermetica/scaffold/api.py @@ -23,7 +23,7 @@ def create__init__(self): if self.api == 'restful': return self.create_restful__init__() if self.api == 'decorator': - return '' + return self.create_decorator__init__() if self.api == 'class': return self.create_method_view__init__() @@ -46,7 +46,7 @@ def create_restful__init__(self): api_v1 = Blueprint('api/v1', __name__) api = Api(api_v1) - api.add_resource({Name}, '/{names}/') + api.add_resource({Name}, '/{names}', '/{names}/') """.format( name=Inflector().underscore(self.name), names=Inflector().pluralize(self.name), @@ -105,7 +105,8 @@ def create_method_view__init__(self): from app.api.v1.{name} import {Name} api_v1 = Blueprint('api/v1', __name__) - api_v1.add_url_rule('/{names}', view_func={Name}.as_view('{name}'), methods=['GET', 'POST', 'PUT', 'DELETE']) + api_v1.add_url_rule('/{names}', view_func={Name}.as_view('{name}'), methods=['GET', 'POST']) + api_v1.add_url_rule('/{names}/', view_func={Name}.as_view('{name}'), methods=['GET', 'PUT', 'DELETE']) """.format( name=Inflector().underscore(self.name), names=Inflector().pluralize(self.name), @@ -113,58 +114,73 @@ def create_method_view__init__(self): ) return dedent(source_code).strip() - def create_decorator(self): - instance = Inflector().underscore(self.name) + def create_method_view(self): source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- - from flask import Blueprint + from flask.views import MethodView - {instance} = Blueprint('{instances}', __name__) + class {name}(MethodView): + def get(self, id=None): + if not id: return 'index' + return 'show' - @{instance}.route('{instances}/<:id>', methods=['GET']) - def get(id): - return 'GET' + def post(self): + return 'create' - @{instance}.route('{instances}/', methods=['POST']) - def post(): - return 'POST' + def put(self, id): + return 'update' - @{instance}.route('{instances}/<:id>', methods=['PUT']) - def put(id): - return 'PUT' + def delete(self, id): + return 'destroy' - @{instance}.route('{instances}/<:id>', methods=['DELETE']) - def delete(id): - return 'PUT' + """.format( + name=Inflector().camelize(self.name), + ) + return dedent(source_code).strip() + def create_decorator__init__(self): + instance = Inflector().underscore(self.name) + source_code = """ + #! /usr/bin/env python3 + # -*- encoding: utf-8 -*- + from app.api.v1.{instance} import {instance} """.format( instance=instance, - instances=Inflector().pluralize(instance) ) return dedent(source_code).strip() - def create_method_view(self): + def create_decorator(self): + instance = Inflector().underscore(self.name) source_code = """ #! /usr/bin/env python3 # -*- encoding: utf-8 -*- - from flask.views import MethodView + from flask import Blueprint - class {name}(MethodView): - def get(self, id=None): - if not id: return 'index' - return 'show' + {instance} = Blueprint('{instances}', __name__) - def post(self): - return 'create' + @{instance}.route('/{instances}', methods=['GET']) + def index(): + return 'index' - def put(self, id): - return 'update' + @{instance}.route('/{instances}/', methods=['GET']) + def show(id): + return 'show' - def delete(self, id): - return 'delete' + @{instance}.route('/{instances}/', methods=['POST']) + def create(): + return 'create' + + @{instance}.route('/{instances}/', methods=['PUT']) + def update(id): + return 'update' + + @{instance}.route('/{instances}/', methods=['DELETE']) + def destroy(id): + return 'destroy' """.format( - name=Inflector().camelize(self.name), + instance=instance, + instances=Inflector().pluralize(instance) ) return dedent(source_code).strip() diff --git a/hermetica/scaffold/app.py b/hermetica/scaffold/app.py index b1c00e2..6084628 100644 --- a/hermetica/scaffold/app.py +++ b/hermetica/scaffold/app.py @@ -56,7 +56,7 @@ def create_header(self): if self.redis == 'redis': import_redis = 'from app.extensions import redis' if self.api: - import_api = 'from app.api import api_v1' + import_api = 'from app import api' header = """ {} @@ -79,7 +79,8 @@ def create_extension(self): return source_code.strip() def create_api(self): - if self.api: - return "app.register_blueprint(api_v1, url_prefix='/api/v1')" + if self.api in ('restful', 'class'): + return "app.register_blueprint(api.api_v1, url_prefix='/api/v1')" else: - return '' + # TODO register dynamic api name + return "app.register_blueprint(api.root, url_prefix='/api/v1')" diff --git a/hermetica/scaffold/docker.py b/hermetica/scaffold/docker.py index 7a01f8f..89f350d 100644 --- a/hermetica/scaffold/docker.py +++ b/hermetica/scaffold/docker.py @@ -44,19 +44,37 @@ def create_docker_compose_yml(self): FLASK_APP: "wsgi.py" FLASK_DEBUG: "1" command: "flask run --host=0.0.0.0" - links: - - redis - - db ports: - "5000:5000" + {links} {db} {redis} """.format( + links=self.create_links(), db=self.create_db(), redis=self.create_redis(), ) return dedent(source_code).strip() + def create_links(self): + if self.db and self.redis: + return """ + links: + - redis + - db + """.strip() + if self.db: + return """ + links: + - db + """.strip() + if self.redis: + return """ + links: + - redis + """.strip() + return '' + def create_db(self): source_code = '' if self.db == 'sqlalchemy': From 232c5b3f5bfeb619fa19106dcc1481e4e13d7df5 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 16:03:41 +0900 Subject: [PATCH 17/20] add easy usage --- README.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2457a87..2d471ce 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,79 @@ pip install hermetica # Usage -comming soon +``` +→ hermetica --help +Usage: hermetica [OPTIONS] COMMAND [ARGS]... + +Options: + --help Show this message and exit. + +Commands: + api create api + decorator create decorator + init initialize your flask app + model create model +``` + +initialize your flask project. + +``` +→ hermetica init --help +Usage: hermetica init [OPTIONS] + + initialize your flask app + +Options: + --api [restful|decorator|class] + Flask-Restful or Flask decorator or + methodview + --db [sqlalchemy|mongoengine] SQLAlchemy or Mongoengine or None + --decorator create decorator or None + --redis using Redis or None + --docker using container + --help Show this message and exit. +``` + +add api to your flask project. + +``` +→ hermetica api --help +Usage: hermetica api [OPTIONS] NAME + + create api + +Options: + --api [restful|decorator|class] + Flask-Restful or Flask decorator or + methodview + --version TEXT API version + --help Show this message and exit. +``` + +add model to your flask project. + +``` +→ hermetica model --help +Usage: hermetica model [OPTIONS] NAME + + create model + +Options: + --db [sqlalchemy|mongoengine] SQLAlchemy or Mongoengine or None + --help Show this message and exit. +``` + +add decorator to your flask project. + +``` +→ hermetica decorator --help +Usage: hermetica decorator [OPTIONS] NAME + + create decorator + +Options: + --help Show this message and exit. +``` # Development From abf855d9d882c24dc7f85b825e006e2d1af36a63 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 16:21:54 +0900 Subject: [PATCH 18/20] fix readme --- README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2d471ce..544947c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,14 @@ pip install hermetica # Usage +## Overview the usage + +hermetica has some subcommands, to create scaffold api, decorator, model. + +* api (url and routing method base or class base or flask-restful) +* model (database models, sqlalchemy or mongoengine) +* decorator (you can insert some code before enter the api, like a 'authentication') + ``` → hermetica --help Usage: hermetica [OPTIONS] COMMAND [ARGS]... @@ -66,7 +74,15 @@ Options: --help Show this message and exit. ``` -add api to your flask project. +After create project scaffold, you will check `Pipfile` contents, if there are shortages in list of packages, you can +add other packages into `Pipfile`, and lock your package. +(We recommend you to use `pipenv` https://github.com/pypa/pipenv) + +``` +pipenv lock +``` + +* add api to your flask project. ``` → hermetica api --help @@ -82,7 +98,7 @@ Options: --help Show this message and exit. ``` -add model to your flask project. +* add model to your flask project. ``` → hermetica model --help @@ -95,7 +111,7 @@ Options: --help Show this message and exit. ``` -add decorator to your flask project. +* add decorator to your flask project. ``` → hermetica decorator --help From 4798dbcf4413a745882d2ca1e9d6cb5fae3e1001 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 16:33:16 +0900 Subject: [PATCH 19/20] fix Readme --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 544947c..98430f7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ pip install hermetica # Usage -## Overview the usage +### Overview the usage hermetica has some subcommands, to create scaffold api, decorator, model. @@ -55,7 +55,7 @@ Commands: model create model ``` -initialize your flask project. +### initialize your flask project. ``` → hermetica init --help @@ -78,11 +78,18 @@ After create project scaffold, you will check `Pipfile` contents, if there are s add other packages into `Pipfile`, and lock your package. (We recommend you to use `pipenv` https://github.com/pypa/pipenv) +Hermetica support docker. you can see `Dockerfile` and `docker-compose.yml` at your root of project. +We recommend you to use docker-compose, it will helpful to separate from other projects. + ``` pipenv lock + +# if you set docker option, you can up the app container +docker-compose build +docker-compose up ``` -* add api to your flask project. +### add api to your flask project. ``` → hermetica api --help @@ -98,7 +105,7 @@ Options: --help Show this message and exit. ``` -* add model to your flask project. +### add model to your flask project. ``` → hermetica model --help @@ -111,7 +118,7 @@ Options: --help Show this message and exit. ``` -* add decorator to your flask project. +### add decorator to your flask project. ``` → hermetica decorator --help @@ -131,9 +138,9 @@ we will grad if you send PRs... # See Before Wiki (Flask Best Practices) Why we apply broken change? Because, before repo source code is slightly trivial, -and we believe this change will not cause negative impact to ohters. +and we believe this change will not cause any negative impact to others. To create scaffold tools for flask will cause good affect the world rather than remain trivial code. -But there are no warries. Flask-best-Practices contents (wiki docs) remain here (for japanese). +But there are no warries. Flask-best-Practices contents (wiki docs) remain here (but only for japanese). https://github.com/yoshiya0503/Hermetica/wiki From 1b55ec381624b666793742745f17e2e51897a1f4 Mon Sep 17 00:00:00 2001 From: yoshiya0503 Date: Tue, 29 May 2018 16:39:42 +0900 Subject: [PATCH 20/20] fix circleci config --- .circleci/config.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 68a2af5..35b58ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2 jobs: - build: + pypi: docker: - image: circleci/python:3.6.1 working_directory: ~/repo @@ -30,6 +30,16 @@ jobs: - run: name: run build command: | - . venv/bin/activate - python setup.py sdist bdist_wheel - twine upload --repository pypi dist/* + echo 'dry run' + #. venv/bin/activate + #python setup.py sdist bdist_wheel + #twine upload --repository pypi dist/* + +workflows: + version: 2 + release: + jobs: + - pypi: + filters: + branches: + only: master