Initial commit, re-branded from first attempt..

This commit is contained in:
Lance Edgar 2022-03-02 16:41:28 -06:00
commit 7058ebf75e
17 changed files with 871 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
messkit.egg-info/

5
README.rst Normal file
View file

@ -0,0 +1,5 @@
messkit
=======
Hopefully some kind of generic data manipulation thingy, we'll see...

27
messkit/__init__.py Normal file
View file

@ -0,0 +1,27 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit
"""
from ._version import __version__

3
messkit/_version.py Normal file
View file

@ -0,0 +1,3 @@
# -*- coding: utf-8; -*-
__version__ = '0.1.0'

35
messkit/appsettings.py Normal file
View file

@ -0,0 +1,35 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit app settings
"""
# bring in some common settings from rattail
from rattail.settings import (
# (General)
rattail_app_title,
# Mail
rattail_mail_record_attempts,
)

249
messkit/commands.py Normal file
View file

@ -0,0 +1,249 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit commands
"""
import os
import sys
import subprocess
import sqlalchemy as sa
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
from rich import print as rprint
from alembic.util.messaging import obfuscate_url_pw
from rattail import commands
from messkit import __version__
def main(*args):
"""
Main entry point for messkit command system
"""
args = list(args or sys.argv[1:])
cmd = Command()
cmd.run(*args)
class Command(commands.Command):
"""
Main command for messkit
"""
name = 'messkit'
version = __version__
description = "messkit (Generic Data App)"
long_description = ''
class Install(commands.Subcommand):
"""
Install a messkit app
"""
name = 'install'
description = __doc__.strip()
def run(self, args):
rprint("\n\t[blue]Welcome to messkit![/blue]")
rprint("\n\tThis tool will install and configure a new app.")
rprint("\n\t[italic]NB. You should already have created a new database in PostgreSQL.[/italic]")
# get appdir path
appdir = os.path.join(sys.prefix, 'app')
appdir = self.basic_prompt('appdir path', appdir)
# appdir must not yet exist
if os.path.exists(appdir):
rprint("\n\t[bold red]appdir already exists:[/bold red] {}\n".format(appdir))
sys.exit(1)
# get db info
dbhost = self.basic_prompt('postgres host', 'localhost')
dbport = self.basic_prompt('postgres port', '5432')
dbname = self.basic_prompt('postgres db', 'messkit')
dbuser = self.basic_prompt('postgres user', 'rattail')
# get db password
dbpass = None
while not dbpass:
dbpass = self.basic_prompt('postgres pass', is_password=True)
# test db connection
rprint("\n\ttesting db connection... ", end='')
dburl = self.make_db_url(dbhost, dbport, dbname, dbuser, dbpass)
error = self.test_db_connection(dburl)
if error:
rprint("[bold red]cannot connect![/bold red] ..error was:")
rprint("\n{}".format(error))
rprint("\n\t[bold yellow]aborting mission[/bold yellow]\n")
sys.exit(1)
rprint("[bold green]good[/bold green]")
# make the appdir
self.app.make_appdir(appdir)
# make config files
context = {
'app_title': "messkit",
'appdir': appdir,
'db_url': dburl,
'pyramid_egg': 'messkit',
'beaker_key': 'messkit',
}
rattail_conf = self.app.make_config_file(
'rattail-complete', os.path.join(appdir, 'rattail.conf'),
**context)
quiet_conf = self.app.make_config_file('quiet', appdir)
web_conf = self.app.make_config_file(
'web-complete', os.path.join(appdir, 'web.conf'),
**context)
rprint("\n\tappdir created at: [bold green]{}[/bold green]".format(appdir))
bindir = os.path.join(sys.prefix, 'bin')
schema_installed = False
if self.basic_prompt("install db schema?", True, is_bool=True):
rprint()
# install db schema
alembic = os.path.join(bindir, 'alembic')
cmd = [alembic, '-c', rattail_conf, 'upgrade', 'heads']
subprocess.check_call(cmd)
schema_installed = True
rprint("\n\tdb schema installed to: [bold green]{}[/bold green]".format(
obfuscate_url_pw(dburl)))
if self.basic_prompt("create admin user?", True, is_bool=True):
# get admin credentials
username = self.basic_prompt('admin username', 'admin')
password = None
while not password:
password = self.basic_prompt('admin password', is_password=True)
if password:
confirm = self.basic_prompt('confirm password', is_password=True)
if not confirm or confirm != password:
rprint("[bold yellow]passwords did not match[/bold yellow]")
password = None
rprint()
# make admin user
rattail = os.path.join(bindir, 'rattail')
cmd = [rattail, '-c', quiet_conf, 'make-user', '-A', username,
'--password', password]
subprocess.check_call(cmd)
rprint("\n\tadmin user created: [bold green]{}[/bold green]".format(
username))
if self.basic_prompt("make poser dir?", True, is_bool=True):
rprint()
poser_handler = self.app.get_poser_handler()
poserdir = poser_handler.make_poser_dir()
rprint("\n\tposer dir created: [bold green]{}[/bold green]".format(
poserdir))
rprint("\n\t[bold green]initial setup is complete![/bold green]")
if schema_installed:
rprint("\n\tyou can run the web app with this command:")
pserve = os.path.join(bindir, 'pserve')
rprint("\n\t[blue]{} --reload file+ini:{}[/blue]".format(pserve, web_conf))
rprint()
def basic_prompt(self, info, default=None, is_password=False, is_bool=False):
# message formatting styles
style = Style.from_dict({
'': '',
'bold': 'bold',
})
# build prompt message
message = [
('', '\n'),
('class:bold', info),
]
if default is not None:
if is_bool:
message.append(('', ' [{}]: '.format('Y' if default else 'N')))
else:
message.append(('', ' [{}]: '.format(default)))
else:
message.append(('', ': '))
# prompt user for input
try:
text = prompt(message, style=style, is_password=is_password)
except (KeyboardInterrupt, EOFError):
rprint("\n\t[bold yellow]operation canceled by user[/bold yellow]\n",
file=self.stderr)
sys.exit(2)
if is_bool:
if text == '':
return default
elif text.upper() == 'Y':
return True
elif text.upper() == 'N':
return False
rprint("\n\t[bold yellow]ambiguous, please try again[/bold yellow]\n")
return self.basic_prompt(info, default, is_bool=True)
return text or default
def make_db_url(self, dbhost, dbport, dbname, dbuser, dbpass):
try:
# newer style
from sqlalchemy.engine import URL
factory = URL.create
except ImportError:
# older style
from sqlalchemy.engine.url import URL
factory = URL
return factory(drivername='postgresql+psycopg2',
username=dbuser,
password=dbpass,
host=dbhost,
port=dbport,
database=dbname)
def test_db_connection(self, url):
engine = sa.create_engine(url)
# check for random table; does not matter if it exists, we
# just need to test interaction and this is a neutral way
try:
engine.has_table('whatever')
except Exception as error:
return str(error)

51
messkit/config.py Normal file
View file

@ -0,0 +1,51 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
Config extension for messkit
"""
import os
import sys
from rattail.config import ConfigExtension
class MesskitConfig(ConfigExtension):
"""
Rattail config extension for messkit
"""
key = 'messkit'
def configure(self, config):
# set some default config values
config.setdefault('rattail', 'app_title', "messkit")
config.setdefault('tailbone', 'menus', 'messkit.web.menus')
config.setdefault('rattail', 'model', 'messkit.db.model')
config.setdefault('rattail', 'settings', 'messkit.appsettings')
# # always try to append poser to path
# # TODO: location of poser dir should come from config/app/handler?
# poser = os.path.join(sys.prefix, 'app', 'poser')
# if poser not in sys.path and os.path.isdir(poser):
# sys.path.append(poser)

0
messkit/db/__init__.py Normal file
View file

32
messkit/db/model.py Normal file
View file

@ -0,0 +1,32 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit Data Model
"""
from rattail.db.model import *
try:
from poser.db.model import *
except ImportError:
pass

0
messkit/web/__init__.py Normal file
View file

54
messkit/web/app.py Normal file
View file

@ -0,0 +1,54 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit web app
"""
from tailbone import app
def main(global_config, **settings):
"""
This function returns a Pyramid WSGI application.
"""
# prefer messkit templates over Tailbone
settings.setdefault('mako.directories', ['messkit.web:templates',
'tailbone:templates',])
# for graceful handling of postgres restart
settings.setdefault('retry.attempts', 2)
# make config objects
rattail_config = app.make_rattail_config(settings)
pyramid_config = app.make_pyramid_config(settings)
# bring in the rest of messkit
pyramid_config.include('messkit.web.static')
pyramid_config.include('messkit.web.subscribers')
pyramid_config.include('messkit.web.views')
# for graceful handling of postgres restart
pyramid_config.add_tween('tailbone.tweens.sqlerror_tween_factory',
under='pyramid_tm.tm_tween_factory')
return pyramid_config.make_wsgi_app()

140
messkit/web/menus.py Normal file
View file

@ -0,0 +1,140 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
Web Menus
"""
def simple_menus(request):
url = request.route_url
# reports_menu = {
# 'title': "Reports",
# 'type': 'menu',
# 'items': [
# {
# 'title': "New Report",
# 'url': url('report_output.create'),
# 'perm': 'report_output.create',
# },
# {
# 'title': "Generated Reports",
# 'url': url('report_output'),
# 'perm': 'report_output.list',
# },
# {
# 'title': "Problem Reports",
# 'url': url('problem_reports'),
# 'perm': 'problem_reports.list',
# },
# ],
# }
# other_menu = {
# 'title': "Other",
# 'type': 'menu',
# 'items': [
# {
# 'title': "Generate New Feature",
# 'url': url('generate_feature'),
# 'perm': 'common.generate_feature',
# },
# {
# 'title': "Generate New Project",
# 'url': url('generate_project'),
# 'perm': 'common.generate_project',
# },
# ],
# }
admin_menu = {
'title': "Admin",
'type': 'menu',
'items': [
{
'title': "Users",
'url': url('users'),
'perm': 'users.list',
},
# {
# 'title': "User Events",
# 'url': url('userevents'),
# 'perm': 'userevents.list',
# },
{
'title': "Roles",
'url': url('roles'),
'perm': 'roles.list',
},
{'type': 'sep'},
{
'title': "App Settings",
'url': url('appsettings'),
'perm': 'settings.list',
},
{
'title': "Email Settings",
'url': url('emailprofiles'),
'perm': 'emailprofiles.list',
},
# {
# 'title': "Email Attempts",
# 'url': url('email_attempts'),
# 'perm': 'email_attempts.list',
# },
{
'title': "Raw Settings",
'url': url('settings'),
'perm': 'settings.list',
},
{'type': 'sep'},
# {
# 'title': "DataSync Changes",
# 'url': url('datasyncchanges'),
# 'perm': 'datasync_changes.list',
# },
# {
# 'title': "Importing / Exporting",
# 'url': url('importing'),
# 'perm': 'importing.list',
# },
{
'title': "Tables",
'url': url('tables'),
'perm': 'tables.list',
},
{
'title': "messkit Upgrades",
'url': url('upgrades'),
'perm': 'upgrades.list',
},
],
}
menus = [
# reports_menu,
# other_menu,
admin_menu,
]
return menus

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit static assets
"""
def includeme(config):
config.include('tailbone.static')
config.add_static_view('messkit', 'messkit.web:static', cache_max_age=3600)

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
Pyramid event subscribers
"""
import messkit
def add_messkit_to_context(event):
renderer_globals = event
renderer_globals['messkit'] = messkit
def includeme(config):
config.include('tailbone.subscribers')
config.add_subscriber(add_messkit_to_context, 'pyramid.events.BeforeRender')

View file

@ -0,0 +1,49 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit web views
"""
def includeme(config):
# core
config.include('messkit.web.views.common')
config.include('tailbone.views.auth')
config.include('tailbone.views.menus')
# config.include('tailbone.views.importing')
config.include('tailbone.views.poser')
config.include('tailbone.views.progress')
# config.include('tailbone.views.features')
config.include('tailbone.views.reports')
# main tables
config.include('tailbone.views.email')
config.include('tailbone.views.people')
config.include('tailbone.views.roles')
config.include('tailbone.views.settings')
config.include('tailbone.views.tables')
config.include('tailbone.views.upgrades')
config.include('tailbone.views.users')

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
Common views
"""
from tailbone.views import common as base
import messkit
class CommonView(base.CommonView):
project_title = "messkit"
project_version = messkit.__version__ + '+dev'
def includeme(config):
CommonView.defaults(config)

119
setup.py Normal file
View file

@ -0,0 +1,119 @@
# -*- coding: utf-8; -*-
######################################################################
#
# messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar
#
# This file is part of messkit.
#
# messkit 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.
#
# messkit 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 messkit. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################
"""
messkit setup script
"""
import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
exec(open(os.path.join(here, 'messkit', '_version.py')).read())
README = open(os.path.join(here, 'README.rst')).read()
requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
# TODO: user should get to choose which of these is needed?
'mysql-connector-python', # 8.0.28
'psycopg2', # 2.9.3
'prompt_toolkit', # 3.0.28
'rich', # 11.2.0
'Tailbone', # 0.8.206
]
setup(
name = "messkit",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "https://rattailproject.org",
description = "Generic-ish Data Utility App",
long_description = README,
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Environment :: Web Environment',
'Framework :: Pyramid',
'Intended Audience :: Developers',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Office/Business',
],
install_requires = requires,
packages = find_packages(),
include_package_data = True,
entry_points = {
'rattail.config.extensions': [
'messkit = messkit.config:MesskitConfig',
],
'console_scripts': [
'messkit = messkit.commands:main',
],
'messkit.commands': [
'install = messkit.commands:Install',
],
'paste.app_factory': [
'main = messkit.web.app:main',
],
},
)