Add rattail_shopfoo project generator
				
					
				
			also *remove* the `db/alembic/env.py` script from rattail-adjacent generator. it didn't seem necessary..now we'll see if it ever is
This commit is contained in:
		
							parent
							
								
									9834e1276d
								
							
						
					
					
						commit
						4c331e3875
					
				
					 8 changed files with 176 additions and 96 deletions
				
			
		| 
						 | 
				
			
			@ -30,12 +30,11 @@ import re
 | 
			
		|||
import shutil
 | 
			
		||||
import string
 | 
			
		||||
import sys
 | 
			
		||||
import unicodedata
 | 
			
		||||
 | 
			
		||||
import colander
 | 
			
		||||
from mako.template import Template
 | 
			
		||||
 | 
			
		||||
from rattail.util import get_class_hierarchy
 | 
			
		||||
from rattail.util import get_class_hierarchy, get_studly_prefix
 | 
			
		||||
from rattail.mako import ResourceTemplateLookup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -276,13 +275,6 @@ class PythonProjectGenerator(ProjectGenerator):
 | 
			
		|||
 | 
			
		||||
        return schema
 | 
			
		||||
 | 
			
		||||
    def get_studly_prefix(self, name):
 | 
			
		||||
        # cf. https://stackoverflow.com/a/3194567
 | 
			
		||||
        name = unicodedata.normalize('NFD', name)
 | 
			
		||||
        name = name.encode('ascii', 'ignore').decode('ascii')
 | 
			
		||||
        words = re.split(r'[\- ]', name)
 | 
			
		||||
        return ''.join([word.capitalize() for word in words])
 | 
			
		||||
 | 
			
		||||
    def normalize_context(self, context):
 | 
			
		||||
        context = super(PythonProjectGenerator, self).normalize_context(context)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +288,7 @@ class PythonProjectGenerator(ProjectGenerator):
 | 
			
		|||
            context['egg_name'] = context['pypi_name'].replace('-', '_')
 | 
			
		||||
 | 
			
		||||
        if 'studly_prefix' not in context:
 | 
			
		||||
            context['studly_prefix'] = self.get_studly_prefix(context['name'])
 | 
			
		||||
            context['studly_prefix'] = get_studly_prefix(context['name'])
 | 
			
		||||
 | 
			
		||||
        if 'env_name' not in context:
 | 
			
		||||
            context['env_name'] = context['folder']
 | 
			
		||||
| 
						 | 
				
			
			@ -478,14 +470,12 @@ class RattailAdjacentProjectGenerator(PythonProjectGenerator):
 | 
			
		|||
            # model
 | 
			
		||||
            ####################
 | 
			
		||||
 | 
			
		||||
            if context['has_model']:
 | 
			
		||||
            model = os.path.join(db, 'model')
 | 
			
		||||
            os.makedirs(model)
 | 
			
		||||
 | 
			
		||||
                model = os.path.join(db, 'model')
 | 
			
		||||
                os.makedirs(model)
 | 
			
		||||
 | 
			
		||||
                self.generate('package/db/model/__init__.py.mako',
 | 
			
		||||
                              os.path.join(model, '__init__.py'),
 | 
			
		||||
                              context)
 | 
			
		||||
            self.generate('package/db/model/__init__.py.mako',
 | 
			
		||||
                          os.path.join(model, '__init__.py'),
 | 
			
		||||
                          context)
 | 
			
		||||
 | 
			
		||||
            ####################
 | 
			
		||||
            # alembic
 | 
			
		||||
| 
						 | 
				
			
			@ -496,11 +486,6 @@ class RattailAdjacentProjectGenerator(PythonProjectGenerator):
 | 
			
		|||
                alembic = os.path.join(db, 'alembic')
 | 
			
		||||
                os.makedirs(alembic)
 | 
			
		||||
 | 
			
		||||
                # TODO: can we get rid of this? why not?
 | 
			
		||||
                self.generate('package/db/alembic/env.py.mako',
 | 
			
		||||
                              os.path.join(alembic, 'env.py'),
 | 
			
		||||
                              context)
 | 
			
		||||
 | 
			
		||||
                versions = os.path.join(alembic, 'versions')
 | 
			
		||||
                os.makedirs(versions)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,74 +0,0 @@
 | 
			
		|||
# -*- coding: utf-8; mode: python; -*-
 | 
			
		||||
"""
 | 
			
		||||
Alembic environment script
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from alembic import context
 | 
			
		||||
from sqlalchemy.orm import configure_mappers
 | 
			
		||||
 | 
			
		||||
from rattail.config import make_config
 | 
			
		||||
from rattail.db.util import get_default_engine
 | 
			
		||||
from rattail.db.continuum import configure_versioning
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# this is the Alembic Config object, which provides
 | 
			
		||||
# access to the values within the .ini file in use.
 | 
			
		||||
alembic_config = context.config
 | 
			
		||||
 | 
			
		||||
# use same config file for Rattail
 | 
			
		||||
rattail_config = make_config(alembic_config.config_file_name, usedb=False, versioning=False)
 | 
			
		||||
 | 
			
		||||
# configure Continuum..this is trickier than we want but it works..
 | 
			
		||||
configure_versioning(rattail_config, force=True)
 | 
			
		||||
from ${pkg_name}.db import model
 | 
			
		||||
configure_mappers()
 | 
			
		||||
 | 
			
		||||
# needed for 'autogenerate' support
 | 
			
		||||
target_metadata = model.Base.metadata
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_migrations_offline():
 | 
			
		||||
    """Run migrations in 'offline' mode.
 | 
			
		||||
 | 
			
		||||
    This configures the context with just a URL
 | 
			
		||||
    and not an Engine, though an Engine is acceptable
 | 
			
		||||
    here as well.  By skipping the Engine creation
 | 
			
		||||
    we don't even need a DBAPI to be available.
 | 
			
		||||
 | 
			
		||||
    Calls to context.execute() here emit the given string to the
 | 
			
		||||
    script output.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    engine = get_default_engine(rattail_config)
 | 
			
		||||
    context.configure(
 | 
			
		||||
        url=engine.url,
 | 
			
		||||
        target_metadata=target_metadata)
 | 
			
		||||
 | 
			
		||||
    with context.begin_transaction():
 | 
			
		||||
        context.run_migrations()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_migrations_online():
 | 
			
		||||
    """Run migrations in 'online' mode.
 | 
			
		||||
 | 
			
		||||
    In this scenario we need to create an Engine
 | 
			
		||||
    and associate a connection with the context.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    engine = get_default_engine(rattail_config)
 | 
			
		||||
    connection = engine.connect()
 | 
			
		||||
    context.configure(
 | 
			
		||||
        connection=connection,
 | 
			
		||||
        target_metadata=target_metadata)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        with context.begin_transaction():
 | 
			
		||||
            context.run_migrations()
 | 
			
		||||
    finally:
 | 
			
		||||
        connection.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if context.is_offline_mode():
 | 
			
		||||
    run_migrations_offline()
 | 
			
		||||
else:
 | 
			
		||||
    run_migrations_online()
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ import os
 | 
			
		|||
import colander
 | 
			
		||||
 | 
			
		||||
from rattail.projects import RattailAdjacentProjectGenerator
 | 
			
		||||
from rattail.util import get_studly_prefix, get_package_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RattailIntegrationProjectGenerator(RattailAdjacentProjectGenerator):
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +62,14 @@ class RattailIntegrationProjectGenerator(RattailAdjacentProjectGenerator):
 | 
			
		|||
            'Topic :: Software Development :: Libraries :: Python Modules',
 | 
			
		||||
        ]))
 | 
			
		||||
 | 
			
		||||
        if 'integration_studly_prefix' not in context:
 | 
			
		||||
            context['integration_studly_prefix'] = get_studly_prefix(
 | 
			
		||||
                context['integration_name'])
 | 
			
		||||
 | 
			
		||||
        if 'integration_pkgname' not in context:
 | 
			
		||||
            context['integration_pkgname'] = get_package_name(
 | 
			
		||||
                context['integration_name'])
 | 
			
		||||
 | 
			
		||||
        if 'year' not in context:
 | 
			
		||||
            context['year'] = self.app.today().year
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										67
									
								
								rattail/projects/rattail_shopfoo.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								rattail/projects/rattail_shopfoo.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
# -*- coding: utf-8; -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  Rattail -- Retail Software Framework
 | 
			
		||||
#  Copyright © 2010-2023 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of Rattail.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is free software: you can redistribute it and/or modify it under the
 | 
			
		||||
#  terms of the GNU General Public License as published by the Free Software
 | 
			
		||||
#  Foundation, either version 3 of the License, or (at your option) any later
 | 
			
		||||
#  version.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
			
		||||
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
			
		||||
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | 
			
		||||
#  details.
 | 
			
		||||
#
 | 
			
		||||
#  You should have received a copy of the GNU General Public License along with
 | 
			
		||||
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
"""
 | 
			
		||||
Generator for 'rattail-shopfoo' integration projects
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from rattail.projects.rattail_integration import RattailIntegrationProjectGenerator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RattailShopfooProjectGenerator(RattailIntegrationProjectGenerator):
 | 
			
		||||
    """
 | 
			
		||||
    Generator for projects which integrate Rattail with some type of
 | 
			
		||||
    e-commerce system.  This is for generating projects such as
 | 
			
		||||
    rattail-instacart, rattail-mercato etc. which involve a nightly
 | 
			
		||||
    export/upload of product data to external server.
 | 
			
		||||
    """
 | 
			
		||||
    key = 'rattail_shopfoo'
 | 
			
		||||
 | 
			
		||||
    def normalize_context(self, context):
 | 
			
		||||
 | 
			
		||||
        # nb. auto-set some flags
 | 
			
		||||
        context['extends_db'] = True
 | 
			
		||||
        context['has_model'] = True
 | 
			
		||||
 | 
			
		||||
        # then do normal logic
 | 
			
		||||
        context = super(RattailShopfooProjectGenerator, self).normalize_context(context)
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def generate_project(self, output, context, **kwargs):
 | 
			
		||||
        super(RattailShopfooProjectGenerator, self).generate_project(
 | 
			
		||||
            output, context, **kwargs)
 | 
			
		||||
 | 
			
		||||
        package = os.path.join(output, context['pkg_name'])
 | 
			
		||||
 | 
			
		||||
        ##############################
 | 
			
		||||
        # db/model
 | 
			
		||||
        ##############################
 | 
			
		||||
 | 
			
		||||
        db = os.path.join(package, 'db')
 | 
			
		||||
        model = os.path.join(db, 'model')
 | 
			
		||||
 | 
			
		||||
        self.generate('package/db/model/shopfoo.py.mako',
 | 
			
		||||
                      os.path.join(model, '{}.py'.format(context['integration_pkgname'])),
 | 
			
		||||
                      context)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
## -*- coding: utf-8; mode: python; -*-
 | 
			
		||||
# -*- coding: utf-8; -*-
 | 
			
		||||
"""
 | 
			
		||||
${name} data models
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# bring in all models for ${integration_name} integration
 | 
			
		||||
from .${integration_pkgname} import ${integration_studly_prefix}Product, ${integration_studly_prefix}ProductExport
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
## -*- coding: utf-8; mode: python; -*-
 | 
			
		||||
# -*- coding: utf-8; -*-
 | 
			
		||||
"""
 | 
			
		||||
Integration data models for ${integration_name}
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
from sqlalchemy.ext.declarative import declared_attr
 | 
			
		||||
 | 
			
		||||
from rattail.db import model
 | 
			
		||||
from rattail.db.model.shopfoo import ShopfooProductBase, ShopfooProductExportBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ${integration_studly_prefix}Product(ShopfooProductBase, model.Base):
 | 
			
		||||
    """
 | 
			
		||||
    ${integration_name} extensions to :class:`rattail:rattail.db.model.Product`.
 | 
			
		||||
    """
 | 
			
		||||
    __tablename__ = '${integration_pkgname}_product'
 | 
			
		||||
 | 
			
		||||
    @declared_attr
 | 
			
		||||
    def __table_args__(cls):
 | 
			
		||||
        return cls.__product_table_args__() + (
 | 
			
		||||
            sa.UniqueConstraint('${integration_pkgname}_id', name='${integration_pkgname}_product_uq_${integration_pkgname}_id'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    __versioned__ = {
 | 
			
		||||
        # 'exclude': [
 | 
			
		||||
        #     'in_stock',
 | 
			
		||||
        #     'last_sold',
 | 
			
		||||
        #     'last_updated',
 | 
			
		||||
        #     'units_on_hand',
 | 
			
		||||
        # ],
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ${integration_pkgname}_id = sa.Column(sa.String(length=25), nullable=False)
 | 
			
		||||
 | 
			
		||||
    description = sa.Column(sa.String(length=512), nullable=True)
 | 
			
		||||
 | 
			
		||||
    # price = sa.Column(sa.Numeric(precision=13, scale=2), nullable=True)
 | 
			
		||||
 | 
			
		||||
    # in_stock = sa.Column(sa.Boolean(), nullable=True)
 | 
			
		||||
 | 
			
		||||
    # last_sold = sa.Column(sa.Date(), nullable=True)
 | 
			
		||||
 | 
			
		||||
    # last_updated = sa.Column(sa.Date(), nullable=True)
 | 
			
		||||
 | 
			
		||||
    # units_on_hand = sa.Column(sa.Numeric(precision=13, scale=2), nullable=True)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.description or ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ${integration_studly_prefix}ProductExport(ShopfooProductExportBase, model.Base):
 | 
			
		||||
    """
 | 
			
		||||
    History table for product exports which have been submitted to ${integration_name}
 | 
			
		||||
    """
 | 
			
		||||
    __tablename__ = '${integration_pkgname}_product_export'
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import collections
 | 
			
		|||
import importlib
 | 
			
		||||
import re
 | 
			
		||||
import shlex
 | 
			
		||||
import unicodedata
 | 
			
		||||
import datetime
 | 
			
		||||
import decimal
 | 
			
		||||
import subprocess
 | 
			
		||||
| 
						 | 
				
			
			@ -166,6 +167,32 @@ def get_class_hierarchy(klass, topfirst=True):
 | 
			
		|||
    return hierarchy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_package_name(name):
 | 
			
		||||
    """
 | 
			
		||||
    Generic logic to derive a "package name" from the given name.
 | 
			
		||||
 | 
			
		||||
    E.g. if ``name`` is "Poser Plus" this will return "poser_plus"
 | 
			
		||||
    """
 | 
			
		||||
    # cf. https://stackoverflow.com/a/3194567
 | 
			
		||||
    name = unicodedata.normalize('NFD', name)
 | 
			
		||||
    name = name.encode('ascii', 'ignore').decode('ascii')
 | 
			
		||||
    words = re.split(r'[\- ]', name)
 | 
			
		||||
    return '_'.join([word.lower() for word in words])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_studly_prefix(name):
 | 
			
		||||
    """
 | 
			
		||||
    Generic logic to derive a "studly prefix" from the given name.
 | 
			
		||||
 | 
			
		||||
    E.g. if ``name`` is "Poser Plus" this will return "PoserPlus"
 | 
			
		||||
    """
 | 
			
		||||
    # cf. https://stackoverflow.com/a/3194567
 | 
			
		||||
    name = unicodedata.normalize('NFD', name)
 | 
			
		||||
    name = name.encode('ascii', 'ignore').decode('ascii')
 | 
			
		||||
    words = re.split(r'[\- ]', name)
 | 
			
		||||
    return ''.join([word.capitalize() for word in words])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_module_path(module_path):
 | 
			
		||||
    """
 | 
			
		||||
    Import an arbitrary Python module.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -272,6 +272,7 @@ setup(
 | 
			
		|||
            'fabric = rattail.projects.fabric:FabricProjectGenerator',
 | 
			
		||||
            'rattail = rattail.projects.rattail:RattailProjectGenerator',
 | 
			
		||||
            'rattail_integration = rattail.projects.rattail_integration:RattailIntegrationProjectGenerator',
 | 
			
		||||
            'rattail_shopfoo = rattail.projects.rattail_shopfoo:RattailShopfooProjectGenerator',
 | 
			
		||||
            'tailbone_integration = rattail.projects.tailbone_integration:TailboneIntegrationProjectGenerator',
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue