def test_getting_inline_table_is_still_an_inline_table(): content = """\ [tool.poetry] name = "foo" [tool.poetry.dependencies] [tool.poetry.dev-dependencies] """ doc = parse(content) poetry_section = doc["tool"]["poetry"] dependencies = poetry_section["dependencies"] dependencies["foo"] = tomlkit.inline_table() dependencies["foo"]["version"] = "^2.0" dependencies["foo"]["source"] = "local" dependencies["bar"] = tomlkit.inline_table() dependencies["bar"]["version"] = "^3.0" dependencies["bar"]["source"] = "remote" dev_dependencies = poetry_section["dev-dependencies"] dev_dependencies["baz"] = tomlkit.inline_table() dev_dependencies["baz"]["version"] = "^4.0" dev_dependencies["baz"]["source"] = "other" assert ("""\ [tool.poetry] name = "foo" [tool.poetry.dependencies] foo = {version = "^2.0", source = "local"} bar = {version = "^3.0", source = "remote"} [tool.poetry.dev-dependencies] baz = {version = "^4.0", source = "other"} """ == doc.as_string())
def format_lockfile(mapping, fetched_dependencies, summary_collection): """Format lock file from a dict of resolved candidates, a mapping of dependencies and a collection of package summaries. """ packages = tomlkit.aot() metadata = tomlkit.table() for k, v in sorted(mapping.items()): base = tomlkit.table() base.update(v.as_lockfile_entry()) base.add("summary", summary_collection[strip_extras(k)[0]]) deps = tomlkit.table() for r in fetched_dependencies[k].values(): name, req = r.as_req_dict() if getattr(req, "items", None) is not None: inline = tomlkit.inline_table() inline.update(req) deps.add(name, inline) else: deps.add(name, req) if len(deps) > 0: base.add("dependencies", deps) packages.append(base) if v.hashes: key = f"{k} {v.version}" array = tomlkit.array() array.multiline(True) for filename, hash_value in v.hashes.items(): inline = tomlkit.inline_table() inline.update({"file": filename, "hash": hash_value}) array.append(inline) if array: metadata.add(key, array) doc = tomlkit.document() doc.update({"package": packages, "metadata": metadata}) return doc
def _make_env(from_format: str, from_path: str, to_format: str, to_path: str) -> Table: table = tomlkit.table() table['from'] = tomlkit.inline_table() table['from']['format'] = from_format table['from']['path'] = from_path table['to'] = tomlkit.inline_table() table['to']['format'] = to_format table['to']['path'] = to_path return table
def _make_env(from_format, from_path, to_format, to_path): table = tomlkit.table() table['from'] = tomlkit.inline_table() table['from']['format'] = from_format table['from']['path'] = from_path table['to'] = tomlkit.inline_table() table['to']['format'] = to_format table['to']['path'] = to_path return table
def set_changed_dependencies(changed_dependencies: Dependencies) -> None: """update the poerty dependencies in pyproject.toml""" pyproject_path = get_pyproject_path() # remove the changed dependencies pyproject = tomlkit.parse(pyproject_path.read_text()) dep = pyproject["tool"]["poetry"]["dependencies"] for name in changed_dependencies: del dep[name] pyproject_path.write_text(tomlkit.dumps(pyproject)) subprocess.call(["poetry", "update"]) # make the actual change pyproject = tomlkit.parse(pyproject_path.read_text()) dep = pyproject["tool"]["poetry"]["dependencies"] for name, req in changed_dependencies.items(): if isinstance(req, dict): if list(req.keys()) == ["version"]: req = req["version"] else: new_req = tomlkit.inline_table() new_req.update(req) req = new_req dep[name] = req pyproject_path.write_text(tomlkit.dumps(pyproject)) subprocess.call(["poetry", "update"])
def test_replace_with_comment(): content = 'a = "1"' doc = parse(content) a = tomlkit.item(int(doc["a"])) a.comment("`a` should be an int") doc["a"] = a expected = "a = 1 # `a` should be an int" assert doc.as_string() == expected content = 'a = "1, 2, 3"' doc = parse(content) a = tomlkit.array() a.comment("`a` should be an array") for x in doc["a"].split(","): a.append(int(x.strip())) doc["a"] = a expected = "a = [1, 2, 3] # `a` should be an array" assert doc.as_string() == expected doc = parse(content) a = tomlkit.inline_table() a.comment("`a` should be an inline-table") for x in doc["a"].split(","): i = int(x.strip()) a.append(chr(ord("a") + i - 1), i) doc["a"] = a expected = "a = {a = 1, b = 2, c = 3} # `a` should be an inline-table" assert doc.as_string() == expected
def add_dependencies(self, requirements: Dict[str, Requirement], show_message: bool = True) -> None: for name, dep in requirements.items(): if dep.from_section == "default": deps = self.tool_settings["dependencies"] elif dep.from_section == "dev": deps = self.tool_settings["dev-dependencies"] else: section = f"{dep.from_section}-dependencies" if section not in self.tool_settings: self.tool_settings[section] = tomlkit.table() deps = self.tool_settings[section] matched_name = next( filter(lambda k: strip_extras(name)[0] == safe_name(k).lower(), deps.keys()), None, ) name_to_save = dep.name if matched_name is None else matched_name _, req_dict = dep.as_req_dict() if isinstance(req_dict, dict): req = tomlkit.inline_table() req.update(req_dict) req_dict = req deps[name_to_save] = req_dict self.write_pyproject(show_message)
def write_toml(self, data, path=None): """Writes the given data structure out as TOML.""" if path is None: path = self.pipfile_location data = convert_toml_outline_tables(data) try: formatted_data = tomlkit.dumps(data).rstrip() except Exception: document = tomlkit.document() for section in ("packages", "dev-packages"): document[section] = tomlkit.container.Table() # Convert things to inline tables — fancy :) for package in data.get(section, {}): if hasattr(data[section][package], "keys"): table = tomlkit.inline_table() table.update(data[section][package]) document[section][package] = table else: document[section][package] = tomlkit.string(data[section][package]) formatted_data = tomlkit.dumps(document).rstrip() if ( vistir.compat.Path(path).absolute() == vistir.compat.Path(self.pipfile_location).absolute() ): newlines = self._pipfile_newlines else: newlines = DEFAULT_NEWLINES formatted_data = cleanup_toml(formatted_data) with io.open(path, "w", newline=newlines) as f: f.write(formatted_data) # pipfile is mutated! self.clear_pipfile_cache()
def get_package_include(self) -> InlineTable | None: package = inline_table() # If a project is created in the root directory (this is reasonable inside a # docker container, eg <https://github.com/python-poetry/poetry/issues/5103>) # then parts will be empty. parts = self._package_path_relative.parts if not parts: return None include = parts[0] package.append("include", include) # type: ignore[no-untyped-call] if self.basedir != Path(): package.append( # type: ignore[no-untyped-call] "from", self.basedir.as_posix(), ) else: if include == self._project: # package include and package name are the same, # packages table is redundant here. return None return package
def add_package_to_pipfile(self, package, dev=False): from .vendor.requirementslib import Requirement # Read and append Pipfile. p = self.parsed_pipfile # Don't re-capitalize file URLs or VCSs. if not isinstance(package, Requirement): package = Requirement.from_line(package.strip()) _, converted = package.pipfile_entry key = "dev-packages" if dev else "packages" # Set empty group if it doesn't exist yet. if key not in p: p[key] = {} name = self.get_package_name_in_pipfile(package.name, dev) if name and is_star(converted): # Skip for wildcard version return # Add the package to the group. if isinstance(converted, dict): package_table = tomlkit.inline_table() package_table.update(converted) converted = package_table p[key][name or package.normalized_name] = converted # Write Pipfile. self.write_toml(p)
def __parseLine(self, matches: Match[str]): if matches.group(3) is None: return [matches.group(1), matches.group(2).strip()] it = inline_table() it.append('version', matches.group(2)) it.append('markers', matches.group(3)) return [matches.group(1), it]
def test_trim_comments_when_building_inline_table(): table = inline_table() row = parse('foo = "bar" # Comment') table.update(row) assert table.as_string() == '{foo = "bar"}' value = item("foobaz") value.comment("Another comment") table.append("baz", value) assert "# Another comment" not in table.as_string()
def test_items_are_pickable(): n = item(12) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12" n = item(12.34) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12.34" n = item(True) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "true" n = item(datetime(2018, 10, 11, 12, 34, 56, 123456)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11T12:34:56.123456" n = item(date(2018, 10, 11)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11" n = item(time(12, 34, 56, 123456)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12:34:56.123456" n = item([1, 2, 3]) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "[1, 2, 3]" n = item({"foo": "bar"}) s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n' n = inline_table() n["foo"] = "bar" s = pickle.dumps(n) assert pickle.loads(s).as_string() == '{foo = "bar"}' n = item("foo") s = pickle.dumps(n) assert pickle.loads(s).as_string() == '"foo"' n = item([{"foo": "bar"}]) s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n'
def format_toml(pdm_settings): """Ensure the dependencies are inline tables""" for section in pdm_settings: if not section.endswith("dependencies"): continue for name in pdm_settings[section]: if getattr(pdm_settings[section][name], "items", None): table = tomlkit.inline_table() table.update(pdm_settings[section][name]) pdm_settings[section][name] = table
def __getStandardDependency(self, dependency: Table): it = inline_table() if 'marker' in dependency: it.append('version', dependency['version']) it.append('markers', dependency['marker']) return dependency['name'], it return dependency['name'], dependency['version']
def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator(): table = inline_table() table["foo"] = "bar" table["baz"] = "boom" assert '{foo = "bar", baz = "boom"}' == table.as_string() del table["baz"] assert '{foo = "bar"}' == table.as_string() table = inline_table() table["foo"] = "bar" del table["foo"] table["baz"] = "boom" assert '{baz = "boom"}' == table.as_string()
def test_inline_table_should_append_a_newline(): content = "[foo]\nh = 1\n[bar]\nc = 2\n" doc = parse(content) insert = inline_table() insert["z"] = 3 doc["foo"]["added"] = insert result = dumps(doc) assert "}[" not in result
def convert_tomlkit_table(section): if isinstance(section, tomlkit.items.Table): body = section.value._body else: body = section._body for key, value in body: if not key: continue if hasattr(value, "keys") and not isinstance( value, tomlkit.items.InlineTable): table = tomlkit.inline_table() table.update(value.value) section[key.key] = table
def migrate_dependencies(self, *, dev: bool = False): prefix = "dev-" if dev else "" pipenv_key = prefix + "packages" poetry_key = prefix + "dependencies" for name, ver in self._pipenv[pipenv_key].items(): if name in self._pyproject["tool"]["poetry"][poetry_key]: continue if isinstance(ver, dict): tmp = inline_table() tmp.update(ver) ver = tmp self._pyproject["tool"]["poetry"][poetry_key].add(name, ver)
def line_to_kv(line): match = BREW_LINE_RE.match(line) if match is None: raise ValueError(f"Don't understand line {line!r}") kind, name, arglist = (x.strip() for x in match.groups()) if arglist: args = inline_table() for item in arglist.split(','): if not item: continue k, v = item.split(':') args[k.strip()] = v.strip() return kind, name, args else: return kind, name, True
def _add_entrypoints(section: Table, entrypoints: Tuple[EntryPoint, ...]) -> None: # drop old console_scripts if 'scripts' in section: scripts = { e.name for e in entrypoints if e.group == 'console_scripts' } for script_name in list(section['scripts']): if script_name not in scripts: del section['scripts'][script_name] # add console_scripts for entrypoint in entrypoints: if entrypoint.group != 'console_scripts': continue if 'scripts' not in section: section['scripts'] = tomlkit.table() if entrypoint.extras: content = tomlkit.inline_table() content['callable'] = entrypoint.path content['extras'] = entrypoint.extras else: content = entrypoint.path section['scripts'][entrypoint.name] = content # drop old plugins if 'plugins' in section: groups = defaultdict(set) for entrypoint in entrypoints: if entrypoint.group != 'console_scripts': groups[entrypoint.group].add(entrypoint.name) for group_name, group_content in section['plugins'].items(): if group_name not in groups: del section['plugins'][group_name] continue for script_name in group_content: if script_name not in groups[group_name]: del section['plugins'][group_name][script_name] # add plugins for entrypoint in entrypoints: if entrypoint.group == 'console_scripts': continue if 'plugins' not in section: section['plugins'] = tomlkit.table() if entrypoint.group not in section['plugins']: section['plugins'][entrypoint.group] = tomlkit.table() section['plugins'][entrypoint.group][ entrypoint.name] = entrypoint.path
def get_package_include(self) -> InlineTable | None: package = inline_table() include = self._package_path_relative.parts[0] package.append("include", include) if self.basedir != Path(): package.append("from", self.basedir.as_posix()) else: if include == self._project: # package include and package name are the same, # packages table is redundant here. return None return package
def _reformat_dependency_properties( extras: Optional[str], properties: Union[str, Dict[str, Any]] ) -> Union[str, InlineTable]: formatted = inline_table() if extras is not None: formatted.update({"extras": extras.split(",")}) if isinstance(properties, dict): formatted.update(translate_properties(properties)) else: formatted.append("version", properties) return ( formatted["version"] if len(formatted) == 1 and "version" in formatted.keys() else formatted )
def test_basic_withMarkers(self): t = table() t.append('name', 'mypackage') t.append('version', '1.2.3') t.append('marker', 'sys_platform == "darwin"') packageName, packageDefinition = self.__lock2PyprojectConverter.convert( t) it = inline_table() it.append('version', '1.2.3') it.append('markers', 'sys_platform == "darwin"') self.assertEqual('mypackage', packageName) self.assertEqual(it, packageDefinition)
def add_line_to_pipfile(self, line, develop): from requirementslib import Requirement requirement = Requirement.from_line(line) section = self._get_pipfile_section(develop=develop) key = requirement.normalized_name entry = next(iter(requirement.as_pipfile().values())) if isinstance(entry, dict): # HACK: TOMLKit prefers to expand tables by default, but we # always want inline tables here. Also tomlkit.inline_table # does not have `update()`. table = tomlkit.inline_table() for k, v in entry.items(): table[k] = v entry = table section[key] = entry
def _format_requirements( self, requirements: List[Dict[str, str]] ) -> Dict[str, Union[str, Dict[str, str]]]: requires = {} for requirement in requirements: name = requirement.pop("name") if "version" in requirement and len(requirement) == 1: constraint = requirement["version"] else: constraint = inline_table() constraint.trivia.trail = "\n" constraint.update(requirement) requires[name] = constraint return requires
def _format_requirements( self, requirements: list[dict[str, str]]) -> Requirements: requires: Requirements = {} for requirement in requirements: name = requirement.pop("name") constraint: str | InlineTable if "version" in requirement and len(requirement) == 1: constraint = requirement["version"] else: constraint = inline_table() constraint.trivia.trail = "\n" constraint.update(requirement) requires[name] = constraint return requires
def _format_req(self, req: Requirement) -> Union[InlineTable, String]: result = tomlkit.inline_table() for name, value in req: if name in self.fields: if isinstance(value, tuple): value = list(value) result[name] = value if req.prereleases: result['allows-prereleases'] = True if 'version' not in result and 'git' not in result: result['version'] = '*' # if we have only version, return string instead of table if tuple(result.value) == ('version', ): return result['version'] return result
def _format_req(self, req): result = tomlkit.inline_table() for name, value in req: if name in self.fields: if isinstance(value, tuple): value = list(value) result[name] = value if 'version' not in result: result['version'] = '*' # if we have only version, return string instead of table if tuple(result.value) == ('version', ): return result['version'] # do not specify version explicit if result['version'] == '*': del result['version'] return result
def test_string_output_order_is_preserved_for_out_of_order_tables(): content = """ [tool.poetry] name = "foo" [tool.poetry.dependencies] python = "^3.6" bar = "^1.0" [build-system] requires = ["poetry-core"] backend = "poetry.core.masonry.api" [tool.other] a = "b" """ doc = parse(content) constraint = tomlkit.inline_table() constraint["version"] = "^1.0" doc["tool"]["poetry"]["dependencies"]["bar"] = constraint assert "^1.0" == doc["tool"]["poetry"]["dependencies"]["bar"]["version"] expected = """ [tool.poetry] name = "foo" [tool.poetry.dependencies] python = "^3.6" bar = {version = "^1.0"} [build-system] requires = ["poetry-core"] backend = "poetry.core.masonry.api" [tool.other] a = "b" """ assert expected == doc.as_string()
def handle(self): from poetry.installation import Installer from poetry.semver import parse_constraint from tomlkit import inline_table packages = self.argument("name") is_dev = self.option("dev") if (self.option("git") or self.option("path") or self.option("extras")) and len( packages ) > 1: raise ValueError( "You can only specify one package " "when using the --git or --path options" ) if self.option("git") and self.option("path"): raise RuntimeError("--git and --path cannot be used at the same time") section = "dependencies" if is_dev: section = "dev-dependencies" original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] if section not in poetry_content: poetry_content[section] = {} for name in packages: for key in poetry_content[section]: if key.lower() == name.lower(): raise ValueError("Package {} is already present".format(name)) if self.option("git") or self.option("path"): requirements = {packages[0]: ""} else: requirements = self._determine_requirements( packages, allow_prereleases=self.option("allow-prereleases") ) requirements = self._format_requirements(requirements) # validate requirements format for constraint in requirements.values(): parse_constraint(constraint) for name, _constraint in requirements.items(): constraint = inline_table() constraint["version"] = _constraint if self.option("git"): del constraint["version"] constraint["git"] = self.option("git") elif self.option("path"): del constraint["version"] constraint["path"] = self.option("path") if self.option("optional"): constraint["optional"] = True if self.option("allow-prereleases"): constraint["allows-prereleases"] = True if self.option("extras"): extras = [] for extra in self.option("extras"): if " " in extra: extras += [e.strip() for e in extra.split(" ")] else: extras.append(extra) constraint["extras"] = self.option("extras") if self.option("python"): constraint["python"] = self.option("python") if self.option("platform"): constraint["platform"] = self.option("platform") if len(constraint) == 1 and "version" in constraint: constraint = constraint["version"] poetry_content[section][name] = constraint # Write new content self.poetry.file.write(content) # Cosmetic new line self.line("") # Update packages self.reset_poetry() installer = Installer( self.output, self.venv, 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" "Addition failed, reverting pyproject.toml " "to its original content." ) self.poetry.file.write(original_content) return status