Make SQLITE_ROOT relative to WEEWX_ROOT.

Instead of an absolute path.
This commit is contained in:
Tom Keffer
2022-12-27 11:48:34 -08:00
parent e129a14d67
commit 35c7b61b8e
10 changed files with 191 additions and 124 deletions

26
TODO.md
View File

@@ -10,6 +10,10 @@ Change default directory to `~/weewx-data`.
Check for validity of station_url.
SQLITE_ROOT is now relative to WEEWX_ROOT.
Get rid of all the config update stuff.
## Obsolete
@@ -26,4 +30,24 @@ weectl daemon uninstall
weectl extension install
weectl extension uninstall
weectl extension list
```
```
## Documentation
Update "where to find things" in user's guide.
Use a dollar sign when referring to symbolic names. For example, $WEEWX_ROOT instead of WEEWX_ROOT.
Note that SQLITE is now relative to WEEWX_ROOT.
## Testing
Try doctest in `manager.py`.
## Upgrade guide
Note that SQLITE is now relative to WEEWX_ROOT.
Note the change in the weedb.sqlite API. Can't imagine it will affect anyone.
Installs to ~/weewx-data now.

View File

@@ -4212,7 +4212,7 @@ ephem.Eros = eros</pre>
[[SQLite]]
driver = weedb.sqlite
# Directory in which the database files are located
SQLITE_ROOT = %(WEEWX_ROOT)s/archive
SQLITE_ROOT = archive
[[MySQL]]
driver = weedb.mysql

View File

@@ -3022,9 +3022,10 @@ longitude = -77.0366</pre>
<p class="config_option">SQLITE_ROOT</p>
<p>
The location of the directory holding the SQLite databases. For <span class="code">setup.py</span>
installations, the default is the <span class="symcode">WEEWX_ROOT</span><span class="code">/archive</span>
directory. For DEB or RPM installations, it is <span class="code">/var/lib/weewx</span>.
The location of the directory holding the SQLite databases, relative to
<span class="symcode">WEEWX_ROOT</span>. For <span class="code">setup.py</span>
installations, the default is <span class="code">archive</span>.
For DEB or RPM installations, it is <span class="code">/var/lib/weewx</span>.
</p>
<p class="config_option" id='archive_timeout'>timeout</p>

View File

@@ -457,8 +457,8 @@ version = 4.10.0a1
# Defaults for SQLite databases
[[SQLite]]
driver = weedb.sqlite
# Directory in which the database files are located
SQLITE_ROOT = %(WEEWX_ROOT)s/archive
# Directory in which the database files are located, relative to WEEWX_ROOT:
SQLITE_ROOT = archive
# Defaults for MySQL databases
[[MySQL]]

View File

@@ -413,7 +413,7 @@ def config_roots(config_dict, weewx_root=None, skin_root=None, html_root=None, s
if sqlite_root:
config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'] = sqlite_root
elif 'SQLITE_ROOT' not in config_dict['DatabaseTypes']['SQLite']:
config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'] = '%(WEEWX_ROOT)s/archive'
config_dict['DatabaseTypes']['SQLite']['SQLITE_ROOT'] = 'archive'
# Turn interpolation back on.
config_dict.interpolation = hold

0
bin/weectl.py Normal file → Executable file
View File

View File

@@ -17,7 +17,6 @@ Weedb generally follows the MySQL exception model. Specifically:
"""
import importlib
import sys
# The exceptions that the weedb package can raise:
class DatabaseError(Exception):
@@ -205,3 +204,77 @@ class Transaction(object):
except DatabaseError:
pass
def get_database_dict_from_config(config_dict, database):
"""Convenience function that given a configuration dictionary and a database name,
returns a database dictionary that can be used to open the database using Manager.open().
Args:
config_dict (dict): The configuration dictionary.
database (str): The database whose database dict is to be retrieved
(example: 'archive_sqlite')
Returns:
dict: A database dictionary, with everything needed to pass on to a Manager or weedb in
order to open a database.
Example:
Given a configuration file snippet that looks like:
>>> import configobj
>>> from six.moves import StringIO
>>> config_snippet = '''
... WEEWX_ROOT = /home/weewx
... [DatabaseTypes]
... [[SQLite]]
... driver = weedb.sqlite
... SQLITE_ROOT = archive
... [Databases]
... [[archive_sqlite]]
... database_name = weewx.sdb
... database_type = SQLite'''
>>> config_dict = configobj.ConfigObj(StringIO(config_snippet))
>>> database_dict = get_database_dict_from_config(config_dict, 'archive_sqlite')
>>> keys = sorted(database_dict.keys())
>>> for k in keys:
... print("%15s: %12s" % (k, database_dict[k]))
SQLITE_ROOT: /home/weewx/archive
database_name: weewx.sdb
driver: weedb.sqlite
"""
import weewx
import weeutil.config
try:
database_dict = dict(config_dict['Databases'][database])
except KeyError as e:
raise weewx.UnknownDatabase("Unknown database '%s'" % e)
# See if a 'database_type' is specified. This is something
# like 'SQLite' or 'MySQL'. If it is, use it to augment any
# missing information in the database_dict:
if 'database_type' in database_dict:
database_type = database_dict.pop('database_type')
# Augment any missing information in the database dictionary with
# the top-level stanza
if database_type in config_dict['DatabaseTypes']:
weeutil.config.conditional_merge(database_dict,
config_dict['DatabaseTypes'][database_type])
else:
raise weewx.UnknownDatabaseType('database_type')
# Import the driver and see if it wants to modify the database dictionary
db_mod = importlib.import_module(database_dict['driver'])
if hasattr(db_mod, 'modify_config'):
database_dict = getattr(db_mod, 'modify_config')(config_dict, database_dict)
return database_dict
if __name__ == '__main__':
import doctest
if not doctest.testmod().failed:
print("PASSED")

View File

@@ -1,11 +1,10 @@
#
# Copyright (c) 2009-2022 Tom Keffer <tkeffer@gmail.com>
# Copyright (c) 2009-2023 Tom Keffer <tkeffer@gmail.com>
#
# See the file LICENSE.txt for your full rights.
#
"""weedb driver for sqlite"""
from __future__ import with_statement
import os.path
# Import sqlite3. If it does not support the 'with' statement, then
@@ -57,91 +56,95 @@ def guard(fn):
return guarded_fn
def connect(database_name='', SQLITE_ROOT='', driver='', **argv): # @UnusedVariable
"""Factory function, to keep things compatible with DBAPI. """
return Connection(database_name=database_name, SQLITE_ROOT=SQLITE_ROOT, **argv)
def connect(db_path='', driver='', **argv): # @UnusedVariable
"""Factory function, to keep things compatible with DBAPI.
Args:
db_path(str): A path to a SQLite database file
driver(str): The weedb driver to use
argv(dict): Any additional arguments to be passed on to the underlying Python driver
Returns:
Connection: An open weedb.sqlite.Connection connection.
"""
return Connection(db_path=db_path, **argv)
@guard
def create(database_name='', SQLITE_ROOT='', driver='', **argv): # @UnusedVariable
"""Create the database specified by the db_dict. If it already exists,
an exception of type DatabaseExistsError will be thrown."""
file_path = _get_filepath(SQLITE_ROOT, database_name, **argv)
def create(db_path='', driver='', **argv):
"""Create a SQLite database.
Args:
db_path(str): A path to where the SQLite database file should be created.
driver(str): The weedb driver to use.
argv(dict): Any additional arguments to be passed on to the underlying Python
sqlite3 driver.
Raises:
DatabaseExistsError: If the SQLite database already exists.
"""
# Check whether the database file exists:
if os.path.exists(file_path):
raise weedb.DatabaseExistsError("Database %s already exists" % (file_path,))
if os.path.exists(db_path):
raise weedb.DatabaseExistsError(f"Database {db_path} already exists")
else:
if file_path != ':memory:':
# If it doesn't exist, create the parent directories
fileDirectory = os.path.dirname(file_path)
if not os.path.exists(fileDirectory):
try:
os.makedirs(fileDirectory)
except OSError:
raise weedb.PermissionError("No permission to create %s" % fileDirectory)
if db_path != ':memory:':
# Make any directories required, if any
sqlite_dir = os.path.dirname(db_path)
try:
os.makedirs(sqlite_dir, exist_ok=True)
except OSError:
raise weedb.PermissionError(f"Cannot create {sqlite_dir}")
timeout = to_int(argv.get('timeout', 5))
isolation_level = argv.get('isolation_level')
# Open, then immediately close the database.
connection = sqlite3.connect(file_path, timeout=timeout, isolation_level=isolation_level)
connection = sqlite3.connect(db_path, timeout=timeout, isolation_level=isolation_level)
connection.close()
def drop(database_name='', SQLITE_ROOT='', driver='', **argv): # @UnusedVariable
file_path = _get_filepath(SQLITE_ROOT, database_name, **argv)
def drop(db_path, driver='', **argv):
try:
os.remove(file_path)
os.remove(db_path)
except OSError as e:
errno = getattr(e, 'errno', 2)
if errno == 13:
raise weedb.PermissionError("No permission to drop database %s" % file_path)
raise weedb.PermissionError(f"No permission to drop database {db_path}")
else:
raise weedb.NoDatabaseError("Attempt to drop non-existent database %s" % file_path)
def _get_filepath(SQLITE_ROOT, database_name, **argv):
"""Utility function to calculate the path to the sqlite database file."""
if database_name == ':memory:':
return database_name
# For backwards compatibility, allow the keyword 'root', if 'SQLITE_ROOT' is
# not defined:
root_dir = SQLITE_ROOT or argv.get('root', '')
return os.path.join(root_dir, database_name)
raise weedb.NoDatabaseError(f"Attempt to drop non-existent database {db_path}")
class Connection(weedb.Connection):
"""A wrapper around a sqlite3 connection object."""
@guard
def __init__(self, database_name='', SQLITE_ROOT='', pragmas=None, **argv):
def __init__(self, db_path='', pragmas={}, **argv):
"""Initialize an instance of Connection.
Parameters:
Args:
database_name: The name of the Sqlite database. This is generally the file name
SQLITE_ROOT: The path to the directory holding the database. Joining "SQLITE_ROOT" with
"database_name" results in the full path to the sqlite file.
pragmas: Any pragma statements, in the form of a dictionary.
timeout: The amount of time, in seconds, to wait for a lock to be released.
db_path(str): The path to the SQLite database file.
pragmas(dict): Any pragma statements, in the form of a dictionary.
timeout(float): The amount of time, in seconds, to wait for a lock to be released.
Optional. Default is 5.
isolation_level: The type of isolation level to use. One of None,
isolation_level(str): The type of isolation level to use. One of None,
DEFERRED, IMMEDIATE, or EXCLUSIVE. Default is None (autocommit mode).
If the operation fails, an exception of type weedb.OperationalError will be raised.
Raises:
NoDatabaseError: If the database file does not exist.
"""
self.file_path = _get_filepath(SQLITE_ROOT, database_name, **argv)
if self.file_path != ':memory:' and not os.path.exists(self.file_path):
raise weedb.NoDatabaseError("Attempt to open a non-existent database %s"
% self.file_path)
self.db_path = db_path
if self.db_path != ':memory:' and not os.path.exists(db_path):
raise weedb.NoDatabaseError(f"Attempt to open a non-existent database {db_path}")
timeout = to_int(argv.get('timeout', 5))
isolation_level = argv.get('isolation_level')
connection = sqlite3.connect(self.file_path, timeout=timeout,
isolation_level=isolation_level)
connection = sqlite3.connect(db_path, timeout=timeout, isolation_level=isolation_level)
if pragmas is not None:
for pragma in pragmas:
connection.execute("PRAGMA %s=%s;" % (pragma, pragmas[pragma]))
weedb.Connection.__init__(self, connection, database_name, 'sqlite')
for pragma in pragmas:
connection.execute("PRAGMA %s=%s;" % (pragma, pragmas[pragma]))
# Wrap the sqlite3 connection in a weedb Connection object
weedb.Connection.__init__(self, connection, os.path.basename(db_path), 'sqlite')
@guard
def cursor(self):
@@ -295,3 +298,24 @@ class Cursor(sqlite3.Cursor):
# It is not an error to close a sqlite3 cursor multiple times,
# so there's no reason to guard it with a "try" clause:
self.close()
def modify_config(config_dict, database_dict):
"""Modify the database dictionary returned by weedb.get_database_dict_from_config into
something that suits our on purposes.
"""
# Make a copy
db_dict = dict(database_dict)
# Very old versions of weedb used option 'root', instead of 'SQLITE_ROOT'
sqlite_dir = db_dict.get('SQLITE_ROOT', db_dict.get('root', 'archive'))
db_path = os.path.join(config_dict['WEEWX_ROOT'],
sqlite_dir,
db_dict['database_name'])
db_dict['db_path'] = db_path
# These are no longer needed:
db_dict.pop('SQLITE_ROOT', None)
db_dict.pop('root', None)
return db_dict

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2009-2022 Tom Keffer <tkeffer@gmail.com>
# Copyright (c) 2009-2023 Tom Keffer <tkeffer@gmail.com>
#
# See the file LICENSE.txt for your full rights.
#
@@ -20,7 +20,7 @@ import weedb
import weedb.sqlite
from weeutil.weeutil import version_compare
sqlite_db_dict = {'database_name': '/var/tmp/test.sdb', 'driver': 'weedb.sqlite', 'timeout': '2'}
sqlite_db_dict = {'db_path': '/var/tmp/test.sdb', 'driver': 'weedb.sqlite', 'timeout': '2'}
mysql_db_dict = {'database_name': 'test_weewx1', 'user': 'weewx1', 'password': 'weewx1',
'driver': 'weedb.mysql'}

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2009-2022 Tom Keffer <tkeffer@gmail.com>
# Copyright (c) 2009-2023 Tom Keffer <tkeffer@gmail.com>
#
# See the file LICENSE.txt for your full rights.
#
@@ -783,63 +783,8 @@ default_binding_dict = {'database': 'archive_sqlite',
def get_database_dict_from_config(config_dict, database):
"""Convenience function that given a configuration dictionary and a database name,
returns a database dictionary that can be used to open the database using Manager.open().
Args:
config_dict (dict): The configuration dictionary.
database (str): The database whose database dict is to be retrieved
(example: 'archive_sqlite')
Returns:
dict: Adatabase dictionary, with everything needed to pass on to a Manager or weedb in
order to open a database.
Example:
Given a configuration file snippet that looks like:
>>> import configobj
>>> from six.moves import StringIO
>>> config_snippet = '''
... WEEWX_ROOT = /home/weewx
... [DatabaseTypes]
... [[SQLite]]
... driver = weedb.sqlite
... SQLITE_ROOT = %(WEEWX_ROOT)s/archive
... [Databases]
... [[archive_sqlite]]
... database_name = weewx.sdb
... database_type = SQLite'''
>>> config_dict = configobj.ConfigObj(StringIO(config_snippet))
>>> database_dict = get_database_dict_from_config(config_dict, 'archive_sqlite')
>>> keys = sorted(database_dict.keys())
>>> for k in keys:
... print("%15s: %12s" % (k, database_dict[k]))
SQLITE_ROOT: /home/weewx/archive
database_name: weewx.sdb
driver: weedb.sqlite
"""
try:
database_dict = dict(config_dict['Databases'][database])
except KeyError as e:
raise weewx.UnknownDatabase("Unknown database '%s'" % e)
# See if a 'database_type' is specified. This is something
# like 'SQLite' or 'MySQL'. If it is, use it to augment any
# missing information in the database_dict:
if 'database_type' in database_dict:
database_type = database_dict.pop('database_type')
# Augment any missing information in the database dictionary with
# the top-level stanza
if database_type in config_dict['DatabaseTypes']:
weeutil.config.conditional_merge(database_dict,
config_dict['DatabaseTypes'][database_type])
else:
raise weewx.UnknownDatabaseType('database_type')
return database_dict
"""Backwards compatible shim that redirects to weedb."""
return weedb.get_database_dict_from_config(config_dict, database)
#