-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathPropertiesObject.py
172 lines (140 loc) · 6.05 KB
/
PropertiesObject.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
import os
import sys
class WillNotRunError(Exception):
"""Error for Webware components that will not run."""
def versionString(version):
"""Convert the given version tuple to a string.
For a sequence containing version information such as (2, 0, 0, 'b'),
this returns a printable string such as '2.0b'.
The micro version number is excluded from the string if it is zero.
"""
ver = list(map(str, version))
ver, suffix = ver[:3], '.'.join(ver[3:])
if len(ver) > 2 and ver[2] == '0':
ver = ver[:2]
return '.'.join(ver) + suffix
class PropertiesObject(dict):
"""A Properties Object.
A PropertiesObject represents, in a dictionary-like fashion, the values
found in a Properties.py file. That file is always included with a Webware
component to advertise its name, version, status, etc. Note that a Webware
component is a Python package that follows additional conventions.
Also, the top level Webware directory contains a Properties.py.
Component properties are often used for:
* generation of documentation
* runtime examination of components, especially prior to loading
PropertiesObject provides additional keys:
* filename - the filename from which the properties were read
* versionString - a nicely printable string of the version
* requiredPyVersionString - like versionString,
but for requiredPyVersion instead
* willRun - 1 if the component will run.
So far that means having the right Python version.
* willNotRunReason - defined only if willRun is 0,
contains a readable error message
Using a PropertiesObject is better than investigating the Properties.py
file directly, because the rules for determining derived keys and any
future convenience methods will all be provided here.
Usage example:
from MiscUtils.PropertiesObject import PropertiesObject
props = PropertiesObject(filename)
for key, value in props.items():
print(f'{key}: {value}')
Note: We don't normally suffix a class name with "Object" as we have
with this class, however, the name Properties.py is already used in
our containing package and all other packages.
"""
# region Init and reading
def __init__(self, filename=None):
dict.__init__(self)
if filename:
self.readFileNamed(filename)
def loadValues(self, *args, **kwargs):
self.update(*args, **kwargs)
self.cleanPrivateItems()
def readFileNamed(self, filename):
results = {}
with open(filename, encoding='utf-8') as f:
exec(f.read(), results)
self.update(results)
self.cleanPrivateItems()
self.createDerivedItems()
# endregion Init and reading
# region Self utility
def cleanPrivateItems(self):
"""Remove items whose keys start with a double underscore."""
keys = list(self)
for key in keys:
if key.startswith('__'):
del self[key]
def addBuiltinItems(self):
pass
def createDerivedItems(self):
self.createVersionString()
self.createRequiredPyVersionString()
self.createWillRun()
def createVersionString(self):
if 'version' in self:
self['versionString'] = versionString(self['version'])
def createRequiredPyVersionString(self):
if 'requiredPyVersion' in self:
self['requiredPyVersionString'] = versionString(
self['requiredPyVersion'])
def createWillRun(self):
try:
# Invoke each of the checkFoo() methods
for key in self.willRunKeys():
methodName = 'check' + key[0].upper() + key[1:]
method = getattr(self, methodName)
method()
except WillNotRunError as msg:
self['willRun'] = False
self['willNotRunReason'] = msg
else:
self['willRun'] = True # we passed all the tests
def willRunKeys(self):
"""Return keys to be examined before running the component.
This returns a set of all keys whose values should be examined in
order to determine if the component will run. Used by createWillRun().
"""
return {
'requiredPyVersion', 'requiredOpSys', 'deniedOpSys', 'willRunFunc'}
def checkRequiredPyVersion(self):
if 'requiredPyVersion' in self and tuple(
sys.version_info) < tuple(self['requiredPyVersion']):
pythonVersion = '.'.join(map(str, sys.version_info))
requiredVersion = self['requiredPyVersionString']
raise WillNotRunError(
f'Required Python version is {requiredVersion},'
f' but actual version is {pythonVersion}.')
def checkRequiredOpSys(self):
requiredOpSys = self.get('requiredOpSys')
if requiredOpSys:
# We accept a string or list of strings
if isinstance(requiredOpSys, str):
requiredOpSys = [requiredOpSys]
opSys = os.name
if opSys not in requiredOpSys:
requiredOpSys = '/'.join(requiredOpSys)
raise WillNotRunError(
f'Required operating system is {requiredOpSys},'
f' but actual operating system is {opSys}.')
def checkDeniedOpSys(self):
deniedOpSys = self.get('deniedOpSys')
if deniedOpSys:
# We accept a string or list of strings
if isinstance(deniedOpSys, str):
deniedOpSys = [deniedOpSys]
opSys = os.name
if opSys in deniedOpSys:
deniedOpSys = '/'.join(deniedOpSys)
raise WillNotRunError(
f'Will not run on operating system {deniedOpSys}'
f' and actual operating system is {opSys}.')
def checkWillRunFunc(self):
willRunFunc = self.get('willRunFunc')
if willRunFunc:
whyNotMsg = willRunFunc()
if whyNotMsg:
raise WillNotRunError(whyNotMsg)
# endregion Self utility