Skip to content

Commit 9925ce9

Browse files
committed
added entry point "pww-pacakges" for managing packages from the command-line; weka.core.packages.is_installed method can restrict check to version now as well; weka.core.packages.Package class now has as_dict() method to retrieve the properties as simple dictionary
1 parent f73dcbd commit 9925ce9

File tree

3 files changed

+285
-39
lines changed

3 files changed

+285
-39
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Changelog
1212
datasets rather than classifiers
1313
- the `SimpleExperiment` class and derived classes (module: `weka.experiments`) now have the additional
1414
parameters in the constructor: class_for_ir_statistics, attribute_id, pred_target_column
15+
- the method `is_installed` (module: `weka.core.packages`) now can check whether a specific version is installed
16+
- added `pww-packages` entry point to allow managing of Weka packges from the command-line
17+
(actions: list/info/install/uninstall/suggest/is-installed)
1518
- ...
1619

1720

python/weka/core/packages.py

Lines changed: 281 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1313

1414
# packages.py
15-
# Copyright (C) 2014-2021 Fracpete (pythonwekawrapper at gmail dot com)
15+
# Copyright (C) 2014-2022 Fracpete (pythonwekawrapper at gmail dot com)
1616

17+
import argparse
1718
import javabridge
19+
import json
1820
import sys
1921
import traceback
2022
import weka.core.jvm as jvm
@@ -118,6 +120,22 @@ def install(self):
118120
"""
119121
return javabridge.call(self.jobject, "install", "()V")
120122

123+
def as_dict(self):
124+
"""
125+
Turns the package information into a dictionary. Not to be confused with 'to_dict'!
126+
127+
:return: the package information as dictionary
128+
:rtype: dict
129+
"""
130+
return {
131+
"name": self.name,
132+
"version": self.version,
133+
"url": self.url,
134+
"is_installed": self.is_installed,
135+
"dependencies": self.dependencies,
136+
"metadata": self.metadata,
137+
}
138+
121139
def __str__(self):
122140
"""
123141
Just returns name/version.
@@ -531,19 +549,25 @@ def uninstall_packages(names):
531549
"(Ljava/lang/String;Z[Ljava/io/PrintStream;)V", name, True, [])
532550

533551

534-
def is_installed(name):
552+
def is_installed(name, version=None):
535553
"""
536554
Checks whether a package with the name is already installed.
537555
538556
:param name: the name of the package
539557
:type name: str
558+
:param version: the version to check as well, ignored if None
559+
:type version: str
540560
:return: whether the package is installed
541561
:rtype: bool
542562
"""
543563
pkgs = installed_packages()
544564
for pkge in pkgs:
545565
if pkge.name == name:
546-
return True
566+
if version is not None:
567+
if pkge.version == version:
568+
return True
569+
else:
570+
return True
547571
return False
548572

549573

@@ -562,42 +586,260 @@ def suggest_package(name, exact=False):
562586
return classes.suggest_package(name, exact)
563587

564588

565-
if __name__ == "__main__":
566-
jvm.start()
589+
def _output_text(content, format_lambda, output):
590+
"""
591+
Outputs the text to the specified file or, if None, to stdout.
592+
593+
:param content: the content to output
594+
:type content: list
595+
:param format_lambda: the lambda to format the list
596+
:param output: the file to output the content to, uses stdout if None
597+
:type output: str
598+
"""
599+
formatted = []
600+
for c in content:
601+
formatted.append(format_lambda(c))
602+
if output is None:
603+
print("\n".join(formatted))
604+
else:
605+
with open(output, "w") as fp:
606+
fp.write("\n".join(formatted))
607+
608+
609+
def _output_json(content, content_filter, output):
610+
"""
611+
Outputs the content to the specified json file or, if None, to stdout.
612+
613+
:param content: the content to output
614+
:type content: list
615+
:param content_filter: the lambda for filtering the content dictionaries, ignored if None
616+
:param output: the file to output the content to, uses stdout if None
617+
:type output: str
618+
"""
619+
if content_filter is None:
620+
filtered = content
621+
else:
622+
filtered = [content_filter(x) for x in content]
623+
if output is None:
624+
print(json.dumps(filtered, indent=2))
625+
else:
626+
with open(output, "w") as fp:
627+
json.dump(filtered, fp, indent=2)
628+
629+
630+
def _output_pkg_list(pkgs, args):
631+
"""
632+
Outputs a package list.
633+
634+
:param pkgs: the list of packages
635+
:type pkgs: list
636+
:param args: the parsed command-line arguments
637+
:type args: argparse.Namespace
638+
"""
639+
content = [pkg.as_dict() for pkg in pkgs]
640+
if args.format == "text":
641+
_output_text(content, lambda x: "%s/%s" % (x["name"], x["version"]), args.output)
642+
elif args.format == "json":
643+
_output_json(content, lambda x: {"name": x["name"], "version": x["version"]}, args.output)
644+
645+
646+
def _output_pkg_info(pkgs, args):
647+
"""
648+
Outputs package information.
649+
650+
:param pkgs: the list of packages
651+
:type pkgs: list
652+
:param args: the parsed command-line arguments
653+
:type args: argparse.Namespace
654+
"""
655+
if args.format == "text":
656+
if args.type == "brief":
657+
_output_text(pkgs, lambda x: "%s/%s\n dependencies: %s" % (x["name"], x["version"], x["dependencies"]),
658+
args.output)
659+
elif args.type == "full":
660+
_output_text(pkgs, lambda x: "%s/%s\n url: %s\n dependencies: %s\n metadata: %s" % (
661+
x["name"], x["version"], x["url"], x["dependencies"], x["metadata"]), args.output)
662+
else:
663+
raise Exception("Unhandled type: %s" % args.type)
664+
elif args.format == "json":
665+
if args.type == "brief":
666+
_output_json(pkgs,
667+
lambda x: {"name": x["name"], "version": x["version"], "dependencies": x["dependencies"]},
668+
args.output)
669+
elif args.type == "full":
670+
_output_json(pkgs, None, args.output)
671+
else:
672+
raise Exception("Unhandled type: %s" % args.type)
673+
674+
675+
def _subcmd_list(args):
676+
"""
677+
Lists packages (name and version).
678+
"""
679+
if args.type == "all":
680+
pkgs = all_packages()
681+
elif args.type == "installed":
682+
pkgs = installed_packages()
683+
elif args.type == "available":
684+
pkgs = available_packages()
685+
else:
686+
raise Exception("Unhandled list type: %s" % args.type)
687+
_output_pkg_list(pkgs, args)
688+
689+
690+
def _subcmd_info(args):
691+
"""
692+
Outputs information for packages.
693+
"""
694+
pkgs = [all_package(x).as_dict() for x in args.name]
695+
_output_pkg_info(pkgs, args)
696+
697+
698+
def _subcmd_install(args):
699+
"""
700+
Installs one or more packages. Specific versions are suffixed with "==VERSION".
701+
"""
702+
for pkg in args.packages:
703+
version = LATEST
704+
if "==" in pkg:
705+
pkg, version = pkg.split("==")
706+
print("Installing: %s/%s" % (pkg, version))
707+
success = install_package(pkg, version)
708+
print(" installed successfully" if success else " failed to install")
709+
710+
711+
def _subcmd_uninstall(args):
712+
"""
713+
Uninstalls one or more packages.
714+
"""
715+
for pkg in args.packages:
716+
print("Uninstalling: %s" % pkg)
717+
if is_installed(pkg):
718+
uninstall_package(pkg)
719+
print(" uninstalled")
720+
else:
721+
print(" not installed, skipping")
722+
723+
724+
def _subcmd_suggest(args):
725+
"""
726+
Suggests packages that contain the specified classname.
727+
"""
728+
suggestions = []
729+
for classname in args.classname:
730+
suggestions.extend(suggest_package(classname, exact=args.exact))
731+
pkgs = []
732+
for suggestion in suggestions:
733+
if suggestion is None:
734+
continue
735+
pkgs.append(all_package(suggestion))
736+
_output_pkg_list(pkgs, args)
737+
738+
739+
def _subcmd_is_installed(args):
740+
content = []
741+
for name in args.name:
742+
version = None
743+
if "==" in name:
744+
name, version = name.split("==")
745+
installed = is_installed(name, version=version)
746+
if version is None:
747+
if installed:
748+
version = installed_package(name).version
749+
else:
750+
version = LATEST
751+
content.append({"name": name, "version": version, "installed": installed})
752+
753+
if args.format == "text":
754+
_output_text(content, lambda x: "%s/%s: %s" % (x["name"], x["version"], str(x["installed"])), args.output)
755+
elif args.format == "json":
756+
_output_json(content, None, args.output)
757+
758+
759+
def main(args=None):
760+
"""
761+
Performs the specified package operation from the command-line. Calls JVM start/stop automatically.
762+
Use -h to see all options.
763+
764+
:param args: the command-line arguments to use, uses sys.argv if None
765+
:type args: list
766+
"""
767+
768+
main_parser = argparse.ArgumentParser(
769+
description='Manages Weka packages.')
770+
sub_parsers = main_parser.add_subparsers()
771+
772+
# list
773+
parser = sub_parsers.add_parser("list", help="For listing all/installed/available packages")
774+
parser.add_argument("type", nargs="?", choices=["all", "installed", "available"], default="all", help="defines what packages to list")
775+
parser.add_argument("-f", "--format", choices=["text", "json"], default="text", help="the output format to use")
776+
parser.add_argument("-o", "--output", metavar="FILE", default=None, help="the file to store the output in, uses stdout if not supplied")
777+
parser.set_defaults(func=_subcmd_list)
778+
779+
# info
780+
parser = sub_parsers.add_parser("info", help="Outputs information about packages")
781+
parser.add_argument("name", nargs="+", help="the package(s) to output the information for")
782+
parser.add_argument("-t", "--type", choices=["brief", "full"], default="brief", help="the type of information to output")
783+
parser.add_argument("-f", "--format", choices=["text", "json"], default="text", help="the output format to use")
784+
parser.add_argument("-o", "--output", metavar="FILE", default=None, help="the file to store the output in, uses stdout if not supplied")
785+
parser.set_defaults(func=_subcmd_info)
786+
787+
# install
788+
parser = sub_parsers.add_parser("install", help="For installing one or more packages")
789+
parser.add_argument("packages", nargs="+", help="the name of the package(s) to install, append '==VERSION' to pin to a specific version")
790+
parser.set_defaults(func=_subcmd_install)
791+
792+
# uninstall/remove
793+
parser = sub_parsers.add_parser("uninstall", aliases=["remove"], help="For uninstalling one or more packages")
794+
parser.add_argument("packages", nargs="+", help="the name of the package(s) to uninstall")
795+
parser.set_defaults(func=_subcmd_uninstall)
796+
797+
# suggest
798+
parser = sub_parsers.add_parser("suggest", help="For suggesting packages that contain the specified class")
799+
parser.add_argument("classname", nargs=1, help="the classname to suggest packages for")
800+
parser.add_argument("-e", "--exact", action="store_true", help="whether to match the name exactly or perform substring matching")
801+
parser.add_argument("-f", "--format", choices=["text", "json"], default="text", help="the output format to use")
802+
parser.add_argument("-o", "--output", metavar="FILE", default=None, help="the file to store the output in, uses stdout if not supplied")
803+
parser.set_defaults(func=_subcmd_suggest)
804+
805+
# is-installed
806+
parser = sub_parsers.add_parser("is-installed", help="Checks whether a package is installed, simply outputs true/false")
807+
parser.add_argument("name", nargs="+", help="the name of the package to check, append '==VERSION' to pin to a specific version")
808+
parser.add_argument("-f", "--format", choices=["text", "json"], default="text", help="the output format to use")
809+
parser.add_argument("-o", "--output", metavar="FILE", default=None, help="the file to store the output in, uses stdout if not supplied")
810+
parser.set_defaults(func=_subcmd_is_installed)
811+
812+
parsed = main_parser.parse_args(args=args)
813+
814+
# execute action
815+
jvm.start(packages=True)
567816
try:
568-
print("Establish cache")
569-
print("===============")
570-
establish_cache()
571-
572-
print("Refresh cache")
573-
print("=============")
574-
refresh_cache()
575-
576-
print("All packages")
577-
print("============")
578-
packages = all_packages()
579-
for pkg in packages:
580-
print(pkg.name)
581-
print(" url: " + pkg.url)
582-
print("")
583-
584-
print("Available packages")
585-
print("==================")
586-
packages = available_packages()
587-
p = packages[0]
588-
for pkg in packages:
589-
print(pkg.name)
590-
print(" url: " + pkg.url)
591-
print("")
592-
593-
print("Installed packages")
594-
print("==================")
595-
packages = installed_packages()
596-
for pkg in packages:
597-
print(pkg.name)
598-
print(" url: " + pkg.url)
599-
print("")
600-
except Exception as e:
601-
print(e)
817+
parsed.func(parsed)
818+
except Exception:
819+
print(traceback.format_exc())
602820
finally:
603821
jvm.stop()
822+
823+
824+
def sys_main():
825+
"""
826+
Runs the main function using the system cli arguments, and
827+
returns a system error code.
828+
829+
:return: 0 for success, 1 for failure.
830+
:rtype: int
831+
"""
832+
833+
try:
834+
main()
835+
return 0
836+
except Exception:
837+
print(traceback.format_exc())
838+
return 1
839+
840+
841+
if __name__ == "__main__":
842+
try:
843+
main()
844+
except Exception:
845+
print(traceback.format_exc())

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def _read(f):
7575
"pww-clusterer=weka.clusterers:sys_main",
7676
"pww-datagenerator=weka.datagenerators:sys_main",
7777
"pww-filter=weka.filters:sys_main",
78+
"pww-packages=weka.core.packages:sys_main",
7879
]
7980
}
8081
)

0 commit comments

Comments
 (0)