Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

# 

# LSST Data Management System 

# Copyright 2008, 2009, 2010 LSST Corporation. 

# 

# This product includes software developed by the 

# LSST Project (http://www.lsst.org/). 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the LSST License Statement and 

# the GNU General Public License along with this program. If not, 

# see <http://www.lsstcorp.org/LegalNotices/>. 

# 

 

__all__ = ["ListField"] 

 

import collections.abc 

 

from .config import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath 

from .comparison import compareScalars, getComparisonName 

from .callStack import getCallStack, getStackFrame 

 

 

class List(collections.abc.MutableSequence): 

"""List collection used internally by `ListField`. 

 

Parameters 

---------- 

config : `lsst.pex.config.Config` 

Config instance that contains the ``field``. 

field : `ListField` 

Instance of the `ListField` using this ``List``. 

value : sequence 

Sequence of values that are inserted into this ``List``. 

at : `list` of `lsst.pex.config.callStack.StackFrame` 

The call stack (created by `lsst.pex.config.callStack.getCallStack`). 

label : `str` 

Event label for the history. 

setHistory : `bool`, optional 

Enable setting the field's history, using the value of the ``at`` 

parameter. Default is `True`. 

 

Raises 

------ 

FieldValidationError 

Raised if an item in the ``value`` parameter does not have the 

appropriate type for this field or does not pass the 

`ListField.itemCheck` method of the ``field`` parameter. 

""" 

 

def __init__(self, config, field, value, at, label, setHistory=True): 

self._field = field 

self._config = config 

self._history = self._config._history.setdefault(self._field.name, []) 

self._list = [] 

self.__doc__ = field.doc 

if value is not None: 

try: 

for i, x in enumerate(value): 

self.insert(i, x, setHistory=False) 

except TypeError: 

msg = "Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value)) 

raise FieldValidationError(self._field, self._config, msg) 

if setHistory: 

self.history.append((list(self._list), at, label)) 

 

def validateItem(self, i, x): 

"""Validate an item to determine if it can be included in the list. 

 

Parameters 

---------- 

i : `int` 

Index of the item in the `list`. 

x : object 

Item in the `list`. 

 

Raises 

------ 

FieldValidationError 

Raised if an item in the ``value`` parameter does not have the 

appropriate type for this field or does not pass the field's 

`ListField.itemCheck` method. 

""" 

 

if not isinstance(x, self._field.itemtype) and x is not None: 

msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % \ 

(i, x, _typeStr(x), _typeStr(self._field.itemtype)) 

raise FieldValidationError(self._field, self._config, msg) 

 

if self._field.itemCheck is not None and not self._field.itemCheck(x): 

msg = "Item at position %d is not a valid value: %s" % (i, x) 

raise FieldValidationError(self._field, self._config, msg) 

 

def list(self): 

"""Sequence of items contained by the `List` (`list`). 

""" 

return self._list 

 

107 ↛ exitline 107 didn't run the lambda on line 107 history = property(lambda x: x._history) 

"""Read-only history. 

""" 

 

def __contains__(self, x): 

return x in self._list 

 

def __len__(self): 

return len(self._list) 

 

def __setitem__(self, i, x, at=None, label="setitem", setHistory=True): 

if self._config._frozen: 

raise FieldValidationError(self._field, self._config, 

"Cannot modify a frozen Config") 

if isinstance(i, slice): 

k, stop, step = i.indices(len(self)) 

for j, xj in enumerate(x): 

xj = _autocast(xj, self._field.itemtype) 

self.validateItem(k, xj) 

x[j] = xj 

k += step 

else: 

x = _autocast(x, self._field.itemtype) 

self.validateItem(i, x) 

 

self._list[i] = x 

if setHistory: 

if at is None: 

at = getCallStack() 

self.history.append((list(self._list), at, label)) 

 

def __getitem__(self, i): 

return self._list[i] 

 

def __delitem__(self, i, at=None, label="delitem", setHistory=True): 

if self._config._frozen: 

raise FieldValidationError(self._field, self._config, 

"Cannot modify a frozen Config") 

del self._list[i] 

if setHistory: 

if at is None: 

at = getCallStack() 

self.history.append((list(self._list), at, label)) 

 

def __iter__(self): 

return iter(self._list) 

 

def insert(self, i, x, at=None, label="insert", setHistory=True): 

"""Insert an item into the list at the given index. 

 

Parameters 

---------- 

i : `int` 

Index where the item is inserted. 

x : object 

Item that is inserted. 

at : `list` of `lsst.pex.config.callStack.StackFrame`, optional 

The call stack (created by 

`lsst.pex.config.callStack.getCallStack`). 

label : `str`, optional 

Event label for the history. 

setHistory : `bool`, optional 

Enable setting the field's history, using the value of the ``at`` 

parameter. Default is `True`. 

""" 

if at is None: 

at = getCallStack() 

self.__setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory) 

 

def __repr__(self): 

return repr(self._list) 

 

def __str__(self): 

return str(self._list) 

 

def __eq__(self, other): 

try: 

if len(self) != len(other): 

return False 

 

for i, j in zip(self, other): 

if i != j: 

return False 

return True 

except AttributeError: 

# other is not a sequence type 

return False 

 

def __ne__(self, other): 

return not self.__eq__(other) 

 

def __setattr__(self, attr, value, at=None, label="assignment"): 

if hasattr(getattr(self.__class__, attr, None), '__set__'): 

# This allows properties to work. 

object.__setattr__(self, attr, value) 

elif attr in self.__dict__ or attr in ["_field", "_config", "_history", "_list", "__doc__"]: 

# This allows specific private attributes to work. 

object.__setattr__(self, attr, value) 

else: 

# We throw everything else. 

msg = "%s has no attribute %s" % (_typeStr(self._field), attr) 

raise FieldValidationError(self._field, self._config, msg) 

 

 

class ListField(Field): 

"""A configuration field (`~lsst.pex.config.Field` subclass) that contains 

a list of values of a specific type. 

 

Parameters 

---------- 

doc : `str` 

A description of the field. 

dtype : class 

The data type of items in the list. 

default : sequence, optional 

The default items for the field. 

optional : `bool`, optional 

Set whether the field is *optional*. When `False`, 

`lsst.pex.config.Config.validate` will fail if the field's value is 

`None`. 

listCheck : callable, optional 

A callable that validates the list as a whole. 

itemCheck : callable, optional 

A callable that validates individual items in the list. 

length : `int`, optional 

If set, this field must contain exactly ``length`` number of items. 

minLength : `int`, optional 

If set, this field must contain *at least* ``minLength`` number of 

items. 

maxLength : `int`, optional 

If set, this field must contain *no more than* ``maxLength`` number of 

items. 

deprecated : None or `str`, optional 

A description of why this Field is deprecated, including removal date. 

If not None, the string is appended to the docstring for this Field. 

 

See also 

-------- 

ChoiceField 

ConfigChoiceField 

ConfigDictField 

ConfigField 

ConfigurableField 

DictField 

Field 

RangeField 

RegistryField 

""" 

def __init__(self, doc, dtype, default=None, optional=False, 

listCheck=None, itemCheck=None, 

length=None, minLength=None, maxLength=None, 

deprecated=None): 

259 ↛ 260line 259 didn't jump to line 260, because the condition on line 259 was never true if dtype not in Field.supportedTypes: 

raise ValueError("Unsupported dtype %s" % _typeStr(dtype)) 

if length is not None: 

262 ↛ 263line 262 didn't jump to line 263, because the condition on line 262 was never true if length <= 0: 

raise ValueError("'length' (%d) must be positive" % length) 

minLength = None 

maxLength = None 

else: 

267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true if maxLength is not None and maxLength <= 0: 

raise ValueError("'maxLength' (%d) must be positive" % maxLength) 

269 ↛ 271line 269 didn't jump to line 271, because the condition on line 269 was never true if minLength is not None and maxLength is not None \ 

and minLength > maxLength: 

raise ValueError("'maxLength' (%d) must be at least" 

" as large as 'minLength' (%d)" % (maxLength, minLength)) 

 

274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true if listCheck is not None and not hasattr(listCheck, "__call__"): 

raise ValueError("'listCheck' must be callable") 

276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true if itemCheck is not None and not hasattr(itemCheck, "__call__"): 

raise ValueError("'itemCheck' must be callable") 

 

source = getStackFrame() 

self._setup(doc=doc, dtype=List, default=default, check=None, optional=optional, source=source, 

deprecated=deprecated) 

 

self.listCheck = listCheck 

"""Callable used to check the list as a whole. 

""" 

 

self.itemCheck = itemCheck 

"""Callable used to validate individual items as they are inserted 

into the list. 

""" 

 

self.itemtype = dtype 

"""Data type of list items. 

""" 

 

self.length = length 

"""Number of items that must be present in the list (or `None` to 

disable checking the list's length). 

""" 

 

self.minLength = minLength 

"""Minimum number of items that must be present in the list (or `None` 

to disable checking the list's minimum length). 

""" 

 

self.maxLength = maxLength 

"""Maximum number of items that must be present in the list (or `None` 

to disable checking the list's maximum length). 

""" 

 

def validate(self, instance): 

"""Validate the field. 

 

Parameters 

---------- 

instance : `lsst.pex.config.Config` 

The config instance that contains this field. 

 

Raises 

------ 

lsst.pex.config.FieldValidationError 

Raised if: 

 

- The field is not optional, but the value is `None`. 

- The list itself does not meet the requirements of the `length`, 

`minLength`, or `maxLength` attributes. 

- The `listCheck` callable returns `False`. 

 

Notes 

----- 

Individual item checks (`itemCheck`) are applied when each item is 

set and are not re-checked by this method. 

""" 

Field.validate(self, instance) 

value = self.__get__(instance) 

if value is not None: 

lenValue = len(value) 

if self.length is not None and not lenValue == self.length: 

msg = "Required list length=%d, got length=%d" % (self.length, lenValue) 

raise FieldValidationError(self, instance, msg) 

elif self.minLength is not None and lenValue < self.minLength: 

msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue) 

raise FieldValidationError(self, instance, msg) 

elif self.maxLength is not None and lenValue > self.maxLength: 

msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue) 

raise FieldValidationError(self, instance, msg) 

elif self.listCheck is not None and not self.listCheck(value): 

msg = "%s is not a valid value" % str(value) 

raise FieldValidationError(self, instance, msg) 

 

def __set__(self, instance, value, at=None, label="assignment"): 

if instance._frozen: 

raise FieldValidationError(self, instance, "Cannot modify a frozen Config") 

 

if at is None: 

at = getCallStack() 

 

if value is not None: 

value = List(instance, self, value, at, label) 

else: 

history = instance._history.setdefault(self.name, []) 

history.append((value, at, label)) 

 

instance._storage[self.name] = value 

 

def toDict(self, instance): 

"""Convert the value of this field to a plain `list`. 

 

`lsst.pex.config.Config.toDict` is the primary user of this method. 

 

Parameters 

---------- 

instance : `lsst.pex.config.Config` 

The config instance that contains this field. 

 

Returns 

------- 

`list` 

Plain `list` of items, or `None` if the field is not set. 

""" 

value = self.__get__(instance) 

return list(value) if value is not None else None 

 

def _compare(self, instance1, instance2, shortcut, rtol, atol, output): 

"""Compare two config instances for equality with respect to this 

field. 

 

`lsst.pex.config.config.compare` is the primary user of this method. 

 

Parameters 

---------- 

instance1 : `lsst.pex.config.Config` 

Left-hand-side `~lsst.pex.config.Config` instance in the 

comparison. 

instance2 : `lsst.pex.config.Config` 

Right-hand-side `~lsst.pex.config.Config` instance in the 

comparison. 

shortcut : `bool` 

If `True`, return as soon as an **inequality** is found. 

rtol : `float` 

Relative tolerance for floating point comparisons. 

atol : `float` 

Absolute tolerance for floating point comparisons. 

output : callable 

If not None, a callable that takes a `str`, used (possibly 

repeatedly) to report inequalities. 

 

Returns 

------- 

equal : `bool` 

`True` if the fields are equal; `False` otherwise. 

 

Notes 

----- 

Floating point comparisons are performed by `numpy.allclose`. 

""" 

l1 = getattr(instance1, self.name) 

l2 = getattr(instance2, self.name) 

name = getComparisonName( 

_joinNamePath(instance1._name, self.name), 

_joinNamePath(instance2._name, self.name) 

) 

if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output): 

return False 

if l1 is None and l2 is None: 

return True 

if not compareScalars("size for %s" % name, len(l1), len(l2), output=output): 

return False 

equal = True 

for n, v1, v2 in zip(range(len(l1)), l1, l2): 

result = compareScalars("%s[%d]" % (name, n), v1, v2, dtype=self.dtype, 

rtol=rtol, atol=atol, output=output) 

if not result and shortcut: 

return False 

equal = equal and result 

return equal