def __construct_file_dict_tree(folder: str) -> dict: """ Recurses through the project directories. Constructs a file tree by subsequent calls to the API """ try: sorted_files_folders = __api_call_list_files(folder) except exceptions.NoDataError as e: if folder is None: raise exceptions.NoDataError( "No files or folders found for the specified project" ) else: raise exceptions.NoDataError(f"Could not find folder: '{folder}'") tree = {} for f in sorted_files_folders: is_folder = f.pop("folder") name = f["name"] if not is_folder: tree[name] = {"name": name, "is_folder": False, "children": {}} if show_size: tree[f["name"]]["size"] = f.get("size") else: children = __construct_file_dict_tree( os.path.join(folder, name) if folder else name ) tree[name] = {"name": name, "is_folder": True, "children": children} return tree
def __construct_file_tree(folder: str, basename: str) -> Tuple[FileTree, int, int]: """ Recurses through the project directories. Constructs a file tree by subsequent calls to the API """ tree = FileTree([], f"{basename}/") try: sorted_files_folders = __api_call_list_files(folder) except exceptions.NoDataError as e: if folder is None: raise exceptions.NoDataError( "No files or folders found for the specified project" ) else: raise exceptions.NoDataError(f"Could not find folder: '{escape(folder)}'") # Get max length of file name max_string = max([len(x["name"]) for x in sorted_files_folders]) # Get max length of size string max_size = max( [ len(x["size"].split(" ")[0]) for x in sorted_files_folders if show_size and "size" in x ], default=0, ) # Rich outputs precisely one line per file/folder for f in sorted_files_folders: is_folder = f.pop("folder") if not is_folder: tree.subtrees.append((escape(f["name"]), f.get("size") if show_size else None)) else: subtree, _max_string, _max_size = __construct_file_tree( os.path.join(folder, f["name"]) if folder else f["name"], f"[bold deep_sky_blue3]{escape(f['name'])}", ) # Due to indentation, the filename strings of # subdirectories are 4 characters deeper than # their parent directories max_string = max(max_string, _max_string + 4) max_size = max(max_size, _max_size) tree.subtrees.append(subtree) return tree, max_string, max_size
def __print_users_table(self, research_users): # TODO: call on future list_project_users function, will be implemented for dds user ls --project default_format = {"justify": "left", "style": "", "footer": "", "overflow": "fold"} column_formatting = { **{x: default_format for x in ["User Name", "Primary email", "Role"]}, } table = Table( title="Project User(s)", show_header=True, header_style="bold", ) # Add columns to table for colname, colformat in column_formatting.items(): table.add_column( colname, justify=colformat["justify"], style=colformat["style"], footer=colformat["footer"], overflow=colformat["overflow"], ) # Add all column values for each row to table for user in research_users: table.add_row(*[user[i] for i in column_formatting]) # Print to stdout if there are any lines if table.rows: # Use a pager if output is taller than the visible terminal if len(research_users) + 5 > dds_cli.utils.console.height: with dds_cli.utils.console.pager(): dds_cli.utils.console.print(table) else: dds_cli.utils.console.print(table) else: raise exceptions.NoDataError("No users found.")
def __api_call_list_files(folder: str): # Make call to API try: resp_json = requests.get( DDSEndpoint.LIST_FILES, params={"project": self.project}, json={"subpath": folder, "show_size": show_size}, headers=self.token, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: raise exceptions.APIError(f"Problem with database response: '{err}'") resp_json = resp_json.json() if not "files_folders" in resp_json: raise exceptions.NoDataError(f"Could not find folder: '{folder}'") sorted_files_folders = sorted(resp_json["files_folders"], key=lambda f: f["name"]) if not sorted_files_folders: raise exceptions.NoDataError(f"Could not find folder: '{folder}'") return sorted_files_folders
def list_projects(self, sort_by="Updated"): """Get a list of project(s) the user is involved in.""" # Get projects from API try: response = requests.get( DDSEndpoint.LIST_PROJ, headers=self.token, json={"usage": self.show_usage}, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: raise exceptions.ApiRequestError( message=( "Failed to get list of projects" + ( ": The database seems to be down." if isinstance(err, requests.exceptions.ConnectionError) else "." ) ) ) # Check response if not response.ok: raise exceptions.APIError(f"Failed to get any projects: {response.text}") # Get result from API try: resp_json = response.json() except simplejson.JSONDecodeError as err: raise exceptions.APIError(f"Could not decode JSON response: {err}") # Cancel if user not involved in any projects usage_info = resp_json.get("total_usage") total_size = resp_json.get("total_size") project_info = resp_json.get("project_info") always_show = resp_json.get("always_show", False) if not project_info: raise exceptions.NoDataError("No project info was retrieved. No files to list.") for project in project_info: try: last_updated = pytz.timezone("UTC").localize( datetime.datetime.strptime(project["Last updated"], "%a, %d %b %Y %H:%M:%S GMT") ) except ValueError as err: raise exceptions.ApiResponseError( f"Time zone mismatch: Incorrect zone '{project['Last updated'].split()[-1]}'" ) else: project["Last updated"] = last_updated.astimezone(tzlocal.get_localzone()).strftime( "%a, %d %b %Y %H:%M:%S %Z" ) # Sort projects according to chosen or default, first ID sorted_projects = self.__sort_projects(projects=project_info, sort_by=sort_by) if not self.json: self.__print_project_table(sorted_projects, usage_info, total_size, always_show) # Return the list of projects return sorted_projects
def list_files(self, folder: str = None, show_size: bool = False): """Create a tree displaying the files within the project.""" LOG.info(f"Listing files for project '{self.project}'") if folder: LOG.info(f"Showing files in folder '{escape(folder)}'") if folder is None: folder = "" # Make call to API try: response = requests.get( DDSEndpoint.LIST_FILES, params={"project": self.project}, json={"subpath": folder, "show_size": show_size}, headers=self.token, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: raise exceptions.APIError( message=( f"Failed to get list of files in project '{self.project}'" + ( ": The database seems to be down." if isinstance(err, requests.exceptions.ConnectionError) else "." ) ) ) if not response.ok: raise exceptions.APIError(f"Failed to get list of files: '{response.text}'") # Get response try: resp_json = response.json() except simplejson.JSONDecodeError as err: raise exceptions.APIError(f"Could not decode JSON response: '{err}'") # Check if project empty if "num_items" in resp_json and resp_json["num_items"] == 0: raise exceptions.NoDataError(f"Project '{self.project}' is empty.") # Get files files_folders = resp_json["files_folders"] # Sort the file/folders according to names sorted_files_folders = sorted(files_folders, key=lambda f: f["name"]) # Create tree tree_title = escape(folder) or f"Files / directories in project: [green]{self.project}" tree = Tree(f"[bold magenta]{tree_title}") if not sorted_files_folders: raise exceptions.NoDataError(f"Could not find folder: '{escape(folder)}'") # Get max length of file name max_string = max([len(x["name"]) for x in sorted_files_folders]) # Get max length of size string max_size = max( [ len( dds_cli.utils.format_api_response( response=x["size"], key="Size", binary=self.binary ).split(" ", maxsplit=1)[0] ) for x in sorted_files_folders if show_size and "size" in x ], default=0, ) # Visible folders visible_folders = [] # Add items to tree for x in sorted_files_folders: # Check if string is folder is_folder = x.pop("folder") # Att 1 for folders due to trailing / tab = th.TextHandler.format_tabs( string_len=len(x["name"]) + (1 if is_folder else 0), max_string_len=max_string, ) # Add formatting if folder and set string name line = "" if is_folder: line = "[bold deep_sky_blue3]" visible_folders.append(x["name"]) line += escape(x["name"]) + ("/" if is_folder else "") # Add size to line if option specified if show_size and "size" in x: size = dds_cli.utils.format_api_response( response=x["size"], key="Size", binary=self.binary ) line += f"{tab}{size.split()[0]}" # Define space between number and size format tabs_bf_format = th.TextHandler.format_tabs( string_len=len(size), max_string_len=max_size, tab_len=2 ) line += f"{tabs_bf_format}{size.split()[1]}" tree.add(line) # Print output to stdout if len(files_folders) + 5 > dds_cli.utils.console.height: with dds_cli.utils.console.pager(): dds_cli.utils.console.print(Padding(tree, 1)) else: dds_cli.utils.console.print(Padding(tree, 1)) # Return variable return visible_folders