diff --git a/.gitignore b/.gitignore index 253fad6..bbf3434 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ dist/ RO.egg-info/ -*.pyc \ No newline at end of file +*.pyc + +# Pycharm settings +.idea + diff --git a/README.txt b/README.txt index 879411f..ceaf989 100755 --- a/README.txt +++ b/README.txt @@ -4,3 +4,5 @@ Tkinter-compatible networking. The docs directory contains installation instructions, an overview, version history and the license. + +> **WARNING:** this version is Python 3 only in preparation for SDSS-V. For SDSS-IV, use the ``python2`` branch. diff --git a/python/RO/Alg/IDGen.py b/python/RO/Alg/IDGen.py index 606cb98..9837bc6 100644 --- a/python/RO/Alg/IDGen.py +++ b/python/RO/Alg/IDGen.py @@ -15,7 +15,7 @@ class IDGen(object): """generate a sequence of integer ID numbers, wrapping around if desired. - + Warning: can be used as an iterator, but there is no stop condition! """ def __init__(self, startVal=1, wrapVal=None, incr=1): @@ -49,6 +49,6 @@ def __next__(self): if self.wrapVal is not None: self.ind %= self.nSteps return newID - + def __repr__(self): return "IDGen(startVal=%s, wrapVal=%s, incr=%s)" % (self.startVal, self.wrapVal, self.incr) diff --git a/python/RO/Alg/ListPlus.py b/python/RO/Alg/ListPlus.py index b9ea2fc..4a1467f 100644 --- a/python/RO/Alg/ListPlus.py +++ b/python/RO/Alg/ListPlus.py @@ -14,14 +14,14 @@ def get(self, key, defValue = None): return self[key] except (LookupError, TypeError): return defValue - + def has_key(self, key): try: self[key] return True except (LookupError, TypeError): return False - + def iteritems(self): for key in self.keys(): yield (key, self[key]) @@ -34,6 +34,6 @@ def itervalues(self): def keys(self): return list(range(len(self))) - + def values(self): return self[:] diff --git a/python/RO/Alg/MultiDict.py b/python/RO/Alg/MultiDict.py index 04bdc4e..f6ccd7c 100755 --- a/python/RO/Alg/MultiDict.py +++ b/python/RO/Alg/MultiDict.py @@ -13,14 +13,13 @@ and added SetDict. 2010-05-18 ROwen Modified SetDict to use sets 2015-09-24 ROwen Replace "== None" with "is None" to future-proof array tests and modernize the code. +2020-01-13 DGatlin Changed set([val]) to set(val) for Python 3 """ __all__ = ["ListDict", "SetDict"] - try: - from UserDict import UserDict -except ImportError: from collections import UserDict - +except ImportError: + from UserDict import UserDict class ListDict(UserDict): """A dictionary whose values are a list of items. @@ -34,10 +33,10 @@ def __setitem__(self, key, val): self.data[key].append(val) else: self.data[key] = [val] - + def addList(self, key, valList): """Append values to the list of values for a given key, creating a new entry if necessary. - + Inputs: - valList: an iterable collection (preferably ordered) of values """ @@ -59,25 +58,25 @@ class SetDict(ListDict): """A dictionary whose values are a set of items, meaning a list of unique items. Duplicate items are silently not added. """ - + def __setitem__(self, key, val): """Add a value to the set of values for a given key, creating a new entry if necessary. - + Duplicate values are silently ignored. - + Supports the notation: aListDict[key] = val """ valSet = self.data.get(key) if valSet is None: - self.data[key] = set([val]) + self.data[key] = set(val) else: valSet.add(val) - + def addList(self, key, valList): """Add values to the set of values for a given key, creating a new entry if necessary. - + Duplicate values are silently ignored. - + Inputs: - valList: an iterable collection of values """ @@ -107,7 +106,7 @@ def addList(self, key, valList): print((RO.StringUtil.prettyDict(ad))) print("listdict copy (modified):") print((RO.StringUtil.prettyDict(ad2))) - + ad = SetDict() ad["a"] = "foo a" diff --git a/python/RO/Alg/MultiListIter.py b/python/RO/Alg/MultiListIter.py index d8092eb..600b1c7 100755 --- a/python/RO/Alg/MultiListIter.py +++ b/python/RO/Alg/MultiListIter.py @@ -14,10 +14,10 @@ class MultiListIter(object): def __init__(self, *lists): self.iters = list(map(iter, lists)) - + def __iter__(self): return self - + def __next__(self): return [next(elem) for elem in self.iters] diff --git a/python/RO/Alg/OrderedDict.py b/python/RO/Alg/OrderedDict.py index f94e875..0a8e425 100644 --- a/python/RO/Alg/OrderedDict.py +++ b/python/RO/Alg/OrderedDict.py @@ -8,7 +8,7 @@ History: 2002-02-01 ROwen First release. -2002-02-04 ROwen Added code for iterkeys, itervalues and iteritems +2002-02-04 ROwen Added code for iterkeys, itervalues and iteritems (as I feared I would have to do, but my limited tests suggested otherwise). Thanks to Jason Orendorff for insisting and for supplying the nice code for itervalues and iteritems. Also added __str__ and copy methods, @@ -34,9 +34,13 @@ Modified __repr__ to return a string that can recreate the dict. Added __str__ to output the traditional dict representation. 2015-09-24 ROwen Replace "== None" with "is None" to modernize the code. +2020-05-13 DGatlin Removed a number of functions in OrderedDict that can simply + be inhereted by dict in Python 3 """ __all__ = ["OrderedDict", "ReverseOrderedDict"] + + class OrderedDict(dict): """A dictionary in which the order of adding items is preserved. Replacing an existing item replaces it at its current location. @@ -56,23 +60,21 @@ def __init__(self, seqOrDict=None): for key, val in seqOrDict: self[key] = val - def clear(self): - self.__keyList = [] - dict.clear(self) + # def clear(self): + # self.__keyList = [] + # dict.clear(self) def copy(self): return self.__class__(self) def iterkeys(self): - return iter(self.__keyList) + return self.keys() def itervalues(self): - for key in self.keys(): - yield self[key] + return iter(self) def iteritems(self): - for key in self: - yield (key, self[key]) + return self.items() def index(self, key): """Return the index of key. @@ -92,71 +94,71 @@ def insert(self, ind, key, value): self.__keyList.insert(ind, key) dict.__setitem__(self, key, value) - def keys(self): - return self.__keyList[:] - - def pop(self, key): - val = self[key] - self.__delitem__(key) - return val - - def popitem(self, i=-1): - """Remove the ith item from the dictionary (the last item if i is omitted) - and returns (key, value). This emulates list.pop() instead of dict.popitem(), - since ordered dictionaries have a defined order. - """ - key = self.__keyList[i] - item = (key, self[key]) - self.__delitem__(key) - return item - - def setdefault(self, key, value): - if key not in self: - self[key] = value - return self[key] + # def keys(self): + # return self.__keyList[:] + + # def pop(self, key): + # val = self[key] + # self.__delitem__(key) + # return val + + # def popitem(self, i=-1): + # """Remove the ith item from the dictionary (the last item if i is omitted) + # and returns (key, value). This emulates list.pop() instead of dict.popitem(), + # since ordered dictionaries have a defined order. + # """ + # key = self.__keyList[i] + # item = (key, self[key]) + # self.__delitem__(key) + # return item + + # def setdefault(self, key, value): + # if key not in self: + # self[key] = value + # return self[key] def sort(self, cmpFunc=None): """Sort the keys. """ - self.__keyList.sort(cmpFunc) + pass - def update(self, aDict): - """Add all items from dictionary aDict to self (in order if aDict is an ordered dictionary). - """ - for key, value in aDict.items(): - self[key] = value + # def update(self, aDict): + # """Add all items from dictionary aDict to self (in order if aDict is an ordered dictionary). + # """ + # for key, value in aDict.items(): + # self[key] = value - def values(self): - return [self[key] for key in self.keys()] + # def values(self): + # return [self[key] for key in self.keys() def _checkIntegrity(self): """Perform an internal consistency check and raise an AssertionError if anything is wrong. In principal a bug could lead to the system getting out of synch, hence this method. """ - assert len(self) == len(self.__keyList), \ - "length of dict %r != length of key list %r" % (len(self), len(self.__keyList)) + assert len(self) == len(self.keys()), \ + "length of dict %r != length of key list %r" % (len(self), len(self.keys())) for key in self.keys(): assert key in self, \ "key %r in key list missing from dictionary" % (key,) - def __delitem__(self, key): - dict.__delitem__(self, key) - self.__keyList.remove(key) + # def __delitem__(self, key): + # dict.__delitem__(self, key) + # self.__keyList.remove(key) - def __iter__(self): - return iter(self.keys()) + # def __iter__(self): + # return iter(self.keys()) - def __repr__(self): - return "%s([%s])" % (self.__class__.__name__, ', '.join(["(%r, %r)" % item for item in self.items()])) + # def __repr__(self): + # return "%s([%s])" % (self.__class__.__name__, ', '.join(["(%r, %r)" % item for item in self.items()])) - def __str__(self): - return "{%s}" % (', '.join(["(%r, %r)" % item for item in self.items()]),) + # def __str__(self): + # return "{%s}" % (', '.join(["(%r, %r)" % item for item in self.items()]),) - def __setitem__(self, key, value): - if key not in self: - self.__keyList.append(key) - dict.__setitem__(self, key, value) + # def __setitem__(self, key, value): + # if key not in self: + # self.__keyList.append(key) + # dict.__setitem__(self, key, value) class ReverseOrderedDict(OrderedDict): @@ -192,17 +194,18 @@ def __repr__(self): descrList.reverse() return "%s([%s])" % (self.__class__.__name__, ', '.join(descrList)) + if __name__ == "__main__": print("testing OrderedDict") import copy import random - + # basic setup showOutput = 0 # display results or just complain if failure? nItems = 10 # length of dictionary to test nToDelete = 2 # number of items to delete nToReplace = 5 # number of items to replace - + assert nToDelete > 0 assert nToReplace > 0 assert nItems >= nToDelete + nToReplace @@ -211,29 +214,29 @@ def testDict(desKeys, desValues, theDict): """Test an ordered dictionary, given the expected keys and values (in order)""" actKeys = list(theDict.keys()) assert desKeys == actKeys, "keys() failed; keys %r != %r" % (desKeys, actKeys) - + actValues = list(theDict.values()) assert desValues == actValues, "values() failed; values %r != %r" % (desValues, actValues) - + assert len(theDict) == len(desKeys), "len() failed: %r != %r" % (len(desKeys), len(theDict)) - + # verify that iteration works: actKeys = [key for key in theDict] assert desKeys == actKeys, "__iter__() failed; keys %r != %r" % (desKeys, actKeys) - + actValues = [v for v in theDict.values()] assert desValues == actValues, "itervalues() failed; values %r != %r" % (desValues, actValues) - + desKeyValues = list(map(lambda key, v: (key, v), desKeys, desValues)) actKeyValues = [kv for kv in theDict.items()] assert desKeyValues == actKeyValues, "iteritems() failed; values %r != %r" % (desKeyValues, actKeyValues) + + theDict._checkIntegrity() - theDict._checkIntegrity() - - + def keyToValue(key): return "val[%r]" % (key,) - + def altKeyToValue(key): return "alt[%r]" % (key,) @@ -270,17 +273,18 @@ def altKeyToValue(key): testDict(inKeys, inValues, oDict) if showOutput: print(("after replacing %r items: %r" % (nToReplace, oDict))) - + # test copying dictCopy = oDict.copy() assert list(dictCopy.keys()) == list(oDict.keys()), "copy failed; keys %r != %r" % (list(dictCopy.keys()), list(testDict.keys())) - + testKey = list(dictCopy.keys())[0] dictCopy[testKey] = "changed value" assert list(dictCopy.values()) != list(oDict.values()), "copy failed; changing a value in one affected the other" - + # add a new item to dictCopy and make sure the integrity of both are preserved # (verifies that the __keyList lists in each dictionary are separate entities) dictCopy[()] = "value for ()" dictCopy._checkIntegrity() oDict._checkIntegrity() + print('Tests complete') diff --git a/python/RO/Alg/RandomWalk.py b/python/RO/Alg/RandomWalk.py index 0930510..9fb11d0 100755 --- a/python/RO/Alg/RandomWalk.py +++ b/python/RO/Alg/RandomWalk.py @@ -25,7 +25,7 @@ def __init__(self, homeValue, sigma, minValue, maxValue): def __iter__(self): return self - + def __next__(self): """Randomly change the value and return the next value """ @@ -58,7 +58,7 @@ def __init__(self, initialValue, sigma): def __iter__(self): return self - + def __next__(self): """Randomly change the value and return the new value """ diff --git a/python/RO/Astro/Cnv/AppGeoData.py b/python/RO/Astro/Cnv/AppGeoData.py index 42f7121..37cc27c 100755 --- a/python/RO/Astro/Cnv/AppGeoData.py +++ b/python/RO/Astro/Cnv/AppGeoData.py @@ -8,8 +8,9 @@ """ __all__ = ["AppGeoData"] -from RO.Astro import llv from RO.Astro import Tm +from RO.Astro import llv + class AppGeoData(object): """Position-independent data for conversion diff --git a/python/RO/Astro/Cnv/AzAltFromHADec.py b/python/RO/Astro/Cnv/AzAltFromHADec.py index b1d3378..83d7c6d 100755 --- a/python/RO/Astro/Cnv/AzAltFromHADec.py +++ b/python/RO/Astro/Cnv/AzAltFromHADec.py @@ -8,8 +8,10 @@ __all__ = ["azAltFromHADec"] import numpy + import RO.MathUtil + def azAltFromHADec (haDec, lat): """Converts cartesian HA/Dec position to alt/az. diff --git a/python/RO/Astro/Cnv/CoordConv.py b/python/RO/Astro/Cnv/CoordConv.py index a637eb6..51aa9dc 100755 --- a/python/RO/Astro/Cnv/CoordConv.py +++ b/python/RO/Astro/Cnv/CoordConv.py @@ -30,6 +30,7 @@ __all__ = ["coordConv"] import numpy + import RO.CoordSys from RO.Astro import Tm from .AppGeoData import AppGeoData diff --git a/python/RO/Astro/Cnv/FK4FromICRS.py b/python/RO/Astro/Cnv/FK4FromICRS.py index 2ee3bb4..a74d4ba 100755 --- a/python/RO/Astro/Cnv/FK4FromICRS.py +++ b/python/RO/Astro/Cnv/FK4FromICRS.py @@ -8,8 +8,9 @@ __all__ = ["fk4FromICRS"] import numpy -import RO.PhysConst + import RO.MathUtil +import RO.PhysConst from RO.Astro import llv # Constants diff --git a/python/RO/Astro/Cnv/FK5Prec.py b/python/RO/Astro/Cnv/FK5Prec.py index 5b94cbe..ae2eeea 100755 --- a/python/RO/Astro/Cnv/FK5Prec.py +++ b/python/RO/Astro/Cnv/FK5Prec.py @@ -8,8 +8,10 @@ __all__ = ["fk5Prec"] import numpy + from RO.Astro import llv + def fk5Prec(fromP, fromV, fromDate, toDate): """ Inputs: diff --git a/python/RO/Astro/Cnv/GeoFromICRS.py b/python/RO/Astro/Cnv/GeoFromICRS.py index 8634c89..7028061 100755 --- a/python/RO/Astro/Cnv/GeoFromICRS.py +++ b/python/RO/Astro/Cnv/GeoFromICRS.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -""" +""" History: 2002-07-12 ROwen Converted to Python from the TCC's cnv_J2AppGeo 7-3 2007-04-24 ROwen Converted from Numeric to numpy. @@ -8,24 +8,26 @@ __all__ = ["geoFromICRS"] import numpy + from RO.Astro import llv + def geoFromICRS(icrsP, icrsV, agData): """ Converts ICRS coordinates to apparent geocentric coordinates. - + Inputs: - icrsP(3) ICRS cartesian position (au) - icrsV(3) ICRS cartesian velocity (au/year) (e.g. proper motion and radial velocity) - agData an AppGeoData object containing star-independent apparent geocentric data - + Returns: - appGeoP(3) apparent geocentric cartesian position (au), a numpy.array - + Warnings: - Uses the approximation ICRS = FK5 J2000. - Not fully accurate for solar system objects. - + Details: The following approximations have been used: - FK5 J2000 is the same as ICRS @@ -34,22 +36,22 @@ def geoFromICRS(icrsP, icrsV, agData): - No correction is applied for the bending of light by sun's gravity. This introduces errors on the order of 0.02" at a distance of 20 degrees from the sun (Wallace, 1986) - + References: ABERAT, from the APPLE (J2000) subroutine library, U.S. Naval Observatory P.T. Wallace's MAPQK routine P.T. Wallace, "Proposals for Keck Tel. Point. Algorithms", 1986 (unpub.) "The Astronomical Almanac" for 1978, U.S. Naval Observatory """ - # make sure inputs are floating-point numpy arrays + # make sure inputs are floating-point numpy arrays icrsP = numpy.asarray(icrsP, dtype = float) icrsV = numpy.asarray(icrsV, dtype = float) - + # correct for velocity and Earth's offset from the barycenter p2 = icrsP + icrsV * agData.dtPM - agData.bPos - + # here is where the correction for sun's gravity belongs - + # correct for annual aberration p2Dir, p2Mag = llv.vn(p2) dot2 = numpy.dot(p2Dir, agData.bVelC) @@ -57,7 +59,7 @@ def geoFromICRS(icrsP, icrsV, agData): # but handles |p2|=0 gracefully (by setting dot2 to 0) vfac = p2Mag * (1.0 + dot2 / (1.0 + agData.bGamma)) p3 = ((p2 * agData.bGamma) + (vfac * agData.bVelC)) / (1.0 + dot2) - + # correct position for precession and nutation return numpy.dot(agData.pnMat, p3) diff --git a/python/RO/Astro/Cnv/GeoFromTopo.py b/python/RO/Astro/Cnv/GeoFromTopo.py index ab055b6..5704c34 100755 --- a/python/RO/Astro/Cnv/GeoFromTopo.py +++ b/python/RO/Astro/Cnv/GeoFromTopo.py @@ -10,10 +10,12 @@ __all__ = ["geoFromTopo"] import numpy + import RO.MathUtil from RO.Astro import llv from .HADecFromAzAlt import haDecFromAzAlt + def geoFromTopo(appTopoP, last, obsData): """ Converts apparent topocentric coordinates to apparent geocentric coordinates. diff --git a/python/RO/Astro/Cnv/HADecFromAzAlt.py b/python/RO/Astro/Cnv/HADecFromAzAlt.py index a8960ec..7a56297 100755 --- a/python/RO/Astro/Cnv/HADecFromAzAlt.py +++ b/python/RO/Astro/Cnv/HADecFromAzAlt.py @@ -9,8 +9,10 @@ __all__ = ["haDecFromAzAlt"] import numpy + import RO.MathUtil + def haDecFromAzAlt(azAlt, lat): """Converts alt/az position to HA/Dec position. diff --git a/python/RO/Astro/Cnv/ICRSFromFixedFK4.py b/python/RO/Astro/Cnv/ICRSFromFixedFK4.py index ac55077..41dfc31 100755 --- a/python/RO/Astro/Cnv/ICRSFromFixedFK4.py +++ b/python/RO/Astro/Cnv/ICRSFromFixedFK4.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python """ History: @@ -9,8 +9,9 @@ __all__ = ["icrsFromFixedFK4"] import numpy -import RO.PhysConst + import RO.MathUtil +import RO.PhysConst from RO.Astro import llv, Tm # Constants @@ -29,29 +30,29 @@ def icrsFromFixedFK4(fk4P, fk4Date): """ Converts mean catalog fk4 coordinates to ICRS for a fixed star. Uses the approximation that ICRS = FK5 J2000. - + Inputs: - fk4Date TDB date of fk4 coordinates (Besselian epoch) note: TDT will always do and UTC is usually adequate - fk4P(3) mean catalog fk4 cartesian position (au) - + Returns: - icrsP(3) ICRS cartesian position (au), a numpy.array - + Error Conditions: none - + Warnings: The FK4 date is in Besselian years. - + The star is assumed fixed on the celestial sphere. That is a bit different than assuming it has zero proper motion because FK4 system has slight ficticious proper motion. - + The FK4 system refers to a specific set of precession constants; not all Besselian-epoch data was precessed using these constants (especially data for epochs before B1950). - + References: P.T. Wallace's routine FK45Z """ diff --git a/python/RO/Astro/Cnv/ICRSFromGeo.py b/python/RO/Astro/Cnv/ICRSFromGeo.py index 40f4db0..5707ea3 100755 --- a/python/RO/Astro/Cnv/ICRSFromGeo.py +++ b/python/RO/Astro/Cnv/ICRSFromGeo.py @@ -9,6 +9,7 @@ __all__ = ["icrsFromGeo"] import numpy + import RO.MathUtil from RO.Astro import llv diff --git a/python/RO/Astro/Cnv/ObsFromTopo.py b/python/RO/Astro/Cnv/ObsFromTopo.py index 2ffbfaa..52708e3 100755 --- a/python/RO/Astro/Cnv/ObsFromTopo.py +++ b/python/RO/Astro/Cnv/ObsFromTopo.py @@ -9,10 +9,12 @@ __all__ = ["obsFromTopo"] from math import sqrt + import numpy -import RO.SysConst -import RO.PhysConst + import RO.MathUtil +import RO.PhysConst +import RO.SysConst # Constants _MaxZDU = 85.0 diff --git a/python/RO/Astro/Cnv/ObserverData.py b/python/RO/Astro/Cnv/ObserverData.py index ac1ebff..6050714 100755 --- a/python/RO/Astro/Cnv/ObserverData.py +++ b/python/RO/Astro/Cnv/ObserverData.py @@ -7,10 +7,13 @@ __all__ = ["ObserverData"] import math + import numpy + import RO.PhysConst from RO.Astro import llv + class ObserverData(object): """Observatory-specific (or observer-specific) data. diff --git a/python/RO/Astro/Cnv/TopoFromGeo.py b/python/RO/Astro/Cnv/TopoFromGeo.py index e412ba5..bc98e43 100755 --- a/python/RO/Astro/Cnv/TopoFromGeo.py +++ b/python/RO/Astro/Cnv/TopoFromGeo.py @@ -9,10 +9,12 @@ __all__ = ["topoFromGeo"] import numpy + import RO.MathUtil from RO.Astro import llv from .AzAltFromHADec import azAltFromHADec + def topoFromGeo(appGeoP, last, obsData): """ Converts apparent geocentric coordinates to apparent topocentric coordinates diff --git a/python/RO/Astro/Cnv/TopoFromObs.py b/python/RO/Astro/Cnv/TopoFromObs.py index c7d3138..618b931 100755 --- a/python/RO/Astro/Cnv/TopoFromObs.py +++ b/python/RO/Astro/Cnv/TopoFromObs.py @@ -11,10 +11,12 @@ __all__ = ["topoFromObs"] from math import sqrt + import numpy -import RO.SysConst -import RO.PhysConst + import RO.MathUtil +import RO.PhysConst +import RO.SysConst # Constants # For zdu > _MaxZDU the correction is computed at _MaxZDU. diff --git a/python/RO/Astro/Cnv/__init__.py b/python/RO/Astro/Cnv/__init__.py index 935d5b3..ca0b816 100644 --- a/python/RO/Astro/Cnv/__init__.py +++ b/python/RO/Astro/Cnv/__init__.py @@ -34,12 +34,12 @@ from .GeoFromICRS import * from .GeoFromTopo import * from .HADecFromAzAlt import * -from .ICRSFromFixedFK4 import * from .ICRSFromFK4 import * +from .ICRSFromFixedFK4 import * from .ICRSFromGal import * from .ICRSFromGeo import * -from .ObserverData import * from .ObsFromTopo import * +from .ObserverData import * from .TopoFromGeo import * from .TopoFromGeoSimple import * from .TopoFromObs import * diff --git a/python/RO/Astro/Sph/AngSep.py b/python/RO/Astro/Sph/AngSep.py index b7e92bd..c09e718 100755 --- a/python/RO/Astro/Sph/AngSep.py +++ b/python/RO/Astro/Sph/AngSep.py @@ -9,10 +9,13 @@ __all__ = ["angSep"] import math + import numpy + import RO.MathUtil from .DCFromSC import dcFromSC + def angSep(posA, posB): """Computes the angular separation between two points on a sphere. diff --git a/python/RO/Astro/Sph/AzAltFromHADec.py b/python/RO/Astro/Sph/AzAltFromHADec.py index eafd768..5bffe9b 100755 --- a/python/RO/Astro/Sph/AzAltFromHADec.py +++ b/python/RO/Astro/Sph/AzAltFromHADec.py @@ -3,9 +3,10 @@ __all__ = ["azAltFromHADec"] +from RO.Astro import Cnv from .DCFromSC import dcFromSC from .SCFromDC import scFromDC -from RO.Astro import Cnv + def azAltFromHADec(haDec, lat): """Converts HA/Dec position to az/alt. diff --git a/python/RO/Astro/Sph/CCFromSC.py b/python/RO/Astro/Sph/CCFromSC.py index b609516..c438bb5 100755 --- a/python/RO/Astro/Sph/CCFromSC.py +++ b/python/RO/Astro/Sph/CCFromSC.py @@ -8,8 +8,10 @@ __all__ = ["ccFromSC"] import numpy + import RO.MathUtil + def ccFromSC(pos, magP): """ Converts a spherical position to cartesian coordinates. diff --git a/python/RO/Astro/Sph/CCFromSCPV.py b/python/RO/Astro/Sph/CCFromSCPV.py index 02e596e..4767409 100755 --- a/python/RO/Astro/Sph/CCFromSCPV.py +++ b/python/RO/Astro/Sph/CCFromSCPV.py @@ -3,8 +3,8 @@ __all__ = ["ccFromSCPV"] -import RO.PhysConst import RO.MathUtil +import RO.PhysConst from .CCFromSC import ccFromSC # Magic Numbers diff --git a/python/RO/Astro/Sph/CoordConv.py b/python/RO/Astro/Sph/CoordConv.py index 620b419..e56d260 100755 --- a/python/RO/Astro/Sph/CoordConv.py +++ b/python/RO/Astro/Sph/CoordConv.py @@ -3,12 +3,13 @@ __all__ = ["coordConv"] -from . import Const -from RO.Astro import Cnv import RO.MathUtil +from RO.Astro import Cnv +from . import Const from .CCFromSCPVOff import ccFromSCPVOff from .SCFromCCPVOff import scFromCCPVOff + def coordConv( fromPos, fromSys, fromDate, toSys, toDate, fromPM = (0.0, 0.0), fromParlax=0.0, fromRadVel=0.0, diff --git a/python/RO/Astro/Sph/HADecFromAzAlt.py b/python/RO/Astro/Sph/HADecFromAzAlt.py index 2921282..f799ce1 100755 --- a/python/RO/Astro/Sph/HADecFromAzAlt.py +++ b/python/RO/Astro/Sph/HADecFromAzAlt.py @@ -5,9 +5,10 @@ import RO.MathUtil import RO.SysConst +from RO.Astro import Cnv from .DCFromSC import dcFromSC from .SCFromDC import scFromDC -from RO.Astro import Cnv + def haDecFromAzAlt (azAlt, lat): """Converts alt/az position to ha/dec position. diff --git a/python/RO/Astro/Sph/SCFromCC.py b/python/RO/Astro/Sph/SCFromCC.py index 67794da..6c6d1f6 100755 --- a/python/RO/Astro/Sph/SCFromCC.py +++ b/python/RO/Astro/Sph/SCFromCC.py @@ -4,9 +4,11 @@ __all__ = ["scFromCC"] import math + import RO.MathUtil import RO.SysConst + def scFromCC(p): """ Converts cartesian position to spherical coordinates. diff --git a/python/RO/Astro/Sph/SCFromCCPV.py b/python/RO/Astro/Sph/SCFromCCPV.py index f90426f..e0c1337 100755 --- a/python/RO/Astro/Sph/SCFromCCPV.py +++ b/python/RO/Astro/Sph/SCFromCCPV.py @@ -4,6 +4,7 @@ __all__ = ["scFromCCPV"] import math + import RO.PhysConst from .SCFromCC import scFromCC diff --git a/python/RO/Astro/Sph/SCFromCCPVOff.py b/python/RO/Astro/Sph/SCFromCCPVOff.py index 3ca28cd..5277116 100755 --- a/python/RO/Astro/Sph/SCFromCCPVOff.py +++ b/python/RO/Astro/Sph/SCFromCCPVOff.py @@ -4,8 +4,9 @@ __all__ = ["scFromCCPVOff"] from .AngSideAng import angSideAng -from .SCFromCCPV import scFromCCPV from .SCFromCC import scFromCC +from .SCFromCCPV import scFromCCPV + def scFromCCPVOff(p, v, offP): """ diff --git a/python/RO/Astro/Tm/GMSTFromUT1.py b/python/RO/Astro/Tm/GMSTFromUT1.py index b028692..bdfd255 100755 --- a/python/RO/Astro/Tm/GMSTFromUT1.py +++ b/python/RO/Astro/Tm/GMSTFromUT1.py @@ -4,8 +4,10 @@ __all__ = ["gmstFromUT1"] import math -import RO.PhysConst + import RO.MathUtil +import RO.PhysConst + def gmstFromUT1(ut1): """Convert from universal time (MJD) diff --git a/python/RO/Astro/Tm/LASTFromUT1.py b/python/RO/Astro/Tm/LASTFromUT1.py index 12fc9f8..6900cca 100644 --- a/python/RO/Astro/Tm/LASTFromUT1.py +++ b/python/RO/Astro/Tm/LASTFromUT1.py @@ -2,11 +2,12 @@ __all__ = ["lastFromUT1"] -import RO.PhysConst import RO.MathUtil +import RO.PhysConst from RO.Astro import llv from .LMSTFromUT1 import lmstFromUT1 + def lastFromUT1(ut1, longitude): """Convert from universal time (MJD) to local apparent sidereal time (deg). diff --git a/python/RO/Astro/Tm/MJDFromPyTuple.py b/python/RO/Astro/Tm/MJDFromPyTuple.py index a90088d..aad012d 100755 --- a/python/RO/Astro/Tm/MJDFromPyTuple.py +++ b/python/RO/Astro/Tm/MJDFromPyTuple.py @@ -4,8 +4,10 @@ __all__ = ["mjdFromPyTuple"] import math + import RO.PhysConst + def mjdFromPyTuple(timeTuple): """Converts a python time tuple to Modified Julian Date. Only the first six elements of the time tuple are used: diff --git a/python/RO/Astro/Tm/UTCFromPySec.py b/python/RO/Astro/Tm/UTCFromPySec.py index 8140f73..a1dfcd3 100644 --- a/python/RO/Astro/Tm/UTCFromPySec.py +++ b/python/RO/Astro/Tm/UTCFromPySec.py @@ -8,6 +8,7 @@ __all__ = ["setClockError", "getClockError", "getCurrPySec", "utcFromPySec", "pySecFromUTC"] import time + import RO.PhysConst # Python time tuple for J2000: 2000-01-01 12:00:00 (a Saturday) diff --git a/python/RO/Astro/Tm/__init__.py b/python/RO/Astro/Tm/__init__.py index 704130d..a4b0971 100644 --- a/python/RO/Astro/Tm/__init__.py +++ b/python/RO/Astro/Tm/__init__.py @@ -23,10 +23,10 @@ """ from .EpJFromMJD import * from .GMSTFromUT1 import * +from .ISODate import * from .LASTFromUT1 import * from .LMSTFromUT1 import * from .MJDFromEpJ import * from .MJDFromPyTuple import * from .TAI import * from .UTCFromPySec import * -from .ISODate import * diff --git a/python/RO/Astro/llv/eqeqx.py b/python/RO/Astro/llv/eqeqx.py index eb97e31..89dcba9 100755 --- a/python/RO/Astro/llv/eqeqx.py +++ b/python/RO/Astro/llv/eqeqx.py @@ -4,6 +4,7 @@ __all__ = ["eqeqx"] import math + import RO.PhysConst from .nutc import nutc diff --git a/python/RO/Astro/llv/etrms.py b/python/RO/Astro/llv/etrms.py index ae7865b..575282f 100755 --- a/python/RO/Astro/llv/etrms.py +++ b/python/RO/Astro/llv/etrms.py @@ -9,9 +9,12 @@ __all__ = ["etrms"] import math + import numpy + import RO.PhysConst + def etrms(bep): """ Compute the e-terms (elliptic component of annual aberration) diff --git a/python/RO/Astro/llv/euler.py b/python/RO/Astro/llv/euler.py index d198713..cd1ca79 100755 --- a/python/RO/Astro/llv/euler.py +++ b/python/RO/Astro/llv/euler.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python """ History: @@ -9,24 +9,26 @@ __all__ = ["euler"] import math + import numpy + def euler(axisAngSet): """ Form a rotation matrix from successive rotations about specified Cartesian axes. - + Inputs: - axisAngSet a list of axis, angle lists, where: - axis is the index of the axis (0 for x, 1 for y, 2 for z) - angle is the angle of rotation (rad) - + Returns: - rMat the rotation matrix as a 3x3 numpy.array - + Rotation is via the right-hand rule. For example: a positive rotation about x is from y to z. - + Based on EULER by Pat Wallace. """ # Initialise result matrix @@ -44,14 +46,14 @@ def euler(axisAngSet): [0.0, ca, sa], [0.0, -sa, ca], ]) - + elif axis == 1: # y axis currRotMat = numpy.array([ [ ca, 0.0, -sa], [0.0, 1.0, 0.0], [ sa, 0.0, ca], ]) - + elif axis == 2: # z axis currRotMat = numpy.array([ [ ca, sa, 0.0], @@ -61,12 +63,12 @@ def euler(axisAngSet): else: raise RuntimeError("unknown axis %s; must be one of 0, 1, 2" % (axis,)) - + # Apply the current rotation (currRotMat x netRotMat) netRotMat = numpy.dot(currRotMat, netRotMat) return netRotMat - + if __name__ == "__main__": print("testing euler") # test data is formatted as follows: diff --git a/python/RO/Astro/llv/evp.py b/python/RO/Astro/llv/evp.py index 363d0b5..5c34c2a 100755 --- a/python/RO/Astro/llv/evp.py +++ b/python/RO/Astro/llv/evp.py @@ -9,9 +9,11 @@ __all__ = ["evp"] from math import sin, cos, pi, sqrt, fmod + import numpy -from .prec import prec + from .epj import epj +from .prec import prec # Constants TWOPI = pi * 2.0 diff --git a/python/RO/Astro/llv/mappa.py b/python/RO/Astro/llv/mappa.py index 40acac5..fa0161f 100755 --- a/python/RO/Astro/llv/mappa.py +++ b/python/RO/Astro/llv/mappa.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -""" -History: +""" +History: P.T.Wallace Starlink 21 July 1994 2002-07-11 ROwen Converted to Python. 2007-04-24 ROwen Removed unused import of Numeric @@ -9,10 +9,11 @@ __all__ = ["mappa"] from math import sqrt -from .vn import vn + +from .epj import epj from .evp import evp from .prenut import prenut -from .epj import epj +from .vn import vn # Constants # Light time for 1 au (sec) @@ -24,17 +25,17 @@ def mappa (eq, tdb): """ Compute star-independent parameters in preparation for conversions between mean place and geocentric apparent place. - + The parameters produced by this routine are required in the parallax, light deflection, aberration, and precession/nutation parts of the mean/apparent transformations. - + The reference frames and timescales used are post IAU 1976. - + Inputs: - eq epoch of mean equinox to be used (Julian) - tdb TDB as a modified Julian date (JD-2400000.5) - + Returned a tuple containing the following star-independent mean-to-apparent parameters: - time interval for proper motion (Julian years) @@ -44,24 +45,24 @@ def mappa (eq, tdb): - bvc: barycentric velocity of Earth in units of c - sqrt(1-v**2) where v=modulus(bvc) - precession/nutation (3,3) matrix - + References: 1984 Astronomical Almanac, pp b39-b41. (also Lederle & Schwan, Astron. Astrophys. 134, 1-6, 1984) - + Notes: - + 1) For tdb, the distinction between the required TDB and TT is always negligible. Moreover, for all but the most critical applications UTC is adequate. - + 2) The accuracy of the routines using the parameters amprms is limited by the routine EVP, used here to compute the Earth position and velocity by the methods of Stumpff. The maximum error in the resulting aberration corrections is about 0.3 milliarcsecond. - + 3) The barycentric position of the Earth and heliocentric direction of the Earth are referred to the mean equinox and equator of epoch eq. @@ -79,22 +80,22 @@ def mappa (eq, tdb): return ( # Time interval for proper motion correction (years) epj(tdb)-eq, - + # Barycentric position of Earth (au) bPos, - + # Heliocentric direction of earth hDir, # Light deflection parameter _GR2/hDist, - + # Barycentric velocity of Earth, in units of C vbc, - + # sqrt(1-v**2) where v=modulus(bvc) sqrt(1.0 - (vbcMag * vbcMag)), - + # Precession/nutation matrix prenut(eq,tdb), ) diff --git a/python/RO/Astro/llv/nut.py b/python/RO/Astro/llv/nut.py index 64a063c..5d32755 100755 --- a/python/RO/Astro/llv/nut.py +++ b/python/RO/Astro/llv/nut.py @@ -1,31 +1,32 @@ #!/usr/bin/env python -""" -History: +""" +History: P.T.Wallace Starlink 1 January 1993 2002-07-11 ROwen Converted to Python. 2007-04-24 ROwen Converted from Numeric to numpy (in test code). """ __all__ = ["nut"] -from .nutc import nutc from .euler import euler +from .nutc import nutc + def nut(tdb): """ Form the matrix of nutation for a given TDB - IAU 1980 theory (double precision) - + References: Final report of the IAU Working Group on Nutation, chairman P.K.Seidelmann, 1980. Kaplan,G.H., 1981, USNO circular no. 163, pA3-6. - + Inputs: - TDB TDB date (loosely et) as Modified Julian Date Returns the nutation matrix as a 3x3 numpy.array - + The matrix is in the sense V(true) = rmatn * V(mean) """ # Nutation components and mean obliquity diff --git a/python/RO/Astro/llv/nutc.py b/python/RO/Astro/llv/nutc.py index 50a375f..e132619 100755 --- a/python/RO/Astro/llv/nutc.py +++ b/python/RO/Astro/llv/nutc.py @@ -4,6 +4,7 @@ __all__ = ["nutc"] import math + import RO.PhysConst _ArcSecPerRev = RO.PhysConst.ArcSecPerDeg * 360.0 diff --git a/python/RO/Astro/llv/prebn.py b/python/RO/Astro/llv/prebn.py index 60d2ec4..adb7829 100755 --- a/python/RO/Astro/llv/prebn.py +++ b/python/RO/Astro/llv/prebn.py @@ -10,9 +10,11 @@ __all__ = ["prebn"] import numpy + import RO.PhysConst from .euler import euler + def prebn(bep0, bep1): """ Generate the matrix of precession between two epochs, diff --git a/python/RO/Astro/llv/prec.py b/python/RO/Astro/llv/prec.py index dd1b1fe..b6776ff 100755 --- a/python/RO/Astro/llv/prec.py +++ b/python/RO/Astro/llv/prec.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python """ History: @@ -15,15 +15,15 @@ def prec(begEpoch, endEpoch): """ Return the matrix of precession between two epochs (IAU 1976, FK5) - + Inputs: - begEpoch beginning Julian epoch (e.g. 2000 for J2000) - endEpoch ending Julian epoch - + Returns: - pMat the precession matrix as a 3x3 numpy.array, where pos(endEpoch) = rotMat * pos(begEpoch) - + Based on Pat Wallace's PREC. His notes follow: - The epochs are TDB (loosely ET) Julian epochs. - Though the matrix method itself is rigorous, the precession @@ -38,7 +38,7 @@ def prec(begEpoch, endEpoch): 5600AD and exceed 1000 arcsec outside 6800BC to 8200AD. The routine PRECL implements a more elaborate model which is suitable for problems spanning several thousand years. - + References: Lieske,J.H., 1979. Astron.Astrophys.,73,282. equations (6) & (7), p283. diff --git a/python/RO/Astro/llv/prenut.py b/python/RO/Astro/llv/prenut.py index 151b891..242367f 100755 --- a/python/RO/Astro/llv/prenut.py +++ b/python/RO/Astro/llv/prenut.py @@ -9,9 +9,11 @@ __all__ = ["prenut"] import numpy -from .prec import prec -from .nut import nut + from .epj import epj +from .nut import nut +from .prec import prec + def prenut(epoch, mjd): """ diff --git a/python/RO/Astro/llv/vn.py b/python/RO/Astro/llv/vn.py index c79b45e..6a69cfb 100755 --- a/python/RO/Astro/llv/vn.py +++ b/python/RO/Astro/llv/vn.py @@ -9,9 +9,11 @@ __all__ = ["vn"] import numpy + import RO.MathUtil import RO.SysConst + def vn(vec): """ Normalises a vector. diff --git a/python/RO/CanvasUtil.py b/python/RO/CanvasUtil.py index 4987505..bac4454 100755 --- a/python/RO/CanvasUtil.py +++ b/python/RO/CanvasUtil.py @@ -22,9 +22,11 @@ __all__ = ["ctrCircle", "ctrPlus", "ctrX", "radialLine", "Spiral"] import math -from six.moves import tkinter +import tkinter + import RO.MathUtil + def ctrCircle(cnv, xpos, ypos, rad, width = 1, **kargs): """Draws a centered circle on the specified canvas. diff --git a/python/RO/Comm/BrowseURL.py b/python/RO/Comm/BrowseURL.py index ebce824..32df339 100644 --- a/python/RO/Comm/BrowseURL.py +++ b/python/RO/Comm/BrowseURL.py @@ -10,7 +10,7 @@ __all__ = ["browseURL"] import threading -import six.moves.urllib.parse as parse +import urllib.parse import webbrowser class _BrowseURLThread(threading.Thread): @@ -29,10 +29,10 @@ def run(self): # failed! if this is a file URL with an anchor, # try again without the anchor - urlTuple = parse.urlparse(url) + urlTuple = urllib.parse.urlparse(url) if urlTuple[0] == "file" and urlTuple[-1] != '': urlTuple = urlTuple[0:-1] + ('',) - url = parse.urlunparse(urlTuple) + url = urllib.parse.urlunparse(urlTuple) if not url: return try: diff --git a/python/RO/Comm/FTPGet.py b/python/RO/Comm/FTPGet.py index 66c094b..604b07f 100644 --- a/python/RO/Comm/FTPGet.py +++ b/python/RO/Comm/FTPGet.py @@ -41,17 +41,17 @@ """ __all__ = ['FTPGet'] +import ftplib import os import sys -import six.moves.urllib.parse as parse import threading -import ftplib +import urllib.parse _Debug = False class FTPGet: """Retrieves the specified url to a file. - + Inputs: - host IP address of ftp host - fromPath full path of file on host to retrieve @@ -78,7 +78,7 @@ class FTPGet: Done = "Done" Aborted = "Aborted" Failed = "Failed" - + _AllStates = set(( Queued, Connecting, @@ -115,9 +115,9 @@ def __init__(self, self.createDir = createDir self.username = username or "anonymous" self.password = password or "abc@def.org" - + if dispStr is None: - self.dispStr = parse.urljoin("ftp://" + self.host, self.fromPath) + self.dispStr = urllib.parse.urljoin("ftp://" + self.host, self.fromPath) else: self.dispStr = dispStr @@ -128,17 +128,17 @@ def __init__(self, self._state = self.Queued self._exception = None self._stateLock = threading.RLock() - + # set up background thread self._getThread = threading.Thread(name="get", target=self._getTask) self._getThread.setDaemon(True) if startNow: self.start() - + def start(self): """Start the download. - + If state is not Queued, raises RuntimeError """ self._stateLock.acquire() @@ -163,26 +163,26 @@ def abort(self): else: return finally: - self._stateLock.release() + self._stateLock.release() def getException(self): """If the state is Failed, returns the exception that caused the failure. Otherwise returns None. """ return self._exception - + @property def isAbortable(self): """True if the transaction can be aborted """ return self._state in self._AbortableStates - + @property def isDone(self): """True if the transaction is finished (succeeded, aborted or failed), False otherwise. """ return self._state in self._DoneStates - + @property def readBytes(self): """bytes read so far @@ -192,26 +192,26 @@ def readBytes(self): @property def totBytes(self): """total bytes in file, if known, None otherwise. - + The value is certain to be unknown until the transfer starts; after that it depends on whether the server sends the info. """ return self._totBytes - + @property def state(self): """Current state, as a string """ return self._state - + def _cleanup(self, newState, exception=None): """Clean up everything. Must only be called from the _getTask thread. - + Close the input and output files. If not isDone (transfer not finished) then updates the state If newState in (Aborted, Failed) and not isDone, deletes the file If newState == Failed and not isDone, sets the exception - + Inputs: - newState: new state; ignored if isDone - exception: exception that is the reason for failure; @@ -236,8 +236,8 @@ def _cleanup(self, newState, exception=None): sys.stderr.write("FTPGet._cleanup invalid cleanup state %r; assuming %s=Failed\n" % \ (newState, self.Failed)) newState = self.Failed - - self._stateLock.acquire() + + self._stateLock.acquire() try: if self.isDone: # already finished; do nothing @@ -246,13 +246,13 @@ def _cleanup(self, newState, exception=None): self._state = newState finally: self._stateLock.release() - + if didOpen and newState in (self.Aborted, self.Failed): try: os.remove(self.toPath) except OSError: pass - + if newState == self.Failed: self._exception = exception @@ -274,13 +274,13 @@ def _getTask(self): mode = "wb" else: mode = "w" - self._toFile = file(self.toPath, mode) + self._toFile = open(self.toPath, mode) # open input socket if _Debug: print("FTPGet: open ftp connection to %r" % (self.host)) ftp = ftplib.FTP(self.host, self.username, self.password) - + if _Debug: print("FTPGet: set connection isbinary=%r on %r" % (self.isBinary, self.host)) if self.isBinary: @@ -296,12 +296,12 @@ def _getTask(self): try: self._state = self.Running finally: - self._stateLock.release() + self._stateLock.release() if _Debug: print("FTPGet: totBytes = %r; read %r on %r " % \ (self._totBytes, self.fromPath, self.host)) - + while True: nextData = self._fromSocket.recv(8192) if not nextData: @@ -311,25 +311,25 @@ def _getTask(self): return self._readBytes += len(nextData) self._toFile.write(nextData) - + self._cleanup(self.Done) except Exception as e: self._cleanup(self.Failed, exception = e) - - + + def __str__(self): return "%s(%s)" % (self.__class__.__name__, self.fromPath) def _toPrep(self): """Create or verify the existence of the output directory and check if output file already exists. - + Raises an exception if anything is wrong. """ # if output file exists and not overwrite, complain if not self.overwrite and os.path.exists(self.toPath): raise ValueError("toPath %r already exists" % (self.toPath,)) - + # if directory does not exist, create it or fail, depending on createDir; # else if "directory" exists but is a file, fail toDir = os.path.dirname(self.toPath) diff --git a/python/RO/Comm/Generic.py b/python/RO/Comm/Generic.py index 774192d..639b879 100755 --- a/python/RO/Comm/Generic.py +++ b/python/RO/Comm/Generic.py @@ -14,10 +14,10 @@ Here are some examples: -1) Using Tkinter with the Tcl event loop (no Twisted) +1) Using tkinter with the Tcl event loop (no Twisted) -from six.moves import tkinter -root = Tkinter.Tk() +import tkinter +root = tkinter.Tk() import RO.Comm.Generic RO.Comm.Generic.setFramework("tk") @@ -39,11 +39,11 @@ #... reactor.run() -3) Using Twisted framework with Tkinter +3) Using Twisted framework with tkinter -from six.moves import tkinter +import tkinter import twisted.internet.tksupport -root = Tkinter.Tk() +root = tkinter.Tk() twisted.internet.tksupport.install(root) from twisted.internet import reactor @@ -60,10 +60,12 @@ __all__ = ["setFramework", "getFramework", "getFrameworkSet", "TCPSocket", "TCPSocket", "Timer", "WaitForTCPServer"] import time + from RO.AddCallback import safeCall2 _Framework = None + def setFramework(framework): """Set which framework you wish to use. @@ -77,7 +79,8 @@ def setFramework(framework): global _Framework, TCPSocket, TCPServer, Timer if framework not in getFrameworkSet(): frameworkList = sorted(list(getFrameworkSet())) - raise ValueError("framework=%r; must be one of %s" % (frameworkList,)) + raise ValueError("framework=%r; must be one of %s".fomrat( + *frameworkList)) if framework == "tk": from RO.Comm.TkSocket import TCPSocket, TCPServer @@ -166,7 +169,7 @@ def __repr__(self): if __name__ == "__main__": - from six.moves import tkinter + import tkinter root = tkinter.Tk() root.withdraw() setFramework("tk") # since it is almost always installed diff --git a/python/RO/Comm/HTTPGet.py b/python/RO/Comm/HTTPGet.py index 40ab751..95b91ae 100755 --- a/python/RO/Comm/HTTPGet.py +++ b/python/RO/Comm/HTTPGet.py @@ -40,17 +40,14 @@ 2015-09-24 ROwen Replace "== None" with "is None" to modernize the code. 2015-11-03 ROwen Replace "!= None" with "is not None" to modernize the code. """ - -from __future__ import print_function - - __all__ = ['HTTPGet'] import atexit import os import sys import time -from six.moves import tkinter +import tkinter + import RO.AddCallback import RO.StringUtil import RO.TkUtil @@ -69,18 +66,18 @@ def __init__(self, timeLim = 2.0, dtime = 0.1): self.timeLim = timeLim self.dtime = max(dtime, 0.01) self.didRegisterExit = False - + def addTransfer(self, httpGetter): """Add one httpGetter. """ if _DebugExit: print("HTTPGet._Exit.addTransfer(%s)" % (httpGetter,)) - httpGetter.addDoneCallback(self.removeTransfer) + httpGetter.addDoneCallback(self.removeTransfer) self.transferDict[httpGetter] = None if not self.didRegisterExit: atexit.register(self.abortAll) self.didRegisterExit = True - + def removeTransfer(self, httpGetter): """Remove one httpGetter. Does not verify that the getter is finished. @@ -88,7 +85,7 @@ def removeTransfer(self, httpGetter): if _DebugExit: print("HTTPGet._Exit.removeTransfer(%s)" % (httpGetter,)) self.transferDict.pop(httpGetter) - + def abortAll(self): """Abort all outsanding transfers. Meant to be registered with atexit. @@ -122,7 +119,7 @@ def abortAll(self): class HTTPGet(RO.AddCallback.BaseMixin): """Downloads the specified url to a file. - + Inputs: - fromURL url of file to download - toPath full path of destination file @@ -140,7 +137,7 @@ class HTTPGet(RO.AddCallback.BaseMixin): - dispStr a string to display while downloading the file; if omitted, fromURL is displayed - timeLim time limit (sec) for the total transfer; if None then no limit - + Callbacks receive one argument: this object. """ # state constants @@ -164,13 +161,13 @@ class HTTPGet(RO.AddCallback.BaseMixin): _AbortableStates = set((Queued, Connecting, Running)) _DoneStates = set((Done, Aborted, Failed)) _FailedStates = set((Aborted, Failed)) - + StateStrMaxLen = 0 for stateStr in _AllStates: StateStrMaxLen = max(StateStrMaxLen, len(stateStr)) del(stateStr) _tkApp = None - + def __init__(self, fromURL, toPath, @@ -213,9 +210,9 @@ def __init__(self, self._totBytes = None self._state = self.Queued self._errMsg = None - + self._createdFile = False - + self._tkApp.eval('package require http') RO.AddCallback.BaseMixin.__init__(self, stateFunc, callNow=False) @@ -229,19 +226,19 @@ def __init__(self, if startNow: self.start() - + def addDoneCallback(self, func): """Add a function that will be called when the transfer completes""" self._doneCallbacks.append(func) - + def removeDoneCallback(self, func): """Remove a done callback. """ self._doneCallbacks.remove(func) - + def start(self): """Start the download. - + If state is not Queued, raises RuntimeError """ if _Debug: @@ -254,7 +251,7 @@ def start(self): try: # verify output file and verify/create output directory, as appropriate self._toPrep() - + # open output file if _Debug: print("HTTPGet: opening output file %r" % (self.toPath,)) @@ -265,7 +262,7 @@ def start(self): self._tkApp.call('fconfigure', self._tclFile, "-encoding", "binary", "-translation", "binary") except tkinter.TclError as e: raise RuntimeError("Could not open %r: %s" % (self.toPath, e)) - + # start http transfer doneCallback = RO.TkUtil.TclFunc(self._httpDoneCallback, debug=_Debug) progressCallback = RO.TkUtil.TclFunc(self._httpProgressCallback, debug=_Debug) @@ -312,13 +309,13 @@ def errMsg(self): """If the transfer failed, an explanation as a string, else None """ return self._errMsg - + @property def state(self): """Returns the current state as a string. """ return self._state - + @property def isAbortable(self): """True if the transaction can be aborted @@ -330,13 +327,13 @@ def isDone(self): """Return True if the transaction is finished (succeeded, aborted or failed), False otherwise. """ return self._state in self._DoneStates - + @property def didFail(self): """Return True if the transaction failed or was aborted """ return self._state in self._FailedStates - + @property def readBytes(self): """Bytes read so far @@ -346,7 +343,7 @@ def readBytes(self): @property def totBytes(self): """Total bytes in file, if known, None otherwise. - + The value is certain to be unknown until the transfer starts; after that it depends on whether the server sends the info. """ @@ -364,14 +361,14 @@ def _setState(self, newState, errMsg=None): # if state is not valid, reject if self.isDone: return - + if newState not in self._AllStates: raise RuntimeError("Unknown state %r" % (newState,)) - + self._state = newState if newState == self.Failed: self._errMsg = errMsg - + isDone = self.isDone if isDone: self._cleanup() @@ -381,8 +378,8 @@ def _setState(self, newState, errMsg=None): # call done callbacks # use a copy in case a callback deregisters itself for func in self._doneCallbacks[:]: - RO.AddCallback.safeCall2(str(self), func, self) - + RO.AddCallback.safeCall2(str(self), func, self) + # remove all callbacks self._removeAllCallbacks() self._doneCallbacks = [] @@ -390,9 +387,9 @@ def _setState(self, newState, errMsg=None): def _cleanup(self): """Clean up everything except references to callbacks. - + Warning: this is a private method: call only from _setState! - + Close the input and output files and deregister the tcl callbacks. If state in (Aborted, Failed), delete the output file. """ @@ -412,7 +409,7 @@ def _cleanup(self): self._tclFile = None if _Debug: print("output file closed") - + if self._createdFile and self._state in (self.Aborted, self.Failed): try: os.remove(self.toPath) @@ -428,14 +425,14 @@ def _httpDoneCallback(self, token=None): if self._tclHTTPConn is None: sys.stderr.write("HTTPGet warning: _httpDoneCallback called but no http connection\n") return - + if _Debug: print("%s.httpDoneCallback()" % (self,)) print("status=%r" % (self._tkApp.call('::http::status', self._tclHTTPConn),)) print("code=%r" % (self._tkApp.call('::http::code', self._tclHTTPConn),)) print("ncode=%r" % (self._tkApp.call('::http::ncode', self._tclHTTPConn),)) print("error=%r" % (self._tkApp.call('::http::error', self._tclHTTPConn),)) - + httpState = self._tkApp.call('::http::status', self._tclHTTPConn) errMsg = None if httpState == "ok": @@ -462,9 +459,9 @@ def _httpDoneCallback(self, token=None): errMsg = self._tkApp.call('::http::error', self._tclHTTPConn) if not errMsg: errMsg = httpState - + self._setState(newState, errMsg) - + def _httpProgressCallback(self, token, totBytes, readBytes): """http callback function. """ @@ -482,14 +479,14 @@ def _httpProgressCallback(self, token, totBytes, readBytes): if (newTime - self._lastProgReportTime) > _ProgressInterval: self._doCallbacks() self._lastProgReportTime = newTime - + def __str__(self): return "%s(%s)" % (self.__class__.__name__, self.fromURL) def _toPrep(self): """Create or verify the existence of the output directory and check if output file already exists. - + Raise RuntimeError or IOError if anything is wrong. """ if _Debug: @@ -497,7 +494,7 @@ def _toPrep(self): # if output file exists and not overwrite, complain if not self.overwrite and os.path.exists(self.toPath): raise RuntimeError("toPath %r already exists" % (self.toPath,)) - + # if directory does not exist, create it or fail, depending on createDir; # else if "directory" exists but is a file, fail toDir = os.path.dirname(self.toPath) @@ -518,10 +515,10 @@ def _toPrep(self): testURL = "http://www.astro.washington.edu/" outFile = "httpget_test.html" - + _Debug = False _DebugExit = True - + def stateCallback(httpObj): print("state =", httpObj.state, end=' ') print("read %s of %s bytes" % (httpObj.readBytes, httpObj.totBytes)) @@ -529,7 +526,7 @@ def stateCallback(httpObj): if httpObj.errMsg: print("error message =", httpObj.errMsg) root.quit() - + httpObj = HTTPGet( fromURL = testURL, toPath = outFile, diff --git a/python/RO/Comm/HubConnection.py b/python/RO/Comm/HubConnection.py index b3e4f62..8a3f99b 100755 --- a/python/RO/Comm/HubConnection.py +++ b/python/RO/Comm/HubConnection.py @@ -40,9 +40,10 @@ shaClass = sha.sha import sys -from .TCPConnection import TCPConnection import RO.ParseMsg import RO.StringUtil +from .TCPConnection import TCPConnection + class HubConnection(TCPConnection): """Connection to Apache Point Observatory hub @@ -80,7 +81,7 @@ def __init__ (self, ) self._initData() self._loginExtra = loginExtra - + def _initData(self): self.desProgID = None self.desUsername = None @@ -88,7 +89,7 @@ def _initData(self): self._authState = 0 self.__password = None self._didStartAuth = False - + def connect (self, progID, password, @@ -112,7 +113,7 @@ def connect (self, self.__password = password self.desUsername = username TCPConnection.connect(self, host, port) - + def getCmdr(self): """Returns the commander (in the form progID.username) last assigned by the Hub. @@ -138,7 +139,7 @@ def getUsername(self): """ cmdr = self.getCmdr() return cmdr and cmdr.split(".")[1] - + def _authRead(self, sock, hubMsg): """Read callback for authorization. """ @@ -158,13 +159,13 @@ def _authRead(self, sock, hubMsg): raise RuntimeError("knockKnock failed: %s" % (errMsg,)) elif (nonce is None): raise RuntimeError("nonce missing; got: %r" % (hubMsg,)) - + # generate the combined password combPassword = shaClass(nonce+password).hexdigest() self._setState(self.Authorizing, "nonce received") self._authState = 1 - + # send the command auth login program= password= loginCmd = '1 auth login program="%s" password="%s" username="%s"' \ % (self.desProgID, combPassword, self.desUsername) @@ -183,16 +184,16 @@ def _authRead(self, sock, hubMsg): raise RuntimeError("login failed: %s" % (errMsg,)) elif cmdr is None: raise RuntimeError("cmdr missing; got: %r" % (hubMsg,)) - + self.cmdr = cmdr - + # authorization succeeded; start normal reads self._authState = 2 self._authDone("you are %r" % (self.cmdr,)) elif self._authState == 2: sys.stderr.write("warning: lost message: %r", hubMsg) else: - raise RuntimeError("bug: unknown auth state %r" % (self._authState,)) + raise RuntimeError("bug: unknown auth state %r" % (self._authState,)) except Exception as e: self._authState = -1 self.disconnect(False, RO.StringUtil.strFromException(e)) @@ -209,19 +210,19 @@ class NullConnection(HubConnection): Always acts as if it is connected (so one can write data), but prohibits explicit connection (maybe not necessary, but done to make it clear to users that it is a fake). - + cmdr = "TU01.me" """ def __init__ (self, *args, **kargs): HubConnection.__init__(self, *args, **kargs) - + self.desUsername = "me" self.cmdr = "TU01.me" self._state = self.Connected def connect(self, *args, **kargs): raise RuntimeError("NullConnection is always connected") - + def disconnect(self): raise RuntimeError("NullConnection cannot disconnect") @@ -230,7 +231,7 @@ def writeLine(self, str): if __name__ == "__main__": - from six.moves import tkinter + import tkinter import RO.Wdg root = tkinter.Tk() @@ -256,16 +257,16 @@ def stateCallback (sock): def doConnect(host, port): class PasswordDialog(RO.Wdg.ModalDialogBase): def body(self, master): - + tkinter.Label(master, text="Program ID:").grid(row=0, column=0) tkinter.Label(master, text="Password :").grid(row=1, column=0) - + self.nameEntry = tkinter.Entry(master) self.nameEntry.grid(row=0, column=1) self.pwdEntry = tkinter.Entry(master, show="*") self.pwdEntry.grid(row=1, column=1) return self.nameEntry # return the item that gets initial focus - + def setResult(self): self.result = (self.nameEntry.get(), self.pwdEntry.get()) diff --git a/python/RO/Comm/TCPConnection.py b/python/RO/Comm/TCPConnection.py index 2e85178..cccb440 100755 --- a/python/RO/Comm/TCPConnection.py +++ b/python/RO/Comm/TCPConnection.py @@ -58,9 +58,11 @@ __all__ = ["TCPConnection"] import sys -from RO.Comm.BaseSocket import NullTCPSocket -from RO.AddCallback import safeCall2 + import RO.Comm.Generic +from RO.AddCallback import safeCall2 +from RO.Comm.BaseSocket import NullTCPSocket + if RO.Comm.Generic.getFramework() is None: print("Warning: RO.Comm.Generic framework not set; setting to tk") RO.Comm.Generic.setFramework("tk") @@ -415,7 +417,7 @@ def __str__(self): if __name__ == "__main__": """Demo using a simple echo server. """ - from six.moves import tkinter + import tkinter root = tkinter.Tk() root.withdraw() from RO.Comm.Generic import TCPServer diff --git a/python/RO/Comm/TkSerial.py b/python/RO/Comm/TkSerial.py index 43bbaaf..b431399 100644 --- a/python/RO/Comm/TkSerial.py +++ b/python/RO/Comm/TkSerial.py @@ -30,19 +30,17 @@ __all__ = ["TkSerial", "NullSerial"] import sys +import tkinter import traceback -from six.moves import tkinter + import RO.SeqUtil import RO.TkUtil -try: - set -except NameError: - from sets import Set as set + class TkBaseSerial(object): """Base class for communication via a serial port using the tcl event loop. This class handles state and supports TckSerial and NullSerial. - + Inputs: - chanID the tk socket connection; if not None then sockArgs is ignored - state the initial state @@ -50,9 +48,9 @@ class TkBaseSerial(object): Open = "Open" Closed = "Closed" Failed = "Failed" - + _StateSet = set((Open, Closed, Failed)) - + def __init__(self, portName, state, @@ -65,7 +63,7 @@ def __init__(self, self._reason = "" self._stateCallback = stateCallback self._tkCallbackDict = dict() - + @property def state(self): """Returns the current state as a tuple: @@ -79,11 +77,11 @@ def isOpen(self): """Return True if serial connection is open" """ return (self._state == self.Open) - + def setStateCallback(self, callFunc=None): """Specifies a state callback function (replacing the current one, if one exists). - + Inputs: - callFunc: the callback function, or None if none wanted The function is sent one argument: this TkSerial @@ -105,10 +103,10 @@ def __del__(self): """ #print "%s.__del()" self._stateCallback = None - + def _setState(self, newState, reason=None): """Change the state. - + Inputs: - newState: the new state - reason: an explanation (None to leave alone) @@ -121,21 +119,21 @@ def _setState(self, newState, reason=None): stateCallback = self._stateCallback if not self.isOpen: self._clearCallbacks() - + if stateCallback: try: stateCallback(self) except Exception as e: sys.stderr.write("%s state callback %s failed: %s\n" % (self, self._stateCallback, e,)) traceback.print_exc(file=sys.stderr) - + def __str__(self): return "%s(port=%s)" % (self.__class__.__name__, self._portName) class TkSerial(TkBaseSerial): """Connection via a serial port using the tcl event loop. - + Inputs: - portName serial port (e.g. "/dev/tty...") - baud desired baud rate @@ -154,7 +152,7 @@ class TkSerial(TkBaseSerial): via Tcl's fconfigure command (after prepending "-" to each keyword). Note: -mode is set using the keywords baud, parity, dataBits and stopBits; it may not be overridden using mode=.... - + For more information about the configuration options see the Tcl documentation for these two commands: - fconfigure (for options that are common to all types of connections) @@ -163,9 +161,9 @@ class TkSerial(TkBaseSerial): Open = "Open" Closed = "Closed" Failed = "Failed" - + _StateSet = set((Open, Closed, Failed)) - + def __init__(self, portName, baud = 9600, @@ -184,7 +182,7 @@ def __init__(self, stateCallback = stateCallback, ) self._readCallback = readCallback - + self._tk = tkinter.StringVar()._tk self._chanID = 0 @@ -192,7 +190,7 @@ def __init__(self, self._chanID = self._tk.call('open', portName, 'r+') if not self._chanID: raise RuntimeError("Failed to open serial port %r" % (portName,)) - + cfgArgs = [ "-blocking", 0, ] @@ -205,9 +203,9 @@ def __init__(self, cfgArgs += ["-handshake", str(handshake)] if translation is not None: cfgArgs += ["-translation", str(translation)] - + self._tk.call("fconfigure", self._chanID, *cfgArgs) - + # add callbacks; the write callback indicates the serial is open # and is just used to detect state self._setSockCallback(self._doRead) @@ -217,9 +215,9 @@ def __init__(self, def close(self, isOK=True, reason=None): """Start closing the serial port. - + Does nothing if the serial is already closed or failed. - + Inputs: - isOK: if True, mark state as Closed, else Failed - reason: a string explaining why, or None to leave unchanged; @@ -241,7 +239,7 @@ def close(self, isOK=True, reason=None): else: self._setState(self.Failed, reason) self._tk = None - + def read(self, nChar=None): """Return up to nChar characters; if nChar is None then return all available characters. @@ -263,11 +261,11 @@ def readLine(self, default=None): """Read one line of data. Do not return the trailing newline. If a full line is not available, return default. - + Inputs: - default value to return if a full line is not available (in which case no data is read) - + Raise RuntimeError if the serial is not open. """ #print "%s.readLine(default=%s)" % (self, default) @@ -281,20 +279,20 @@ def readLine(self, default=None): return default #print "readLine returning %r" % (readStr,) return readStr - + def setReadCallback(self, callFunc=None): """Specifies a read callback function (replacing the current one, if one exists). - + Inputs: - callFunc: the callback function, or None if none wanted. The function is sent one argument: this TkSerial """ self._readCallback = callFunc - + def write(self, data): """Write data to the serial port. Does not block. - + Raises UnicodeError if the data cannot be expressed as ascii. Raises RuntimeError if the serial connection is not open. If an error occurs while sending the data, the serial is closed, @@ -308,7 +306,7 @@ def write(self, data): self.close(isOK = False, reason=str(e)) raise self._assertConn() - + def writeLine(self, data): """Write a line of data terminated by standard newline (which for the net is \r\n, but the serial's auto newline @@ -322,13 +320,13 @@ def writeLine(self, data): self.close(isOK = False, reason=str(e)) raise self._assertConn() - + def _assertConn(self): """If not open, raise RuntimeError. """ if self._state != self.Open: raise RuntimeError("%s not open" % (self,)) - + def _clearCallbacks(self): """Clear any callbacks added by this class. Called just after the serial is closed. @@ -358,14 +356,14 @@ def _setSockCallback(self, callFunc=None, doWrite=False): typeStr = 'writable' else: typeStr = 'readable' - + if callFunc: tclFunc = RO.TkUtil.TclFunc(callFunc) tkFuncName = tclFunc.tclFuncName else: tclFunc = None tkFuncName = "" - + try: self._tk.call('fileevent', self._chanID, typeStr, tkFuncName) except tkinter.TclError as e: @@ -381,7 +379,7 @@ def _setSockCallback(self, callFunc=None, doWrite=False): # Save a reference to the new tclFunc,, if any if tclFunc: self._tkCallbackDict[typeStr] = tclFunc - + def __del__(self): """At object deletion, make sure the serial is properly closed. """ @@ -389,7 +387,7 @@ def __del__(self): self._readCallback = None self._stateCallback = None self.close() - + def __str__(self): return "%s(port=%s, chanID=%s)" % (self.__class__.__name__, self._portName, self._chanID) @@ -408,7 +406,7 @@ def __init__ (self): def read(self, *args, **kargs): raise RuntimeError("Cannot read from null serial") - + def readLine(self, *args, **kargs): raise RuntimeError("Cannot readLine from null serial") diff --git a/python/RO/Comm/TkSocket.py b/python/RO/Comm/TkSocket.py index 245b5d7..6e8417a 100755 --- a/python/RO/Comm/TkSocket.py +++ b/python/RO/Comm/TkSocket.py @@ -94,11 +94,13 @@ import re import sys +import tkinter import traceback -from six.moves import tkinter + import RO.TkUtil from RO.Comm.BaseSocket import BaseSocket, BaseServer, nullCallback + class _TkSocketWrapper(object): """Convenience wrapper around a Tk socket """ diff --git a/python/RO/Comm/TwistedSocket.py b/python/RO/Comm/TwistedSocket.py index b7ed2ec..f3c7af8 100755 --- a/python/RO/Comm/TwistedSocket.py +++ b/python/RO/Comm/TwistedSocket.py @@ -23,15 +23,18 @@ import re import sys import traceback -from twisted.python.failure import Failure + +from twisted.internet import reactor +from twisted.internet.endpoints import TCP4ClientEndpoint, TCP4ServerEndpoint from twisted.internet.error import ConnectionDone from twisted.internet.protocol import Factory, Protocol -from twisted.internet.endpoints import TCP4ClientEndpoint, TCP4ServerEndpoint -from twisted.internet import reactor from twisted.python import log +from twisted.python.failure import Failure + from RO.Comm.BaseSocket import BaseSocket, BaseServer, nullCallback from RO.Comm.TwistedTimer import Timer + class _SocketProtocol(Protocol): """Twisted socket protocol for use with these socket classes diff --git a/python/RO/Comm/VMSTelnet.py b/python/RO/Comm/VMSTelnet.py index b0d9d40..f4d4f49 100755 --- a/python/RO/Comm/VMSTelnet.py +++ b/python/RO/Comm/VMSTelnet.py @@ -30,9 +30,11 @@ __all__ = ["VMSTelnet"] import sys + import RO.Wdg from .TCPConnection import TCPConnection + class VMSTelnet(TCPConnection): """A telnet connection that negotiates the telnet protocol and handles password login. The only thing specific to VMS @@ -59,7 +61,7 @@ def __init__ (self, stateCallback: function to call whenever the socket connects or disconnected; it will be sent two arguments: is connected flag (true if connected, false if not) - this connection object + this connection object """ TCPConnection.__init__(self, host=host, @@ -70,9 +72,9 @@ def __init__ (self, authReadCallback = self._authRead, authReadLines = False, ) - + self._initData() - + def _initData(self): """Initialize extra fields.""" self._iac = 0 @@ -100,22 +102,22 @@ def connect (self, self._username = username TCPConnection.connect(self, host, port) - + def connectDialog (self, master, username, host=None): """Prompt for a password and then connect. """ if host: self.host = host - + class PasswordDialog(RO.Wdg.ModalDialogBase): def body(self, master): - + RO.Wdg.StrLabel(master, text="Password:").grid(row=0, column=0) - + self.pwdEntry = RO.Wdg.StrEntry(master, show="*") self.pwdEntry.grid(row=0, column=1) return self.pwdEntry # initial focus - + def setResult(self): self.result = self.pwdEntry.get() @@ -127,14 +129,14 @@ def setResult(self): username = username, password = passwd, ) - + def _authRead (self, sock, readData): """Handle username/password authentication and telnet negotiation. - + - Handles telnet negotiation by denying all requests. - If negotation fails, closes the connection (and so calls the connection state callback function). - + Intended to be the read callback function while negotiation the connection. """ for c in readData: @@ -198,7 +200,7 @@ def writeLine(self, str): if __name__ == "__main__": - from six.moves import tkinter + import tkinter root = tkinter.Tk() host = "tccdev" @@ -215,7 +217,7 @@ def stateCallback (sock): print(state) myConn = VMSTelnet( - host = host, + host = host, readCallback = readCallback, stateCallback = stateCallback, ) @@ -224,7 +226,7 @@ def stateCallback (sock): sendText.pack(fill=tkinter.X, expand=tkinter.YES) sendText.focus_set() - tkinter.Button(root, text="Disconnect", command=myConn.disconnect).pack() + tkinter.Button(root, text="Disconnect", command=myConn.disconnect).pack() def sendCmd (evt): try: diff --git a/python/RO/Constants.py b/python/RO/Constants.py index 9aa22f2..24eb7a5 100644 --- a/python/RO/Constants.py +++ b/python/RO/Constants.py @@ -26,7 +26,7 @@ """ __all__ = ['sevDebug', 'sevNormal', 'sevWarning', 'sevError', 'sevCritical', 'SevNameDict', 'NameSevDict'] -import six.moves.urllib.parse as parse +import urllib.parse from collections import OrderedDict # severity constants; numeric value increases with severity @@ -59,7 +59,7 @@ def _joinHelpURL(urlSuffix=""): # print "_joinHelpURL(urlSuffix=%r)" % (urlSuffix,) global _HelpURLBase, _gotHelpURLBase _gotHelpURLBase = True - return parse.urljoin(_HelpURLBase, urlSuffix) + return urllib.parse.urljoin(_HelpURLBase, urlSuffix) def _setHelpURLBase(urlBase): """Set the base url for help urls. diff --git a/python/RO/DS9.py b/python/RO/DS9.py index 340e993..528c294 100755 --- a/python/RO/DS9.py +++ b/python/RO/DS9.py @@ -153,12 +153,14 @@ """ __all__ = ["setup", "xpaget", "xpaset", "DS9Win"] -import numpy import os +import subprocess import time import warnings + +import numpy + import RO.OS -import subprocess _DebugSetup = False @@ -168,6 +170,7 @@ _DirFromWhichToRunDS9 = None _DS9Path = None + def _addToPATH(newPath): """Add newPath to the PATH environment variable. Do nothing if newPath already in PATH. @@ -191,13 +194,13 @@ def _findApp(appName, subDirs = None, doRaise = True): """Find a Mac or Windows application by expicitly looking for the in the standard application directories. If found, add directory to the PATH (if necessary). - + Inputs: - appName name of application, with .exe or .app extension - subDirs subdirectories of the main application directories; specify None if no subdirs - doRaise raise RuntimeError if not found? - + Returns a path to the application's directory. Return None or raise RuntimeError if not found. """ @@ -218,7 +221,7 @@ def _findApp(appName, subDirs = None, doRaise = True): if doRaise: raise RuntimeError("Could not find %s in %s" % (appName, dirTrials,)) return None - + def _findUnixApp(appName): """Use the unix "which" command to find the application on the PATH @@ -238,23 +241,22 @@ def _findUnixApp(appName): if errMsg: fullErrMsg = "'which %s' failed: %s" % (appName, errMsg) raise RuntimeError(fullErrMsg) - appPath = p.stdout.read() + appPath = p.stdout.read().strip(b'\n').decode('utf-8') if not appPath.startswith("/"): raise RuntimeError("Could not find %s on your PATH" % (appName,)) finally: p.stdout.close() p.stderr.close() - return appPath def _findDS9AndXPA(): """Locate ds9 and xpa, and add to PATH if not already there. - + Returns: - ds9Dir directory containing ds9 executable - xpaDir directory containing xpaget and (presumably) the other xpa executables - + Sets global variables: - _DirFromWhichToRunDS9 (the default dir from which to open DS9) - On Windows set to xpaDir to make sure that ds9 on Windows can find xpans @@ -264,7 +266,7 @@ def _findDS9AndXPA(): - On MacOS X if using the aqua SAOImage DS9 application then the path to the ds9 command line executable inside the aqua application bundle - Otherwise set to "ds9"; it is assumed to be on the PATH - + Raise RuntimeError if ds9 or xpa are not found. """ global _DirFromWhichToRunDS9, _DS9Path @@ -275,7 +277,7 @@ def _findDS9AndXPA(): # - ~/Applications/ds9.app # - /Applications.ds9.app # - on the PATH (adding /usr/local/bin if necessary) - + # add DISPLAY envinronment variable, if necessary # (since ds9 is an X11 application and environment os.environ.setdefault("DISPLAY", "localhost:0") @@ -304,32 +306,32 @@ def _findDS9AndXPA(): if not foundDS9: ds9Dir = _findUnixApp("ds9") - + if not foundXPA: xpaDir = _findUnixApp("xpaget") - + elif RO.OS.PlatformName == "win": ds9Dir = _findApp("ds9.exe", ["ds9"], doRaise=True) xpaDir = _findApp("xpaget.exe", ["xpa", "ds9"], doRaise=True) _DirFromWhichToRunDS9 = xpaDir - + else: # unix ds9Dir = _findUnixApp("ds9") xpaDir = _findUnixApp("xpaget") - + if _DebugSetup: print("_DirFromWhichToRunDS9=%r" % (_DirFromWhichToRunDS9,)) print("_DS9Path=%r" % (_DS9Path,)) - + return (ds9Dir, xpaDir) - + def setup(doRaise=False): """Search for xpa and ds9 and set globals accordingly. Return None if all is well, else return an error string. The return value is also saved in global variable _SetupError. - + Sets global variables: - _SetupError same value as returned - _Popen subprocess.Popen, if ds9 and xpa found, @@ -349,13 +351,13 @@ def setup(doRaise=False): except Exception as e: _SetupError = "RO.DS9 unusable: %s" % (e,) ds9Dir = xpaDir = None - + if _SetupError: class _Popen(subprocess.Popen): def __init__(self, *args, **kargs): setup(doRaise=True) subprocess.Popen.__init__(self, *args, **kargs) - + if doRaise: raise RuntimeError(_SetupError) else: @@ -377,7 +379,7 @@ def xpaget(cmd, template=_DefTemplate, doRaise = False): """Executes a simple xpaget command: xpaget -p