mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-02 08:58:11 +08:00
annotations CLI and file UX rework (#1049)
* rename config param label-file * annotations rework - CLI params, file naming and backups * lint * improve cli option error checks * enable session cookies * enable session cookies * add session id * name annotations file in multi-dataset and multi-user safe manner * pass data user hash to front-end * add annotation collection name support to front-end * add constant for annotation data collection name * parameterize annotation collection name; make it sticky in the session * clarify comments * hard wire a temporary data collection name for testing * prettier * test comment * package command * set annotations filename dialog * name and hash are visible * wire up data collection capture
This commit is contained in:
@@ -1,52 +1,61 @@
|
||||
"""
|
||||
Helpers for user annotations / label_file parameter
|
||||
Helpers for user annotations
|
||||
"""
|
||||
from os.path import exists, splitext, getsize
|
||||
from os import remove, rename
|
||||
import os
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def read_labels(fname):
|
||||
if exists(fname) and getsize(fname) > 0:
|
||||
if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0:
|
||||
return pd.read_csv(fname, dtype='category', index_col=0, header=0, comment='#')
|
||||
else:
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
def write_labels(fname, df, header=None):
|
||||
rotate_fname(fname)
|
||||
def write_labels(fname, df, header=None, backup_dir=None):
|
||||
if backup_dir is not None:
|
||||
backup(fname, backup_dir)
|
||||
# rotate_fname(fname, backup_dir)
|
||||
if not df.empty:
|
||||
f = open(fname, 'a', newline="")
|
||||
if header is not None:
|
||||
f.write(header)
|
||||
df.to_csv(f)
|
||||
with open(fname, 'w', newline="") as f:
|
||||
if header is not None:
|
||||
f.write(header)
|
||||
df.to_csv(f)
|
||||
else:
|
||||
open(fname, 'a').close()
|
||||
open(fname, 'w').close()
|
||||
|
||||
|
||||
def rotate_fname(fname):
|
||||
def backup(fname, backup_dir, max_backups=9):
|
||||
"""
|
||||
save N backups of file.
|
||||
fname -> fname-0
|
||||
fname-0 -> fname->1
|
||||
...
|
||||
fname-(N-1) -> fname-N
|
||||
save N backups of file to backup_dir.
|
||||
1. fname -> backup_dir/fname-TIME
|
||||
2. delete excess files in backup_dir
|
||||
"""
|
||||
|
||||
def rotate(src, dst):
|
||||
if exists(src):
|
||||
if exists(dst):
|
||||
remove(dst)
|
||||
rename(src, dst)
|
||||
# Make sure there is work to do
|
||||
if not os.path.exists(fname):
|
||||
return
|
||||
|
||||
rotation_size = 9 # rotation size
|
||||
name, ext = splitext(fname)
|
||||
# Ensure backup_dir exists
|
||||
if not os.path.exists(backup_dir):
|
||||
os.mkdir(backup_dir)
|
||||
|
||||
# rotate existing files
|
||||
for i in range(rotation_size - 1, 0, -1):
|
||||
src = f"{name}-{i}{ext}"
|
||||
tgt = f"{name}-{i+1}{ext}"
|
||||
rotate(src, tgt)
|
||||
# Save current file to backup_dir
|
||||
fname_base = os.path.basename(fname)
|
||||
fname_base_root, fname_base_ext = os.path.splitext(fname_base)
|
||||
# don't use ISO standard time format, as it contains characters illegal on some filesytems.
|
||||
nowish = datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
|
||||
backup_fname = os.path.join(backup_dir, f"{fname_base_root}-{nowish}{fname_base_ext}")
|
||||
if os.path.exists(backup_fname):
|
||||
os.remove(backup_fname)
|
||||
os.rename(fname, backup_fname)
|
||||
|
||||
tgt = f"{name}-1{ext}"
|
||||
rotate(fname, tgt)
|
||||
# prune the backup_dir to max number of backup files, keeping the most recent backups
|
||||
backups = list(filter(lambda s: s.startswith(fname_base_root), os.listdir(backup_dir)))
|
||||
excess_count = len(backups) - max_backups
|
||||
if excess_count > 0:
|
||||
backups.sort()
|
||||
for bu in backups[0:excess_count]:
|
||||
os.remove(os.path.join(backup_dir, bu))
|
||||
|
||||
Reference in New Issue
Block a user