Skip to content

Log file created by default for uploads and downloads #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
9c0f024
log file required unless force-no-log
i-oden Mar 4, 2025
8980d25
remove sys exit
i-oden Mar 4, 2025
828dd8a
Update help info to option
i-oden Mar 4, 2025
1d355c8
black
i-oden Mar 4, 2025
0d983c7
generate file name if put or get is used
i-oden Mar 5, 2025
65bd650
black
i-oden Mar 5, 2025
0df622f
remove some comments
i-oden Mar 5, 2025
19aeac4
error message if both used because I didn't get the mutually exclusiv…
i-oden Mar 5, 2025
082249d
removed one sys exit
i-oden Mar 5, 2025
017fc99
black
i-oden Mar 5, 2025
601285f
remove cloup
i-oden Mar 5, 2025
6e4fb2e
update help message
i-oden Mar 5, 2025
3d4bc8f
Merge branch 'dev' into HMS-2292-should-we-reintroduce-the-log-file-o…
i-oden Mar 6, 2025
eb8aad4
Create folder to store all automatically generated logs
i-oden Mar 6, 2025
a5b87bf
sprintlog
i-oden Mar 6, 2025
22bbb50
new line in warning
i-oden Mar 6, 2025
981fadf
less general exception
i-oden Mar 6, 2025
d536030
lazy logging
i-oden Mar 6, 2025
6dbdc7f
remove sys exit
i-oden Mar 6, 2025
1bbcfa1
black
i-oden Mar 6, 2025
a3e0fb4
prettier
i-oden Mar 6, 2025
add61b7
replace slash and backslash with _
i-oden Mar 7, 2025
c392ebb
black
i-oden Mar 7, 2025
b85321e
mount_dir to staging_dir
i-oden Mar 25, 2025
2c776d0
staging location and staging dir created in data_putter
i-oden Mar 25, 2025
c2a9dae
staging dir is destination moved to datagetter
i-oden Mar 25, 2025
4763fb0
remove duplicate code in base class
i-oden Mar 25, 2025
13c19f5
remove unused options
i-oden Mar 25, 2025
07507e8
remove commented option
i-oden Mar 25, 2025
fdd7c36
too many empty lines
i-oden Mar 25, 2025
2294a8f
remove method check - unneccesary
i-oden Mar 25, 2025
650f728
clarify default log in context object
i-oden Mar 25, 2025
f187ecb
start logging to deault file
i-oden Mar 25, 2025
73c627e
save command to log file as first item
i-oden Mar 26, 2025
93ebdbf
remove imports
i-oden Mar 27, 2025
d47c385
comment regarding command
i-oden Mar 27, 2025
bdfe5b4
comment regarding command
i-oden Mar 27, 2025
972bb15
add utils function for setting up logging to file
i-oden Mar 27, 2025
065577c
logging started, but doesn't log from all modules
i-oden Mar 27, 2025
877e198
moved logging to main because otherwise not logging from all modules
i-oden Mar 27, 2025
998b911
restructure and format
i-oden Mar 27, 2025
0ad7bd0
function description of get_default_log_name
i-oden Mar 27, 2025
3ff4671
move of staging dir variables
i-oden Mar 27, 2025
90663ec
commend regarding multiple directory variables
i-oden Mar 27, 2025
8a949b9
comment
i-oden Mar 27, 2025
22e973e
structuring DataGetter diretory variables
i-oden Mar 27, 2025
c79aa7d
logging created for download
i-oden Mar 27, 2025
e05cc7e
sprintlog
i-oden Mar 27, 2025
8aae1e1
black
i-oden Mar 27, 2025
96ae3b7
stage back to mount
i-oden Mar 27, 2025
ac9b579
remove commented out argument
i-oden Mar 27, 2025
47361f4
remove reimported
i-oden Mar 27, 2025
0cd6909
move dir variables to base class
i-oden Mar 27, 2025
8f2c207
black
i-oden Mar 27, 2025
e823105
duplicate imports and unused ones in base module
i-oden Mar 27, 2025
7476f46
imports and fixed unused option which should actually be used
i-oden Mar 27, 2025
1245439
remove unused argument
i-oden Mar 27, 2025
b3468c4
remove unused argument
i-oden Mar 27, 2025
da2371c
remove unused import
i-oden Mar 27, 2025
7682039
so instead of s because of pylint
i-oden Mar 27, 2025
f96941b
s_o instead of so because of pylint
i-oden Mar 27, 2025
06bba8f
move context to after if --help
i-oden Mar 28, 2025
fdcdfa0
black
i-oden Mar 28, 2025
65e1627
Merge branch 'dev' into HMS-2292-should-we-reintroduce-the-log-file-o…
i-oden Apr 9, 2025
b643dfd
Merge branch 'dev' into HMS-2292-should-we-reintroduce-the-log-file-o…
i-oden Apr 24, 2025
30459b3
force no log to have affect on its own
i-oden Apr 24, 2025
d53b87e
black
i-oden Apr 24, 2025
961be7e
change in output after failed upload
i-oden Apr 25, 2025
0d8f569
log file info to both upload and download fail
i-oden Apr 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions SPRINTLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,7 @@ _Empty sprint_
- Re-introduce executable for ubuntu-20.04 ([#744](https://github.com/ScilifelabDataCentre/dds_cli/pull/744))
- Update upload-artifact action to v4 ([#743](https://github.com/ScilifelabDataCentre/dds_cli/pull/743))
- New version: 2.10.1 ([#746](https://github.com/ScilifelabDataCentre/dds_cli/pull/746))

# 2025-04-14 - 2025-04-25

- Generate log file by default for put and get ([#737](https://github.com/ScilifelabDataCentre/dds_cli/pull/737))
1 change: 0 additions & 1 deletion dds_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ class FileSegment:
# Determine if the user is on an old terminal without proper Unicode support
dds_on_legacy_console = rich.console.detect_legacy_windows()


# Required to make the standalone executables build with PyInstaller work.
if __name__ == "__main__":
from dds_cli.__main__ import dds_main
Expand Down
109 changes: 93 additions & 16 deletions dds_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,19 @@
@click.option(
"-v", "--verbose", is_flag=True, default=False, help="Print verbose output to the console."
)
@click.option("-l", "--log-file", help="Save a log to a file.", metavar="<filename>")
@click.option("--force-no-log", help="[NOT RECOMMENDED] Do not save logs to a file.", is_flag=True)
@click.option(
"-l",
"--log-file",
help=(
"Save logs to file. Define a custom path with this option. "
"Recommended format: <command>_<date>_<time>.log."
"In the case of opening a support ticket regarding the DDS, attach this file. "
"Note that a log file will be generated by default for uploads and downloads."
),
metavar="<filename>",
required=False,
)
@click.option(
"--no-prompt", is_flag=True, default=False, help="Run without any interactive features."
)
Expand All @@ -121,7 +133,7 @@
help="List the options of any DDS subcommand and its default settings.",
)
@click.pass_context
def dds_main(click_ctx, verbose, log_file, no_prompt, token_path):
def dds_main(click_ctx, verbose, force_no_log, log_file, no_prompt, token_path):
"""SciLifeLab Data Delivery System (DDS) command line interface.

Access token is saved in a .dds_cli_token file in the home directory.
Expand All @@ -139,6 +151,14 @@ def dds_main(click_ctx, verbose, log_file, no_prompt, token_path):
)

if "--help" not in sys.argv:
# Create context object and save command to context
click_ctx.obj = {
"NO_PROMPT": no_prompt,
"TOKEN_PATH": token_path,
"COMMAND": sys.argv,
"DEFAULT_LOG": True,
}

# Set the base logger to output DEBUG
LOG.setLevel(logging.DEBUG)

Expand All @@ -153,20 +173,24 @@ def dds_main(click_ctx, verbose, log_file, no_prompt, token_path):
)
)

# Set up logs to a file if we asked for one
if log_file:
log_fh = logging.FileHandler(log_file, encoding="utf-8")
log_fh.setLevel(logging.DEBUG)
log_fh.setFormatter(
logging.Formatter(
fmt="[%(asctime)s] %(name)-15s %(lineno)-5s [%(levelname)-7s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
)
LOG.addHandler(log_fh)
# Do not setup log at default location if log_file is specified
click_ctx.obj.update({"DEFAULT_LOG": False})

# Create context object
click_ctx.obj = {"NO_PROMPT": no_prompt, "TOKEN_PATH": token_path}
if force_no_log:
LOG.warning(
"You have used both the '--log-file' and '--force-no-log' option, you can only use one."
)
sys.exit(1)
else:
file_handler = dds_cli.utils.setup_logging_to_file(filename=log_file)
LOG.addHandler(file_handler)
else:
if force_no_log:
LOG.warning(
"You have chosen to turn off the recommended default logging with the '--force-no-log' option."
)
click_ctx.obj.update({"DEFAULT_LOG": False})


# ************************************************************************************************ #
Expand Down Expand Up @@ -1625,9 +1649,37 @@ def put_data(
delivery to finish. To avoid that a delivery fails because of an expired token, we recommend
reauthenticating yourself before uploading data.
"""
# Define staging directory path
staging_dir_path: pathlib.Path = pathlib.Path(
f"DataDelivery_{dds_cli.timestamp.TimeStamp().timestamp}_{project}_upload"
)

# Staging directory should either be in specified mount dir or in current location
if mount_dir:
staging_dir_path = mount_dir / staging_dir_path
else:
staging_dir_path = pathlib.Path.cwd() / staging_dir_path

# Generate staging directory
staging_dir = dds_cli.directory.DDSDirectory(path=staging_dir_path)

# Setup logging -- needs to be in this file to work
if click_ctx.get("DEFAULT_LOG"):
default_log_name = dds_cli.utils.get_default_log_name(
command=click_ctx.get("COMMAND", ["commandnotfound"]),
log_directory=staging_dir.directories["LOGS"],
)

# Start logging to file
file_handler = dds_cli.utils.setup_logging_to_file(filename=default_log_name)
LOG.addHandler(file_handler)

# Log command
LOG.debug("Command: %s", " ".join(click_ctx.get("COMMAND")))

# Run upload
try:
dds_cli.data_putter.put(
mount_dir=mount_dir,
project=project,
source=source,
source_path_file=source_path_file,
Expand All @@ -1638,6 +1690,7 @@ def put_data(
no_prompt=click_ctx.get("NO_PROMPT", False),
token_path=click_ctx.get("TOKEN_PATH"),
destination=destination,
staging_dir=staging_dir,
)
except (
dds_cli.exceptions.AuthenticationError,
Expand Down Expand Up @@ -1725,6 +1778,30 @@ def get_data(
)
sys.exit(1)

# Define staging directory path
staging_dir_path: pathlib.Path = pathlib.Path.cwd() / pathlib.Path(
f"DataDelivery_{dds_cli.timestamp.TimeStamp().timestamp}_{project}_download"
)
if destination:
staging_dir_path = destination

# Generate staging directory
staging_dir = dds_cli.directory.DDSDirectory(path=staging_dir_path)

# Setup logging -- needs to be in this file to work
if click_ctx.get("DEFAULT_LOG"):
default_log_name = dds_cli.utils.get_default_log_name(
command=click_ctx.get("COMMAND", ["commandnotfound"]),
log_directory=staging_dir.directories["LOGS"],
)

# Start logging to file
file_handler = dds_cli.utils.setup_logging_to_file(filename=default_log_name)
LOG.addHandler(file_handler)

# Log command
LOG.debug("Command: %s", " ".join(click_ctx.get("COMMAND")))

try:
# Begin delivery
with dds_cli.data_getter.DataGetter(
Expand All @@ -1733,11 +1810,11 @@ def get_data(
source=source,
source_path_file=source_path_file,
break_on_fail=break_on_fail,
destination=destination,
silent=silent,
verify_checksum=verify_checksum,
no_prompt=click_ctx.get("NO_PROMPT", False),
token_path=click_ctx.get("TOKEN_PATH"),
staging_dir=staging_dir,
) as getter:
with rich.progress.Progress(
"{task.description}",
Expand Down
1 change: 0 additions & 1 deletion dds_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def __init__(
# Initiate DDSBaseClass to authenticate user
super().__init__(
authenticate=authenticate,
method_check=False,
force_renew_token=force_renew_token,
token_path=token_path,
totp=totp,
Expand Down
53 changes: 18 additions & 35 deletions dds_cli/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import dds_cli.utils

from dds_cli import (
DDS_METHODS,
DDS_DIR_REQUIRED_METHODS,
DDS_KEYS_REQUIRED_METHODS,
)
from dds_cli import DDSEndpoint
Expand All @@ -45,50 +43,21 @@ class DDSBaseClass:
def __init__(
self,
project=None,
dds_directory: pathlib.Path = None,
mount_dir: pathlib.Path = None,
method: str = None,
authenticate: bool = True,
method_check: bool = True,
force_renew_token: bool = False,
totp: str = None,
no_prompt: bool = False,
token_path: str = None,
allow_group: bool = False,
staging_dir: dds_cli.directory.DDSDirectory = None,
):
"""Initialize Base class for authenticating the user and preparing for DDS action."""
self.project = project
self.method_check = method_check
self.method = method
self.no_prompt = no_prompt
self.token_path = token_path

if self.method_check:
# Get attempted operation e.g. put/ls/rm/get
if self.method not in DDS_METHODS:
raise exceptions.InvalidMethodError(attempted_method=self.method)
LOG.debug("Attempted operation: %s", self.method)

# Use user defined destination if any specified
if self.method in DDS_DIR_REQUIRED_METHODS:
default_dir = pathlib.Path(
f"DataDelivery_{dds_cli.timestamp.TimeStamp().timestamp}_{self.project}_"
f"{'upload' if self.method == 'put' else 'download'}"
)
if mount_dir:
new_directory = mount_dir / default_dir
elif dds_directory:
new_directory = dds_directory
else:
new_directory = pathlib.Path.cwd() / default_dir

self.temporary_directory = new_directory

self.dds_directory = dds_cli.directory.DDSDirectory(path=new_directory)
self.failed_delivery_log = self.dds_directory.directories["LOGS"] / pathlib.Path(
"dds_failed_delivery.json"
)

# Keyboardinterrupt
self.stop_doing = False

Expand All @@ -105,7 +74,15 @@ def __init__(

# Project access only required if trying to upload, download or list
# files within project
# TODO: Move to DataPutter / DataGetter??
if self.method in DDS_KEYS_REQUIRED_METHODS:
# NOTE: Might be something to refactor in the future, but needed for now
self.dds_directory = staging_dir
self.temporary_directory = self.dds_directory.directories["ROOT"]
self.failed_delivery_log = self.dds_directory.directories["LOGS"] / pathlib.Path(
"dds_failed_delivery.json"
)

if self.method == "put":
self.s3connector = self.__get_safespring_keys()

Expand Down Expand Up @@ -234,15 +211,21 @@ def __printout_delivery_summary(self):
self.filehandler.failed.clear()

if true_failed:
log_file_info: str = (
"When contacting DDS support, please attach the log file(s) located in "
f"{self.dds_directory.directories['LOGS']} to the ticket. "
"If you used the '--log-file' option when running your command, "
"please also attach that file.\n"
"[red][bold]Do not[/bold][/red] delete these files."
)
if self.method == "put":
# Raise exception in order to give exit code 1
raise exceptions.UploadError(
"Errors occurred during upload.\n"
"If you wish to retry the upload, re-run the 'dds data put' command again, "
"specifying the same options as you did now. To also overwrite the files "
"that were uploaded, also add the '--overwrite' flag at the end of the command.\n\n"
f"Please verify that the following error log has been generated: {self.failed_delivery_log}\n"
"[red][bold]Do not[/bold][/red] delete this file; The Data Centre may need it during DDS support."
f"{log_file_info}"
)

# TODO: --destination should be able to >at least< overwrite the files in the
Expand All @@ -252,7 +235,7 @@ def __printout_delivery_summary(self):
"If you wish to retry the download, re-run the `dds data get` command again, "
"specifying the same options as you did now. A new directory will "
"automatically be created and all files will be downloaded again.\n\n"
f"See {self.failed_delivery_log} for more information."
f"{log_file_info}"
)

if nr_uploaded:
Expand Down
4 changes: 2 additions & 2 deletions dds_cli/data_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ def __init__(
get_all: bool = False,
source: tuple = (),
source_path_file: pathlib.Path = None,
destination: pathlib.Path = pathlib.Path(""),
silent: bool = False,
verify_checksum: bool = False,
method: str = "get",
no_prompt: bool = False,
token_path: str = None,
staging_dir: dds_cli.directory.DDSDirectory = None,
):
"""Handle actions regarding downloading data."""
# Initiate DDSBaseClass to authenticate user
super().__init__(
project=project,
dds_directory=destination,
method=method,
no_prompt=no_prompt,
token_path=token_path,
staging_dir=staging_dir,
)

# Initiate DataGetter specific attributes
Expand Down
10 changes: 6 additions & 4 deletions dds_cli/data_putter.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from dds_cli.custom_decorators import verify_proceed, update_status, subpath_required

import dds_cli
import dds_cli.directory
import dds_cli.utils

###############################################################################
Expand All @@ -43,7 +44,6 @@


def put(
mount_dir,
project,
source,
source_path_file,
Expand All @@ -54,11 +54,11 @@ def put(
no_prompt,
token_path,
destination,
staging_dir,
):
"""Handle upload of data."""
# Initialize delivery - check user access etc
with DataPutter(
mount_dir=mount_dir,
project=project,
source=source,
source_path_file=source_path_file,
Expand All @@ -68,7 +68,9 @@ def put(
no_prompt=no_prompt,
token_path=token_path,
destination=destination,
staging_dir=staging_dir,
) as putter:

# Progress object to keep track of progress tasks
with Progress(
"{task.description}",
Expand Down Expand Up @@ -201,7 +203,7 @@ class DataPutter(base.DDSBaseClass):
def __init__(
self,
project: str = None,
mount_dir: pathlib.Path = None,
staging_dir: dds_cli.directory.DDSDirectory = None,
break_on_fail: bool = False,
overwrite: bool = False,
source: tuple = (),
Expand All @@ -216,10 +218,10 @@ def __init__(
# Initiate DDSBaseClass to authenticate user
super().__init__(
project=project,
mount_dir=mount_dir,
method=method,
no_prompt=no_prompt,
token_path=token_path,
staging_dir=staging_dir,
)

# Initiate DataPutter specific attributes
Expand Down
Loading
Loading