From c0bf4025287ae93a6a8821aa977c5a5bb887546b Mon Sep 17 00:00:00 2001 From: Theodore Chiang Date: Sat, 28 Nov 2015 16:50:17 +0800 Subject: [PATCH 1/9] Fixes relationship usages in document posts table creation class name error. to define foreign key user_id, the unsigned argument of table.integer('user_id') should be True --- docs/basic_usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/basic_usage.rst b/docs/basic_usage.rst index 3dfa4d0..e5f9002 100644 --- a/docs/basic_usage.rst +++ b/docs/basic_usage.rst @@ -179,7 +179,7 @@ And we modify the generated file to look like this: from orator.migrations import Migration - class CreateTableUsers(Migration): + class CreatePostsTable(Migration): def up(self): """ @@ -189,7 +189,7 @@ And we modify the generated file to look like this: table.increments('id') table.string('title') table.text('content') - table.integer('user_id') + table.integer('user_id', unsigned=True) table.timestamps() table.foreign('user_id').references('id').on('users') From 98e857fe2b2feba2a555a957824bc26cc74179f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Fri, 27 Nov 2015 23:29:18 -0500 Subject: [PATCH 2/9] Bumps version to 0.1.1 --- CHANGELOG.md | 2 +- docs/conf.py | 2 +- flask_orator/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afeccef..add0901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ### 0.1.1 - +(November 28th, 2015) ##### Fixes diff --git a/docs/conf.py b/docs/conf.py index d4a153d..fa30270 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,7 +58,7 @@ # The short X.Y version. version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.1' +release = '0.1.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/flask_orator/version.py b/flask_orator/version.py index e3a714f..c16d885 100644 --- a/flask_orator/version.py +++ b/flask_orator/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -VERSION = '0.1' +VERSION = '0.1.1' From e3f885572602411bb4c1e6a80778e3d6edab324a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Sat, 23 Jan 2016 15:29:20 -0500 Subject: [PATCH 3/9] Preparing for the next Orator version --- flask_orator/__init__.py | 39 +++++++++++++++++++-------------------- setup.py | 4 +--- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/flask_orator/__init__.py b/flask_orator/__init__.py index 828daa1..fd818f3 100644 --- a/flask_orator/__init__.py +++ b/flask_orator/__init__.py @@ -3,12 +3,8 @@ from flask import current_app, request, jsonify as base_jsonify, Response from orator import DatabaseManager, Model as BaseModel from orator.pagination import Paginator -from orator.commands.migrations import ( - InstallCommand, MigrateCommand, - MigrateMakeCommand, RollbackCommand, - StatusCommand, ResetCommand -) from orator.commands.application import application as orator_application +from orator.commands.command import Command from cleo import Application @@ -20,10 +16,11 @@ class Orator(object): - def __init__(self, app=None): + def __init__(self, app=None, manager_class=DatabaseManager): self.Model = BaseModel self.cli = None self._db = None + self._manager_class = manager_class if app is not None: self.init_app(app) @@ -39,29 +36,31 @@ def init_app(self, app): self._config = app.config['ORATOR_DATABASES'] # Initializing database manager - self._db = DatabaseManager(self._config) + self._db = self._manager_class(self._config) self.Model.set_connection_resolver(self._db) # Setting current page resolver - def current_page_resolver(): - return int(request.args.get('page', 1)) - - Paginator.current_page_resolver(current_page_resolver) + Paginator.current_page_resolver(self._current_page_resolver) # Setting commands self.init_commands() + def _current_page_resolver(self): + return int(request.args.get('page', 1)) + def init_commands(self): - self.cli = Application(orator_application.get_name(), - orator_application.get_version()) - - self.cli.add(InstallCommand(self)) - self.cli.add(MigrateCommand(self)) - self.cli.add(MigrateMakeCommand(self)) - self.cli.add(RollbackCommand(self)) - self.cli.add(StatusCommand(self)) - self.cli.add(ResetCommand(self)) + self.cli = Application( + orator_application.get_name(), + orator_application.get_version(), + complete=True + ) + + for command in orator_application.all().values(): + if isinstance(command, Command): + self.cli.add(command.__class__(self._db)) + else: + self.cli.add(command) def register_handlers(self, app): teardown = app.teardown_appcontext diff --git a/setup.py b/setup.py index e261b7f..21fd42f 100644 --- a/setup.py +++ b/setup.py @@ -32,11 +32,9 @@ def get_version(): packages=['flask_orator'], install_requires=[ 'Flask>=0.10', - 'orator', - 'cleo' + 'orator' ], tests_require=['pytest', 'mock'], - test_suite='nose.collector', classifiers=[ 'Intended Audience :: Developers', 'Operating System :: OS Independent', From b1e8479a4d95c3ca9255dc5e37c8ee142c1d8d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Sun, 24 Jan 2016 21:17:19 -0500 Subject: [PATCH 4/9] Automatically returns a 404 error when ModelNotFound is raised --- flask_orator/__init__.py | 12 +++++++++++- tests/test_orator.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/flask_orator/__init__.py b/flask_orator/__init__.py index fd818f3..8b6f997 100644 --- a/flask_orator/__init__.py +++ b/flask_orator/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -from flask import current_app, request, jsonify as base_jsonify, Response +from flask import current_app, request, jsonify as base_jsonify, Response, make_response from orator import DatabaseManager, Model as BaseModel from orator.pagination import Paginator from orator.commands.application import application as orator_application from orator.commands.command import Command +from orator.exceptions.orm import ModelNotFound from cleo import Application @@ -63,12 +64,21 @@ def init_commands(self): self.cli.add(command) def register_handlers(self, app): + self._register_error_handlers(app) + teardown = app.teardown_appcontext @teardown def disconnect(_): return self._db.disconnect() + def _register_error_handlers(self, app): + @app.errorhandler(ModelNotFound) + def model_not_found(error): + response = make_response(error.message, 404) + + return response + def __getattr__(self, item): return getattr(self._db, item) diff --git a/tests/test_orator.py b/tests/test_orator.py index 2040d3d..66a90e0 100644 --- a/tests/test_orator.py +++ b/tests/test_orator.py @@ -47,6 +47,10 @@ def create(): return jsonify(user) + @app.route('/users/', methods=['GET']) + def show(user_id): + return jsonify(self.User.find_or_fail(user_id)) + self.init_tables() def tearDown(self): @@ -98,6 +102,12 @@ def assertRaisesRegex(self, expected_exception, expected_regex, callable_obj, *args, **kwargs ) + def assertRegex(self, *args, **kwargs): + if PY2: + return self.assertRegexpMatches(*args, **kwargs) + else: + return super(FlaskOratorTestCase, self).assertRegex(*args, **kwargs) + class BasicAppTestCase(FlaskOratorTestCase): @@ -119,6 +129,14 @@ def test_basic_insert(self): self.assertEqual('foo', users[0]['name']) self.assertEqual('bar', users[1]['name']) + def test_model_not_found_returns_404(self): + c = self.app.test_client() + + response = self.get(c, '/users/9999') + + self.assertEqual(404, response.status_code) + self.assertRegex(str(response.data), 'No query results found for model \[User\]') + class PaginatorTestCase(FlaskOratorTestCase): From e8c6a67ccc2facd6b41464c6843360e0b3ae7194 Mon Sep 17 00:00:00 2001 From: Ana Paula Gomes Date: Sat, 23 Jul 2016 03:09:19 -0300 Subject: [PATCH 5/9] Fix the commands to make and run migrations (#2) * Fix the command to make:migration available in 0.8 * Update basic_usage.rst * Update command to run migrations --- docs/basic_usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/basic_usage.rst b/docs/basic_usage.rst index e5f9002..53cc7db 100644 --- a/docs/basic_usage.rst +++ b/docs/basic_usage.rst @@ -65,7 +65,7 @@ You first need to make a migration file to create the table: .. code-block:: text - python db.py migrations:make create_users_table --table users --create + python db.py make:migration create_users_table --table users --create This will add a file in the ``migrations`` folder named ``create_users_table`` and prefixed by a timestamp: @@ -105,7 +105,7 @@ Then, you can run the migration: .. code-block:: text - python db.py migrations:run + python db.py migrate Confirm and you database and table will be created. @@ -170,7 +170,7 @@ and set up the relationship at database level: .. code-block:: text - python db.py migrations:make create_posts_table --table posts --create + python db.py make:migration create_posts_table --table posts --create And we modify the generated file to look like this: @@ -204,7 +204,7 @@ Finally we run it: .. code-block:: text - python db.py migrations:run + python db.py migrate We can now instantiate some posts: From 24f0411d6fe0e0e725e6cafac0ae365d4289700c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Thu, 6 Oct 2016 10:14:26 -0500 Subject: [PATCH 6/9] Adds support for directly returning Orator objects. --- MANIFEST.in | 7 ++++++- flask_orator/__init__.py | 13 +++++-------- setup.py | 14 +++++++------- tests/test_orator.py | 5 ++--- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 0c73842..d737b75 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,6 @@ -include README.rst LICENSE +include README.rst LICENSE requirements.txt +recursive-exclude tests * +recursive-exclude benchmark * +recursive-exclude seeders * +recursive-exclude seeds * +recursive-exclude migrations * diff --git a/flask_orator/__init__.py b/flask_orator/__init__.py index 8b6f997..9753d8f 100644 --- a/flask_orator/__init__.py +++ b/flask_orator/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from flask import current_app, request, jsonify as base_jsonify, Response, make_response +from flask import current_app, request, jsonify as base_jsonify, make_response from orator import DatabaseManager, Model as BaseModel from orator.pagination import Paginator from orator.commands.application import application as orator_application @@ -90,13 +90,10 @@ def jsonify(obj, **kwargs): indent = 2 if hasattr(obj, 'to_json'): - response = Response(obj.to_json(indent=indent), - mimetype='application/json', - **kwargs) - elif isinstance(obj, list): - response = Response(json.dumps(obj, indent=indent), - mimetype='application/json', - **kwargs) + response = current_app.response_class( + obj.to_json(indent=indent), + mimetype='application/json' + ) else: response = base_jsonify(obj, **kwargs) diff --git a/setup.py b/setup.py index 21fd42f..9cb2396 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,10 @@ import os from setuptools import setup, find_packages +here = os.path.dirname(__file__) def get_version(): - basedir = os.path.dirname(__file__) - with open(os.path.join(basedir, 'flask_orator/version.py')) as f: + with open(os.path.join(here, 'flask_orator/version.py')) as f: variables = {} exec(f.read(), variables) @@ -19,6 +19,9 @@ def get_version(): __version__ = get_version() +with open(os.path.join(here, 'requirements.txt')) as f: + requirements = f.readlines() + setup( name='flask-orator', license='MIT', @@ -29,11 +32,8 @@ def get_version(): author_email='sebastien.eustace@gmail.com', url='https://github.com/sdispater/flask-orator', download_url='https://github.com/sdispater/flask-orator/archive/%s.tar.gz' % __version__, - packages=['flask_orator'], - install_requires=[ - 'Flask>=0.10', - 'orator' - ], + packages=find_packages(), + install_requires=requirements, tests_require=['pytest', 'mock'], classifiers=[ 'Intended Audience :: Developers', diff --git a/tests/test_orator.py b/tests/test_orator.py index 66a90e0..bcaf588 100644 --- a/tests/test_orator.py +++ b/tests/test_orator.py @@ -49,7 +49,7 @@ def create(): @app.route('/users/', methods=['GET']) def show(user_id): - return jsonify(self.User.find_or_fail(user_id)) + return self.User.find_or_fail(user_id) self.init_tables() @@ -205,8 +205,7 @@ def test_behaves_like_manager(self): def users(): try: users = jsonify(Collection(self.db.table('users').get()).map(lambda x: dict(x.items()))) - except Exception as e: - print(e) + except Exception: raise return users From 4b14a015e0b1bfc3ac97126d052d431d1c5653cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Thu, 6 Oct 2016 10:15:10 -0500 Subject: [PATCH 7/9] Updates gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7e62b55..049681c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ nosetests.xml .DS_Store .idea/* +.cache/* test.py app.py From d538c67d4ee10f1b2d8ddf0984bf7456c0cc4544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Thu, 6 Oct 2016 10:31:57 -0500 Subject: [PATCH 8/9] Updates documentation --- docs/_static/theme_overrides.css | 36 ++++++++++++++++++++++++++++ docs/basic_usage.rst | 40 +++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css index 934b515..f0567a3 100644 --- a/docs/_static/theme_overrides.css +++ b/docs/_static/theme_overrides.css @@ -955,3 +955,39 @@ tt.code .punctuation, code.code .punctuation { color: #878787 } tt.code .string, code.code .string { color: #59B6CF } tt.code .number, code.code .number { color: #E9767F } tt.code .integer, code.code .integer { color: #E9767F } + + +/* Dark Orator */ + +div[class^="highlight"] { + border: 0; + background: #152B39; + border-radius: 3px; +} + +div[class^="highlight"] > pre { + border: 0; + color: #98AEC5; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + padding: 4% 4%; + background: none; +} + +.highlight .nn, .highlight .nc { color: #89C6BF; } +.highlight .c, .highlight .c1 { color: #727995; font-style: normal; } +.highlight .sd { color: #727995; font-style: normal; } +.highlight .p, .highlight .o, code.code .operator { color: #687E95 } +.highlight .n, code.code .name { color: #98AEC5 } +.highlight .l { color: #98AEC5; } + +.note .last pre, +.warning .last pre, +.tip .last pre, +.caution .last pre, +.versionchanged .last pre, +.versionadded .last pre { + padding-bottom: 4%; + margin-bottom: 0; +} diff --git a/docs/basic_usage.rst b/docs/basic_usage.rst index 53cc7db..e0141e9 100644 --- a/docs/basic_usage.rst +++ b/docs/basic_usage.rst @@ -107,7 +107,7 @@ Then, you can run the migration: python db.py migrate -Confirm and you database and table will be created. +Confirm and you database and the table will be created. Once your database set up, you can create some users: @@ -127,6 +127,18 @@ initiate them and save them later: # Do something else... admin.save() +.. note:: + + Optionally you can use a transaction. + + .. code-block:: python + + from your_application import db, User + + with db.transaction(): + admin = User.create(name='admin', email='admin@example.com') + guest = Guest.create(name='guest', email='guest@example.com') + You can now retrieve them easily from the database: .. code-block:: python @@ -145,25 +157,30 @@ Let's create a ``Post`` model with the ``User`` model as a parent: .. code-block:: python + from orator.orm import belongs_to + class Post(db.Model): __fillable__ = ['title', 'content'] - @property + @belongs_to def user(self): - return self.belongs_to('users') + return User And we add the ``posts`` relationship to the ``User`` model: .. code-block:: python + from orator.orm import has_many + + class User(db.Model): - @property + @has_many def posts(self): - return self.has_many('posts') + return Post Before we can play with these models we need to create the ``posts`` table and set up the relationship at database level: @@ -226,6 +243,17 @@ and associate them with users: # Associate from post.user relation guest_post.user().associate(guest) +.. note:: + + You can also create the posts directly. + + .. code-block:: python + + admin.posts().create( + title='Admin Post', + description='This is a restricted post' + ) + Relationships properties are `dynamic properties `_ meaning that ``user.posts`` is the underlying collection of posts so we can do things like: @@ -277,7 +305,7 @@ or by changing the default ``Paginator`` current page resolver: What's more? ============ -Like said in introduction Flask-Orator is a wrapper around `Orator `_ to integrate it +Like said in the introduction Flask-Orator is a wrapper around `Orator `_ to integrate it more easily with Flask applications. So, basically, everything you can do with Orator is also possible with Flask-Orator. From f1d6e2a7a855928a458c69efc36c7a77451c80b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Eustace?= Date: Thu, 6 Oct 2016 10:35:15 -0500 Subject: [PATCH 9/9] Updates documentation --- docs/cli.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 995aecd..db93d1d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -20,11 +20,11 @@ Migrations Creating Migrations ------------------- -To create a migration, you can use the ``migrations:make`` command on the CLI: +To create a migration, you can use the ``make:migration`` command on the CLI: .. code-block:: bash - python db.py migrations:make create_users_table + python db.py make:migration create_users_table This will create a migration file that looks like this: @@ -55,16 +55,16 @@ If you want the migrations to be stored in another folder, use the ``--path/-p`` .. code-block:: bash - python db.py migrations:make create_users_table -p my/path/to/migrations + python db.py make:migration create_users_table -p my/path/to/migrations The ``--table`` and ``--create`` options can also be used to indicate the name of the table, and whether the migration will be creating a new table: .. code-block:: bash - python db.py migrations:make add_votes_to_users_table --table=users + python db.py make:migration add_votes_to_users_table --table=users - python db.py migrations:make create_users_table --table=users --create + python db.py make:migration create_users_table --table=users --create These commands would respectively create the following migrations: @@ -114,11 +114,11 @@ These commands would respectively create the following migrations: Running Migrations ------------------ -To run all outstanding migrations, just use the ``migrations:run`` command: +To run all outstanding migrations, just use the ``migrate`` command: .. code-block:: bash - python db.py migrations:run + python db.py migrate Rolling back migrations @@ -129,14 +129,14 @@ Rollback the last migration operation .. code-block:: bash - python db.py migrations:rollback + python db.py migrate:rollback Rollback all migrations ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash - python db.py migrations:reset + python db.py migrate:reset Getting migrations status @@ -146,7 +146,7 @@ To see the status of the migrations, just use the ``migrations:status`` command: .. code-block:: bash - python db.py migrations:status + python db.py migrate:status This would output something like this: