Skip to content

Commit b0a90f6

Browse files
committed
Merge pull request #18 from iromli/sqla
SQLAlchemy stuff
2 parents 01b6e67 + bd7ab70 commit b0a90f6

32 files changed

Lines changed: 2661 additions & 440 deletions

README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,34 @@
11
User Map
22
========
33

4-
A simple flask application for creating user community maps.
4+
A simple application for creating user community maps powered by Flask.
55

6-
Hacking
7-
-------
6+
Installation
7+
------------
8+
9+
Prerequisites:
10+
11+
* Python 2.7.x
12+
* PostgreSQL or MySQL database
13+
* pip and (optional) virtualenv
14+
15+
Once you have all prerequisites, the following steps will bootstrap the project:
16+
17+
1. Grab the source code.
18+
19+
git clone git://github.com/id-python/members.git
20+
cd members
21+
22+
2. Activate your virtual environment (or you can skip this step if you're not using `virtualenv`).
23+
24+
3. Install all dependencies using `pip`:
25+
26+
pip install -r requirements.txt
27+
28+
Note: the installation process may take awhile.
29+
30+
Development
31+
-----------
832

933
By default, this app reads configuration from `users/default_config.py`.
1034
To override values set in default config, simply copy the contents
@@ -14,18 +38,44 @@ of `users/default_config.py` and save it elsewhere.
1438

1539
Then edit the custom config file, setting appropriate values as needed.
1640

17-
To run the development server:
41+
Things you might need to override:
42+
43+
* `SQLALCHEMY_DATABASE_URI`
44+
45+
Since this project tested using PostgreSQL and __likely__ will running on MySQL as well,
46+
there's extra package you need to install to before running the app.
47+
48+
* `pip install psycopg2` for PostgreSQL
49+
* `pip install mysql-python` for MySQL
50+
51+
Just pick one and please refer to http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html for details.
52+
53+
Ensure your database schema is up-to-date by invoking this command:
54+
55+
USERS_CONFIG=/path/to/custom/config.py python manage.py db upgrade
56+
57+
Running the app will be as simple as:
1858

1959
USERS_CONFIG=/path/to/custom/config.py python manage.py runserver
2060

2161
Each config item is overridable through environment variable.
2262
For instance, if you want to suppress email delivery, simply set appropriate value:
2363

24-
USERS_MAIL_SUPPRESS_SEND=True python manage.py runserver
64+
USERS_CONFIG=/path/to/custom/config.py USERS_MAIL_SUPPRESS_SEND=1 python manage.py runserver
65+
66+
Testing
67+
-------
68+
69+
NOTE: It is highly-recommended that you're creating a separate database and config file for testing.
2570

2671
To run testcases:
2772

28-
USERS_CONFIG=/path/to/custom/config.py USERS_MAIL_SUPPRESS_SEND=True ./runtests.sh
73+
USERS_CONFIG=/path/to/custom/testing_config.py ./runtests.sh
74+
75+
Each config item is overridable through environment variable.
76+
For instance, if you want to suppress email delivery, simply set appropriate value:
77+
78+
USERS_CONFIG=/path/to/custom/testing_config.py USERS_MAIL_SUPPRESS_SEND=1 ./runtests.sh
2979

3080
Collaboration
3181
-------------

manage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from flask.ext.script import Manager
2+
from flask.ext.migrate import MigrateCommand
23

34
from users import APP
45

56
if __name__ == "__main__":
67
manager = Manager(APP)
8+
manager.add_command("db", MigrateCommand)
79
manager.run()

requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
Flask==0.10.1
22
Flask-Mail==0.9.0
3+
Flask-Migrate==1.2.0
4+
Flask-SQLAlchemy==1.0
35
Flask-Script==2.0.5
6+
Flask-Testing==0.4.1
47
Jinja2==2.7.3
8+
Mako==1.0.0
59
MarkupSafe==0.23
10+
SQLAlchemy==0.9.6
611
Werkzeug==0.9.6
12+
alembic==0.6.5
713
blinker==1.3
14+
dictalchemy==0.1.2.6
815
flask-appconfig==0.9.1
916
itsdangerous==0.24
1017
nose==1.3.3

users/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from flask_mail import Mail
1212
from flask.ext.appconfig import AppConfig
1313

14+
from users.database import db, migrate
15+
1416

1517
def add_handler_once(logger, handler):
1618
"""A helper to add a handler to a logger, ensuring there are no duplicates.
@@ -77,6 +79,11 @@ def setup_logger():
7779
# backward-compat
7880
APP.config['DATABASE'] = APP.config['SQLITE_DB_PATH']
7981

82+
db.init_app(APP)
83+
84+
migration_dir = os.path.join(os.path.dirname(__file__), "migrations")
85+
migrate.init_app(APP, db, directory=migration_dir)
86+
8087
# Don't import actual view methods themselves - see:
8188
# http://flask.pocoo.org/docs/patterns/packages/#larger-applications
8289
# Also views must be imported AFTER app is created above.

users/database.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from dictalchemy import make_class_dictable
2+
from flask.ext.sqlalchemy import SQLAlchemy
3+
from flask.ext.migrate import Migrate
4+
5+
db = SQLAlchemy()
6+
7+
db.Model.metadata.naming_convention = {
8+
"ix": "ix_%(column_0_label)s",
9+
"uq": "uq_%(table_name)s_%(column_0_name)s",
10+
"ck": "ck_%(table_name)s_%(constraint_name)s",
11+
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
12+
"pk": "pk_%(table_name)s",
13+
}
14+
make_class_dictable(db.Model)
15+
16+
migrate = Migrate()

users/default_config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,9 @@
4747
user='/static/img/marker.png',
4848
shadow='/static/img/marker-shadow.png'
4949
)
50+
51+
# By default it uses postgres, hence you'll need to ``psycopg2``.
52+
# If you're using MySQL, refer to
53+
# http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html
54+
# for details.
55+
SQLALCHEMY_DATABASE_URI = "postgresql://scott:tiger@localhost:5432/mydatabase"

users/migrations/alembic.ini

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# template used to generate migration files
5+
# file_template = %%(rev)s_%%(slug)s
6+
7+
# set to 'true' to run the environment during
8+
# the 'revision' command, regardless of autogenerate
9+
# revision_environment = false
10+
11+
12+
# Logging configuration
13+
[loggers]
14+
keys = root,sqlalchemy,alembic
15+
16+
[handlers]
17+
keys = console
18+
19+
[formatters]
20+
keys = generic
21+
22+
[logger_root]
23+
level = WARN
24+
handlers = console
25+
qualname =
26+
27+
[logger_sqlalchemy]
28+
level = WARN
29+
handlers =
30+
qualname = sqlalchemy.engine
31+
32+
[logger_alembic]
33+
level = INFO
34+
handlers =
35+
qualname = alembic
36+
37+
[handler_console]
38+
class = StreamHandler
39+
args = (sys.stderr,)
40+
level = NOTSET
41+
formatter = generic
42+
43+
[formatter_generic]
44+
format = %(levelname)-5.5s [%(name)s] %(message)s
45+
datefmt = %H:%M:%S

users/migrations/env.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import with_statement
2+
from alembic import context
3+
from sqlalchemy import engine_from_config, pool
4+
from logging.config import fileConfig
5+
6+
# this is the Alembic Config object, which provides
7+
# access to the values within the .ini file in use.
8+
config = context.config
9+
10+
# Interpret the config file for Python logging.
11+
# This line sets up loggers basically.
12+
fileConfig(config.config_file_name)
13+
14+
# add your model's MetaData object here
15+
# for 'autogenerate' support
16+
# from myapp import mymodel
17+
# target_metadata = mymodel.Base.metadata
18+
from flask import current_app
19+
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
20+
target_metadata = current_app.extensions['migrate'].db.metadata
21+
22+
# other values from the config, defined by the needs of env.py,
23+
# can be acquired:
24+
# my_important_option = config.get_main_option("my_important_option")
25+
# ... etc.
26+
27+
def run_migrations_offline():
28+
"""Run migrations in 'offline' mode.
29+
30+
This configures the context with just a URL
31+
and not an Engine, though an Engine is acceptable
32+
here as well. By skipping the Engine creation
33+
we don't even need a DBAPI to be available.
34+
35+
Calls to context.execute() here emit the given string to the
36+
script output.
37+
38+
"""
39+
url = config.get_main_option("sqlalchemy.url")
40+
context.configure(url=url)
41+
42+
with context.begin_transaction():
43+
context.run_migrations()
44+
45+
def run_migrations_online():
46+
"""Run migrations in 'online' mode.
47+
48+
In this scenario we need to create an Engine
49+
and associate a connection with the context.
50+
51+
"""
52+
engine = engine_from_config(
53+
config.get_section(config.config_ini_section),
54+
prefix='sqlalchemy.',
55+
poolclass=pool.NullPool)
56+
57+
connection = engine.connect()
58+
context.configure(
59+
connection=connection,
60+
target_metadata=target_metadata
61+
)
62+
63+
try:
64+
with context.begin_transaction():
65+
context.run_migrations()
66+
finally:
67+
connection.close()
68+
69+
if context.is_offline_mode():
70+
run_migrations_offline()
71+
else:
72+
run_migrations_online()
73+

users/migrations/script.py.mako

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision}
5+
Create Date: ${create_date}
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = ${repr(up_revision)}
11+
down_revision = ${repr(down_revision)}
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
${imports if imports else ""}
16+
17+
def upgrade():
18+
${upgrades if upgrades else "pass"}
19+
20+
21+
def downgrade():
22+
${downgrades if downgrades else "pass"}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""create users table
2+
3+
Revision ID: 15e14062732d
4+
Revises: None
5+
Create Date: 2014-07-05 21:40:46.378774
6+
7+
"""
8+
# revision identifiers, used by Alembic.
9+
revision = '15e14062732d'
10+
down_revision = None
11+
12+
from alembic import op
13+
import sqlalchemy as sa
14+
15+
16+
def upgrade():
17+
op.create_table(
18+
"users",
19+
sa.Column("id", sa.Integer, primary_key=True),
20+
sa.Column("guid", sa.String(37), nullable=False, unique=True),
21+
sa.Column("name", sa.String(32), nullable=False),
22+
sa.Column("email", sa.String(254), nullable=False),
23+
sa.Column("website", sa.String(255), default=""),
24+
sa.Column("email_updates", sa.Boolean(name="email_updates"),
25+
default=False),
26+
sa.Column("date_added", sa.DateTime,
27+
server_default=sa.text("CURRENT_TIMESTAMP")),
28+
sa.Column("latitude", sa.Float),
29+
sa.Column("longitude", sa.Float),
30+
)
31+
32+
33+
def downgrade():
34+
op.drop_table("users")

0 commit comments

Comments
 (0)