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
	
	 Lance Edgar
						Lance Edgar