diff --git a/MANIFEST.in b/MANIFEST.in index d77ebec03d5b32da0cbc1e086fc15d08535d7e60..e898cd382afc09fb755c3868870ef0f7c9edabcc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ include *.txt include *.rst + +include rattail/data/rattail.conf.sample diff --git a/rattail/commands.py b/rattail/commands.py index 7f1d92789da0aeba6f401c08a9409fb5cf2201f9..040c80ce2204d48ab894efc87b724a89bf705d39 100644 --- a/rattail/commands.py +++ b/rattail/commands.py @@ -180,6 +180,44 @@ class LoadHostDataCommand(commands.Subcommand): proc.load_all_data(edbob.engines['host'], Progress) +class MakeConfigCommand(commands.Subcommand): + """ + Creates a sample configuration file. + """ + + name = 'make-config' + description = "Create a configuration file" + + def add_parser_args(self, parser): + parser.add_argument('path', default='rattail.conf', metavar='PATH', + help="Path to the new file") + parser.add_argument('-f', '--force', action='store_true', + help="Overwrite an existing file") + + + def run(self, args): + import os + import os.path + import shutil + from rattail.files import resource_path + + dest = os.path.abspath(args.path) + if os.path.exists(dest): + if os.path.isdir(dest): + sys.stderr.write("Path must include the filename; " + "you gave a directory:\n {0}\n".format(dest)) + sys.exit(1) + if not args.force: + sys.stderr.write("File already exists " + "(use --force to overwrite):\n " + "{0}\n".format(dest)) + sys.exit(1) + os.remove(dest) + + src = resource_path('rattail:data/rattail.conf.sample') + shutil.copyfile(src, dest) + + class PalmCommand(commands.Subcommand): """ Manages registration for the HotSync Manager conduit; called as:: diff --git a/rattail/data/rattail.conf.sample b/rattail/data/rattail.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..1136aee17b091f61fe31d8f615fd9f5559f33978 --- /dev/null +++ b/rattail/data/rattail.conf.sample @@ -0,0 +1,323 @@ + +###################################################################### +# +# Rattail Configuration +# +# This file is meant to be used as a sample only. Please copy it to a +# location of your choice and edit for your needs. +# +###################################################################### + + +############################## +# edbob +############################## + +[edbob] + +# Include (inherit) configuration from one or more additional files. See also +# http://rattail.edbob.org/wiki/Configuration#Inheritance_.2BAC8_.22Chaining.22 +#include_config = [r'\\server\rattail\config\site.conf', r'C:\ProgramData\rattail\rattail.conf'] +#include_config = ['/etc/rattail/rattail.conf'] + +# Configure the Python logging mechanisms according to the full configuration +# set loaded upon initialization. +configure_logging = True + +# Modules which should be explicitly initialized on application startup. +#init = edbob.errors + +# Override the default Python interpreter when executing the ``shell`` command. +#shell.python = ipython + +# Location of the ``git`` executable. This is usually only needed on Windows. +#git.executable = "C:\Program Files\Git\bin\git.exe" + + +[edbob.db] + +# Keys which name the database engines configured within this section. Without +# this option, the 'default' key is assumed; but it must be included if the +# option is defined. +#keys = default, host, other + +# Connection details for the default database engine. Configuration is +# (mostly) defined by the SQLAlchemy library; for details see the parameters at +# http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine +#default.url = postgresql://user:pass@localhost/rattail + +# Connection details for additional database engines, as needed. +#host.url = postgresql://user:pass@host-server/rattail +#other.url = mysql://user:pass@another-server/rattail + + +[edbob.errors] + +# Mako template used to generate the email body for an unhandled exception. If +# specified, it may be either an absolute file path or a "resource path." +#template = edbob:templates/errors/redmine.mako + +# Value of the 'Content-Type' header to be used for the error email. The +# default content type is 'text/plain'. +#content_type = text/html + + +[edbob.mail] + +# SMTP server used to send emails. +smtp.server = localhost + +# Credentials for SMTP server, if needed. +#smtp.username = user +#smtp.password = pass + +# Defaults for emails sent with no explicit profile. +sender.default = noreply@localhost +recipients.default = ['root@localhost'] +subject.default = [Rattail] Message + +# Defaults for emails sent with the "errors" profile. +subject.errors = [Rattail] Error + + +[edbob.time] + +# "Olson" name of the effective local time zone. Note that this does not need +# to match the local system (machine) time zone. The full list is online at +# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones +#zone.local = America/Los_Angeles + +# Time zone used to interpret timestamps found within a LOC SMS database. +#zone.rattail.sw.locsms = America/Chicago + + +[edbob.service_config] + +# This section defines effective "master" configuration files for use with +# various Windows services. It is not possible to pass a ``--config=FILE`` +# parameter to a service command; therefore the service *must* look in the +# standard places to find some configuration on startup. The options below +# allow a service to redirect its attention to a secondary file(s) and then +# pretend as though *this* (current) file had never been read. Note that the +# options names below must correspond to the underlying service names. + +# Master configuration file for the file monitoring service. +#RattailFileMonitor = [r'C:\ProgramData\rattail\filemon.conf'] + +# Master configuration file for the database synchronization service. +#RattailDatabaseSynchronizer = [r'C:\ProgramData\rattail\dbsync.conf'] + + +############################## +# Rattail +############################## + +[rattail.db] + +# Whether to record "change" data for the local database (i.e. the 'default' +# engine). This should only be enabled for a parent/master database, as it is +# used to track which data the database synchronization service will sync to +# the child/slave databases. +#record_changes = True + +# Database engine keys (as found in the ``[edbob.db]`` section above) which +# correspond to child/slave databases in the hierarchy. These databases will +# be kept in sync by the database synchronization service, if used. +#syncs = north, south + +# Class specification to use for the primary "data synchronizer" instance. +#sync.synchronizer_class = myapp.sync:Synchronizer + + +[rattail.filemon] + +# This section configures the file monitor service. Its contents may vary +# quite a bit depending on your needs. Several common examples are included +# below; however the underlying implementation is intentionally generic so this +# list is by no means exhaustive. For more information please see +# http://rattail.edbob.org/wiki/FileMonitor#Configuration + +# Location of the PID file; relevant only on Linux. If none is specified, +# defaults to ``/tmp/rattail_filemon.pid``. +#pid_path = /tmp/rattail_filemon_special.pid + +# Comma-separated list of monitor "profile" keys. This defines which of the +# profiles configured below will be considered active by the monitor. +#monitored = foo, bar +#monitored = +# scangenius, +# sms_auxiliary, +# sms_changes, +# sms_inbox_store, +# sms_inbox_host, +# sms_sending_transfers, +# wince + +# Watch the Scan Genius order export folder, and collect files to the local +# queue folder on the server. +#scangenius.dirs = [r'C:\Users\lance\AppData\Local\rattail\Scan Genius\Orders'] +#scangenius.actions = ['rattail.sw.livnat.scangenius:collect_order'] + +# Watch the "Auxiliary" folder within an SMS node, and move files which are +# deployed there to the local server. +#sms_auxiliary.dirs = [r'C:\Storeman\XchRattail\Auxiliary'] +#sms_auxiliary.actions = [('rattail.files:locking_copy', r'\\server\rattail\SMS\Auxiliary'), 'os:remove'] + +# Watch the "Auxiliary" folder within an SMS node, and process the files +# directly so as to update the Rattail database. +#sms_auxiliary.dirs = [r'C:\Storeman\XchRattail\Auxiliary'] +#sms_auxiliary.actions = ['rattail.sw.locsms.auxiliary:process_auxiliary'] + +# Watch the "Changes" folder within an SMS node, and move files which are +# deployed there to the local server. +#sms_changes.dirs = [r'C:\Storeman\XchRattail\Changes'] +#sms_changes.actions = [('rattail.files:locking_copy', r'\\server\rattail\SMS\Changes'), 'os:remove'] + +# Watch the "Changes" folder within an SMS node, and process the files directly +# so as to update the Rattail database. +#sms_changes.dirs = [r'C:\Storeman\XchRattail\Changes'] +#sms_changes.actions = ['rattail.sw.locsms.changes:process_changes'] + +# Watch a "Store Outbox" folder on the server, and deploy files found there to +# the SMS store server's inbox, where they will be processed by Launchpad. +#sms_inbox_store.dirs = [r'\\server\rattail\SMS\Outbox\Store'] +#sms_inbox_store.actions = ['rattail.sw.locsms.deploy:deploy_to_inbox', 'os:remove'] + +# Watch a "Host Outbox" folder on the server, and deploy files found there to +# the SMS host server's inbox, where they will be processed by Launchpad. +#sms_inbox_host.dirs = [r'\\server\rattail\SMS\Outbox\Host'] +#sms_inbox_host.actions = [('rattail.sw.locsms.deploy:deploy_to_inbox', 'host'), 'os:remove'] + +# Watch the "Transfers" folder within an SMS node, and copy files which appear +# there to the relevant receiving ("purchasing") store server. +#sms_sending_transfers.dirs = [r'C:\Storeman\XchRattail\Transfers'] +#sms_sending_transfers.actions = ['rattail.sw.locsms.transfers:copy_transfer', 'os:remove'] + +# Watch the "Incoming Transfers" folder on the server, and process files which +# appear there to ensure the relevant SMS cost records are updated. +#sms_receiving_transfers.dirs = [r'\\server\rattail\SMS\Transfers'] +#sms_receiving_transfers.actions = ['rattail.sw.locsms.transfers:process_transfer', 'os:remove'] + +# Watch the Windows CE device (ActiveSync) documents folder, and convert and +# collect batch files which appear there to the local queue. +#wince.dirs = [r'C:\Users\lance\Documents\Documents on DEVICE\Rattail'] +#wince.actions = [('rattail.wince:collect_batch', 'DEVICE'), 'os:remove'] + + +[rattail.sw.livnat] + +# Collection directory for Scan Genius orders which are exported to CSV. An +# instance of the file monitor is usually responsible for moving exported files +# to this common "queue" location, from where they will be processed further. +#orders.collection_dir = \\server\rattail\ScanGenius\Orders + +# Location of the "Living Naturally Suppliers" CSV file. A copy may be +# obtained online at http://pos.livingnaturally.com/ +#suppliers.csv_file = \\server\rattail\LivingNaturally\SupplierList.csv + + +[rattail.sw.locsms] + +# The root path of the default SMS node. This usually refers to the local +# store server. +#node = \\server\Storeman + +# The root path(s) of additional "named" SMS nodes. +#node.host = \\host-server\Storeman +#node.foo = C:\Storeman + +# The "pseudo" (surrogate) root paths for SMS nodes. From a Linux machine, it +# is not possible to fully deploy a file to the node's inbox, since that +# requires flipping the "archive bit" on the file. The workaround is to use a +# file monitor on the Windows machine, which will watch the "pseudo" inbox +# folder and perform the final deployment to the true inbox. If present, these +# options effectively override the ``node.*`` options for the sake of inbox +# file deployment. +#pseudo_inbox = /srv/rattail/SMS/Outbox/Store +#pseudo_inbox.host = /srv/rattail/SMS/Outbox/Host + +# The connection details of the SMS database engine. This usually will require +# configuring an ODBC DSN as well. +#sqlalchemy.url = mssql://user:pass@SMS-STORESQL + +# The following is necessary when using pyodbc with FreeTDS; see also +# http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine +#sqlalchemy.convert_unicode = True + +# The following is necessary to avoid "write to server failed" errors; see also +# http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine +#sqlalchemy.pool_recycle = 3600 + +# Class specification to use for a custom "auxiliary data processor" instance. +#auxiliary.processor_class = myapp.locsms:AuxiliaryProcessor + +# Class specification to use for a custom "changes data processor" instance. +#changes.processor_class = myapp.locsms:ChangesProcessor + +# Target directories for inter-store transfer files. When a transfer occurs, +# this mapping determines (according to "purchasing" store) where the file +# should go. It is assumed that a file monitor on the receiving end will be +# responsible for final processing. +#transfers.001 = \\store1\rattail\SMS\Transfers +#transfers.002 = \\store2\rattail\SMS\Transfers +#transfers.003 = \\store3\rattail\SMS\Transfers + + +[rattail.wince] + +# Collection directory for batch files coming from Windows CE handheld devices. +# An instance of the file monitor is usually responsible for converting the +# "raw" batch files and moving them to this common "queue" location, from where +# they will be processed further. +#collection_dir = \\server\rattail\handheld-batches + + +############################## +# Logging +############################## + +# All remaining sections and options control the logging mechanism, if +# ``configure_logging`` is enabled above. These settings will be interpreted +# by the standard Python ``logging`` module, so the structure and syntax must +# adhere to what is expected by that facility. More details are online at +# http://docs.python.org/2/library/logging.html + +[loggers] +keys = root, exc_logger + +[handlers] +keys = file, console, email + +[formatters] +keys = generic, console + +[logger_root] +handlers = file, console +level = WARNING + +[logger_exc_logger] +qualname = exc_logger +handlers = email +level = ERROR + +[handler_file] +class = FileHandler +args = ('rattail.log', 'a') +formatter = generic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +formatter = console + +[handler_email] +class = handlers.SMTPHandler +args = ('localhost', 'noreply@localhost', ['root@localhost'], "[Rattail] Logging") +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s +datefmt = %Y-%m-%d %H:%M:%S + +[formatter_console] +format = %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s diff --git a/rattail/files.py b/rattail/files.py index 2fcb99574b51ab03aa70ed6972df31cd6ca0ac0b..39afb785e3c94d2c7cd54cb05f54391f464b234b 100644 --- a/rattail/files.py +++ b/rattail/files.py @@ -33,6 +33,8 @@ import shutil import lockfile from datetime import datetime +import pkg_resources + def count_lines(path): """ @@ -92,3 +94,25 @@ def locking_copy(src, dst): with lockfile.FileLock(dst): shutil.copy(src, dst) + + +def resource_path(path): + """ + Obtain a resource file path, extracting the resource and/or coercing the + path as necessary. + + :param path: May be either a package resource specifier, or a regular file + path. + :type path: string + + :returns: Absolute file path to the resource. + :rtype: string + + If ``path`` is a package resource specifier, and the package containing it + is a zipped egg, then the resource will be extracted and the resultant + filename will be returned. + """ + + if not os.path.isabs(path) and ':' in path: + return pkg_resources.resource_filename(*path.split(':')) + return path diff --git a/setup.py b/setup.py index 54c5b34b2f73e9ddf1786ba7070230f2b8d21fa5..e47ea11c03112bf230a08e1539ad1f18d814e2e8 100644 --- a/setup.py +++ b/setup.py @@ -115,6 +115,7 @@ rattail = rattail.db.extension:RattailExtension dbsync = rattail.commands:DatabaseSyncCommand filemon = rattail.commands:FileMonitorCommand load-host-data = rattail.commands:LoadHostDataCommand +make-config = rattail.commands:MakeConfigCommand palm = rattail.commands:PalmCommand purge-batches = rattail.commands:PurgeBatchesCommand