Skip to content
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

[develop2] New serialization json and printing for conan list #12883

Merged
merged 11 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 23 additions & 16 deletions conan/api/model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from collections import OrderedDict

from conans.util.dates import timestamp_to_str


class Remote:

Expand Down Expand Up @@ -81,26 +83,31 @@ def prefs(self):
return prefs

def add_prefs(self, prefs, configurations=None):
confs = configurations or OrderedDict()
for pref in prefs:
binary_info = confs.get(pref, OrderedDict())
binary_info = configurations.get(pref) if configurations is not None else None
self.recipes.setdefault(pref.ref, []).append((pref, binary_info))

@property
def ordered_recipes_by_name(self):
ret = OrderedDict()
for ref, prefs in self.recipes.items():
ret.setdefault(ref.name, OrderedDict()).setdefault(ref, prefs)
return ret

def serialize(self):
ret = OrderedDict()
for ref, prefs in self.recipes.items():
pref_ret = OrderedDict()
if prefs:
for pref, binary_info in prefs:
pref_ret[pref.repr_notime()] = binary_info
ret[ref.repr_notime()] = pref_ret
ret = {}
for ref, prefs in sorted(self.recipes.items()):
ref_dict = ret.setdefault(str(ref), {})
if ref.revision:
revisions_dict = ref_dict.setdefault("revisions", {})
rev_dict = revisions_dict.setdefault(ref.revision, {})
if ref.timestamp:
rev_dict["timestamp"] = timestamp_to_str(ref.timestamp)
if prefs:
packages_dict = rev_dict.setdefault("packages", {})
for pref_info in prefs:
pref, info = pref_info
pid_dict = packages_dict.setdefault(pref.package_id, {})
if info is not None:
pid_dict["info"] = info
franramirez688 marked this conversation as resolved.
Show resolved Hide resolved
if pref.revision:
prevs_dict = pid_dict.setdefault("revisions", {})
prev_dict = prevs_dict.setdefault(pref.revision, {})
if pref.timestamp:
prev_dict["timestamp"] = timestamp_to_str(pref.timestamp)
return ret


Expand Down
17 changes: 13 additions & 4 deletions conan/api/subapi/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from conan.api.model import Remote, SelectBundle
from conan.internal.api.select_pattern import ListPatternMode
from conan.internal.conan_app import ConanApp
from conans.errors import ConanException
from conans.errors import ConanException, NotFoundException
from conans.model.package_ref import PkgReference
from conans.model.recipe_ref import RecipeReference
from conans.search.search import get_cache_packages_binary_info, filter_packages
Expand Down Expand Up @@ -38,13 +38,16 @@ def recipe_revisions(self, ref: RecipeReference, remote=None):
return results

def latest_package_revision(self, pref: PkgReference, remote=None):
# TODO: This returns None if the given package_id is not existing. It should probably
# raise NotFound, but to keep aligned with the above ``latest_recipe_revision`` which
# is used as an "exists" check too in other places, lets respect the None return
assert pref.revision is None, "latest_package_revision: ref already have a revision"
assert pref.package_id is not None, "package_id must be defined"
app = ConanApp(self.conan_api.cache_folder)
if remote:
ret = app.remote_manager.get_latest_package_reference(pref, remote=remote)
else:
ret = app.cache.get_latest_package_reference(pref)

return ret

def package_revisions(self, pref: PkgReference, remote: Remote=None):
Expand Down Expand Up @@ -116,7 +119,7 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None):
continue

for rrev in rrevs:
packages = {}
packages = None
prefs = []
if pattern.package_id and "*" not in pattern.package_id:
prefs.append(PkgReference(rrev, package_id=pattern.package_id))
Expand All @@ -132,14 +135,20 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None):
# Show all the package IDs and their configurations
if search_mode == ListPatternMode.SHOW_PACKAGE_IDS:
# add pref and its package configuration
# remove timestamp, as server does not provide it
for p in prefs:
p.timestamp = None
select_bundle.add_prefs(prefs, configurations=packages)
continue

for pref in prefs:
if search_mode in (ListPatternMode.SHOW_LATEST_PREV,
ListPatternMode.SHOW_ALL_PREVS):
if pattern.is_latest_prev:
prevs = [self.conan_api.list.latest_package_revision(pref, remote)]
prev = self.conan_api.list.latest_package_revision(pref, remote)
if prev is None:
raise NotFoundException(f"Binary package not found: '{pref}")
prevs = [prev]
else:
prevs = self.conan_api.list.package_revisions(pref, remote)
prevs = pattern.filter_prevs(prevs)
Expand Down
114 changes: 59 additions & 55 deletions conan/cli/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
from conan.api.conan_api import ConanAPI
from conan.api.output import Color, cli_out_write
from conan.cli.command import conan_command, OnceArgument
from conan.cli.commands import ConanJSONEncoder
from conan.cli.formatters.list import list_packages_html
from conan.internal.api.select_pattern import ListPattern, ListPatternMode
from conans.util.dates import timestamp_to_str
from conan.internal.api.select_pattern import ListPattern

# Keep them so we don't break other commands that import them, but TODO: Remove later
remote_color = Color.BRIGHT_BLUE
recipe_name_color = Color.GREEN
recipe_color = Color.BRIGHT_WHITE
Expand All @@ -17,65 +16,71 @@
value_color = Color.CYAN


def print_serial(item, indent=None, color_index=None):
indent = "" if indent is None else (indent + " ")
color_index = 0 if color_index is None else (color_index + 1)
color_array = [Color.BRIGHT_BLUE, Color.GREEN, Color.BRIGHT_WHITE,
Color.BRIGHT_YELLOW, Color.CYAN, Color.WHITE]
color = color_array[color_index % len(color_array)]
if isinstance(item, dict):
for k, v in item.items():
if isinstance(v, str):
if k.lower() == "error":
color = Color.BRIGHT_RED
k = "ERROR"
elif k.lower() == "warning":
color = Color.BRIGHT_YELLOW
k = "WARN"
cli_out_write(f"{indent}{k}: {v}", fg=color)
else:
cli_out_write(f"{indent}{k}", fg=color)
print_serial(v, indent, color_index)
elif isinstance(item, type([])):
for elem in item:
cli_out_write(f"{indent}{elem}", fg=color)
elif item:
cli_out_write(f"{indent}{item}", fg=color)


def print_list_text(results):
""" Do litte format modification to serialized
list bundle so it looks prettier on text output
"""
info = results["results"]
search_mode = results["search_mode"]
indentation = " "

for remote, info_per_ref_name in info.items():
cli_out_write(f"{remote}:", fg=remote_color)

if info_per_ref_name.get("error"):
cli_out_write(f" ERROR: {info_per_ref_name.get('error')}", fg=error_color)
continue
# Extract command single package name
new_info = {}
for remote, remote_info in info.items():
new_remote_info = {}
for ref, content in remote_info.items():
if ref == "error":
new_remote_info[ref] = content
else:
name, _ = ref.split("/", 1)
new_remote_info.setdefault(name, {})[ref] = content
new_info[remote] = new_remote_info
info = new_info

if not info_per_ref_name:
cli_out_write(f" There are no matching recipe references", fg=recipe_color)
continue
info = {remote: {"warning": "There are no matching recipe references"} if not values else values
for remote, values in info.items()}

for ref_name, refs in info_per_ref_name.items():
cli_out_write(f"{indentation}{ref_name}", fg=recipe_name_color)
for ref, prefs in refs.items():
cli_out_write(f"{indentation * 2}{ref.repr_humantime() if ref.timestamp else ref}",
fg=recipe_color)
if prefs:
for pref, binary_info in prefs:
pref_date = f" ({timestamp_to_str(pref.timestamp)})" if pref.timestamp \
else ""
if search_mode == ListPatternMode.SHOW_PACKAGE_IDS:
cli_out_write(f"{indentation * 3}PID: {pref.package_id}{pref_date}",
fg=reference_color)
if not binary_info:
cli_out_write(f"{indentation * 4}Empty package information",
fg=field_color)
continue
elif search_mode in (ListPatternMode.SHOW_ALL_PREVS,
ListPatternMode.SHOW_LATEST_PREV):
cli_out_write(f"{indentation * 3}PID: {pref.package_id}",
fg=reference_color)
cli_out_write(f"{indentation * 4}PREV: {pref.revision}{pref_date}",
fg=field_color)
continue
for item, contents in binary_info.items():
if not contents:
continue
cli_out_write(f"{indentation * 4}{item}:", fg=field_color)
if isinstance(contents, dict):
for k, v in contents.items():
cli_out_write(f"{indentation * 5}{k}={v}", fg=value_color)
else:
for c in contents:
cli_out_write(f"{indentation * 5}{c}", fg=value_color)
elif search_mode in (ListPatternMode.SHOW_PACKAGE_IDS,
ListPatternMode.SHOW_ALL_PREVS,
ListPatternMode.SHOW_LATEST_PREV):
cli_out_write(f"{indentation * 3}There are no packages for this revision.",
fg=field_color)
def format_timestamps(item):
if isinstance(item, dict):
result = {}
for k, v in item.items():
if isinstance(v, dict) and v.get("timestamp"):
timestamp = v.pop("timestamp")
k = f"{k} ({timestamp})"
result[k] = format_timestamps(v)
return result
return item
info = {remote: format_timestamps(values) for remote, values in info.items()}
print_serial(info)


def print_list_json(data):
results = data["results"]
myjson = json.dumps(results, indent=4, cls=ConanJSONEncoder)
myjson = json.dumps(results, indent=4)
cli_out_write(myjson)


Expand Down Expand Up @@ -113,8 +118,7 @@ def list(conan_api: ConanAPI, parser, *args):
except Exception as e:
results[name] = {"error": str(e)}
else:
results[name] = list_bundle.serialize() if args.format in ("json", "html") \
else list_bundle.ordered_recipes_by_name
results[name] = list_bundle.serialize()
return {
"results": results,
"search_mode": ref_pattern.mode,
Expand Down
3 changes: 1 addition & 2 deletions conan/cli/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ def search(conan_api: ConanAPI, parser, *args):
except Exception as e:
results[remote.name] = {"error": str(e)}
else:
results[remote.name] = list_bundle.serialize() if args.format == "json" \
else list_bundle.ordered_recipes_by_name
results[remote.name] = list_bundle.serialize()
return {
"results": results,
"search_mode": search_mode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,5 @@ def test(self):
"build_type": "Release",
"os": "Linux"
}
assert settings_target == pkgs_json["Local Cache"][pref.ref.repr_notime()][pref.repr_notime()]["settings_target"]
revision_dict = pkgs_json["Local Cache"]["gcc/0.1"]["revisions"][pref.ref.revision]
assert settings_target == revision_dict["packages"][pref.package_id]["info"]["settings_target"]
8 changes: 4 additions & 4 deletions conans/test/integration/command/remove_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ def test_remove_all_packages_but_the_recipe_at_remote(self):
RecipeReference.loads("foobar/0.1@user/testing"))
self.client.run("list foobar/0.1@user/testing#{} -r default".format(ref.revision))
default_arch = self.client.get_default_host_profile().settings['arch']
self.assertIn(f"arch={default_arch}", self.client.out)
self.assertIn("arch=x86", self.client.out)
self.assertIn(f"arch: {default_arch}", self.client.out)
self.assertIn("arch: x86", self.client.out)

self.client.run("remove -c foobar/0.1@user/testing:* -r default")
self.client.run("search foobar/0.1@user/testing -r default")
self.assertNotIn(f"arch={default_arch}", self.client.out)
self.assertNotIn("arch=x86", self.client.out)
self.assertNotIn(f"arch: {default_arch}", self.client.out)
self.assertNotIn("arch: x86", self.client.out)


# populated packages of bar
Expand Down
Loading