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 shutil | ||||||
| import string | import string | ||||||
| import sys | import sys | ||||||
| import unicodedata |  | ||||||
| 
 | 
 | ||||||
| import colander | import colander | ||||||
| from mako.template import Template | 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 | from rattail.mako import ResourceTemplateLookup | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -276,13 +275,6 @@ class PythonProjectGenerator(ProjectGenerator): | ||||||
| 
 | 
 | ||||||
|         return schema |         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): |     def normalize_context(self, context): | ||||||
|         context = super(PythonProjectGenerator, self).normalize_context(context) |         context = super(PythonProjectGenerator, self).normalize_context(context) | ||||||
| 
 | 
 | ||||||
|  | @ -296,7 +288,7 @@ class PythonProjectGenerator(ProjectGenerator): | ||||||
|             context['egg_name'] = context['pypi_name'].replace('-', '_') |             context['egg_name'] = context['pypi_name'].replace('-', '_') | ||||||
| 
 | 
 | ||||||
|         if 'studly_prefix' not in context: |         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: |         if 'env_name' not in context: | ||||||
|             context['env_name'] = context['folder'] |             context['env_name'] = context['folder'] | ||||||
|  | @ -478,14 +470,12 @@ class RattailAdjacentProjectGenerator(PythonProjectGenerator): | ||||||
|             # model |             # model | ||||||
|             #################### |             #################### | ||||||
| 
 | 
 | ||||||
|             if context['has_model']: |             model = os.path.join(db, 'model') | ||||||
|  |             os.makedirs(model) | ||||||
| 
 | 
 | ||||||
|                 model = os.path.join(db, 'model') |             self.generate('package/db/model/__init__.py.mako', | ||||||
|                 os.makedirs(model) |                           os.path.join(model, '__init__.py'), | ||||||
| 
 |                           context) | ||||||
|                 self.generate('package/db/model/__init__.py.mako', |  | ||||||
|                               os.path.join(model, '__init__.py'), |  | ||||||
|                               context) |  | ||||||
| 
 | 
 | ||||||
|             #################### |             #################### | ||||||
|             # alembic |             # alembic | ||||||
|  | @ -496,11 +486,6 @@ class RattailAdjacentProjectGenerator(PythonProjectGenerator): | ||||||
|                 alembic = os.path.join(db, 'alembic') |                 alembic = os.path.join(db, 'alembic') | ||||||
|                 os.makedirs(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') |                 versions = os.path.join(alembic, 'versions') | ||||||
|                 os.makedirs(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 | import colander | ||||||
| 
 | 
 | ||||||
| from rattail.projects import RattailAdjacentProjectGenerator | from rattail.projects import RattailAdjacentProjectGenerator | ||||||
|  | from rattail.util import get_studly_prefix, get_package_name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RattailIntegrationProjectGenerator(RattailAdjacentProjectGenerator): | class RattailIntegrationProjectGenerator(RattailAdjacentProjectGenerator): | ||||||
|  | @ -61,6 +62,14 @@ class RattailIntegrationProjectGenerator(RattailAdjacentProjectGenerator): | ||||||
|             'Topic :: Software Development :: Libraries :: Python Modules', |             '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: |         if 'year' not in context: | ||||||
|             context['year'] = self.app.today().year |             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 importlib | ||||||
| import re | import re | ||||||
| import shlex | import shlex | ||||||
|  | import unicodedata | ||||||
| import datetime | import datetime | ||||||
| import decimal | import decimal | ||||||
| import subprocess | import subprocess | ||||||
|  | @ -166,6 +167,32 @@ def get_class_hierarchy(klass, topfirst=True): | ||||||
|     return hierarchy |     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): | def import_module_path(module_path): | ||||||
|     """ |     """ | ||||||
|     Import an arbitrary Python module. |     Import an arbitrary Python module. | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
										
									
									
									
								
							|  | @ -272,6 +272,7 @@ setup( | ||||||
|             'fabric = rattail.projects.fabric:FabricProjectGenerator', |             'fabric = rattail.projects.fabric:FabricProjectGenerator', | ||||||
|             'rattail = rattail.projects.rattail:RattailProjectGenerator', |             'rattail = rattail.projects.rattail:RattailProjectGenerator', | ||||||
|             'rattail_integration = rattail.projects.rattail_integration:RattailIntegrationProjectGenerator', |             'rattail_integration = rattail.projects.rattail_integration:RattailIntegrationProjectGenerator', | ||||||
|  |             'rattail_shopfoo = rattail.projects.rattail_shopfoo:RattailShopfooProjectGenerator', | ||||||
|             'tailbone_integration = rattail.projects.tailbone_integration:TailboneIntegrationProjectGenerator', |             'tailbone_integration = rattail.projects.tailbone_integration:TailboneIntegrationProjectGenerator', | ||||||
|         ], |         ], | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar