Improved .add_database() method design

Closes #1155 - _internal now has a sensible name

Closes #509 - Support opening multiple databases with the same stem
This commit is contained in:
Simon Willison 2020-12-22 12:04:18 -08:00
commit 8919f99c2f
5 changed files with 86 additions and 46 deletions

View file

@ -218,18 +218,18 @@ class Datasette:
self.immutables = set(immutables or [])
self.databases = collections.OrderedDict()
if memory or not self.files:
self.add_database(":memory:", Database(self, ":memory:", is_memory=True))
self.add_database(Database(self, is_memory=True), name=":memory:")
# memory_name is a random string so that each Datasette instance gets its own
# unique in-memory named database - otherwise unit tests can fail with weird
# errors when different instances accidentally share an in-memory database
self.add_database("_internal", Database(self, memory_name=secrets.token_hex()))
self._interna_db_created = False
self.add_database(
Database(self, memory_name=secrets.token_hex()), name="_internal"
)
self.internal_db_created = False
for file in self.files:
path = file
db = Database(self, path, is_mutable=path not in self.immutables)
if db.name in self.databases:
raise Exception(f"Multiple files with same stem: {db.name}")
self.add_database(db.name, db)
self.add_database(
Database(self, file, is_mutable=file not in self.immutables)
)
self.cache_headers = cache_headers
self.cors = cors
metadata_files = []
@ -325,9 +325,9 @@ class Datasette:
async def refresh_schemas(self):
internal_db = self.databases["_internal"]
if not self._interna_db_created:
if not self.internal_db_created:
await init_internal_db(internal_db)
self._interna_db_created = True
self.internal_db_created = True
current_schema_versions = {
row["database_name"]: row["schema_version"]
@ -370,8 +370,20 @@ class Datasette:
name = [key for key in self.databases.keys() if key != "_internal"][0]
return self.databases[name]
def add_database(self, name, db):
def add_database(self, db, name=None):
if name is None:
# Pick a unique name for this database
suggestion = db.suggest_name()
name = suggestion
else:
suggestion = name
i = 2
while name in self.databases:
name = "{}_{}".format(suggestion, i)
i += 1
db.name = name
self.databases[name] = db
return db
def remove_database(self, name):
self.databases.pop(name)

View file

@ -27,30 +27,44 @@ class Database:
def __init__(
self, ds, path=None, is_mutable=False, is_memory=False, memory_name=None
):
self.name = None
self.ds = ds
self.path = path
self.is_mutable = is_mutable
self.is_memory = is_memory
self.memory_name = memory_name
if memory_name is not None:
self.path = memory_name
self.is_memory = True
self.is_mutable = True
self.hash = None
self.cached_size = None
self.cached_table_counts = None
self._cached_table_counts = None
self._write_thread = None
self._write_queue = None
if not self.is_mutable and not self.is_memory:
p = Path(path)
self.hash = inspect_hash(p)
self.cached_size = p.stat().st_size
# Maybe use self.ds.inspect_data to populate cached_table_counts
if self.ds.inspect_data and self.ds.inspect_data.get(self.name):
self.cached_table_counts = {
key: value["count"]
for key, value in self.ds.inspect_data[self.name]["tables"].items()
}
@property
def cached_table_counts(self):
if self._cached_table_counts is not None:
return self._cached_table_counts
# Maybe use self.ds.inspect_data to populate cached_table_counts
if self.ds.inspect_data and self.ds.inspect_data.get(self.name):
self._cached_table_counts = {
key: value["count"]
for key, value in self.ds.inspect_data[self.name]["tables"].items()
}
return self._cached_table_counts
def suggest_name(self):
if self.path:
return Path(self.path).stem
elif self.memory_name:
return self.memory_name
else:
return "db"
def connect(self, write=False):
if self.memory_name:
@ -220,7 +234,7 @@ class Database:
except (QueryInterrupted, sqlite3.OperationalError, sqlite3.DatabaseError):
counts[table] = None
if not self.is_mutable:
self.cached_table_counts = counts
self._cached_table_counts = counts
return counts
@property
@ -229,16 +243,6 @@ class Database:
return None
return Path(self.path).stat().st_mtime_ns
@property
def name(self):
if self.is_memory:
if self.memory_name:
return ":memory:{}".format(self.memory_name)
else:
return ":memory:"
else:
return Path(self.path).stem
async def table_exists(self, table):
results = await self.execute(
"select 1 from sqlite_master where type='table' and name=?", params=(table,)