diff --git a/requirements.txt b/requirements.txt index b9c1bcc..86b5c69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -PyYAML>=3.11 -SQLAlchemy>=0.9.6 -humanize>=0.5.1 -schema>=0.3.1 -psutil>=2.1.1 -click>=3.1 -SQLAlchemy-Utils>=0.26.11 +PyYAML>=5.3.1 +SQLAlchemy>=1.3.22 +humanize>=2.6.0 +schema>=0.7.2 +psutil>=5.8.0 +click>=7.1.2 +SQLAlchemy-Utils>=0.36.8 diff --git a/setup.py b/setup.py index 31a1ac2..1190542 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,6 @@ from setuptools import setup, find_packages -# https://bitbucket.org/zzzeek/alembic/raw/f38eaad4a80d7e3d893c3044162971971ae0 -# 09bf/setup.py with open( os.path.join(os.path.dirname(__file__), 'stellar', 'app.py') ) as app_file: @@ -48,12 +46,14 @@ 'Topic :: Software Development :: Version Control', ], install_requires = [ - 'PyYAML>=3.11', - 'SQLAlchemy>=0.9.6', - 'humanize>=0.5.1', - 'schema>=0.3.1', - 'click>=3.1', - 'SQLAlchemy-Utils>=0.26.11', - 'psutil>=2.1.1', + 'PyYAML>=5.3.1', + 'SQLAlchemy>=1.3.22', + 'humanize>=2.6.0', + 'schema>=0.7.2', + 'click>=7.1.2', + 'SQLAlchemy-Utils>=0.36.8', + 'psutil>=5.8.0', + 'wheel>=0.36.2', + 'psycopg2-binary>=2.8.6' ] ) diff --git a/stellar/app.py b/stellar/app.py index 97406b2..35c832f 100644 --- a/stellar/app.py +++ b/stellar/app.py @@ -21,7 +21,7 @@ from psutil import pid_exists -__version__ = '0.4.5' +__version__ = '0.5.0' logger = logging.getLogger(__name__) @@ -49,7 +49,7 @@ def load_config(self): logging.basicConfig(level=self.config['logging']) def init_database(self): - self.raw_db = create_engine(self.config['url'], echo=False) + self.raw_db = create_engine(self.config['url']) self.raw_conn = self.raw_db.connect() self.operations = Operations(self.raw_conn, self.config) @@ -58,11 +58,11 @@ def init_database(self): except AttributeError: logger.info('Could not set isolation level to 0') - self.db = create_engine(self.config['stellar_url'], echo=False) + self.db = create_engine(self.config['stellar_url']) self.db.session = sessionmaker(bind=self.db)() self.raw_db.session = sessionmaker(bind=self.raw_db)() - tables_missing = self.create_stellar_database() - + + self.create_stellar_database() self.create_stellar_tables() # logger.getLogger('sqlalchemy.engine').setLevel(logger.WARN) diff --git a/stellar/command.py b/stellar/command.py index b6764d9..5e4d4cb 100644 --- a/stellar/command.py +++ b/stellar/command.py @@ -1,3 +1,4 @@ +import textwrap import sys from datetime import datetime from time import sleep @@ -6,31 +7,19 @@ import click import logging from sqlalchemy import create_engine -from sqlalchemy.exc import OperationalError +from sqlalchemy.exc import ArgumentError, OperationalError from .app import Stellar, __version__ from .config import InvalidConfig, MissingConfig, load_config, save_config from .operations import database_exists, list_of_databases, SUPPORTED_DIALECTS -def upgrade_from_old_version(app): - if app.config['migrate_from_0_3_2']: - if app.is_old_database(): - click.echo('Upgrading from old Stellar version...') - def after_rename(old_name, new_name): - click.echo('* Renamed %s to %s' % (old_name, new_name)) - app.update_database_names_to_new_version(after_rename=after_rename) - - app.config['migrate_from_0_3_2'] = False - save_config(app.config) - def get_app(): app = Stellar() - upgrade_from_old_version(app) return app -@click.group() +@click.group(context_settings={"help_option_names": ["-h", "--help"]}) def stellar(): """Fast database snapshots for development. It's like Git for databases.""" pass @@ -49,7 +38,6 @@ def after_delete(database): click.echo("Deleted table %s" % database) app = get_app() - upgrade_from_old_version(app) app.delete_orphan_snapshots(after_delete) @@ -58,7 +46,6 @@ def after_delete(database): def snapshot(name): """Takes a snapshot of the database""" app = get_app() - upgrade_from_old_version(app) name = name or app.default_snapshot_name if app.get_snapshot(name): @@ -183,37 +170,56 @@ def replace(name): @stellar.command() -def init(): +@click.argument('url', required=False) +@click.argument('project', required=False) +def init(url, project): """Initializes Stellar configuration.""" + + def prompt_url(): + msg = textwrap.dedent("""\ + Please enter the url for your database. + + For example: + PostgreSQL: postgresql://localhost:5432/ + MySQL: mysql+pymysql://root@localhost/ + """) + return click.prompt(msg) + while True: - url = click.prompt( - "Please enter the url for your database.\n\n" - "For example:\n" - "PostgreSQL: postgresql://localhost:5432/\n" - "MySQL: mysql+pymysql://root@localhost/" - ) + if not url: + url = prompt_url() + if url.count('/') == 2 and not url.endswith('/'): url = url + '/' - if ( - url.count('/') == 3 and - url.endswith('/') and - url.startswith('postgresql://') - ): - connection_url = url + 'template1' - else: - connection_url = url + # if ( + # url.count('/') == 3 and + # url.endswith('/') and + # url.startswith('postgresql://') + # ): + # connection_url = url + 'template1' + # else: + # connection_url = url + connection_url = url + + try: + engine = create_engine(connection_url, echo=False) + except ArgumentError as err: + click.echo("Error: %s" % err) + url = None + continue - engine = create_engine(connection_url, echo=False) try: conn = engine.connect() except OperationalError as err: click.echo("Could not connect to database: %s" % url) - click.echo("Error message: %s" % err.message) + click.echo("Error message: %s" % err) click.echo('') else: break + url = None + if engine.dialect.name not in SUPPORTED_DIALECTS: click.echo("Your engine dialect %s is not supported." % ( engine.dialect.name @@ -241,31 +247,26 @@ def init(): db_name = url.rsplit('/', 1)[-1] url = url.rsplit('/', 1)[0] + '/' - name = click.prompt( - 'Please enter your project name (used internally, eg. %s)' % db_name, - default=db_name - ) + if project is None: + project = click.prompt( + 'Please enter project name (used internally, eg. %s)' % db_name, + default=db_name + ) raw_url = url if engine.dialect.name == 'postgresql': - raw_url = raw_url + 'template1' + raw_url = raw_url + db_name with open('stellar.yaml', 'w') as project_file: project_file.write( - """ -project_name: '%(name)s' -tracked_databases: ['%(db_name)s'] -url: '%(raw_url)s' -stellar_url: '%(url)sstellar_data' - """.strip() % - { - 'name': name, - 'raw_url': raw_url, - 'url': url, - 'db_name': db_name - } - ) + textwrap.dedent("""\ + project_name: {name} + tracked_databases: ['{db_name}'] + url: '{raw_url}' + stellar_url: '{url}stellar_data' + """) + .format(name=project, db_name=db_name, raw_url=raw_url, url=url)) click.echo("Wrote stellar.yaml") click.echo('') @@ -286,7 +287,7 @@ def main(): sys.exit(1) except ImportError as e: libraries = { - 'psycopg2': 'PostreSQL', + 'psycopg2': 'PostgreSQL', 'pymysql': 'MySQL', } for library, name in libraries.items(): diff --git a/stellar/config.py b/stellar/config.py index 98a358d..271a717 100644 --- a/stellar/config.py +++ b/stellar/config.py @@ -13,16 +13,14 @@ class MissingConfig(Exception): default_config = { - 'logging': 30, - 'migrate_from_0_3_2': True + 'logging': 30 } schema = Schema({ 'stellar_url': Use(str), 'url': Use(str), 'project_name': Use(str), 'tracked_databases': [Use(str)], - Optional('logging'): int, - Optional('migrate_from_0_3_2'): bool + Optional('logging'): int })