-
Notifications
You must be signed in to change notification settings - Fork 0
/
package.py
174 lines (130 loc) · 5.76 KB
/
package.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
class PackageInstallationError(Exception):
pass
class DependenceLoopError(Exception):
pass
class DependencyInstallationError(PackageInstallationError):
pass
class PackageRemovalError(Exception):
pass
class CleanupProcessError(Exception):
pass
class DependentPackageFoundError(Exception):
def __init__(self, package):
super().__init__(f" {package.name} is still needed")
class Package(object):
def __init__(self, name: str):
self.name: str = name
self.installed: bool = False
self.explicitly_installed: bool = False
self.dependencies: list = []
self.dependent_packages: list = []
def depends_on(self, package):
"""
Registers `package` as a dependency (and self as a dependent package of `package`)
:param package: Package to be registered as dependency
:return: None
:raise DependenceLoopError: When trying to register a dependent package as a dependency
"""
if package in self.dependent_packages:
raise DependenceLoopError()
if package not in self.dependencies:
self.dependencies.append(package)
if self not in package.dependent_packages:
package.required_by(self)
def required_by(self, package):
"""
Registers `package` as a dependent package (and self as a dependency of `package`)
:param package: Package to be registered as dependent package
:return: None
:raise DependenceLoopError: When trying to register a dependency as a dependent package
"""
if package in self.dependencies:
raise DependenceLoopError()
if package not in self.dependent_packages:
self.dependent_packages.append(package)
if self not in package.dependencies:
package.depends_on(self)
def install(self, explicitly_installed: bool = False):
"""
Verifies dependencies and installs the package
:param explicitly_installed: If true, the package would be marked as explicitly installed, preventing automatic
removal.
:return: None
:raise PackageInstallationError: Either installation process failed or dependency installation process failed
"""
if self.dependencies is not []:
self._install_dependencies()
if self.installed:
if explicitly_installed:
self.explicitly_installed = True
print(f" {self.name} is already installed")
return None
try:
self.install_process()
except Exception:
raise PackageInstallationError() from Exception
self.installed = True
self.explicitly_installed = explicitly_installed
def _install_dependencies(self):
"""
If dependencies are detected, proceeds to install them
:return: None
:raise PackageInstallationError: If any of the dependent packages fails to install
"""
for dependence in self.dependencies:
try:
dependence.install()
except PackageInstallationError:
raise DependencyInstallationError(
f"There was a problem installing some dependencies: {dependence.name}")
def install_process(self):
"""Depending on the package, the install process might vary. For now, the package installation can be assumed as
nothing else than success"""
print(f" {self.name} successfully installed")
def remove(self, explicity_removal: bool = True):
"""
Verifies dependent packages and removes the package if it is safe to do it
:param explicity_removal: If False, notifies when no longer needed before removing
:raise PackageRemovalError: Package removal process failed
:raise CleanupProcessError: Dependent package removal process failed
:raise DependentPackageFoundError: Package is still needed by another package
"""
if self.dependent_packages is not []:
self._verify_dependent_packages()
if not explicity_removal:
print(f" {self.name} is no longer needed")
if explicity_removal and not self.installed:
print(f" {self.name} is not installed")
return None
try:
self.remove_process()
except Exception:
raise PackageRemovalError() from Exception
self.installed = False
self.explicitly_installed = False
if self.dependencies is not []:
self._clean_dependencies()
def _verify_dependent_packages(self):
"""
:raise DependentPackageFoundError: When a dependent package is still installed
"""
for package in self.dependent_packages:
if package.installed:
raise DependentPackageFoundError(self)
def _clean_dependencies(self):
"""
Clean dependent packages after removing a package
:raise CleanupProcessError: If a problem happens on removing some dependency
"""
for dependence in self.dependencies:
try:
if not dependence.explicitly_installed:
dependence.remove(explicity_removal=False)
except DependentPackageFoundError as exc:
pass
except PackageRemovalError:
raise CleanupProcessError(f"There was a problem removing some dependencies: {dependence.name}")
def remove_process(self):
"""Depending on the package, the removal process might vary. For now, the package removal can be assumed as
nothing else than success (assuming the dependent package verification is already done"""
print(f" {self.name} successfully removed")