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
|
@ -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…
Reference in a new issue