From 99ed7d0075dfcb772e04d0b55c04ffbe468acccb Mon Sep 17 00:00:00 2001 From: why-not-try-calmer Date: Wed, 15 Feb 2023 11:34:15 +0100 Subject: [PATCH] Pushing source files with no matching resource now creates the corresponding resource first. --- .github/workflows/test_pytx.yml | 2 +- pytransifex/api.py | 41 +++++++++++++++++++++++++++------ pytransifex/cli.py | 20 ++++++++++------ pytransifex/utils.py | 11 ++++++--- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test_pytx.yml b/.github/workflows/test_pytx.yml index f76e586..0ab8014 100644 --- a/.github/workflows/test_pytx.yml +++ b/.github/workflows/test_pytx.yml @@ -1,4 +1,4 @@ -name: Test Pytx +name: test pytransifex on: pull_request: diff --git a/pytransifex/api.py b/pytransifex/api.py index 38b4532..fd4ee90 100644 --- a/pytransifex/api.py +++ b/pytransifex/api.py @@ -138,6 +138,10 @@ def update_source_translation( "Unable to fetch resource for this organization; define an 'organization slug' first." ) + logging.info( + f"Updating source translation for resource {resource_slug} from file {path_to_file} (project: {project_slug})." + ) + if project := self.get_project(project_slug=project_slug): if resources := project.fetch("resources"): if resource := resources.get(slug=resource_slug): @@ -267,36 +271,59 @@ def pull( language_codes: list[str], output_dir: Path, ): + """Pull resources from project.""" args = [] for l_code in language_codes: for slug in resource_slugs: args.append(tuple([project_slug, slug, l_code, output_dir])) - logging.info("ARGS", args) - concurrently( + + res = concurrently( fn=self.get_translation, args=args, ) + logging.info(f"Pulled {args} for {len(res)} results).") + @ensure_login def push( self, project_slug: str, resource_slugs: list[str], path_to_files: list[Path] ): + """Push resources with files under project.""" if len(resource_slugs) != len(path_to_files): - raise Exception( + raise ValueError( f"Resources slugs ({len(resource_slugs)}) and Path to files ({len(path_to_files)}) must be equal in size!" ) + resource_zipped_with_path = list(zip(resource_slugs, path_to_files)) + resources = self.list_resources(project_slug) + logging.info( + f"Found {len(resources)} resource(s) for {project_slug}. Checking for missing resources and creating where necessary." + ) + created_when_missing_resource = [] + + for slug, path in resource_zipped_with_path: + logging.info(f"Slug: {slug}. Resources: {resources}.") + if not slug in resources: + logging.info( + f"{project_slug} is missing {slug}. Created it from {path}." + ) + self.create_resource( + project_slug=project_slug, path_to_file=path, resource_slug=slug + ) + created_when_missing_resource.append(slug) + args = [ - tuple([project_slug, res, path]) - for res, path in zip(resource_slugs, path_to_files) + tuple([project_slug, slug, path]) + for slug, path in resource_zipped_with_path + if not slug in created_when_missing_resource ] - concurrently( + res = concurrently( fn=self.update_source_translation, args=args, ) - logging.info(f"Pushes some {len(resource_slugs)} files!") + logging.info(f"Pushed {args} for {len(res)} results.") class Transifex: diff --git a/pytransifex/cli.py b/pytransifex/cli.py index c16d9d8..ca64746 100644 --- a/pytransifex/cli.py +++ b/pytransifex/cli.py @@ -1,3 +1,5 @@ +import logging +import traceback from os import mkdir, rmdir from pathlib import Path @@ -19,7 +21,7 @@ def extract_files(input_dir: Path) -> tuple[list[Path], list[str], str]: files = list(Path.iterdir(input_dir)) slugs = path_to_slug(files) files_status_report = "\n".join( - (f"file:{file} => slug:{slug}" for slug, file in zip(slugs, files)) + (f"file: {file} => slug: {slug}" for slug, file in zip(slugs, files)) ) return (files, slugs, files_status_report) @@ -54,7 +56,9 @@ def init(**opts): reply += f"WARNING: You will need to declare an input directory if you plan on using 'pytx push', as in 'pytx push --input-directory '." except Exception as error: - reply += f"Failed to initialize the CLI, this error occurred: {error} " + reply += ( + f"cli:init > Failed to initialize the CLI, this error occurred: {error}." + ) if has_to_create_dir: rmdir(settings.output_directory) @@ -78,14 +82,14 @@ def push(input_directory: str | None): ) if not input_dir: - raise Exception( - "To use this 'push', you need to initialize the project with a valid path to the directory containing the files to push; alternatively, you can call this commend with 'pytx push --input-directory '." + raise FileExistsError( + "cli:push > To use this 'push', you need to initialize the project with a valid path to the directory containing the files to push; alternatively, you can call this commend with 'pytx push --input-directory '." ) try: files, slugs, files_status_report = extract_files(input_dir) click.echo( - f"Pushing {files_status_report} to Transifex under project {settings.project_slug}..." + f"cli:push > Pushing {files_status_report} to Transifex under project {settings.project_slug}." ) client.push( project_slug=settings.project_slug, @@ -93,7 +97,8 @@ def push(input_directory: str | None): path_to_files=files, ) except Exception as error: - reply += f"cli:push failed because of this error: {error}" + reply += f"cli:push > Failed because of this error: {error}" + logging.error(f"traceback: {traceback.print_exc()}") finally: click.echo(reply) settings.to_disk() @@ -125,7 +130,8 @@ def pull(output_directory: str | Path | None, only_lang: str | None): output_dir=output_directory, ) except Exception as error: - reply += f"cli:pull failed because of this error: {error}" + reply += f"cli:pull > failed because of this error: {error}" + logging.error(f"traceback: {traceback.print_exc()}") finally: click.echo(reply) settings.to_disk() diff --git a/pytransifex/utils.py b/pytransifex/utils.py index 26e894c..f125295 100644 --- a/pytransifex/utils.py +++ b/pytransifex/utils.py @@ -1,6 +1,6 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from functools import wraps -from typing import Any, Callable, Iterable +from typing import Any, Callable def ensure_login(f): @@ -16,9 +16,14 @@ def capture_args(instance, *args, **kwargs): def concurrently( *, fn: Callable | None = None, - args: Iterable[Any] | None = None, - partials: Iterable[Any] | None = None, + args: list[Any] | None = None, + partials: list[Any] | None = None, ) -> list[Any]: + if args and len(args) == 0: + return [] + if partials and len(partials) == 0: + return [] + with ThreadPoolExecutor() as pool: if partials: futures = [pool.submit(p) for p in partials]