def test_argument(): arg = argument("foo", "Foo") assert "Foo" == arg.description assert arg.is_required() assert not arg.is_optional() assert not arg.is_multi_valued() assert arg.default is None arg = argument("foo", "Foo", optional=True, default="bar") assert not arg.is_required() assert arg.is_optional() assert not arg.is_multi_valued() assert "bar" == arg.default arg = argument("foo", "Foo", multiple=True) assert arg.is_required() assert not arg.is_optional() assert arg.is_multi_valued() assert [] == arg.default arg = argument("foo", "Foo", optional=True, multiple=True, default=["bar"]) assert not arg.is_required() assert arg.is_optional() assert arg.is_multi_valued() assert ["bar"] == arg.default
class SetCommand(Command): name = "set" description = "Set value of the key" help = "\n".join([ "", "# Set specific value in the config", "set settings.json key value", "set settings.json key=value", "", "# Set environment variable in current shell", "set key value", "set key=value", ]) arguments = [ argument( name="key", description= "Config key ('my.key') or key-value pair ('my.key=231')", ), argument( name="value", description= "[optional] Value of the key when <key> argument is just a key name", optional=True, ), ] options = [file_type, *scopes] def handle(self): print("Wow, you've set it")
class CloneCommand(Command): name = 'clone' description = 'clone repository from github' arguments = [ argument('repository', 'name of repository that you want to clone'), argument('target', 'target directory name', optional=True), ] options = [option('url', 'u', 'echo only url, not clone')] def handle(self): clone_url = url_parsing(self.argument('repository')) if self.option('url'): self.line(clone_url) return 1 clone(clone_url, self.argument('target'))
class SearchCommand(Command): name = "search" description = "Searches for packages on remote repositories." arguments = [ argument("tokens", "The tokens to search for.", multiple=True) ] options = [option("only-name", "N", "Search only by name.")] def handle(self): from poetry.repositories.pypi_repository import PyPiRepository flags = PyPiRepository.SEARCH_FULLTEXT if self.option("only-name"): flags = PyPiRepository.SEARCH_NAME results = PyPiRepository().search(self.argument("tokens"), flags) for result in results: self.line("") name = "<info>{}</>".format(result.name) name += " (<comment>{}</>)".format(result.version) self.line(name) if result.description: self.line(" {}".format(result.description))
class KSTCommand(TimeCommand): name = 'kst' description = 'convert KST time to target timezone' arguments = [ argument('time', 'target time to change'), argument('tz', 'target timezone to change', default='UTC', optional=True) ] def handle(self): time = self.argument('time') tz = self.argument('tz') parsed = self.time_parse(time, 'Asia/Seoul', tz) self.line("{} in {} is {}".format(time, tz, parsed.strftime('%H:%M')))
class DiscoverCommand(Command): name = "discover" description = "Test dsicovery functionality" arguments = [ argument("parameter", description="What to discover", optional=False) ] options = [ option("input-glob", flag=False), option("output-glob", flag=False) ] def handle(self) -> None: parameter = self.argument("parameter") if parameter == "tests": input_glob = self.option("input-glob") output_glob = self.option("output-glob") if not input_glob: self.line("<error>input-glob option must be specified</error>") return if not output_glob: self.line( "<error>output-glob option must be specified</error>") return tests = Discovery.find_test_cases(input_glob, output_glob) self.line(f"len of tests: {len(tests)}") for t in tests: print(f"key: {t}; input: {tests[t][0]}; output: {tests[t][1]}")
class RunCommand(EnvCommand): name = "run" description = "Runs a command in the appropriate environment." arguments = [ argument("args", "The command and arguments/options to run.", multiple=True) ] def __init__(self): # type: () -> None from poetry.console.args.run_args_parser import RunArgsParser super(RunCommand, self).__init__() self.config.set_args_parser(RunArgsParser()) def handle(self): args = self.argument("args") script = args[0] scripts = self.poetry.local_config.get("scripts") if scripts and script in scripts: return self.run_script(scripts[script], args) return self.env.execute(*args) def run_script(self, script, args): if isinstance(script, dict): script = script["callable"] module, callable_ = script.split(":") src_in_sys_path = "sys.path.append('src'); " if self._module.is_in_src( ) else "" cmd = ["python", "-c"] cmd += [ '"import sys; ' "from importlib import import_module; " "sys.argv = {!r}; {}" "import_module('{}').{}()\"".format(args, src_in_sys_path, module, callable_) ] return self.env.run(*cmd, shell=True, call=True) @property def _module(self): from ...masonry.utils.module import Module poetry = self.poetry package = poetry.package path = poetry.file.parent module = Module(package.name, path.as_posix(), package.packages) return module
class ConfigureCommand(Command): name = "configure" description = "Configure the environment" arguments = [ argument("command", description="Which command to execute: show, get, or set", optional=False), argument("parameter", description="Which parameter to set or get", optional=True) ] options = [ option( "user", description= "Save configuration file in user's home directory when setting parameter", flag=True), option( "system", description= "Save configuration file in system directory when setting parameters", flag=True) ] def handle(self) -> None: command = self.argument("command") parameter = self.argument("parameter") if command == "show": config = Configuration() config.build() table = config.as_table() self.render_table(*table) elif command == "get": if not parameter: self.line("<error>Parameter name must be specified</error>") elif command == "set": if not parameter: self.line("<error>Parameter name must be specified</error>") else: self.line(f"<error>Wrong command: {command}</error>")
class StopCommand(Command): name = 'stop' description = 'stop docker container' arguments = [ argument('name', 'name of container'), ] def handle(self): name = self.argument('name') self.shell_call('docker stop kal-{}'.format(name))
class RunCommand(Command): name = 'run' description = 'Run docker container for local development' arguments = [ argument('name', 'name of container'), ] def handle(self): name = self.argument('name') command = Config.docker(name) self.shell_call(command)
class OcelCommand(Command): name = 'ocel' description = 'ocel command' arguments = [ argument('template', 'name of template', optional=True), argument('target', 'target path', optional=True) ] options = [ option('list', 'l', 'list of templates'), option( 'update', 'u', 'update ocel from github', ) ] def handle(self): ls = self.option('list') update = self.option('update') template = self.argument('template') target = self.argument('target') ocel = Ocel() if template and (ls or update): raise CommandError('Invalid format of command') if template: ocel.run(template, target) self.line('Finished.') elif ls: template_list = ocel.template_list(True) table = self.table(['Name', 'Desc']) for name, meta in template_list.items(): table.add_row([name, meta.get('desc', '')]) table.render(self.io) elif update: ocel.update() else: raise CommandError('Invalid format of command')
class TrainSentencePieceModelCommand(BaseCommand): name = "train-spm" description = "Train Sentence Piece Model for BPE encoding." arguments = [argument("config", description="Config to use for SPM training.")] options = [ option( "serialization-dir", "s", description="Directory to save trained SPM Model.", flag=False, value_required=False, ), option( "extra-vars", None, description=( "Extra variables to inject in JsonNet config in such format: " "{key_name1}={new_value1},{key_name2}={new_value2},..." ), flag=False, value_required=False, ), ] def handle(self) -> None: extra_vars = self.parse_extra_vars() config = Params.from_file(self.argument("config"), ext_vars=extra_vars) # Add serialization directory to config and create it serialization_dir = Path(self.option("serialization-dir")) self.prepare_directory(serialization_dir) # Log config to console and save config["model_prefix"] = str(serialization_dir / config["model_prefix"]) logger.info( "Config: {}".format(json.dumps(config.as_flat_dict(), indent=2, ensure_ascii=False)) ) with (serialization_dir / "config.json").open("w", encoding="utf-8") as file: json.dump(config.as_dict(quiet=True), file, indent=2, ensure_ascii=False) # Train SPM Model spm.SentencePieceTrainer.train(**config) self.line( f"Finished SentencePiece model training and saved at path: `{serialization_dir}`", style="info", ) def parse_extra_vars(self) -> Dict[str, str]: extra_vars = self.option("extra-vars") regex = r"([a-z0-9\_\-\.\+\\\/]+)=([a-z0-9\_\-\.\+\\\/]+)" return ( {param: value for param, value in re.findall(regex, extra_vars, flags=re.I)} if extra_vars is not None else None )
def test_argument(self): validator = Integer() arg = argument( 'foo', 'The foo argument.', required=False, is_list=True, default=['default'], validator=validator ) self.assertIsInstance(arg, InputArgument) self.assertEqual('foo', arg.get_name()) self.assertEqual('The foo argument.', arg.get_description()) self.assertEqual(['default'], arg.get_default()) self.assertTrue(arg.is_list()) self.assertFalse(arg.is_required()) self.assertEqual(validator, arg.get_validator())
class RunCommand(EnvCommand): name = "run" description = "Runs a command in the appropriate environment." arguments = [ argument("args", "The command and arguments/options to run.", multiple=True) ] def __init__(self): # type: () -> None from poetry.console.args.run_args_parser import RunArgsParser super(RunCommand, self).__init__() self.config.set_args_parser(RunArgsParser()) def handle(self): # type: () -> Any args = self.argument("args") script = args[0] scripts = self.poetry.local_config.get("scripts") if scripts and script in scripts: return self.run_script(scripts[script], args) return self.env.execute(*args) def run_script(self, script, args): # type: (Union[str, dict], str) -> Any if isinstance(script, dict): script = script["callable"] module, callable_ = script.split(":") src_in_sys_path = "sys.path.append('src'); " if self._module.is_in_src( ) else "" cmd = ["python", "-c"] cmd += [ "import sys; " "from importlib import import_module; " "sys.argv = {!r}; {}" "import_module('{}').{}()".format(args, src_in_sys_path, module, callable_) ] return self.env.execute(*cmd)
class EnvRemoveCommand(Command): name = "remove" description = "Removes a specific virtualenv associated with the project." arguments = [ argument("python", "The python executable to remove the virtualenv for.") ] def handle(self): # type: () -> None from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) venv = manager.remove(self.argument("python")) self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path))
class GetCommand(Command): name = "get" description = "Get value from the configuration" arguments = [ argument( name="key", description="Key to retrieve", optional=False, ) ] options = [file_type, *scopes] def handle(self): print("Here comes value")
class RunCommand(Command): name = "run" description = "Run fate" arguments = [ argument("path", "The directory in which to run fate or path to executable", optional=True) ] options = [ option("output-type", description= "Instruct executable to print output to file or to stdout", value_required=True), option("execution-environment", description="Which environment to use to run the solution", value_required=True), option("docker", description="Use docker to run the solution", flag=True), option("debug", description="Run solution in debug mode", flag=True), option("input-file", description="Path input file of the test case", value_required=True), option("output-file", description="Path to output file of the test case", value_required=True), option("test-case-name", description="Name of the test case to execute", value_required=True), option( "concurrent", description= "Run tests concurrently (optionally specify number of concurent executions)", value_required=False), option("memory-limit", description= "Impose a memory limit (MB) when running in docker container", value_required=True), option("time-limit", description="Impose a time limit (ms) for each execution", value_required=True) ] def handle(self) -> None: pass
class UpdateCommand(EnvCommand): name = "update" description = ( "Update dependencies as according to the <comment>pyproject.toml</> file." ) arguments = [ argument("packages", "The packages to update", optional=True, multiple=True) ] options = [ option("no-dev", None, "Do not update dev dependencies."), option( "dry-run", None, "Output the operations but do not execute anything " "(implicitly enables --verbose).", ), option("lock", None, "Do not perform operations (only update the lockfile)."), ] loggers = ["poetry.repositories.pypi_repository"] def handle(self): from poetry.installation import Installer packages = self.argument("packages") installer = Installer(self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool) if packages: installer.whitelist({name: "*" for name in packages}) installer.dev_mode(not self.option("no-dev")) installer.dry_run(self.option("dry-run")) installer.execute_operations(not self.option("lock")) # Force update installer.update(True) return installer.run()
class StartAppCommand(Command): name = "startapp" description = "Create a new flubber app" arguments = [argument("name", "The app name.")] options = [option("path", None, "The path to create the app at.", flag=False)] def handle(self) -> None: from flubber.console.commands.utils import get_flubber_path if self.option("path"): path = Path(self.option("path")) / Path(self.argument("name")) else: path = Path.cwd() / Path(self.argument("name")) name = self.argument("name") if path.exists(): if list(path.glob("*")): # Directory is not empty. Aborting. raise RuntimeError( "Destination <fg=yellow>{}</> " "exists and is not empty".format(path) ) self.copy_project_folder(src=get_flubber_path(), dst=path) self.line( "Created flubber project <info>{}</> in <fg=blue>{}</>".format( name, path.relative_to(Path.cwd()) ) ) def copy_project_folder(self, src: str, dst: str) -> None: import shutil self.line("<info>Creating project ...</>") template_project_path = Path(src) / Path("conf/app_template") dst_path = Path(dst) shutil.copytree(template_project_path, dst_path) return
class UpdateCommand(InstallerCommand): name = "update" description = ( "Update the dependencies as according to the <comment>pyproject.toml</> file." ) arguments = [ argument("packages", "The packages to update", optional=True, multiple=True) ] options = [ option("no-dev", None, "Do not update the development dependencies."), option( "dry-run", None, "Output the operations but do not execute anything " "(implicitly enables --verbose).", ), option("lock", None, "Do not perform operations (only update the lockfile)."), ] loggers = ["poetry.repositories.pypi_repository"] def handle(self): # type: () -> int packages = self.argument("packages") self._installer.use_executor( self.poetry.config.get("experimental.new-installer", False)) if packages: self._installer.whitelist({name: "*" for name in packages}) self._installer.dev_mode(not self.option("no-dev")) self._installer.dry_run(self.option("dry-run")) self._installer.execute_operations(not self.option("lock")) # Force update self._installer.update(True) return self._installer.run()
class EnvUseCommand(Command): name = "use" description = "Activates or creates a new virtualenv for the current project." arguments = [argument("python", "The python executable to use.")] def handle(self): # type: () -> None from poetry.utils.env import EnvManager manager = EnvManager(self.poetry) if self.argument("python") == "system": manager.deactivate(self._io) return env = manager.activate(self.argument("python"), self._io) self.line("Using virtualenv: <comment>{}</>".format(env.path))
class SearchCommand(Command): name = "search" description = "Searches for packages on remote repositories." arguments = [argument("tokens", "The tokens to search for.", multiple=True)] def handle(self): from poetry.repositories.pypi_repository import PyPiRepository results = PyPiRepository().search(self.argument("tokens")) for result in results: self.line("") name = "<info>{}</>".format(result.name) name += " (<comment>{}</>)".format(result.version) self.line(name) if result.description: self.line(" {}".format(result.description))
class MakeShardsCommand(BaseCommand): name = "make-shards" description = "Construct shards for Distributed Training from one txt file." arguments = [argument("path", description="Path to txt file.")] options = [ option( "shards", "s", description="Number of shrads to split txt file.", flag=False, value_required=False, ), option( "seed", None, default=13, description="Random seed for to randomly assign sample to a shard.", flag=False, value_required=False, ), ] def handle(self) -> None: rng = random.Random(int(self.option("seed"))) shards = int(self.option("shards")) file_path = Path(self.argument("path")) directory = file_path.parent / file_path.stem self.prepare_directory(directory) with file_path.open("r", encoding="utf-8") as file: for line in tqdm(map(lambda x: x.strip(), file), desc="Sharding dataset"): choice = rng.randint(0, shards - 1) self.write_to_shard(line, directory / f"shard-{choice}.txt") @staticmethod def write_to_shard(line: str, shard_path: Path) -> None: with shard_path.open("a", encoding="utf-8") as file: file.write(line + "\n")
class DebugResolveCommand(InitCommand): name = "resolve" description = "Debugs dependency resolution." arguments = [ argument("package", "The packages to resolve.", optional=True, multiple=True) ] options = [ option( "extras", "E", "Extras to activate for the dependency.", flag=False, multiple=True, ), option("python", None, "Python version(s) to use for resolution.", flag=False), option("tree", None, "Display the dependency tree."), option("install", None, "Show what would be installed for the current system."), ] loggers = ["poetry.repositories.pypi_repository"] def handle(self): from poetry.core.packages.project_package import ProjectPackage from poetry.io.null_io import NullIO from poetry.puzzle import Solver from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository from poetry.utils.env import EnvManager packages = self.argument("package") if not packages: package = self.poetry.package else: # Using current pool for determine_requirements() self._pool = self.poetry.pool package = ProjectPackage( self.poetry.package.name, self.poetry.package.version ) # Silencing output is_quiet = self.io.output.is_quiet() if not is_quiet: self.io.output.set_quiet(True) requirements = self._determine_requirements(packages) if not is_quiet: self.io.output.set_quiet(False) for constraint in requirements: name = constraint.pop("name") dep = package.add_dependency(name, constraint) extras = [] for extra in self.option("extras"): if " " in extra: extras += [e.strip() for e in extra.split(" ")] else: extras.append(extra) for ex in extras: dep.extras.append(ex) package.python_versions = self.option("python") or ( self.poetry.package.python_versions ) pool = self.poetry.pool solver = Solver(package, pool, Repository(), Repository(), self._io) ops = solver.solve() self.line("") self.line("Resolution results:") self.line("") if self.option("tree"): show_command = self.application.find("show") show_command.init_styles(self.io) packages = [op.package for op in ops] repo = Repository(packages) requires = package.requires + package.dev_requires for pkg in repo.packages: for require in requires: if pkg.name == require.name: show_command.display_package_tree(self.io, pkg, repo) break return 0 table = self.table([], style="borderless") rows = [] if self.option("install"): env = EnvManager(self.poetry).get() pool = Pool() locked_repository = Repository() for op in ops: locked_repository.add_package(op.package) pool.add_repository(locked_repository) solver = Solver(package, pool, Repository(), Repository(), NullIO()) with solver.use_environment(env): ops = solver.solve() for op in ops: if self.option("install") and op.skipped: continue pkg = op.package row = [ "<c1>{}</c1>".format(pkg.name), "<b>{}</b>".format(pkg.version), "", ] if not pkg.marker.is_any(): row[2] = str(pkg.marker) rows.append(row) table.set_rows(rows) table.render(self.io)
class ConfigCommand(Command): name = "config" description = "Manages configuration settings." arguments = [ argument("key", "Setting key.", optional=True), argument("value", "Setting value.", optional=True, multiple=True), ] options = [ option("list", None, "List configuration settings."), option("unset", None, "Unset configuration setting."), option("local", None, "Set/Get from the project's local configuration."), ] help = """This command allows you to edit the poetry config settings and repositories. To add a repository: <comment>poetry config repositories.foo https://bar.com/simple/</comment> To remove a repository (repo is a short alias for repositories): <comment>poetry config --unset repo.foo</comment>""" LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} @property def unique_config_values(self): from poetry.config.config import boolean_normalizer from poetry.config.config import boolean_validator from poetry.locations import CACHE_DIR from poetry.utils._compat import Path unique_config_values = { "cache-dir": ( str, lambda val: str(Path(val)), str(Path(CACHE_DIR) / "virtualenvs"), ), "virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), "virtualenvs.path": ( str, lambda val: str(Path(val)), str(Path(CACHE_DIR) / "virtualenvs"), ), } return unique_config_values def handle(self): from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR from poetry.utils._compat import Path from poetry.utils._compat import basestring from poetry.utils.toml_file import TomlFile config = Factory.create_config(self.io) config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") try: local_config_file = TomlFile(self.poetry.file.parent / "poetry.toml") if local_config_file.exists(): config.merge(local_config_file.read()) except RuntimeError: local_config_file = TomlFile(Path.cwd() / "poetry.toml") if self.option("local"): config.set_config_source(FileConfigSource(local_config_file)) if not config_file.exists(): config_file.path.parent.mkdir(parents=True, exist_ok=True) config_file.touch(mode=0o0600) if self.option("list"): self._list_configuration(config.all(), config.raw()) return 0 setting_key = self.argument("key") if not setting_key: return 0 if self.argument("value") and self.option("unset"): raise RuntimeError( "You can not combine a setting value with --unset") # show the value if no value is provided if not self.argument("value") and not self.option("unset"): m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) if m: if not m.group(1): value = {} if config.get("repositories") is not None: value = config.get("repositories") else: repo = config.get("repositories.{}".format(m.group(1))) if repo is None: raise ValueError( "There is no {} repository defined".format( m.group(1))) value = repo self.line(str(value)) else: values = self.unique_config_values if setting_key not in values: raise ValueError( "There is no {} setting.".format(setting_key)) value = config.get(setting_key) if not isinstance(value, basestring): value = json.dumps(value) self.line(value) return 0 values = self.argument("value") unique_config_values = self.unique_config_values if setting_key in unique_config_values: if self.option("unset"): return config.config_source.remove_property(setting_key) return self._handle_single_value( config.config_source, setting_key, unique_config_values[setting_key], values, ) # handle repositories m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) if m: if not m.group(1): raise ValueError( "You cannot remove the [repositories] section") if self.option("unset"): repo = config.get("repositories.{}".format(m.group(1))) if repo is None: raise ValueError( "There is no {} repository defined".format(m.group(1))) config.config_source.remove_property("repositories.{}".format( m.group(1))) return 0 if len(values) == 1: url = values[0] config.config_source.add_property( "repositories.{}.url".format(m.group(1)), url) return 0 raise ValueError( "You must pass the url. " "Example: poetry config repositories.foo https://bar.com") # handle auth m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) if m: if self.option("unset"): keyring_repository_password_del(config, m.group(2)) config.auth_config_source.remove_property("{}.{}".format( m.group(1), m.group(2))) return 0 if m.group(1) == "http-basic": if len(values) == 1: username = values[0] # Only username, so we prompt for password password = self.secret("Password:"******"Expected one or two arguments " "(username, password), got {}".format( len(values))) else: username = values[0] password = values[1] property_value = dict(username=username) try: keyring_repository_password_set(m.group(2), username, password) except RuntimeError: property_value.update(password=password) config.auth_config_source.add_property( "{}.{}".format(m.group(1), m.group(2)), property_value) elif m.group(1) == "pypi-token": if len(values) != 1: raise ValueError( "Expected only one argument (token), got {}".format( len(values))) token = values[0] config.auth_config_source.add_property( "{}.{}".format(m.group(1), m.group(2)), token) return 0 # handle certs m = re.match(r"(?:certificates)\.([^.]+)\.(cert|client-cert)", self.argument("key")) if m: if self.option("unset"): config.auth_config_source.remove_property( "certificates.{}.{}".format(m.group(1), m.group(2))) return 0 if len(values) == 1: config.auth_config_source.add_property( "certificates.{}.{}".format(m.group(1), m.group(2)), values[0]) else: raise ValueError("You must pass exactly 1 value") return 0 raise ValueError("Setting {} does not exist".format( self.argument("key"))) def _handle_single_value(self, source, key, callbacks, values): validator, normalizer, _ = callbacks if len(values) > 1: raise RuntimeError("You can only pass one value.") value = values[0] if not validator(value): raise RuntimeError('"{}" is an invalid value for {}'.format( value, key)) source.add_property(key, normalizer(value)) return 0 def _list_configuration(self, config, raw, k=""): from poetry.utils._compat import basestring orig_k = k for key, value in sorted(config.items()): if k + key in self.LIST_PROHIBITED_SETTINGS: continue raw_val = raw.get(key) if isinstance(value, dict): k += "{}.".format(key) self._list_configuration(value, raw_val, k=k) k = orig_k continue elif isinstance(value, list): value = [ json.dumps(val) if isinstance(val, list) else val for val in value ] value = "[{}]".format(", ".join(value)) if k.startswith("repositories."): message = "<c1>{}</c1> = <c2>{}</c2>".format( k + key, json.dumps(raw_val)) elif isinstance(raw_val, basestring) and raw_val != value: message = "<c1>{}</c1> = <c2>{}</c2> # {}".format( k + key, json.dumps(raw_val), value) else: message = "<c1>{}</c1> = <c2>{}</c2>".format( k + key, json.dumps(value)) self.line(message) def _list_setting(self, contents, setting=None, k=None, default=None): values = self._get_setting(contents, setting, k, default) for value in values: self.line("<comment>{}</comment> = <info>{}</info>".format( value[0], value[1])) def _get_setting(self, contents, setting=None, k=None, default=None): orig_k = k if setting and setting.split(".")[0] not in contents: value = json.dumps(default) return [((k or "") + setting, value)] else: values = [] for key, value in contents.items(): if setting and key != setting.split(".")[0]: continue if isinstance(value, dict) or key == "repositories" and k is None: if k is None: k = "" k += re.sub(r"^config\.", "", key + ".") if setting and len(setting) > 1: setting = ".".join(setting.split(".")[1:]) values += self._get_setting(value, k=k, setting=setting, default=default) k = orig_k continue if isinstance(value, list): value = [ json.dumps(val) if isinstance(val, list) else val for val in value ] value = "[{}]".format(", ".join(value)) value = json.dumps(value) values.append(((k or "") + key, value)) return values def _get_formatted_value(self, value): if isinstance(value, list): value = [ json.dumps(val) if isinstance(val, list) else val for val in value ] value = "[{}]".format(", ".join(value)) return json.dumps(value)
class VersionCommand(Command): name = "version" description = ("Shows the version of the project or bumps it when a valid " "bump rule is provided.") arguments = [ argument( "version", "The version number or the rule to update the version.", optional=True, ) ] options = [option("short", "s", "Output the version number only")] help = """\ The version command shows the current version of the project or bumps the version of the project and writes the new version back to <comment>pyproject.toml</> if a valid bump rule is provided. The new version should ideally be a valid semver string or a valid bump rule: patch, minor, major, prepatch, preminor, premajor, prerelease. """ RESERVED = { "major", "minor", "patch", "premajor", "preminor", "prepatch", "prerelease", } def handle(self): version = self.argument("version") if version: version = self.increment_version( self.poetry.package.pretty_version, version) if self.option("short"): self.line("{}".format(version)) else: self.line( "Bumping version from <b>{}</> to <fg=green>{}</>".format( self.poetry.package.pretty_version, version)) content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] poetry_content["version"] = version.text self.poetry.file.write(content) else: if self.option("short"): self.line("{}".format(self.poetry.package.pretty_version)) else: self.line("<comment>{}</> <info>{}</>".format( self.poetry.package.name, self.poetry.package.pretty_version)) def increment_version(self, version, rule): from poetry.core.semver import Version try: version = Version.parse(version) except ValueError: raise ValueError( "The project's version doesn't seem to follow semver") if rule in {"major", "premajor"}: new = version.next_major if rule == "premajor": new = new.first_prerelease elif rule in {"minor", "preminor"}: new = version.next_minor if rule == "preminor": new = new.first_prerelease elif rule in {"patch", "prepatch"}: new = version.next_patch if rule == "prepatch": new = new.first_prerelease elif rule == "prerelease": if version.is_prerelease(): pre = version.prerelease new_prerelease = int(pre[1]) + 1 new = Version.parse("{}.{}.{}-{}".format( version.major, version.minor, version.patch, ".".join([pre[0], str(new_prerelease)]), )) else: new = version.next_patch.first_prerelease else: new = Version.parse(rule) return new
class RemoveCommand(EnvCommand): name = "remove" description = "Removes a package from the project dependencies." arguments = [ argument("packages", "Packages that should be removed", multiple=True) ] options = [ option("dev", "D", "Removes a package from the development dependencies."), option( "dry-run", None, "Outputs the operations but will not execute anything " "(implicitly enables --verbose).", ), ] help = """The <info>remove</info> command removes a package from the current list of installed packages <info>poetry remove</info>""" loggers = ["poetry.repositories.pypi_repository"] def handle(self): from poetry.installation import Installer packages = self.argument("packages") is_dev = self.option("dev") original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] section = "dependencies" if is_dev: section = "dev-dependencies" # Deleting entries requirements = {} for name in packages: found = False for key in poetry_content[section]: if key.lower() == name.lower(): found = True requirements[key] = poetry_content[section][key] break if not found: raise ValueError("Package {} not found".format(name)) for key in requirements: del poetry_content[section][key] # Write the new content back self.poetry.file.write(content) # Update packages self.reset_poetry() installer = Installer(self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool) installer.dry_run(self.option("dry-run")) installer.update(True) installer.whitelist(requirements) try: status = installer.run() except Exception: self.poetry.file.write(original_content) raise if status != 0 or self.option("dry-run"): # Revert changes if not self.option("dry-run"): self.error("\n" "Removal failed, reverting pyproject.toml " "to its original content.") self.poetry.file.write(original_content) return status
class ShowCommand(EnvCommand): name = "show" description = "Shows information about packages." arguments = [argument("package", "The package to inspect", optional=True)] options = [ option("no-dev", None, "Do not list the development dependencies."), option("tree", "t", "List the dependencies as a tree."), option("latest", "l", "Show the latest version."), option( "outdated", "o", "Show the latest version but only for packages that are outdated.", ), option( "all", "a", "Show all packages (even those not compatible with current system).", ), ] help = """The show command displays detailed information about a package, or lists all packages available.""" colors = ["cyan", "yellow", "green", "magenta", "blue"] def handle(self): from clikit.utils.terminal import Terminal from poetry.io.null_io import NullIO from poetry.puzzle.solver import Solver from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository from poetry.utils.helpers import get_package_version_display_string package = self.argument("package") if self.option("tree"): self.init_styles(self.io) if self.option("outdated"): self._args.set_option("latest", True) include_dev = not self.option("no-dev") locked_repo = self.poetry.locker.locked_repository(True) # Show tree view if requested if self.option("tree") and not package: requires = self.poetry.package.requires + self.poetry.package.dev_requires packages = locked_repo.packages for package in packages: for require in requires: if package.name == require.name: self.display_package_tree(self._io, package, locked_repo) break return 0 table = self.table(style="compact") # table.style.line_vc_char = "" locked_packages = locked_repo.packages pool = Pool(ignore_repository_names=True) pool.add_repository(locked_repo) solver = Solver( self.poetry.package, pool=pool, installed=Repository(), locked=locked_repo, io=NullIO(), ) solver.provider.load_deferred(False) with solver.use_environment(self.env): ops = solver.solve() required_locked_packages = set( [op.package for op in ops if not op.skipped]) if self.option("no-dev"): required_locked_packages = [ p for p in locked_packages if p.category == "main" ] if package: pkg = None for locked in locked_packages: if package.lower() == locked.name: pkg = locked break if not pkg: raise ValueError("Package {} not found".format(package)) if self.option("tree"): self.display_package_tree(self.io, pkg, locked_repo) return 0 rows = [ ["<info>name</>", " : <c1>{}</>".format(pkg.pretty_name)], [ "<info>version</>", " : <b>{}</b>".format(pkg.pretty_version) ], ["<info>description</>", " : {}".format(pkg.description)], ] table.add_rows(rows) table.render(self.io) if pkg.requires: self.line("") self.line("<info>dependencies</info>") for dependency in pkg.requires: self.line(" - <c1>{}</c1> <b>{}</b>".format( dependency.pretty_name, dependency.pretty_constraint)) return 0 show_latest = self.option("latest") show_all = self.option("all") terminal = Terminal() width = terminal.width name_length = version_length = latest_length = 0 latest_packages = {} latest_statuses = {} installed_repo = InstalledRepository.load(self.env) # Computing widths for locked in locked_packages: if locked not in required_locked_packages and not show_all: continue current_length = len(locked.pretty_name) if not self._io.output.supports_ansi(): installed_status = self.get_installed_status( locked, installed_repo) if installed_status == "not-installed": current_length += 4 if show_latest: latest = self.find_latest_package(locked, include_dev) if not latest: latest = locked latest_packages[locked.pretty_name] = latest update_status = latest_statuses[ locked.pretty_name] = self.get_update_status( latest, locked) if not self.option( "outdated") or update_status != "up-to-date": name_length = max(name_length, current_length) version_length = max( version_length, len( get_package_version_display_string( locked, root=self.poetry.file.parent)), ) latest_length = max( latest_length, len( get_package_version_display_string( latest, root=self.poetry.file.parent)), ) else: name_length = max(name_length, current_length) version_length = max( version_length, len( get_package_version_display_string( locked, root=self.poetry.file.parent)), ) write_version = name_length + version_length + 3 <= width write_latest = name_length + version_length + latest_length + 3 <= width write_description = name_length + version_length + latest_length + 24 <= width for locked in locked_packages: color = "cyan" name = locked.pretty_name install_marker = "" if locked not in required_locked_packages: if not show_all: continue color = "black;options=bold" else: installed_status = self.get_installed_status( locked, installed_repo) if installed_status == "not-installed": color = "red" if not self._io.output.supports_ansi(): # Non installed in non decorated mode install_marker = " (!)" if (show_latest and self.option("outdated") and latest_statuses[locked.pretty_name] == "up-to-date"): continue line = "<fg={}>{:{}}{}</>".format( color, name, name_length - len(install_marker), install_marker) if write_version: line += " <b>{:{}}</b>".format( get_package_version_display_string( locked, root=self.poetry.file.parent), version_length, ) if show_latest: latest = latest_packages[locked.pretty_name] update_status = latest_statuses[locked.pretty_name] if write_latest: color = "green" if update_status == "semver-safe-update": color = "red" elif update_status == "update-possible": color = "yellow" line += " <fg={}>{:{}}</>".format( color, get_package_version_display_string( latest, root=self.poetry.file.parent), latest_length, ) if write_description: description = locked.description remaining = width - name_length - version_length - 4 if show_latest: remaining -= latest_length if len(locked.description) > remaining: description = description[:remaining - 3] + "..." line += " " + description self.line(line) def display_package_tree(self, io, package, installed_repo): io.write("<c1>{}</c1>".format(package.pretty_name)) description = "" if package.description: description = " " + package.description io.write_line(" <b>{}</b>{}".format(package.pretty_version, description)) dependencies = package.requires dependencies = sorted(dependencies, key=lambda x: x.name) tree_bar = "├" j = 0 total = len(dependencies) for dependency in dependencies: j += 1 if j == total: tree_bar = "└" level = 1 color = self.colors[level] info = "{tree_bar}── <{color}>{name}</{color}> {constraint}".format( tree_bar=tree_bar, color=color, name=dependency.name, constraint=dependency.pretty_constraint, ) self._write_tree_line(io, info) tree_bar = tree_bar.replace("└", " ") packages_in_tree = [package.name, dependency.name] self._display_tree(io, dependency, installed_repo, packages_in_tree, tree_bar, level + 1) def _display_tree( self, io, dependency, installed_repo, packages_in_tree, previous_tree_bar="├", level=1, ): previous_tree_bar = previous_tree_bar.replace("├", "│") dependencies = [] for package in installed_repo.packages: if package.name == dependency.name: dependencies = package.requires break dependencies = sorted(dependencies, key=lambda x: x.name) tree_bar = previous_tree_bar + " ├" i = 0 total = len(dependencies) for dependency in dependencies: i += 1 current_tree = packages_in_tree if i == total: tree_bar = previous_tree_bar + " └" color_ident = level % len(self.colors) color = self.colors[color_ident] circular_warn = "" if dependency.name in current_tree: circular_warn = "(circular dependency aborted here)" info = "{tree_bar}── <{color}>{name}</{color}> {constraint} {warn}".format( tree_bar=tree_bar, color=color, name=dependency.name, constraint=dependency.pretty_constraint, warn=circular_warn, ) self._write_tree_line(io, info) tree_bar = tree_bar.replace("└", " ") if dependency.name not in current_tree: current_tree.append(dependency.name) self._display_tree(io, dependency, installed_repo, current_tree, tree_bar, level + 1) def _write_tree_line(self, io, line): if not io.output.supports_ansi(): line = line.replace("└", "`-") line = line.replace("├", "|-") line = line.replace("──", "-") line = line.replace("│", "|") io.write_line(line) def init_styles(self, io): from clikit.api.formatter import Style for color in self.colors: style = Style(color).fg(color) io.output.formatter.add_style(style) io.error_output.formatter.add_style(style) def find_latest_package(self, package, include_dev): from clikit.io import NullIO from poetry.puzzle.provider import Provider from poetry.version.version_selector import VersionSelector # find the latest version allowed in this pool if package.source_type in ("git", "file", "directory"): requires = self.poetry.package.requires if include_dev: requires = requires + self.poetry.package.dev_requires for dep in requires: if dep.name == package.name: provider = Provider(self.poetry.package, self.poetry.pool, NullIO()) if dep.is_vcs(): return provider.search_for_vcs(dep)[0] if dep.is_file(): return provider.search_for_file(dep)[0] if dep.is_directory(): return provider.search_for_directory(dep)[0] name = package.name selector = VersionSelector(self.poetry.pool) return selector.find_best_candidate( name, ">={}".format(package.pretty_version)) def get_update_status(self, latest, package): from poetry.core.semver import parse_constraint if latest.full_pretty_version == package.full_pretty_version: return "up-to-date" constraint = parse_constraint("^" + package.pretty_version) if latest.version and constraint.allows(latest.version): # It needs an immediate semver-compliant upgrade return "semver-safe-update" # it needs an upgrade but has potential BC breaks so is not urgent return "update-possible" def get_installed_status(self, locked, installed_repo): for package in installed_repo.packages: if locked.name == package.name: return "installed" return "not-installed"
class DebugResolveCommand(Command): name = "resolve" description = "Debugs dependency resolution." arguments = [ argument("package", "The packages to resolve.", optional=True, multiple=True) ] options = [ option( "extras", "E", "Extras to activate for the dependency.", flag=False, multiple=True, ), option("python", None, "Python version(s) to use for resolution.", flag=False), option("tree", None, "Display the dependency tree."), option("install", None, "Show what would be installed for the current system."), ] loggers = ["poetry.repositories.pypi_repository"] def handle(self): from poetry.packages import ProjectPackage from poetry.puzzle import Solver from poetry.repositories.repository import Repository from poetry.semver import parse_constraint from poetry.utils.env import EnvManager packages = self.argument("package") if not packages: package = self.poetry.package else: package = ProjectPackage(self.poetry.package.name, self.poetry.package.version) requirements = self._format_requirements(packages) for name, constraint in requirements.items(): dep = package.add_dependency(name, constraint) extras = [] for extra in self.option("extras"): if " " in extra: extras += [e.strip() for e in extra.split(" ")] else: extras.append(extra) for ex in extras: dep.extras.append(ex) package.python_versions = self.option("python") or ( self.poetry.package.python_versions) pool = self.poetry.pool solver = Solver(package, pool, Repository(), Repository(), self._io) ops = solver.solve() self.line("") self.line("Resolution results:") self.line("") if self.option("tree"): show_command = self.application.find("show") show_command.init_styles(self.io) packages = [op.package for op in ops] repo = Repository(packages) requires = package.requires + package.dev_requires for pkg in repo.packages: for require in requires: if pkg.name == require.name: show_command.display_package_tree(self.io, pkg, repo) break return 0 env = EnvManager(self.poetry.config).get(self.poetry.file.parent) current_python_version = parse_constraint(".".join( str(v) for v in env.version_info)) table = self.table([], style="borderless") rows = [] for op in ops: pkg = op.package if self.option("install"): if not pkg.python_constraint.allows( current_python_version) or not env.is_valid_for_marker( pkg.marker): continue row = [ "<info>{}</info>".format(pkg.name), "<b>{}</b>".format(pkg.version), "", ] if not pkg.marker.is_any(): row[2] = str(pkg.marker) rows.append(row) table.set_rows(rows) table.render(self.io) def _determine_requirements(self, requires): # type: (List[str]) -> List[str] from poetry.semver import parse_constraint if not requires: return [] requires = self._parse_name_version_pairs(requires) for requirement in requires: if "version" in requirement: parse_constraint(requirement["version"]) return requires def _parse_name_version_pairs(self, pairs): # type: (list) -> list result = [] for i in range(len(pairs)): if pairs[i].startswith("git+https://"): url = pairs[i].lstrip("git+") rev = None if "@" in url: url, rev = url.split("@") pair = {"name": url.split("/")[-1].rstrip(".git"), "git": url} if rev: pair["rev"] = rev result.append(pair) continue pair = re.sub("^([^=: ]+)[=: ](.*)$", "\\1 \\2", pairs[i].strip()) pair = pair.strip() if " " in pair: name, version = pair.split(" ", 2) result.append({"name": name, "version": version}) else: result.append({"name": pair, "version": "*"}) return result def _format_requirements(self, requirements): # type: (List[str]) -> dict requires = {} requirements = self._determine_requirements(requirements) for requirement in requirements: name = requirement.pop("name") requires[name] = requirement return requires
class SelfUpdateCommand(Command): name = "update" description = "Updates Poetry to the latest version." arguments = [ argument("version", "The version to update to.", optional=True) ] options = [option("preview", None, "Install prereleases.")] REPOSITORY_URL = "https://github.com/python-poetry/poetry" BASE_URL = REPOSITORY_URL + "/releases/download" @property def home(self): # type: () -> Path from pathlib import Path return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() @property def bin(self): # type: () -> Path return self.home / "bin" @property def lib(self): # type: () -> Path return self.home / "lib" @property def lib_backup(self): # type: () -> Path return self.home / "lib-backup" def handle(self): # type: () -> None from poetry.__version__ import __version__ from poetry.core.semver import Version from poetry.repositories.pypi_repository import PyPiRepository self._check_recommended_installation() version = self.argument("version") if not version: version = ">=" + __version__ repo = PyPiRepository(fallback=False) packages = repo.find_packages( Dependency("poetry", version, allows_prereleases=self.option("preview"))) if not packages: self.line("No release found for the specified version") return packages.sort(key=cmp_to_key(lambda x, y: 0 if x.version == y.version else int(x.version < y.version or -1))) release = None for package in packages: if package.is_prerelease(): if self.option("preview"): release = package break continue release = package break if release is None: self.line("No new release found") return if release.version == Version.parse(__version__): self.line("You are using the latest version") return self.update(release) def update(self, release): # type: ("Package") -> None version = release.version self.line("Updating to <info>{}</info>".format(version)) if self.lib_backup.exists(): shutil.rmtree(str(self.lib_backup)) # Backup the current installation if self.lib.exists(): shutil.copytree(str(self.lib), str(self.lib_backup)) shutil.rmtree(str(self.lib)) try: self._update(version) except Exception: if not self.lib_backup.exists(): raise shutil.copytree(str(self.lib_backup), str(self.lib)) shutil.rmtree(str(self.lib_backup)) raise finally: if self.lib_backup.exists(): shutil.rmtree(str(self.lib_backup)) self.make_bin() self.line("") self.line("") self.line( "<info>Poetry</info> (<comment>{}</comment>) is installed now. Great!" .format(version)) def _update(self, version): # type: ("Version") -> None from poetry.utils.helpers import temporary_directory release_name = self._get_release_name(version) checksum = "{}.sha256sum".format(release_name) base_url = self.BASE_URL try: r = urlopen(base_url + "/{}/{}".format(version, checksum)) except HTTPError as e: if e.code == 404: raise RuntimeError("Could not find {} file".format(checksum)) raise checksum = r.read().decode().strip() # We get the payload from the remote host name = "{}.tar.gz".format(release_name) try: r = urlopen(base_url + "/{}/{}".format(version, name)) except HTTPError as e: if e.code == 404: raise RuntimeError("Could not find {} file".format(name)) raise meta = r.info() size = int(meta["Content-Length"]) current = 0 block_size = 8192 bar = self.progress_bar(max=size) bar.set_format( " - Downloading <info>{}</> <comment>%percent%%</>".format(name)) bar.start() sha = hashlib.sha256() with temporary_directory(prefix="poetry-updater-") as dir_: tar = os.path.join(dir_, name) with open(tar, "wb") as f: while True: buffer = r.read(block_size) if not buffer: break current += len(buffer) f.write(buffer) sha.update(buffer) bar.set_progress(current) bar.finish() # Checking hashes if checksum != sha.hexdigest(): raise RuntimeError( "Hashes for {} do not match: {} != {}".format( name, checksum, sha.hexdigest())) gz = GzipFile(tar, mode="rb") try: with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f: f.extractall(str(self.lib)) finally: gz.close() def process(self, *args): # type: (*Any) -> str return subprocess.check_output(list(args), stderr=subprocess.STDOUT) def _check_recommended_installation(self): # type: () -> None from pathlib import Path current = Path(__file__) try: current.relative_to(self.home) except ValueError: raise RuntimeError( "Poetry was not installed with the recommended installer. " "Cannot update automatically.") def _get_release_name(self, version): # type: ("Version") -> str platform = sys.platform if platform == "linux2": platform = "linux" return "poetry-{}-{}".format(version, platform) def make_bin(self): # type: () -> None from poetry.utils._compat import WINDOWS self.bin.mkdir(0o755, parents=True, exist_ok=True) python_executable = self._which_python() if WINDOWS: with self.bin.joinpath("poetry.bat").open("w", newline="") as f: f.write( BAT.format( python_executable=python_executable, poetry_bin=str(self.bin / "poetry").replace( os.environ["USERPROFILE"], "%USERPROFILE%"), )) bin_content = BIN if not WINDOWS: bin_content = "#!/usr/bin/env {}\n".format( python_executable) + bin_content self.bin.joinpath("poetry").write_text(bin_content, encoding="utf-8") if not WINDOWS: # Making the file executable st = os.stat(str(self.bin.joinpath("poetry"))) os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC) def _which_python(self): # type: () -> str """ Decides which python executable we'll embed in the launcher script. """ from poetry.utils._compat import WINDOWS allowed_executables = ["python", "python3"] if WINDOWS: allowed_executables += ["py.exe -3", "py.exe -2"] # \d in regex ensures we can convert to int later version_matcher = re.compile( r"^Python (?P<major>\d+)\.(?P<minor>\d+)\..+$") fallback = None for executable in allowed_executables: try: raw_version = subprocess.check_output( executable + " --version", stderr=subprocess.STDOUT, shell=True).decode("utf-8") except subprocess.CalledProcessError: continue match = version_matcher.match(raw_version.strip()) if match and tuple(map(int, match.groups())) >= (3, 0): # favor the first py3 executable we can find. return executable if fallback is None: # keep this one as the fallback; it was the first valid executable we found. fallback = executable if fallback is None: # Avoid breaking existing scripts fallback = "python" return fallback