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:
Lance Edgar 2023-05-08 21:41:00 -05:00
parent 9834e1276d
commit 4c331e3875
8 changed files with 176 additions and 96 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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

View 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)

View file

@ -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

View file

@ -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'

View file

@ -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.

View file

@ -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',
],