def is_database_directory(path: util.PathLike) -> bool: """ Return `True` if *path* is a valid TSDB database directory. A path is a valid database directory if it is a directory containing a schema file. This is a simple test; the schema file itself is not checked for validity. """ path = Path(path).expanduser() return path.is_dir() and path.joinpath(SCHEMA_FILENAME).is_file()
def write_schema(path: util.PathLike, schema: Schema) -> None: """ Serialize *schema* and write it to the relations file at *path*. If *path* is a directory, write to a `relations` file under *path*, otherwise write to the file *path*. """ path = Path(path).expanduser() if path.is_dir(): path = path.joinpath(SCHEMA_FILENAME) path.write_text(_format_schema(schema) + '\n')
def read_schema(path: util.PathLike) -> Schema: """ Instantiate schema dict from a schema file given by *path*. If *path* is a directory, use the relations file under *path*. If *path* is a file, use it directly as the schema's path. Otherwise raise a :exc:`TSDBSchemaError`. """ path = Path(path).expanduser() if path.is_dir(): path = path.joinpath(SCHEMA_FILENAME) if not path.is_file(): raise TSDBSchemaError(f'no valid schema file at {path!s}') return _parse_schema(path.read_text())
def write(dir: util.PathLike, name: str, records: Iterable[Record], fields: Fields, append: bool = False, gzip: bool = False, encoding: str = 'utf-8') -> None: """ Write *records* to relation *name* in the database at *dir*. The simplest way to write data to a file would be something like the following: >>> with open(os.path.join(db.path, 'item'), 'w') as fh: ... print('\\n'.join(map(tsdb.join, db['item'])), file=fh) This function improves on that method by doing the following: * Determining the path from the *gzip* parameter and existing files * Writing plain text or compressed data, as appropriate * Appending or overwriting data, as requested * Using the schema information to format fields * Writing to a temporary file then copying when done; this prevents accidental data loss when overwriting a file that is being read * Deleting any alternative (compressed or plain text) file to avoid having inconsistent files (e.g., delete any existing `item` when writing `item.gz`) Note that *append* cannot be used with *gzip* or with an existing gzipped file and in such a case a :exc:`NotImplementedError` will be raised. This may be allowed in the future, but as appending to a gzipped file (in general) results in inefficient compression, it is better to append to plain text and compress when done. Args: dir: path to the database directory name: name of the relation to write records: iterable of records to write fields: iterable of :class:`Field` objects append: if `True`, append to rather than overwrite the file gzip: if `True` and the file is not empty, compress the file with `gzip`; if `False`, do not compress encoding: character encoding of the file Example: >>> tsdb.write('my-profile', ... 'item', ... item_records, ... schema['item']) """ dir = Path(dir).expanduser() if encoding is None: encoding = 'utf-8' if not dir.is_dir(): raise TSDBError(f'invalid test suite directory: {dir}') tx_path, gz_path, use_gz = _get_paths(dir, name) if append and (gzip or use_gz): raise NotImplementedError('cannot append to a gzipped file') mode = 'ab' if append else 'wb' with tempfile.NamedTemporaryFile(mode='w+b', suffix='.tmp', prefix=name, dir=dir) as f_tmp: for record in records: f_tmp.write((join(record, fields) + '\n').encode(encoding)) # only gzip non-empty files gzip = gzip and f_tmp.tell() != 0 dest, other = (gz_path, tx_path) if gzip else (tx_path, gz_path) # now copy the temp file to the destination f_tmp.seek(0) if gzip: with gzopen(dest, mode) as f_out: shutil.copyfileobj(f_tmp, f_out) else: with dest.open(mode=mode) as f_out: shutil.copyfileobj(f_tmp, f_out) # clean up other (gz or non-gz) file if it exists if other.is_file(): other.unlink()