Coverage for python/astshim/fitsChanContinued.py: 11%

142 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-27 12:05 +0000

1__all__ = ["CardType", "FitsChan", "FitsKeyState"] 

2 

3from ._astshimLib import CardType, FitsChan, FitsKeyState 

4 

5 

6def _calc_card_pos(self, index): 

7 """Convert a python index into a FitsChan position. 

8 

9 Parameters 

10 ---------- 

11 self : `FitsChan` 

12 The FitsChan to index. 

13 index : `int` 

14 0-based index into header. If negative, counts from end. 

15 

16 Raises 

17 ------ 

18 IndexError 

19 Raised if the index exceeds the size of the FitsChan. If the index 

20 equals the size of the FitsChan (noting that in 0-based indexing the 

21 final card is one less than the size) then this refers to the end of 

22 the header. 

23 """ 

24 # Calculate 0-based index 

25 nCards = len(self) 

26 if index < 0: 

27 index = nCards + index 

28 elif abs(index) > nCards: 

29 # We allow index of one higher to indicate append 

30 raise IndexError(f"Index {index} exceeds size of FitsChan ({nCards})") 

31 

32 # Convert to 1-based index 

33 return index + 1 

34 

35 

36def _get_current_card_value(self): 

37 """Retrieve the value of the current card along with the keyword name. 

38 

39 Returns 

40 ------- 

41 name : `str` 

42 The name of the current card. 

43 value : `object` 

44 The value in the correct Python type. 

45 """ 

46 # Method look up table for obtaining values 

47 typeLut = {CardType.INT: self.getFitsI, 

48 CardType.FLOAT: self.getFitsF, 

49 CardType.STRING: self.getFitsS, 

50 CardType.COMPLEXF: self.getFitsCF, 

51 CardType.LOGICAL: self.getFitsL 

52 } 

53 

54 # Get the data type for this matching card 

55 ctype = self.getCardType() 

56 

57 # Get the name of the card 

58 name = self.getCardName() 

59 

60 # Go back one card so we can ask for the value in the correct 

61 # data type (getFitsX starts from the next card) 

62 # thiscard = self.getCard() 

63 # self.setCard(thiscard - 1) 

64 

65 if ctype == CardType.UNDEF: 

66 value = None 

67 elif ctype in typeLut: 

68 found = typeLut[ctype]("") # "" indicates current card 

69 if found.found: 

70 value = found.value 

71 else: 

72 raise RuntimeError(f"Unexpectedly failed to find card '{name}'") 

73 elif ctype == CardType.COMMENT: 

74 value = self.getCardComm() 

75 else: 

76 raise RuntimeError(f"Type, {ctype} of FITS card '{name}' not supported") 

77 

78 return name, value 

79 

80 

81def length(self): 

82 return self.nCard 

83 

84 

85FitsChan.__len__ = length 

86 

87 

88def iter(self): 

89 """The FitsChan is its own iterator, incrementing the card position on 

90 each call. 

91 

92 The position of the iterator is handled internally in the FitsChan and 

93 is moved to the start of the FitsChan by this call. 

94 Whilst iterating do not change the internal card position. 

95 

96 The iterator will return 80-character header cards. 

97 """ 

98 self.clearCard() 

99 return self 

100 

101 

102FitsChan.__iter__ = iter 

103 

104 

105def next(self): 

106 """Return each 80-character header card until we run out of cards. 

107 """ 

108 card = self.findFits("%f", True) 

109 if not card.found: 

110 raise StopIteration 

111 return card.value 

112 

113 

114FitsChan.__next__ = next 

115 

116 

117def to_string(self): 

118 """A FitsChan string representation is a FITS header with newlines 

119 after each 80-character header card. 

120 """ 

121 return "\n".join(c for c in self) 

122 

123 

124FitsChan.__str__ = to_string 

125 

126 

127def contains(self, name): 

128 """Returns True if either the supplied name is present in the FitsChan 

129 or the supplied integer is acceptable to the FitsChan. 

130 """ 

131 if isinstance(name, int): 

132 # index will be zero-based 

133 if name >= 0 and name < self.nCard: 

134 return True 

135 elif isinstance(name, str): 

136 currentCard = self.getCard() 

137 try: 

138 self.clearCard() 

139 result = self.findFits(name, False) 

140 finally: 

141 self.setCard(currentCard) 

142 if result.found: 

143 return True 

144 

145 return False 

146 

147 

148FitsChan.__contains__ = contains 

149 

150 

151def getitem(self, name): 

152 """Return a value associated with the supplied name. 

153 

154 Parameters 

155 ---------- 

156 name : `str` or `int` 

157 If the FitsChan is being accessed by integer index the returned value 

158 will be the corresponding 80-character card. Index values are 0-based. 

159 A negative index counts from the end of the FitsChan. 

160 If the FitsChan is being accessed by string the returned value will 

161 be the scalar value associated with the first card that matches the 

162 supplied name. 

163 

164 Returns 

165 ------- 

166 value : `str`, `int`, `float`, `bool`, or `None` 

167 The complete 80-character header card if an integer index is supplied, 

168 else the first matching value of the named header. 

169 

170 Raises 

171 ------ 

172 IndexError 

173 Raised if an integer index is provided and the index is out of range. 

174 KeyError 

175 Raised if a string is provided and that string is not present in 

176 the FitsChan. Also raised if the supplied name is neither an integer 

177 not a string. 

178 RuntimeError 

179 Raised if there is some problem accessing the value in the FitsChan. 

180 """ 

181 

182 # Save current card position 

183 currentCard = self.getCard() 

184 

185 if isinstance(name, int): 

186 # Calculate position in FitsChan (0-based to 1-based) 

187 newpos = _calc_card_pos(self, name) 

188 self.setCard(newpos) 

189 try: 

190 result = self.findFits("%f", False) 

191 finally: 

192 self.setCard(currentCard) 

193 if not result.found: 

194 raise IndexError(f"No FITS card at index {name}") 

195 return result.value 

196 

197 elif isinstance(name, str): 

198 

199 try: 

200 # Rewind FitsChan so we search all cards 

201 self.clearCard() 

202 

203 # We are only interested in the first matching card 

204 result = self.findFits(name, False) 

205 if not result.found: 

206 raise KeyError(f"{name}'") 

207 

208 this_name, value = _get_current_card_value(self) 

209 if this_name != name: 

210 raise RuntimeError(f"Internal inconsistency in get: {this_name} != {name}") 

211 

212 finally: 

213 # Reinstate the original card position 

214 self.setCard(currentCard) 

215 

216 return value 

217 

218 raise KeyError(f"Supplied key, '{name}' of unsupported type") 

219 

220 

221FitsChan.__getitem__ = getitem 

222 

223 

224def setitem(self, name, value): 

225 """Set a new value. 

226 

227 Parameters 

228 ---------- 

229 name : `str` or `int` 

230 If the name is an integer index this corresponds to a position within 

231 the FitsChan. Index values are 0-based. A negative index counts 

232 from the end of the FitsChan. If the index matches the number of 

233 cards (e.g. the return value of `len()`) the new value will be 

234 appended to the end of the FitsChan. 

235 If the name is an empty string or `None`, the value will be inserted 

236 at the current card position as a comment card. 

237 If the name is a string corresponding to a header card that is already 

238 present in the FitsChan, the new value will overwrite the existing 

239 value leaving the header name and any comment unchanged. 

240 Any other cards matching that name later in the header will 

241 be removed. If there is no header with that name, a new card will 

242 be inserted at the end of the FitsChan. 

243 value : `str`, `int`, `float`, `bool`, `None` 

244 The new value to be inserted. If an integer index is given it must be 

245 a complete FITS header card. The string will be padded to 80 

246 characters. 

247 

248 Raises 

249 ------ 

250 IndexError 

251 Raised if the supplied integer index is out of range. 

252 KeyError 

253 Raised if the supplied name is neither a string or an integer. 

254 TypeError 

255 Raised if an integer index is given but the supplied value is not 

256 a string. 

257 """ 

258 

259 if isinstance(name, int): 

260 # Calculate position in FitsChan (0-based to 1-based) 

261 newpos = _calc_card_pos(self, name) 

262 self.setCard(newpos) 

263 

264 if not value: 

265 value = " " 

266 

267 # Overwrite the entire value 

268 self.putFits(value, True) 

269 return 

270 

271 # A blank name results in a comment card being inserted at the 

272 # current position 

273 if not name: 

274 self.setFitsCM(value, False) 

275 return 

276 

277 if not isinstance(name, str): 

278 raise KeyError(f"Supplied key, '{name}' of unsupported type") 

279 

280 # Get current card position and rewind 

281 currentCard = self.getCard() 

282 try: 

283 self.clearCard() 

284 

285 # Look for a card with the specified name. 

286 # We do not care about the result; if nothing is found we will be 

287 # at the end of the header. 

288 self.findFits(name, False) 

289 

290 # pyast seems to want to delete items if the value is None but 

291 # astropy and PropertyList think the key should be undefined. 

292 if value is None: 

293 self.setFitsU(name, overwrite=True) 

294 elif isinstance(value, int): 

295 self.setFitsI(name, value, overwrite=True) 

296 elif isinstance(value, float): 

297 self.setFitsF(name, value, overwrite=True) 

298 elif isinstance(value, bool): 

299 self.setFitsL(name, value, overwrite=True) 

300 else: 

301 # Force to string. 

302 self.setFitsS(name, str(value), overwrite=True) 

303 

304 # Delete any later cards with matching keyword 

305 while True: 

306 found = self.findFits(name, False) 

307 if not found.found: 

308 break 

309 self.delFits() 

310 finally: 

311 # Try to reinstate the current card 

312 self.setCard(currentCard) 

313 

314 

315FitsChan.__setitem__ = setitem 

316 

317 

318def delitem(self, name): 

319 """Delete an item from the FitsChan either by index (0-based) or by name. 

320 

321 Parameters 

322 ---------- 

323 name : `str` or `int` 

324 If the name is an integer index the card at that position will be 

325 removed. The index is zero-based. A negative index counts from the 

326 end of the FitsChan. 

327 If the name is a string all cards matching that name will be removed. 

328 

329 Raises 

330 ------ 

331 IndexError 

332 Raised if the supplied index is out of range. 

333 KeyError 

334 Raised if the supplied name is not found in the FitsChan. 

335 """ 

336 if isinstance(name, int): 

337 # Correct to 1-based 

338 newpos = _calc_card_pos(self, name) 

339 if newpos <= 0 or newpos > self.nCard: 

340 # AST will ignore this but we raise if the index is out of range 

341 raise IndexError(f"No FITS card at index {name}") 

342 self.setCard(newpos) 

343 self.delFits() 

344 return 

345 

346 if not isinstance(name, str): 

347 raise KeyError(f"Supplied key, '{name}' of unsupported type") 

348 

349 self.clearCard() 

350 # Delete any cards with matching keyword 

351 deleted = False 

352 while True: 

353 found = self.findFits(name, False) 

354 if not found.found: 

355 break 

356 self.delFits() 

357 deleted = True 

358 

359 if not deleted: 

360 raise KeyError(f"No FITS card named {name}") 

361 

362 

363FitsChan.__delitem__ = delitem 

364 

365 

366def items(self): 

367 """Iterate over each card, returning the keyword and value in a tuple. 

368 

369 Returns 

370 ------- 

371 key : `str` 

372 The key associated with the card. Can be an empty string for some 

373 comment styles. The same key name can be returned multiple times 

374 and be associated with different values. 

375 value : `str`, `int`, `bool`, `float` 

376 The value. 

377 

378 Notes 

379 ----- 

380 The position of the iterator is internal to the FitsChan. Do not 

381 change the card position when iterating. 

382 """ 

383 self.clearCard() 

384 nCards = self.nCard 

385 thisCard = self.getCard() 

386 

387 while thisCard <= nCards: 

388 name, value = _get_current_card_value(self) 

389 yield name, value 

390 self.setCard(thisCard + 1) 

391 thisCard = self.getCard() 

392 

393 

394FitsChan.items = items