Merge branch 'development' of github.com:weewx/weewx into development

This commit is contained in:
matthew wall
2024-01-02 10:12:52 -05:00
6 changed files with 390 additions and 279 deletions

11
TODO.md
View File

@@ -1,9 +1,14 @@
# V5.0 "To Do"
## startup
- tk before logger is initialized, output to stdout/stderr as appropriate
- tk Rationalize startup procedure, making it consistent.
## Docs
- tk WEEWX-ROOT is now relative to the location of the config file. Reflect
this in docs
## weectl
- mw Resolve "FIXME" in `station_actions.copy_utils()`
## deb/rpm installs

View File

@@ -7,13 +7,12 @@
import getpass
import grp
import importlib
import logging
import os
import stat
import os.path
import re
import shutil
import stat
import sys
import urllib.parse
@@ -34,44 +33,31 @@ from weeutil.weeutil import to_float, to_bool, bcolors
log = logging.getLogger('weectl-station')
def station_create(config_path, *args,
def station_create(weewx_root=weecfg.default_weewx_root,
rel_config_path='./weewx.conf',
driver='weewx.drivers.simulator',
location='WeeWX station',
altitude='0, foot',
latitude=0, longitude=0,
register=False, station_url='https://example.com',
unit_system='us',
skin_root='./skins',
sqlite_root='./archive',
html_root='./public_html',
examples_root='./examples',
user_root='./bin/user',
dist_config_path=None,
weewx_root=None,
examples_root=None,
user_root=None,
dry_run=False,
**kwargs):
"""Create a brand-new station by creating a new configuration file.
If a value of weewx_root is not given, then it will be chosen as the
directory the resultant configuration file is in.
This function first checks whether the configuration file already exists.
If it does, then an exception is raised.
It then:
1. If no_prompt is false, it creates the configuration file by prompting
the user. If true, it uses defaults.
2. Copies the examples and utility files out of package resources and
into WEEWX_ROOT.
"""
no_prompt=False,
dry_run=False):
"""Create a brand-new station data area at weewx_root, then equip it with a
configuration file."""
if dry_run:
print("This is a dry run. Nothing will actually be done.")
# If no configuration file was specified, use the default (which is only
# 'correct' for pip/git installs).
if not config_path:
config_path = weecfg.default_config_path
# If a value of WEEWX_ROOT was specified, use that, overriding whatever
# might have been specified for the configuration file. Otherwise, use the
# directory in which the configuration file resides.
if weewx_root:
_, filename = os.path.split(config_path)
config_path = os.path.join(weewx_root, filename)
else:
weewx_root = os.path.abspath(os.path.dirname(config_path))
# Invert the relationship between weewx_root and the relative path to the config file.
# When done, weewx_root will be relative to the directory the config file will be in.
config_path, rel_weewx_root = _calc_paths(weewx_root, rel_config_path)
# Make sure there is not a configuration file at the designated location.
if os.path.exists(config_path):
@@ -81,7 +67,7 @@ def station_create(config_path, *args,
# If a distribution configuration was specified, use the contents from that
# for the new configuration. Otherwise, extract the contents from the
# config in the python package resources.
# configuration file in the python package resources.
if dist_config_path:
dist_config_dict = configobj.ConfigObj(dist_config_path, encoding='utf-8', file_error=True)
else:
@@ -89,8 +75,24 @@ def station_create(config_path, *args,
with weeutil.weeutil.get_resource_fd('weewx_data', 'weewx.conf') as fd:
dist_config_dict = configobj.ConfigObj(fd, encoding='utf-8', file_error=True)
config_config(config_path, dist_config_dict, weewx_root=weewx_root,
dry_run=dry_run, *args, **kwargs)
dist_config_dict['WEEWX_ROOT_ORIG'] = rel_weewx_root
config_dir = os.path.dirname(config_path)
dist_config_dict['WEEWX_ROOT'] = os.path.abspath(os.path.join(config_dir, rel_weewx_root))
# Modify the configuration dictionary config_dict, prompting if necessary.
config_config(config_dict=dist_config_dict,
config_path=config_path,
driver=driver,
location=location,
altitude=altitude,
latitude=latitude, longitude=longitude,
register=register, station_url=station_url,
unit_system=unit_system,
skin_root=skin_root,
sqlite_root=sqlite_root,
html_root=html_root,
user_root=user_root,
no_prompt=no_prompt)
copy_skins(dist_config_dict, dry_run=dry_run)
copy_examples(dist_config_dict, examples_root=examples_root, dry_run=dry_run)
copy_user(dist_config_dict, user_root=user_root, dry_run=dry_run)
@@ -105,10 +107,73 @@ def station_create(config_path, *args,
return dist_config_dict
def station_reconfigure(config_dict, no_backup=False, dry_run=False, *args, **kwargs):
def _calc_paths(weewx_root=None, rel_config_path=None):
"""When creating a station, the config path is specified relative to WEEWX_ROOT.
However, within the configuration file, WEEWX_ROOT is relative to the location
of the config file. So, we need to invert their relationships.
Args:
weewx_root (str): A path to the station data area.
rel_config_path (str): A path relative to weewx_root where the configuration
file will be located.
Returns:
tuple[str, str]: A 2-way tuple containing the absolute path to the config file, and
the path to the station data area, relative to the config file
"""
# Apply defaults if necessary:
if not weewx_root:
weewx_root = weecfg.default_weewx_root
if not rel_config_path:
rel_config_path = './weewx.conf'
# Convert to absolute paths
abs_weewx_root = os.path.abspath(weewx_root)
abs_config_path = os.path.abspath(os.path.join(abs_weewx_root, rel_config_path))
# Get WEEWX_ROOT relative to the directory the configuration file is sitting in:
final_weewx_root = os.path.relpath(weewx_root, os.path.dirname(abs_config_path))
# Don't let the relative path get out of hand. If we have to ascend more than two levels,
# use the absolute path:
if '../..' in final_weewx_root:
final_weewx_root = abs_weewx_root
if final_weewx_root == '.':
final_weewx_root = './'
return abs_config_path, final_weewx_root
def station_reconfigure(config_dict,
driver,
location,
altitude,
latitude, longitude,
register, station_url,
unit_system,
weewx_root,
skin_root,
sqlite_root,
html_root,
user_root,
no_prompt=False,
no_backup=False,
dry_run=False):
"""Reconfigure an existing station"""
config_config(config_dict['config_path'], config_dict, dry_run=dry_run, *args, **kwargs)
if weewx_root:
config_dict['WEEWX_ROOT'] = config_dict['WEEWX_ROOT_ORIG'] = weewx_root
config_config(config_dict,
config_path=config_dict['config_path'],
driver=driver,
location=location,
altitude=altitude,
latitude=latitude, longitude=longitude,
register=register, station_url=station_url,
unit_system=unit_system,
skin_root=skin_root,
sqlite_root=sqlite_root,
html_root=html_root,
user_root=user_root,
no_prompt=no_prompt)
print(f"Saving configuration file {config_dict['config_path']}")
if dry_run:
@@ -120,17 +185,20 @@ def station_reconfigure(config_dict, no_backup=False, dry_run=False, *args, **kw
print(f"Saved old configuration file as {backup_path}")
def config_config(config_path, config_dict,
driver=None, location=None,
altitude=None, latitude=None, longitude=None,
def config_config(config_dict,
config_path,
driver=None,
location=None,
altitude=None,
latitude=None, longitude=None,
register=None, station_url=None,
unit_system=None,
weewx_root=None, skin_root=None,
html_root=None, sqlite_root=None,
skin_root=None,
sqlite_root=None,
html_root=None,
user_root=None,
no_prompt=False,
dry_run=False):
"""Modify a configuration file."""
no_prompt=False):
"""Set the various options in a configuration file"""
print(f"Processing configuration file {config_path}")
config_location(config_dict, location=location, no_prompt=no_prompt)
config_altitude(config_dict, altitude=altitude, no_prompt=no_prompt)
@@ -138,7 +206,7 @@ def config_config(config_path, config_dict,
config_units(config_dict, unit_system=unit_system, no_prompt=no_prompt)
config_driver(config_dict, driver=driver, no_prompt=no_prompt)
config_registry(config_dict, register=register, station_url=station_url, no_prompt=no_prompt)
config_roots(config_dict, weewx_root, skin_root, html_root, sqlite_root, user_root)
config_roots(config_dict, skin_root, html_root, sqlite_root, user_root)
def config_location(config_dict, location=None, no_prompt=False):
@@ -456,15 +524,9 @@ def config_registry(config_dict, register=None, station_url=None, no_prompt=Fals
weecfg.inject_station_url(config_dict, final_station_url)
def config_roots(config_dict,
weewx_root=None,
skin_root=None,
html_root=None,
sqlite_root=None,
user_root=None):
def config_roots(config_dict, skin_root=None, html_root=None, sqlite_root=None, user_root=None):
"""Set the location of various root directories in the configuration dictionary."""
if weewx_root:
config_dict['WEEWX_ROOT'] = weewx_root
if user_root:
config_dict['USER_ROOT'] = user_root

View File

@@ -7,19 +7,19 @@
import os.path
import sys
import weecfg.station_config
import weecfg
import weectllib
import weectllib.station_actions
import weewx
from weeutil.weeutil import bcolors
station_create_usage = f"""{bcolors.BOLD}weectl station create
station_create_usage = f"""{bcolors.BOLD}weectl station create [WEEWX-ROOT]
[--driver=DRIVER]
[--location=LOCATION]
[--altitude=ALTITUDE,(foot|meter)]
[--latitude=LATITUDE] [--longitude=LONGITUDE]
[--register=(y,n) [--station-url=URL]]
[--units=(us|metricwx|metric)]
[--weewx-root=DIRECTORY]
[--skin-root=DIRECTORY]
[--sqlite-root=DIRECTORY]
[--html-root=DIRECTORY]
@@ -37,10 +37,11 @@ station_reconfigure_usage = f"""{bcolors.BOLD}weectl station reconfigure
[--latitude=LATITUDE] [--longitude=LONGITUDE]
[--register=(y,n) [--station-url=URL]]
[--units=(us|metricwx|metric)]
[--weewx-root=DIRECTORY]
[--skin-root=DIRECTORY]
[--sqlite-root=DIRECTORY]
[--html-root=DIRECTORY]
[--user-root=DIRECTORY]
[--weewx-root=DIRECTORY]
[--no-backup]
[--no-prompt]
[--config=FILENAME]
@@ -60,15 +61,18 @@ station_upgrade_usage = f"""{bcolors.BOLD}weectl station upgrade
station_usage = '\n '.join((station_create_usage, station_reconfigure_usage,
station_upgrade_usage))
WEEWX_ROOT_DESCRIPTION = f"""In what follows, {bcolors.BOLD}WEEWX_ROOT{bcolors.ENDC} is the
directory that contains the configuration file. For example, if
"--config={weecfg.default_config_path}", then WEEWX_ROOT will be "{weecfg.default_weewx_root}"."""
CREATE_DESCRIPTION = f"""Create a new station data area at the location WEEWX-ROOT. If WEEWX-ROOT
is not provided, the location {weecfg.default_weewx_root} will be used."""
CREATE_DESCRIPTION = """Create a new station data area, including a configuration file. """ \
+ WEEWX_ROOT_DESCRIPTION
RECONFIGURE_DESCRIPTION = f"""Reconfigure an existing configuration file at the location given
by the --config option. If the option is not provided, the location {weecfg.default_config_path}
will be used. Unless the --no-prompt option has been specified, the user will be prompted for
new values."""
UPGRADE_DESCRIPTION = """Upgrade an existing station data area, including any combination of the
examples, utility files, configuration file, and skins. """ + WEEWX_ROOT_DESCRIPTION
UPGRADE_DESCRIPTION = f"""Upgrade an existing station data area managed by the configuration file
given by the --config option. If the option is not provided, the location
{weecfg.default_config_path} will be used. Any combination of the examples, utility files,
configuration file, and skins can be upgraded, """
def add_subparser(subparsers):
@@ -85,59 +89,143 @@ def add_subparser(subparsers):
title='Which action to take')
# ---------- Action 'create' ----------
station_create_parser = action_parser.add_parser('create',
description=CREATE_DESCRIPTION,
usage=station_create_usage,
help='Create a new station data area, '
'including a configuration file.')
_add_common_args(station_create_parser)
station_create_parser.add_argument('--user-root',
metavar='DIRECTORY',
help='Where to put the "user" directory, relative to '
'WEEWX_ROOT. Default is "bin/user"')
station_create_parser.add_argument('--examples-root',
metavar='DIRECTORY',
help='Where to put the examples, relative to '
'WEEWX_ROOT. Default is "examples".')
station_create_parser.add_argument('--no-prompt', action='store_true',
help='Do not prompt. Use default values.')
station_create_parser.add_argument('--config',
metavar='FILENAME',
help=f'Path to configuration file. It must not already '
f'exist. Default is "{weecfg.default_config_path}".')
station_create_parser.add_argument('--dist-config',
metavar='FILENAME',
help='Use configuration file DIST-CONFIG-PATH as the '
'new configuration file. Default is to retrieve it '
'from package resources. The average user is '
'unlikely to need this option.')
station_create_parser.add_argument('--dry-run',
action='store_true',
help='Print what would happen, but do not actually '
'do it.')
station_create_parser.set_defaults(func=create_station)
create_parser = action_parser.add_parser('create',
description=CREATE_DESCRIPTION,
usage=station_create_usage,
help='Create a new station data area, including a '
'configuration file.')
create_parser.add_argument('--driver',
help='Driver to use. Default is "weewx.drivers.simulator".')
create_parser.add_argument('--location',
help='A description of the station. This will be used for report '
'titles. Default is "WeeWX".')
create_parser.add_argument('--altitude', metavar='ALTITUDE,{foot|meter}',
help='The station altitude in either feet or meters. For example, '
'"750,foot" or "320,meter". Default is "0, foot".')
create_parser.add_argument('--latitude',
help='The station latitude in decimal degrees. Default is "0.00".')
create_parser.add_argument('--longitude',
help='The station longitude in decimal degrees. Default is "0.00".')
create_parser.add_argument('--register', choices=['y', 'n'],
help='Register this station in the weewx registry? Default is "n" '
'(do not register).')
create_parser.add_argument('--station-url',
metavar='URL',
help='Unique URL to be used if registering the station. Required '
'if the station is to be registered.')
create_parser.add_argument('--units', choices=['us', 'metricwx', 'metric'],
dest='unit_system',
help='Set display units to us, metricwx, or metric. '
'Default is "us".')
create_parser.add_argument('--skin-root',
metavar='DIRECTORY',
help='Where to put the skins, relatve to WEEWX_ROOT. '
'Default is "skins".')
create_parser.add_argument('--sqlite-root',
metavar='DIRECTORY',
help='Where to put the SQLite database, relative to WEEWX_ROOT. '
'Default is "archive".')
create_parser.add_argument('--html-root',
metavar='DIRECTORY',
help='Where to put the generated HTML and images, relative to '
'WEEWX_ROOT. Default is "public_html".')
create_parser.add_argument('--user-root',
metavar='DIRECTORY',
help='Where to put the user extensions, relative to WEEWX_ROOT. '
'Default is "bin/user".')
create_parser.add_argument('--examples-root',
metavar='DIRECTORY',
help='Where to put the examples, relative to WEEWX_ROOT. '
'Default is "examples".')
create_parser.add_argument('--no-prompt', action='store_true',
help='Do not prompt. Use default values.')
create_parser.add_argument('--config',
metavar='FILENAME',
help='Where to put the configuration file, relative to WEEWX-ROOT. '
'It must not already exist. Default is "./weewx.conf".')
create_parser.add_argument('--dist-config',
metavar='FILENAME',
help='Use configuration file DIST-CONFIG-PATH as the new '
'configuration file. Default is to retrieve it from package '
'resources. The average user is unlikely to need this option.')
create_parser.add_argument('--dry-run',
action='store_true',
help='Print what would happen, but do not actually do it.')
create_parser.add_argument('weewx_root',
nargs='?',
metavar='WEEWX_ROOT',
help='Path to the WeeWX station data area to be created. '
f'Default is {weecfg.default_weewx_root}.')
create_parser.set_defaults(func=create_station)
# ---------- Action 'reconfigure' ----------
station_reconfigure_parser = \
reconfigure_parser = \
action_parser.add_parser('reconfigure',
description=RECONFIGURE_DESCRIPTION,
usage=station_reconfigure_usage,
help='Reconfigure a station configuration file.')
help='Reconfigure an existing station configuration file.')
_add_common_args(station_reconfigure_parser)
station_reconfigure_parser.add_argument('--no-backup', action='store_true',
help='Do not backup the old configuration file.')
station_reconfigure_parser.add_argument('--no-prompt', action='store_true',
help='Do not prompt. Use default values.')
station_reconfigure_parser.add_argument('--config',
metavar='FILENAME',
help=f'Path to configuration file. '
f'Default is "{weecfg.default_config_path}"')
station_reconfigure_parser.add_argument('--dry-run',
action='store_true',
help='Print what would happen, but do not actually '
'do it.')
station_reconfigure_parser.set_defaults(func=weectllib.dispatch)
station_reconfigure_parser.set_defaults(action_func=reconfigure_station)
reconfigure_parser.add_argument('--driver',
help='New driver to use. Default is the old driver.')
reconfigure_parser.add_argument('--location',
help='A new description for the station. This will be used '
'for report titles. Default is the old description.')
reconfigure_parser.add_argument('--altitude', metavar='ALTITUDE,{foot|meter}',
help='The new station altitude in either feet or meters. '
'For example, "750,foot" or "320,meter". '
'Default is the old altitude.')
reconfigure_parser.add_argument('--latitude',
help='The new station latitude in decimal degrees. '
'Default is the old latitude.')
reconfigure_parser.add_argument('--longitude',
help='The new station longitude in decimal degrees. '
'Default is the old longitude.')
reconfigure_parser.add_argument('--register', choices=['y', 'n'],
help='Register this station in the weewx registry? '
'Default is the old value.')
reconfigure_parser.add_argument('--station-url',
metavar='URL',
help='A new unique URL to be used if registering the . '
'station. Default is the old URL.')
reconfigure_parser.add_argument('--units', choices=['us', 'metricwx', 'metric'],
dest='unit_system',
help='New display units. Set to to us, metricwx, or metric. '
'Default is the old unit system.')
reconfigure_parser.add_argument('--skin-root',
metavar='DIRECTORY',
help='New location where to find the skins, relatve '
'to WEEWX_ROOT. Default is the old location.')
reconfigure_parser.add_argument('--sqlite-root',
metavar='DIRECTORY',
help='New location where to find the SQLite database, '
'relative to WEEWX_ROOT. Default is the old location.')
reconfigure_parser.add_argument('--html-root',
metavar='DIRECTORY',
help='New location where to put the generated HTML and '
'images, relative to WEEWX_ROOT. '
'Default is the old location.')
reconfigure_parser.add_argument('--user-root',
metavar='DIRECTORY',
help='New location where to find the user extensions, '
'relative to WEEWX_ROOT. Default is the old location.')
reconfigure_parser.add_argument('--weewx-root',
metavar='WEEWX_ROOT',
help='New path to the WeeWX station data area. '
'Default is the old path.')
reconfigure_parser.add_argument('--no-backup', action='store_true',
help='Do not backup the old configuration file.')
reconfigure_parser.add_argument('--no-prompt', action='store_true',
help='Do not prompt. Use default values.')
reconfigure_parser.add_argument('--config',
metavar='FILENAME',
help=f'Path to configuration file. '
f'Default is "{weecfg.default_config_path}"')
reconfigure_parser.add_argument('--dry-run',
action='store_true',
help='Print what would happen, but do not actually '
'do it.')
reconfigure_parser.set_defaults(func=weectllib.dispatch)
reconfigure_parser.set_defaults(action_func=reconfigure_station)
# ---------- Action 'upgrade' ----------
station_upgrade_parser = \
@@ -194,25 +282,27 @@ def add_subparser(subparsers):
def create_station(namespace):
"""Map 'namespace' to a call to station_create()"""
try:
config_dict = weecfg.station_config.station_create(config_path=namespace.config,
dist_config_path=namespace.dist_config,
driver=namespace.driver,
location=namespace.location,
altitude=namespace.altitude,
latitude=namespace.latitude,
longitude=namespace.longitude,
register=namespace.register,
station_url=namespace.station_url,
unit_system=namespace.unit_system,
weewx_root=namespace.weewx_root,
skin_root=namespace.skin_root,
sqlite_root=namespace.sqlite_root,
html_root=namespace.html_root,
examples_root=namespace.examples_root,
no_prompt=namespace.no_prompt,
dry_run=namespace.dry_run)
config_dict = weectllib.station_actions.station_create(
weewx_root=namespace.weewx_root,
rel_config_path=namespace.config,
driver=namespace.driver,
location=namespace.location,
altitude=namespace.altitude,
latitude=namespace.latitude,
longitude=namespace.longitude,
register=namespace.register,
station_url=namespace.station_url,
unit_system=namespace.unit_system,
skin_root=namespace.skin_root,
sqlite_root=namespace.sqlite_root,
html_root=namespace.html_root,
examples_root=namespace.examples_root,
user_root=namespace.user_root,
dist_config_path=namespace.dist_config,
no_prompt=namespace.no_prompt,
dry_run=namespace.dry_run)
except weewx.ViolatedPrecondition as e:
sys.exit(e)
sys.exit(str(e))
# if this is an operating system for which we have a setup script, then
# provide some guidance about running that script.
@@ -238,80 +328,33 @@ def create_station(namespace):
def reconfigure_station(config_dict, namespace):
"""Map namespace to a call to station_reconfigure()"""
try:
weecfg.station_config.station_reconfigure(config_dict=config_dict,
driver=namespace.driver,
location=namespace.location,
altitude=namespace.altitude,
latitude=namespace.latitude,
longitude=namespace.longitude,
register=namespace.register,
station_url=namespace.station_url,
unit_system=namespace.unit_system,
weewx_root=namespace.weewx_root,
skin_root=namespace.skin_root,
sqlite_root=namespace.sqlite_root,
html_root=namespace.html_root,
no_prompt=namespace.no_prompt,
no_backup=namespace.no_backup,
dry_run=namespace.dry_run)
weectllib.station_actions.station_reconfigure(config_dict=config_dict,
driver=namespace.driver,
location=namespace.location,
altitude=namespace.altitude,
latitude=namespace.latitude,
longitude=namespace.longitude,
register=namespace.register,
station_url=namespace.station_url,
unit_system=namespace.unit_system,
weewx_root=namespace.weewx_root,
skin_root=namespace.skin_root,
sqlite_root=namespace.sqlite_root,
html_root=namespace.html_root,
user_root=namespace.user_root,
no_prompt=namespace.no_prompt,
no_backup=namespace.no_backup,
dry_run=namespace.dry_run)
except weewx.ViolatedPrecondition as e:
sys.exit(e)
sys.exit(str(e))
def upgrade_station(config_dict, namespace):
weecfg.station_config.station_upgrade(config_dict=config_dict,
dist_config_path=namespace.dist_config,
examples_root=namespace.examples_root,
skin_root=namespace.skin_root,
what=namespace.what,
no_prompt=namespace.no_prompt,
no_backup=namespace.no_backup,
dry_run=namespace.dry_run)
# ==============================================================================
# Utilities
# ==============================================================================
def _add_common_args(parser):
"""Add common arguments"""
parser.add_argument('--driver',
help='Driver to use. Default is "weewx.drivers.simulator".')
parser.add_argument('--location',
help='A description of the station. This will be used '
'for report titles. Default is "WeeWX".')
parser.add_argument('--altitude', metavar='ALTITUDE,{foot|meter}',
help='The station altitude in either feet or meters. '
'For example, "750,foot" or "320,meter". '
'Default is "0, foot".')
parser.add_argument('--latitude',
help='The station latitude in decimal degrees. '
'Default is "0.00".')
parser.add_argument('--longitude',
help='The station longitude in decimal degrees. '
'Default is "0.00".')
parser.add_argument('--register', choices=['y', 'n'],
help='Register this station in the weewx registry? '
'Default is "n" (do not register).')
parser.add_argument('--station-url',
metavar='URL',
help='Unique URL to be used if registering the station. '
'Required if the station is to be registered.')
parser.add_argument('--units', choices=['us', 'metricwx', 'metric'],
dest='unit_system',
help='Set display units to us, metricwx, or metric. '
'Default is "us".')
parser.add_argument('--weewx-root',
metavar='DIRECTORY',
help="Location of WEEWX_ROOT.")
parser.add_argument('--skin-root',
metavar='DIRECTORY',
help='Where to put the skins, relatve to WEEWX_ROOT. Default is "skins".')
parser.add_argument('--sqlite-root',
metavar='DIRECTORY',
help='Where to put the SQLite database, relative to WEEWX_ROOT. '
'Default is "archive".')
parser.add_argument('--html-root',
metavar='DIRECTORY',
help='Where to put the generated HTML and images, relative to WEEWX_ROOT. '
'Default is "public_html".')
weectllib.station_actions.station_upgrade(config_dict=config_dict,
dist_config_path=namespace.dist_config,
examples_root=namespace.examples_root,
skin_root=namespace.skin_root,
what=namespace.what,
no_prompt=namespace.no_prompt,
no_backup=namespace.no_backup,
dry_run=namespace.dry_run)

View File

@@ -13,9 +13,7 @@ from unittest.mock import patch
import configobj
import weecfg.extension
import weecfg.station_config
import weecfg.update_config
import weectllib.station_actions
import weeutil.config
import weeutil.weeutil
import weewx
@@ -48,75 +46,75 @@ class CommonConfigTest(unittest.TestCase):
class LocationConfigTest(CommonConfigTest):
def test_default_config_location(self):
weecfg.station_config.config_location(self.config_dict, no_prompt=True)
weectllib.station_actions.config_location(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['Station']['location'], "WeeWX station")
del self.config_dict['Station']['location']
weecfg.station_config.config_location(self.config_dict, no_prompt=True)
weectllib.station_actions.config_location(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['Station']['location'], "WeeWX station")
def test_arg_config_location(self):
weecfg.station_config.config_location(self.config_dict, location='foo', no_prompt=True)
weectllib.station_actions.config_location(self.config_dict, location='foo', no_prompt=True)
self.assertEqual(self.config_dict['Station']['location'], "foo")
@suppress_stdout
def test_prompt_config_location(self):
with patch('weecfg.station_config.input', side_effect=['']):
weecfg.station_config.config_location(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['']):
weectllib.station_actions.config_location(self.config_dict)
self.assertEqual(self.config_dict['Station']['location'], "WeeWX station")
with patch('weecfg.station_config.input', side_effect=['bar']):
weecfg.station_config.config_location(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['bar']):
weectllib.station_actions.config_location(self.config_dict)
self.assertEqual(self.config_dict['Station']['location'], "bar")
class AltitudeConfigTest(CommonConfigTest):
def test_default_config_altitude(self):
weecfg.station_config.config_altitude(self.config_dict, no_prompt=True)
weectllib.station_actions.config_altitude(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['Station']['altitude'], ["0", "foot"])
# Delete the value in the configuration dictionary
del self.config_dict['Station']['altitude']
# Now we should get the hardwired default
weecfg.station_config.config_altitude(self.config_dict, no_prompt=True)
weectllib.station_actions.config_altitude(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['Station']['altitude'], ["0", "foot"])
def test_arg_config_altitude(self):
weecfg.station_config.config_altitude(self.config_dict, altitude="500, meter")
weectllib.station_actions.config_altitude(self.config_dict, altitude="500, meter")
self.assertEqual(self.config_dict['Station']['altitude'], ["500", "meter"])
def test_badarg_config_altitude(self):
with self.assertRaises(ValueError):
# Bad unit
weecfg.station_config.config_altitude(self.config_dict, altitude="500, foo")
weectllib.station_actions.config_altitude(self.config_dict, altitude="500, foo")
with self.assertRaises(ValueError):
# Bad value
weecfg.station_config.config_altitude(self.config_dict, altitude="500f, foot")
weectllib.station_actions.config_altitude(self.config_dict, altitude="500f, foot")
@suppress_stdout
def test_prompt_config_altitude(self):
with patch('weecfg.station_config.input', side_effect=['']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["0", "foot"])
with patch('weecfg.station_config.input', side_effect=['110, meter']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['110, meter']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["110", "meter"])
# Try 'feet' instead of 'foot'
with patch('weecfg.station_config.input', side_effect=['700, feet']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['700, feet']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["700", "foot"])
# Try 'meters' instead of 'meter':
with patch('weecfg.station_config.input', side_effect=['110, meters']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['110, meters']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["110", "meter"])
@suppress_stdout
def test_badprompt_config_altitude(self):
# Include a bad unit. It should prompt again
with patch('weecfg.station_config.input', side_effect=['100, foo', '110, meter']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['100, foo', '110, meter']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["110", "meter"])
# Include a bad value. It should prompt again
with patch('weecfg.station_config.input', side_effect=['100f, foot', '110, meter']):
weecfg.station_config.config_altitude(self.config_dict)
with patch('weectllib.station_actions.input', side_effect=['100f, foot', '110, meter']):
weectllib.station_actions.config_altitude(self.config_dict)
self.assertEqual(self.config_dict['Station']['altitude'], ["110", "meter"])
@@ -124,30 +122,31 @@ class LatLonConfigTest(CommonConfigTest):
def test_default_config_latlon(self):
# Use the default as supplied by CONFIG_DICT
weecfg.station_config.config_latlon(self.config_dict, no_prompt=True)
weectllib.station_actions.config_latlon(self.config_dict, no_prompt=True)
self.assertEqual(float(self.config_dict['Station']['latitude']), 0.0)
self.assertEqual(float(self.config_dict['Station']['longitude']), 0.0)
# Delete the values in the configuration dictionary
del self.config_dict['Station']['latitude']
del self.config_dict['Station']['longitude']
# Now the defaults should be the hardwired defaults
weecfg.station_config.config_latlon(self.config_dict, no_prompt=True)
weectllib.station_actions.config_latlon(self.config_dict, no_prompt=True)
self.assertEqual(float(self.config_dict['Station']['latitude']), 0.0)
self.assertEqual(float(self.config_dict['Station']['longitude']), 0.0)
def test_arg_config_latlon(self):
weecfg.station_config.config_latlon(self.config_dict, latitude='-20', longitude='-40')
weectllib.station_actions.config_latlon(self.config_dict, latitude='-20', longitude='-40')
self.assertEqual(float(self.config_dict['Station']['latitude']), -20.0)
self.assertEqual(float(self.config_dict['Station']['longitude']), -40.0)
def test_badarg_config_latlon(self):
with self.assertRaises(ValueError):
weecfg.station_config.config_latlon(self.config_dict, latitude="-20f", longitude='-40')
weectllib.station_actions.config_latlon(self.config_dict, latitude="-20f",
longitude='-40')
@suppress_stdout
def test_prompt_config_latlong(self):
with patch('weecfg.input', side_effect=['-21', '-41']):
weecfg.station_config.config_latlon(self.config_dict)
weectllib.station_actions.config_latlon(self.config_dict)
self.assertEqual(float(self.config_dict['Station']['latitude']), -21.0)
self.assertEqual(float(self.config_dict['Station']['longitude']), -41.0)
@@ -155,23 +154,24 @@ class LatLonConfigTest(CommonConfigTest):
class RegistryConfigTest(CommonConfigTest):
def test_default_register(self):
weecfg.station_config.config_registry(self.config_dict, no_prompt=True)
weectllib.station_actions.config_registry(self.config_dict, no_prompt=True)
self.assertFalse(
self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
def test_args_register(self):
# Missing station_url:
with self.assertRaises(weewx.ViolatedPrecondition):
weecfg.station_config.config_registry(self.config_dict, register='True',
no_prompt=True)
weectllib.station_actions.config_registry(self.config_dict, register='True',
no_prompt=True)
# This time we supply a station_url. Should be OK.
weecfg.station_config.config_registry(self.config_dict, register='True',
station_url=STATION_URL, no_prompt=True)
weectllib.station_actions.config_registry(self.config_dict, register='True',
station_url=STATION_URL, no_prompt=True)
self.assertTrue(self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
self.assertEqual(self.config_dict['Station']['station_url'], STATION_URL)
# Alternatively, the config file already had a station_url:
self.config_dict['Station']['station_url'] = STATION_URL
weecfg.station_config.config_registry(self.config_dict, register='True', no_prompt=True)
weectllib.station_actions.config_registry(self.config_dict, register='True',
no_prompt=True)
self.assertTrue(self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
self.assertEqual(self.config_dict['Station']['station_url'], STATION_URL)
@@ -179,21 +179,21 @@ class RegistryConfigTest(CommonConfigTest):
def test_prompt_register(self):
with patch('weeutil.weeutil.input', side_effect=['y']):
with patch('weecfg.input', side_effect=[STATION_URL]):
weecfg.station_config.config_registry(self.config_dict)
weectllib.station_actions.config_registry(self.config_dict)
self.assertTrue(self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
self.assertEqual(self.config_dict['Station']['station_url'], STATION_URL)
# Try again, but without specifying an URL. Should ask twice.
with patch('weeutil.weeutil.input', side_effect=['y']):
with patch('weecfg.input', side_effect=["", STATION_URL]):
weecfg.station_config.config_registry(self.config_dict)
weectllib.station_actions.config_registry(self.config_dict)
self.assertTrue(self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
self.assertEqual(self.config_dict['Station']['station_url'], STATION_URL)
# Now with a bogus URL
with patch('weeutil.weeutil.input', side_effect=['y']):
with patch('weecfg.input', side_effect=['https://www.example.com', STATION_URL]):
weecfg.station_config.config_registry(self.config_dict)
weectllib.station_actions.config_registry(self.config_dict)
self.assertTrue(self.config_dict['StdRESTful']['StationRegistry']['register_this_station'])
self.assertEqual(self.config_dict['Station']['station_url'], STATION_URL)
@@ -201,40 +201,40 @@ class RegistryConfigTest(CommonConfigTest):
class UnitsConfigTest(CommonConfigTest):
def test_default_units(self):
weecfg.station_config.config_units(self.config_dict, no_prompt=True)
weectllib.station_actions.config_units(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['StdReport']['Defaults']['unit_system'], 'us')
def test_custom_units(self):
del self.config_dict['StdReport']['Defaults']['unit_system']
weecfg.station_config.config_units(self.config_dict, no_prompt=True)
weectllib.station_actions.config_units(self.config_dict, no_prompt=True)
self.assertNotIn('unit_system', self.config_dict['StdReport']['Defaults'])
def test_args_units(self):
weecfg.station_config.config_units(self.config_dict, unit_system='metricwx',
no_prompt=True)
weectllib.station_actions.config_units(self.config_dict, unit_system='metricwx',
no_prompt=True)
self.assertEqual(self.config_dict['StdReport']['Defaults']['unit_system'], 'metricwx')
@suppress_stdout
def test_prompt_units(self):
with patch('weecfg.input', side_effect=['metricwx']):
weecfg.station_config.config_units(self.config_dict)
weectllib.station_actions.config_units(self.config_dict)
self.assertEqual(self.config_dict['StdReport']['Defaults']['unit_system'], 'metricwx')
# Do it again, but with a wrong unit system name. It should ask again.
with patch('weecfg.input', side_effect=['metricwz', 'metricwx']):
weecfg.station_config.config_units(self.config_dict)
weectllib.station_actions.config_units(self.config_dict)
self.assertEqual(self.config_dict['StdReport']['Defaults']['unit_system'], 'metricwx')
class DriverConfigTest(CommonConfigTest):
def test_default_config_driver(self):
weecfg.station_config.config_driver(self.config_dict, no_prompt=True)
weectllib.station_actions.config_driver(self.config_dict, no_prompt=True)
self.assertEqual(self.config_dict['Station']['station_type'], 'Simulator')
self.assertEqual(self.config_dict['Simulator']['driver'], 'weewx.drivers.simulator')
def test_arg_config_driver(self):
weecfg.station_config.config_driver(self.config_dict, driver='weewx.drivers.vantage',
no_prompt=True)
weectllib.station_actions.config_driver(self.config_dict, driver='weewx.drivers.vantage',
no_prompt=True)
self.assertEqual(self.config_dict['Station']['station_type'], 'Vantage')
self.assertEqual(self.config_dict['Vantage']['driver'], 'weewx.drivers.vantage')
@@ -246,9 +246,9 @@ class DriverConfigTest(CommonConfigTest):
del weewx.drivers.vantage.confeditor_loader
# At this point, there is no configuration loader, so a minimal version of [Vantage]
# should be supplied.
weecfg.station_config.config_driver(self.config_dict,
driver='weewx.drivers.vantage',
no_prompt=True)
weectllib.station_actions.config_driver(self.config_dict,
driver='weewx.drivers.vantage',
no_prompt=True)
self.assertEqual(self.config_dict['Station']['station_type'], 'Vantage')
self.assertEqual(self.config_dict['Vantage']['driver'], 'weewx.drivers.vantage')
# The rest of the [Vantage] stanza should be missing. Try a key.
@@ -259,13 +259,13 @@ class DriverConfigTest(CommonConfigTest):
@suppress_stdout
def test_prompt_config_driver(self):
with patch('weecfg.input', side_effect=['6', '', '/dev/ttyS0']):
weecfg.station_config.config_driver(self.config_dict)
weectllib.station_actions.config_driver(self.config_dict)
self.assertEqual(self.config_dict['Station']['station_type'], 'Vantage')
self.assertEqual(self.config_dict['Vantage']['port'], '/dev/ttyS0')
# Do it again. This time, the stanza ['Vantage'] will exist, and we'll just modify it
with patch('weecfg.input', side_effect=['', '', '/dev/ttyS1']):
weecfg.station_config.config_driver(self.config_dict)
weectllib.station_actions.config_driver(self.config_dict)
self.assertEqual(self.config_dict['Station']['station_type'], 'Vantage')
self.assertEqual(self.config_dict['Vantage']['port'], '/dev/ttyS1')
@@ -273,8 +273,8 @@ class DriverConfigTest(CommonConfigTest):
class TestConfigRoots(CommonConfigTest):
def test_args_config_roots(self):
weecfg.station_config.config_roots(self.config_dict, skin_root='foo',
html_root='bar', sqlite_root='baz')
weectllib.station_actions.config_roots(self.config_dict, skin_root='foo',
html_root='bar', sqlite_root='baz')
self.assertEqual(self.config_dict['StdReport']['SKIN_ROOT'], 'foo')
self.assertEqual(self.config_dict['StdReport']['HTML_ROOT'], 'bar')
self.assertEqual(self.config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'], 'baz')
@@ -283,7 +283,7 @@ class TestConfigRoots(CommonConfigTest):
del self.config_dict['StdReport']['SKIN_ROOT']
del self.config_dict['StdReport']['HTML_ROOT']
del self.config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT']
weecfg.station_config.config_roots(self.config_dict)
weectllib.station_actions.config_roots(self.config_dict)
self.assertEqual(self.config_dict['StdReport']['SKIN_ROOT'], 'skins')
self.assertEqual(self.config_dict['StdReport']['HTML_ROOT'], 'public_html')
self.assertEqual(self.config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'],
@@ -295,29 +295,30 @@ class TestCreateStation(unittest.TestCase):
def test_create_default(self):
"Test creating a new station"
# Get a temporary directory to create it in
with tempfile.TemporaryDirectory(dir='/var/tmp') as dirname:
config_path = os.path.join(dirname, 'weewx.conf')
with tempfile.TemporaryDirectory(dir='/var/tmp') as weewx_root:
# We have not run 'pip', so the only copy of weewxd.py is the one in the repository.
# Create a station using the defaults
weecfg.station_config.station_create(config_path, no_prompt=True)
weectllib.station_actions.station_create(weewx_root=weewx_root, no_prompt=True)
config_path = os.path.join(weewx_root, 'weewx.conf')
# Retrieve the config file that was created and check it:
config_dict = configobj.ConfigObj(config_path, encoding='utf-8')
self.assertEqual(config_dict['WEEWX_ROOT'], dirname)
self.assertEqual(config_dict['WEEWX_ROOT'], './')
self.assertNotIn('WEEWX_ROOT_ORIG', config_dict)
self.assertEqual(config_dict['Station']['station_type'], 'Simulator')
self.assertEqual(config_dict['Simulator']['driver'], 'weewx.drivers.simulator')
self.assertEqual(config_dict['StdReport']['SKIN_ROOT'], 'skins')
self.assertEqual(config_dict['StdReport']['HTML_ROOT'], 'public_html')
self.assertEqual(config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'], 'archive')
self.assertEqual(config_dict['StdReport']['SKIN_ROOT'], './skins')
self.assertEqual(config_dict['StdReport']['HTML_ROOT'], './public_html')
self.assertEqual(config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'], './archive')
# Make sure all the skins are there
for skin in ['Seasons', 'Smartphone', 'Mobile', 'Standard',
'Ftp', 'Rsync']:
p = os.path.join(dirname, config_dict['StdReport']['SKIN_ROOT'], skin)
p = os.path.join(weewx_root, config_dict['StdReport']['SKIN_ROOT'], skin)
self.assertTrue(os.path.isdir(p))
# Retrieve the systemd utility file and check it
path = os.path.join(dirname, 'util/systemd/weewx.service')
path = os.path.join(weewx_root, 'util/systemd/weewx.service')
with open(path, 'rt') as fd:
for line in fd:
if line.startswith('ExecStart'):

View File

@@ -4,8 +4,8 @@
# See the file LICENSE.txt for your full rights.
#
import unittest
import datetime
import unittest
from weectllib import parse_dates

View File

@@ -10,7 +10,7 @@
# Set to 1 for extra debug info, otherwise comment it out or set to zero
debug = 0
# Root directory of the weewx data file hierarchy for this station
# Root directory of the weewx station data area, relative to this file.
WEEWX_ROOT = ./
# Whether to log successful operations. May get overridden below.