From ba25a3276242ac01d82a14bd582a6d0e30347955 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Tue, 7 May 2024 15:14:36 +0100 Subject: [PATCH 01/22] Removed errant file --- swifttools/ukssdc/query/Jamie_BackEnd.py | 363 ----------------------- 1 file changed, 363 deletions(-) delete mode 100644 swifttools/ukssdc/query/Jamie_BackEnd.py diff --git a/swifttools/ukssdc/query/Jamie_BackEnd.py b/swifttools/ukssdc/query/Jamie_BackEnd.py deleted file mode 100644 index 29107e74..00000000 --- a/swifttools/ukssdc/query/Jamie_BackEnd.py +++ /dev/null @@ -1,363 +0,0 @@ -from .common import TOOAPI_Baseclass, TOOAPI_ObsID -from .too_server import Swift_TOO_User -from .swift_status import Swift_TOO_Status -from .swift_afst import Swift_AFST -import requests -from bs4 import BeautifulSoup -import os - - -class Swift_Data_File(TOOAPI_Baseclass): - def __init__(self, filename=None, url=None, quicklook=None, fullpath=None, path=None): - TOOAPI_Baseclass.__init__(self) - self.filename = filename - self.path = path - self.fullpath = fullpath - self.url = url - self.quicklook = quicklook - self._size = None - self._type = None - self.rows = ['filename', 'path', 'url', 'quicklook', 'type'] - self.extrarows = [] - - @property - def type(self): - if 'xrt' in self.path: - inst = 'XRT' - elif 'bat' in self.path: - inst = 'BAT' - elif 'uvot' in self.path: - inst = 'UVOT' - elif 'auxil' in self.path: - inst = 'Auxillary' - elif 'log' in self.path: - inst = "Log" - - if "po_" in self.filename: - acs = "pointed" - elif "sl_" in self.filename: - acs = "slew" - elif "st_" in self.filename: - acs = "settling" - elif "sp_" in self.filename: - acs = "slew/point" - else: - acs = '' - - if "_uf" in self.filename: - filter = "unfiltered" - elif "_cl" in self.filename: - filter = "cleaned" - elif "_ex" in self.filename: - filter = "exposure map" - elif "_sk" in self.filename or "skim" in self.filename: - filter = "sky" - elif "_rw" in self.filename: - filter = "raw" - else: - filter = '' - - if "xpc" in self.filename: - mode = 'PC' - elif 'xwt' in self.filename: - mode = 'WT' - elif 'xim' in self.filename: - mode = 'Image' - elif 'uuu' in self.filename: - mode = 'u' - elif 'uvv' in self.filename: - mode = 'v' - elif 'ubb' in self.filename: - mode = 'b' - elif 'uw1' in self.filename: - mode = 'uvw1' - elif 'uw2' in self.filename: - mode = 'uvw2' - elif 'um2' in self.filename: - mode = 'uvm2' - elif 'uwh' in self.filename: - mode = 'white' - else: - mode = '' - - if 'hk' in self.path: - dtype = 'housekeeping' - elif 'event' in self.path: - dtype = 'event' - # elif 'image' in self.path: - # dtype = 'image' - # elif 'products' in self.path: - # dtype = 'products' - elif 'rate' in self.path: - dtype = 'rate' - elif 'masktag' in self.path: - dtype = 'mask tagged' - elif 'survey' in self.path: - dtype = 'survey' - else: - dtype = '' - - if 'gif' in self.filename: - filetype = 'GIF preview' - elif 'lc' in self.filename: - filetype = 'lightcurve' - elif 'dph' in self.filename: - filetype = 'DPH' - elif 'hk' in self.filename and dtype != 'housekeeping': - filetype = 'housekeeping' - elif '.cat' in self.filename: - filetype = 'catalog' - elif 'img' in self.filename: - filetype = 'image' - elif 'at.fits' in self.filename: - filetype = 'attitude file' - else: - filetype = 'file' - return f"{inst} {acs} {filter} {mode} {dtype} {filetype}".strip().replace(" ", " ") - - @property - def fullpath(self): - return os.path.join(self.path, self.filename) - - @fullpath.setter - def fullpath(self, fp): - if fp is not None: - self.path, self.filename = os.path.split(fp) - - @property - def size(self): - if self._size is None: - self._size = int(requests.get( - self.url, stream=True).headers['Content-length']) - return self._size - - -class Swift_Data(TOOAPI_Baseclass, TOOAPI_ObsID): - ''' - Class to download Swift data from the UK or US SDC for a given observation ID. - ''' - - # obsid=None,bat=None,xrt=None,uvot=None,uksdc=False,quicklook=False,outdir=".",all=None,clobber=False): - def __init__(self, **kwargs): - ''' - Construct the Swift_Data class, and download data if required parameters - are supplied. - - Parameters - ---------- - obsid : str / int - the observation ID of the data to download. Can be in SDC or - spacecraft format. Note: can also parse target_id and segment. - auxil : boolean - set to True to download Auxil data (default = True) - bat : boolean - set to True to download BAT data - xrt : boolean - set to True to download XRT data - uvot : boolean - set to True to download UVOT data - log : boolean - set to True to download SDC processing logs - all : boolean - set to True to download all data products - quicklook : boolean - set to True to only search Quicklook data. Default searches archive, - then quicklook - clobber : boolean - overwrite existing data on disk (default: False) - outdir : str - directory where data should be downloaded to - ''' - TOOAPI_Baseclass.__init__(self) - TOOAPI_ObsID.__init__(self) - self.obsid = None - # Only look in quicklook - self.quicklook = False - self.uksdc = None - # Two data locations "obs" = archive, "ql" = quicklook - # Data types to download. Always download auxil as default - self.auxil = True - self.bat = None - self.uvot = None - self.xrt = None - self.log = None - self.clobber = False - self.outdir = "." - # Parse keywords - for key in kwargs.keys(): - setattr(self, key, kwargs[key]) - # The files downloaded and the directories they live in - self._files = list() - self.directories = None - self.datadir = None - self.year = None - self.month = None - if self.obsid is not None: - self.download() - self.fileobj = [] - self.username = None - self.subclasses = [Swift_Data_File] - self.rows = ['username', 'obsid', 'quicklook', - 'auxil', 'bat', 'xrt', 'uvot', 'log', 'uksdc'] - self.extrarows = ['entries', 'status'] - self.status = Swift_TOO_Status() - self.user = Swift_TOO_User() - - def __getitem__(self, i): - return self.entries[i] - - @property - def table(self): - header = ['Path', 'Filename', 'Size', 'Description'] - tabdata = [] - lastpath = '' - for file in self.entries: - if file.path != lastpath: - path = file.path - lastpath = path - else: - path = "''" - tabdata.append([path, file.filename, file.size, file.type]) - return header, tabdata - - @property - def all(self): - if self.xrt and self.uvot and self.bat and self.log: - return True - return False - - @all.setter - def all(self, bool): - if self.all is not None: - self.xrt = self.bat = self.uvot = self.log = self.auxil = bool - - def __find_latest_ql(self): - '''For US SDC, look for the latest version of data available''' - qldataurl = "https://swift.gsfc.nasa.gov/data/swift/" - qldata = requests.get(qldataurl) - ql = BeautifulSoup(qldata.text, 'html.parser') - available = ql.find_all('pre')[0].text - for line in available.splitlines(): - if self.obsid in line: - return line.strip() - return False - - @property - def __datatype(self): - '''For UK SDC, this is the difference between QL and Archive data''' - if self.quicklook: - return 'ql' - else: - return 'obs' - - @property - def __top_dirs(self): - '''Return the directories we want to download, based on requested''' - dirs = [] - if self.auxil: - dirs.append('auxil/') - if self.xrt: - dirs.append('xrt/') - if self.bat: - dirs.append('bat/') - if self.uvot: - dirs.append('uvot/') - if self.log: - dirs.append('log/') - return dirs - - @property - def dataurl(self): - '''The URL for data, depending on which SDC you selected''' - if self.uksdc: - # UK SDC URLs are simples - dataurl = f"http://www.swift.ac.uk/archive/{self.__datatype}/{self.obsid}" - return dataurl - else: - # US SDC URLs are not - if self.quicklook: - # Quicklook data has different versions - figure out which version we need. - if self.datadir is None: - self.datadir = self.__find_latest_ql() - if self.datadir is False: - return False - else: - # If we found the data, construct the URL and return it - dataurl = f"https://swift.gsfc.nasa.gov/data/swift/{self.datadir}" - return dataurl - else: - # US SDC puts data in directories by month, so we have to figure out what directory it's - # going to be in first - if self.year is None: - afst = Swift_AFST(obsnum=self.obsid) - afst.query() - if len(afst.entries) > 0: - self.year, self.month = afst.entries[0].begin.year, afst.entries[0].begin.month - else: - return False - # Return the base URL for the data - dataurl = f"https://heasarc.gsfc.nasa.gov/FTP/swift/data/obs/{self.year}_{self.month:02d}/{self.obsid}" - return dataurl - - def scanfiles(self): - '''Make a list of the available files and directories''' - directories = self.__top_dirs - - if self.dataurl is False: - return False - # Scan through SDC web pages looking for files associated with ObsID - i = 0 - subdir = requests.get(self.dataurl) - while(i < len(directories)): - url = f"{self.dataurl}/{directories[i]}" - - subdir = requests.get(url) - - if "Error 404" not in subdir.text and "Sorry" not in subdir.text: - soup = BeautifulSoup(subdir.text, 'html.parser') - links = soup.find_all('a') - newdirs = [ld.text.strip() for ld in links if "/" in ld.text] - newfiles = [lf.text.strip() for lf in links if "/" not in lf.text] - [directories.append(f"{directories[i]}{ddir}") - for ddir in newdirs if ddir not in directories] - self._files += [Swift_Data_File(fullpath=os.path.join(self.obsid, directories[i], dfile), - url=f"{url}{dfile}", quicklook=self.quicklook) for dfile in newfiles if 'SWIFT_TLE_ARCHIVE' in dfile or self.obsid in dfile] - i += 1 - - self.directories = directories - if len(self._files) > 0: - return len(self._files) - else: - # Return False if nothing was found - return False - - @property - def files(self): - '''A list of files associated with this obsid data''' - # If no files, run scanfiles. - if len(self._files) == 0: - if not self.scanfiles() and self.quicklook is False: - # If no files found in scan, and quicklook is not set, look in quicklook - self.quicklook = True - self.scanfiles() - return self._files - - def validate(self): - if self.obsid is None: - self.status.error("Must supply Observation ID") - if self.auxil is not True and self.xrt is not True and self.log is not True and self.bat is not True and self.uvot is not True: - self.status.error("No data products selected") - if len(self.status.errors) > 0: - return False - else: - return True - - def query(self): - if self.validate(): - self.entries = self.files - if len(self.entries) == 0: - self.status.error( - f"No data found for observation ID {self.obsid}") - self.status.status = 'Accepted' - else: - self.status.status = 'Rejected' From 5975d090c6e84b6ffcda3d115b74b8cf34d23b91 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Tue, 7 May 2024 15:41:57 +0100 Subject: [PATCH 02/22] Pulled in some formatting a doc changes that should have been synced previously --- swifttools/ukssdc/data/datarequest_base.py | 1016 ++++++++++++++++++++ swifttools/ukssdc/data/download.py | 12 +- swifttools/ukssdc/query/GRB.py | 12 +- 3 files changed, 1028 insertions(+), 12 deletions(-) create mode 100644 swifttools/ukssdc/data/datarequest_base.py diff --git a/swifttools/ukssdc/data/datarequest_base.py b/swifttools/ukssdc/data/datarequest_base.py new file mode 100644 index 00000000..d2fc6286 --- /dev/null +++ b/swifttools/ukssdc/data/datarequest_base.py @@ -0,0 +1,1016 @@ +# import json +from . import allowedConeRadiusUnits +from .. import main as base +from ..access import downloadObsData +import pandas as pd +from .DBFilter import filter + +if base.HAS_ASTROPY: + import astropy.coordinates + + +# allowedConeRadiusUnits = ("arcsec", "arcmin", "degree", "deg") +class dataRequest: + """The base case for UKSSDC data requests. A 'virtual' class. + + This class should never in itself be instantiated, only those + dervied from it. In fact, it will contain enough functionality to + run for the most basic of cases, except that it will never select + which table / catalogue is being searched. + + MORE DOCS - maybe give a summary of the methods here. + """ + + def __init__(self, silent=True, verbose=False): + """Create a dataRequest instance. + + Parameters + ---------- + silent : bool Whether to suppress all console output + (default: True). + + verbose : bool Whether to give verbose output for everything + (default: False; overridden by silent). + + """ + + # This 'abstract' class has no table defined. + self._dbName = None + self._table = None + self._silent = silent + self._verbose = verbose + self._metadata = None + self._colsToGet = None + self._defaultCols = None + self._defaultColSets = None + self._tables = [] + self._coneRA = None + self._coneDec = None + self._coneName = None + self._coneRadius = None + self._coneUnits = "arcsec" + self._doConeSearch = False + self._filters = [] + self._sortCol = None + self._sortDir = "ASC" + self._results = None + # numRows and firstRows, if None, means no limits + self._maxRows = 5000 + self._firstRow = None + self._numRows = None + self._resolverDetails = None + self._resolvedRA = None + self._resolvedDec = None + self._locked = False + self._raw = None # DEBUG PROPERTY, DELETE LATER + self._obsCol = None + self._targetCol = None + + if self._verbose: + self._silent = False + print("Disabling silent mode, verbose mode was requested.") + + # End of __init__ + + # ----------------------------------------------------------------- + # Now set up variable access, via properties to control read/write + # etc. + + # Silent + @property + def silent(self): + """Whether to suppress output.""" + return self._silent + + @silent.setter + def silent(self, silent): + if not isinstance(silent, bool): + raise ValueError("Silent must be a bool") + self._silent = silent + + # Verbose + @property + def verbose(self): + """Whether to write extra output.""" + return self._verbose + + @verbose.setter + def verbose(self, verbose): + if not isinstance(verbose, bool): + raise ValueError("Verbose must be a bool") + self._verbose = verbose + + # make setting table unimplemented here, allowing the subclasses to + # decide whether you can change table mid query; also some classes + # may only support one table so they would want these functions not + # to work (i.e. set self._table in constructor. + # table + @property + def table(self): + return self._table + + @table.setter + def table(self, table): + self.checkLock() + if table not in self._tables: + raise ValueError(f"{table} is not a valid table. The list `tables` shows valid values.") + self._table = table + self._metadata = None + # if (self._defaultColSets is not None) and table in (self._defaultColSets): + # self._defaultCols = self._defaultColSets[table] + # If unlocking the table, we have to forget results as various results handling functions are tied to the table + # that was used. + self.reset() + if self.verbose: + print(f"Selecting table `{table}`") + + # dbName is also unset here, may not be changeable depending on sub-class. + # dbName + @property + def dbName(self): + """Which database is to be queried.""" + if self._dbName is None: + raise NotImplementedError + return self._dbName + + @dbName.setter + def dbName(self, dbName): + raise NotImplementedError + + # coneRA + @property + def coneRA(self): + """The RA on which to centre a cone search (J2000).""" + return self._coneRA + + @coneRA.setter + def coneRA(self, RA): + self.checkLock() + gotRA = False + if base.HAS_ASTROPY: + if isinstance(RA, astropy.coordinates.Angle): + RA = float(RA.deg) + gotRA = True + elif isinstance(RA, str): + tmp = astropy.coordinates.Angle(RA) + RA = float(tmp.deg) + gotRA = True + # If we don't have astropy, or it wasn't something astropy has + # parsed, then it must be int or float and we will parse it + if not gotRA: + if not isinstance(RA, (int, float)): + raise ValueError("RA must be int or float") + self._coneRA = float(RA) + + # coneDEC + @property + def coneDec(self): + """The Dec on which to centre a cone search (J2000).""" + return self._coneDec + + @coneDec.setter + def coneDec(self, Dec): + self.checkLock() + gotDec = False + if base.HAS_ASTROPY: + if isinstance(Dec, astropy.coordinates.Angle): + Dec = float(Dec.deg) + gotDec = True + elif isinstance(Dec, str): + tmp = astropy.coordinates.Angle(Dec) + Dec = float(tmp.deg) + gotDec = True + # If we don't have astropy, or it wasn't something astropy has + # parsed, then it must be int or float and we will parse it + if not gotDec: + if not isinstance(Dec, (int, float)): + raise ValueError("Dec must be int or float") + self._coneDec = float(Dec) + + # coneName + @property + def coneName(self): + """The name of the object on which to centre a cone search.""" + return self._coneName + + @coneName.setter + def coneName(self, name): + self.checkLock() + if not isinstance(name, str): + raise ValueError("Name must be a string") + self._coneName = name + + # coneRadius + @property + def coneRadius(self): + """The Radius on which to centre a cone search.""" + return self._coneRadius + + @coneRadius.setter + def coneRadius(self, Radius, Units=None): + self.checkLock() + if not isinstance(Radius, (int, float)): + raise ValueError("Radius must be int or float") + self._coneRadius = float(Radius) + if Units is not None: + self.coneUnits = Units + + # coneUnits + @property + def coneUnits(self): + """The units of the cone-search radius.""" + return self._coneUnits + + @coneUnits.setter + def coneUnits(self, Units): + self.checkLock() + if not isinstance(Units, str): + raise ValueError(f"Units must be a string, one of: {', '.join(allowedConeRadiusUnits)}") + if Units in allowedConeRadiusUnits: + self._coneUnits = Units + else: + raise ValueError(f"Units must be one of: {', '.join(allowedConeRadiusUnits)}") + + # maxRows + @property + def maxRows(self): + """How many rows to retrieve. None=all.""" + self.checkLock() + return self._maxRows + + @maxRows.setter + def maxRows(self, num): + if not isinstance(num, (int, float)) and (num is not None): + raise ValueError("num must be a number or None") + self._maxRows = num + + # firstRow + @property + def firstRow(self): + """The first row to retrieve. None=auto.""" + return self._firstRow + + @firstRow.setter + def firstRow(self, num): + self.checkLock() + if (not isinstance(num, (int, float))) and (num is not None): + raise ValueError("num must be a number or None") + self._firstRow = num + + # --------------------------- + # And some which we want to be read-only when accessed as a variable. + + # doConeSearch + @property + def doConeSearch(self): + """Whether a cone search will be done.""" + return self._doConeSearch + + # allowedConeRadiusUnits + @property + def allowedConeRadiusUnits(self): + """The allowedConeRadiusUnits for with the selected table. Read-only.""" + return allowedConeRadiusUnits + + # metadata + @property + def metadata(self): + """The metadata for with the selected table. Read-only.""" + # Need to get metadata if we don't have it: + if self._metadata is None: + if not self.silent: + print("Need to get the metadata.") + self.getMetadata() + + return self._metadata + + # colsToGet + @property + def colsToGet(self): + """The columns selected for retrieval.""" + return self._colsToGet + + # filters + @property + def filters(self): + """The filters that will be applied.""" + return self._filters + + # Results + @property + def results(self): + """The results of the query.""" + return self._results + + # haveResults + @property + def haveResults(self): + """Whether we have results from this query.""" + return self._results is not None + + @property + def locked(self): + """Is this object locked?""" + return self._locked + + @property + def numRows(self): + """The number of rows returned by the query.""" + return self._numRows + + @property + def resolverDetails(self): + """The ouput of the name resolver.""" + return self._resolverDetails + + @property + def resolvedRA(self): + """The RA returned by the name resolver.""" + return self._resolvedRA + + @property + def resolvedDec(self): + """The dec returned by the name resolver.""" + return self._resolvedDec + + @property + def tables(self): + """The tables that can be selected.""" + return self._tables + + @property + def defaultCols(self): + """The columns that are retrieved if no selection is created.""" + + if self._defaultCols is None: + # self._checkMetaData() + if "Class" in self.metadata.columns: + # Can do this all in one line, but it's a bit hard to read, so lets be nice: + # First, filter the metadata on cases where Class has "BASIC" in it + tmp = self.metadata.loc[self.metadata["Class"].str.contains("BASIC")] + # Now get the columns in this: + self._defaultCols = tmp["ColName"].tolist() + + return self._defaultCols + + @property + def cols(self): + """Columns in this table.""" + return self.metadata["ColName"].values + + @property + def obsColumn(self): + """Which column contains the observation identifier.""" + return self._obsCol + + @property + def targetColumn(self): + """Which column contains the target identifier.""" + return self._targetCol + + # --------------------------------------------------------------- + # Functions. First some standard things: + + def __str__(self): + str = f"PRINTING AN {type(self)} object." + return str + + def __repr__(self): + str = f"I AM AN {type(self)} object." + return str + + def checkLock(self): + """Check if this object is locked.""" + if self._locked: + raise RuntimeError("Cannot edit this request as it is locked.") + + def unlock(self): + """Unlock the object for editing.""" + self._locked = False + + # --------------------------------------------------------------- + # Metadata + def getMetadata(self): + """Retrieve the metadata for this catalogue from the server. + + This queries the server for the metadata associated with the + database/table of the current object, and saves it into the + metadata variable as a pandas object? Or a dict? TBC + + """ + self.checkLock() + sendData = {"database": self.dbName, "table": self.table} + if self.verbose: + print(f"Getting metadata for {self.dbName}.{self.table}") + + ret = base.submitAPICall("getMetadata", sendData, minKeys=["metadata"], verbose=self._verbose) + + # metadata should have two entries: 'columns' and 'data' + self._metadata = pd.DataFrame(ret["metadata"]["metadata"], columns=ret["metadata"]["columns"]) + + self._obsCol = None + self._targetCol = None + if "IsObsCol" in self._metadata: + self._metadata["IsObsCol"] = pd.to_numeric(self._metadata["IsObsCol"]) + tmp = self._metadata.loc[self._metadata["IsObsCol"] == 1]["ColName"] + if len(tmp) > 0: + self._obsCol = tmp.iloc[0] + if len(tmp) > 1 and not self.silent: + print( + "WARNING: Metadata contains TWO obs columns! This may be a bug; " + "please notify swift-help@leicester.ac.uk" + ) + + if "IsTargetCol" in self._metadata: + self._metadata["IsTargetCol"] = pd.to_numeric(self._metadata["IsTargetCol"]) + tmp = self._metadata.loc[self._metadata["IsTargetCol"] == 1]["ColName"] + if len(tmp) > 0: + self._targetCol = tmp.iloc[0] + if len(tmp) > 1 and not self.silent: + print( + "WARNING: Metadata contains TWO target columns! This may be a bug; " + "please notify swift-help@leicester.ac.uk" + ) + + # -------------------------------------------------------------- + # Misc + def reset(self): + """Remove all results - reset this object.""" + + if not self.silent: + print("Resetting query details") + self.unlock() + self.removeAllFilters() + self.removeConeSearch() + self._colsToGet = None + self._resolverDetails = None + self._resolvedRA = None + self._resolvedDec = None + self._results = None + self.sortCol = None + self._metadata = None + self._defaultCols = None + + self._raw = None # TEMP LINE + + # --------------------------------------------------------------- + # Cone search functions + # These allow a cone search to be build, or requested. + # Note that simply setting coneRA/Dec above on their own doesn't force + # a cone search to run unless doConeSearch is set. These methods are + # preferred ways of managing the cone search. + + def addConeSearch(self, ra=None, dec=None, radius=None, name=None, coords=None, units="arcsec"): + """Include a cone search filter on the dataRequest. + + This function adds a cone search to the set of filters to be + applied to your request. If a cone serach already existed, this + will overwrite it; you cannot (at present) have multiple cone + searches combined with an OR function; you will have to do + multiple cone searches instead. + + Note: name, or ra & dec must be specified, but they cannot + both be specified. If you supply both, an error will be raised. + + Parameters + ---------- + ra : Union[float, astropy.coordiantes.Angle] Central RA + of the cone, in J2000. + + dec : Union[float, astropy.coordiantes.Angle] Central Dec + of the cone, in J2000. + + name: str The name of an object to centre on. Will be resolved. + + coords : str Free-form coordinates to attempt to parse + + radius : float Radius of the cone search. + + units : str The units of coneRadius. Default 'arcsec', permitted + values are given in the allowedConeRadiusUnits variable. + + """ + self.checkLock() + # Set mainly via @property functions so that checks on values are done. + if name is not None: + if (ra is not None) or (dec is not None) or (coords is not None): + raise ValueError("You must supply name OR position, not both.") + self._coneRA = None + self._coneDec = None + self.coneName = name + elif coords is not None: + self.coneName = coords + elif (ra is None) or (dec is None): + raise ValueError("You must supply name or position.") + else: + self.coneRA = ra + self.coneDec = dec + self._coneName = None + + if radius is None: + raise ValueError("radius must be supplied") + + self.coneRadius = radius + self.coneUnits = units + + self._doConeSearch = True + + def editConeSearch(self, coneRA, coneDec, coneRadius, coneUnits="arcsec"): + """Changes the cone search. Just a wrapper to addConeSearch.""" + self.addConeSearch(coneRA, coneDec, coneRadius, coneUnits) + + def removeConeSearch(self): + """Remove the cone search from the filters to apply.""" + self.checkLock() + self._coneRA = None + self._coneDec = None + self._coneRadius = None + self._coneUnits = "arcsec" + self._doConeSearch = False + + def isValid(self): + """Whether the current request is valid and can be submitted.""" + + # Need to get metadata if we don't have it: + if self._metadata is None: + if not self.silent: + print("Need to get the metadata to check the query is valid.") + self.getMetadata() + + # This needs to check: + # All columns are permissable + # If not set, check defaults as user could have been naughty. + if self.verbose: + print("Checking requested columns...") + tmp = self._colsToGet + if tmp is None: + tmp = self.defaultCols + if tmp is None: + print("No columns selected to retrieve!") + return False + if tmp != "*": + for c in tmp: + if c not in self.metadata["ColName"].values: + if not self.silent: + print(f"Requested column {c} does not exist.") + return False + + # Now check filters + # Filters + if self.verbose: + print("Checking filters...") + for f in self._filters: + if not f.isValid(self.metadata): + return False + + # And check the cone search + if self._doConeSearch: + if self.verbose: + print("Checking cone search parameters...") + # Need a radius + if self.coneRadius is None: + if not self.silent: + print("A cone search is selected, but radius is not set") + return False + # Need name or ra AND dec + if (self.coneName is None) and ((self.coneRA is None) or (self.coneDec is None)): + if not self.silent: + print("A cone search is selected, but neither name, nor RA & Dec are set.") + return False + if (self.coneName is not None) and ((self.coneRA is not None) or (self.coneDec is not None)): + if not self.silent: + print("You should supply name OR position for a cone search.") + return False + + return True + + # --------------------------------------------------------------- + # Column functions + + def _addCol(self, colName): + """Internal function to add a column to the list. + + This is called by addCol() (which can support strings or lists) + but must recieve a string. It adds the item to _colsToGet, after + verifying it. + + The table metadata must be known, so it will retrieve it if it + doesn't exist. + + Will raise ValueError if the supplied parameter is not a string, + or is not a valid column (or '*'). + + Parameters + ---------- + + colName : str The column to add. + + """ + self.checkLock() + # First, check whether the metadata is retrieved and up to date. + if not isinstance(colName, str): + raise ValueError("colName should be a string") + + if colName == "*": + if self.verbose: + print("Setting to retrieve all columns.") + self._colsToGet = self.metadata["ColName"].values.tolist() + else: + # Is the column name valid? + if colName not in self.metadata["ColName"].values: + raise ValueError(f"`{colName}` is not a valid column name.") + # If previously we had selected all, then warn if not silent. + if self._colsToGet == "*": + if not self.silent: + print("WARNING: previously you were selecting all data; you are now requesting specific columns.") + self._colsToGet = [ + colName, + ] + + # If this is the first column, create the list + if self._colsToGet is None: + self._colsToGet = [ + colName, + ] + if self.verbose: + print(f"Will retrieve column {colName}") + else: + # This 'else' assumes a list, i.e. colsToGet is either '*', None or a list. + # If it is not, then likely this will throw an error. That's OK, since if it is + # not one of these then a user has edited this "hidden" field directly, and they + # deserve what they get. + if colName in self._colsToGet: + if not self.silent: + print(f"Cannot add column {colName}; it is already selected.") + else: + self._colsToGet.append(colName) + if self.verbose: + print(f"Will retrieve column {colName}") + + def addCol(self, colName): + """Add a column/columns to the list of those to retrieve. + + This can receive either a string, which is a column name or '*', + or a list of column names to add to the list to retrieve. + Note: '*' cannot appear in a list. + + The names are checked against valid column names, so if it has + not already been called, getMetadata() will run first. + + Parameters + ---------- + + colName : Union[str,list] The column(s) to add. + + """ + self.checkLock() + if isinstance(colName, str): + self._addCol(colName) + elif isinstance(colName, (list, tuple)): + if "*" in colName: + raise ValueError("You cannot include '*' in a list of columns.") + for c in colName: + self._addCol(c) + else: + raise ValueError("colName must be a string or list") + + if self.verbose: + print(f"Set to retrieve columns: {self._colsToGet}") + + def removeAllCols(self): + """Empty the list of defined columns to retrieve.""" + self.checkLock() + self._colsToGet = None + + def _removeCol(self, colName): + """Internal function to remove a column from the list. + + This is called by removeCol() (which supports strings or lists) + but must recieve a string. It removes the item to _colsToGet. + + Parameters + ---------- + + colName : str The column to remove. + + """ + self.checkLock() + if not isinstance(colName, str): + raise ValueError("colName should be a string") + + self.colsToGet.remove(colName) + + def removeCol(self, colName): + """Remove a column/columns to the list of those to retrieve. + + This can receive either a string, which is a column name or a + list of column names to add to the list to retrieve. + + Parameters + ---------- + + colName : Union[str,list] The column(s) to add. + + """ + self.checkLock() + if isinstance(colName, str): + self._removeCol(colName) + elif isinstance(colName, (list, tuple)): + for c in colName: + self._removeCol(c) + else: + raise ValueError("colName must be a string or list") + + if len(self._colsToGet) == 0: + self._colsToGet = None + + if self.verbose: + print(f"Will retrieve columns: {self._colsToGet}") + + # ---------------------- + # Filter functions + + # addFilter + # editFilter + # removeFilter + # showFilters (maybe with option for 'as SQL' vs as Dict) + + def removeAllFilters(self): + """Remove all defined search filters.""" + self.checkLock() + self._filters = [] + + def addFilter(self, filterDef): + """Add a filter to the query. + + This can be done two ways: + + 1) By passing a dict with the following keys -- + some are optional: + + colName: The name of the column to filter on + + filter: The filter to apply ('<', '>', 'IS NULL' etc) + + val: The value to apply to the filter, if appropriate. e.g. + if the filter is '<', val may be 3.1234. If the filter + takes no arguments (IS [NOT] NULL) this is ignored. + If the filter takes two arguments (BETWEEN) this should + be a list. + + combiner: OPTIONAL: This can be AND or OR, if this filter + has to components. + + filter2: As filter, for the second clause. + + val2: As val, for the second clause. + + 2) By passing a list, whose values are the above keys, in order, + e.g. [ + 'foo', + '<', + 3, + 'OR', + 'foo', + 'BETWEEN', + [7,10] + ] + + Parameters + ---------- + + filterDef : Union[string,list,tuple] The filter definition + + """ + self.checkLock() + # colname is + self._filters.append(filter(filterDef, self.metadata)) + + def showFilters(self): + """List all filters currently applied""" + i = 0 + for f in self._filters: + print(f"{i}:\t{f}") + i = i + 1 + + def removeFilter(self, ix): + """Remove a filter, by index. + + To see filters and their indices, call showFilters + + Parameters + ---------- + + ix : int Index of filter to remove + + """ + self.checkLock() + if not isinstance(ix, int): + raise ValueError("ix must be an int") + if (ix < 0) or (ix >= len(self._filters)): + raise ValueError(f"ix must be between 0 and {len(self._filters)-1}") + del self._filters[ix] + if not self._silent: + self.showFilters() + + # --------------------------------------------------------------- + # Actual search functions + + def submit(self): + """Submit the query.""" + self.checkLock() + + # First, check validity. Do this by function call + if not self.isValid(): + if not self.silent: + print("Cannot submit query - it is not valid.") + return False + + # Build the API request dict: + sendData = { + "database": self.dbName, + "table": self.table, + "adUnits": self._coneUnits, + "sortDir": self._sortDir, + "numRows": base.MAXROWS, + } + + # Specify columns, if we can + if self._colsToGet is not None: + sendData["cols"] = self._colsToGet + elif self.defaultCols is not None: + sendData["cols"] = self.defaultCols + + # And the sort Col: + if self._sortCol is not None: + sendData["sortBy"] = self._sortCol + + # Add filters + if len(self._filters) > 0: + sendData["constraints"] = [] + for f in self._filters: + sendData["constraints"].append(f.data) + + # Add cone information + if self._doConeSearch: + if self._coneName is None: + if (self._coneRA is None) or (self._coneDec is None): + raise RuntimeError("You have requested a cone search but without specifying name/position") + sendData["searchRA"] = self._coneRA + sendData["searchDec"] = self._coneDec + # print("SETTING CONE BY POSITION") + else: + sendData["searchName"] = self._coneName + # print("SETTING CONE BY NAME") + + sendData["searchRad"] = self._coneRadius + + # Now do the actual work. Note - the server can only return so + # many rows at once because of memory constraints. I will limit + # the max in one go to base.MAXROWS. NB, if you try to return + # more than this and the allocated memory is overrun you just + # get a 500 error. + + fR = 0 # First row from this query + if self._firstRow is not None: + fR = int(self._firstRow) + + maxRows = 1e80 # i.e. BIG + if self._maxRows is not None: + maxRows = int(self._maxRows) + # If we want < the maximum in one go, need to ammend numRows + if maxRows < base.MAXROWS: + sendData["numRows"] = maxRows + + # Create a local variable for the result for now, I don't want + # to update self._results until the query has definitely succeeded. + result = None + + done = False + while not done: + # Update the first row to get + sendData["firstRow"] = fR + + if not self._silent: + print(f"Calling DB look-up for rows {fR} -- {sendData['numRows']+fR}") + + ret = base.submitAPICall( + "queryDB", + sendData, + minKeys=["Results", "NumRows"], + verbose=self._verbose, + ) + if result is None: + result = ret + else: + result["NumRows"] = result["NumRows"] + ret["NumRows"] + result["Results"].extend(ret["Results"]) + + # Are we done? If we did not get as many rows as was requested then we are + if ret["NumRows"] < sendData["numRows"]: + done = True + if self._verbose: + print(f"Received {ret['NumRows']} rows / {sendData['numRows']} requested. Query complete.") + # If we have now hit the user-set limit, then we are done: + elif (self._maxRows is not None) and (result["NumRows"] >= self._maxRows): + done = True + if self._verbose: + print(f"{result['NumRows']} rows retrieved in total. Query complete.") + else: + # Increase the start row for the next call + fR = fR + sendData["numRows"] + # We may need to decrease the number of rows + if (self._maxRows is not None) and (self._maxRows < result["NumRows"] + sendData["numRows"]): + sendData["numRows"] = self._maxRows - result["NumRows"] + if self._verbose: + print(f"Reducing the number of rows requested to {sendData['numRows']}.") + # End of while not Done + # We now should have our results. Maybe do one sanity check: + if result["NumRows"] != len(result["Results"]): + raise RuntimeError(f"Should have {result['NumRows']} rows, but have {len(result['Results'])}!") + + if (self._doConeSearch) and (not self.silent) and ("ResolvedInfo" in result): + print(result["ResolvedInfo"]) + + self._numRows = result["NumRows"] + if (self._doConeSearch) and ("ResolvedInfo" in result): + self._resolverDetails = result["ResolvedInfo"] + self._resolvedRA = result["ResolvedRA"] + self._resolvedDec = result["ResolvedDec"] + self._results = pd.DataFrame(result["Results"]) + + useAst = None + if base.HAS_ASTROPY: + useAst = "_apy" + if not self.silent: + print(f"Received {self.numRows} rows.") + base.manageResults(self._results, self._metadata, "_s", useAst, self.silent, self.verbose) + self._locked = True + + self._raw = result # TEMPORARY LINE + + # --------------------------------------------------------------- + # Data retrieval + + def downloadObsData(self, subset=None, **kwargs): + """Download data for the observations returned by the query. + + If the excuted query returned a column which includes + observation identifiers, then this function will download the + data for those columns. This function essentially wraps + ukssdc.access.downloadObsData(), so for valid values of the + **kwargs, see the documentation for that function. + + The subset parameter is optional, and can be used to apply + a filter to the results this query has obtained. The easiest way + to generate this is using the pandas syntax for filtering on + value. + + e.g. if your datarequest object is called 'req' then: + + => req.downloadObsData(subset=req.results['xrt_expo_pc']<1000) + + would request a download of all observations that the request + found, which had a value of less than 1000 in the 'xrt_expo_pc' + column. + + Obviously, this function cannot be called before this request + has been submitted and has completed; if you try, your computer + will explode and your eyeballs will be eaten by ants (just + kidding; you'll get a RuntimeError). + + Parameters + ---------- + + subset : pandas.Series OPTIONAL: A pandas series defining a + subset of rows to download. + + """ + + if not self.haveResults: + raise RuntimeError("This query has not been executed, cannot download!") + + if self._obsCol is None: + raise RuntimeError("These is no column containing observation ID, cannot download.") + + if self._obsCol not in self.results.columns: + raise RuntimeError( + f"The column {self._obsCol} was not retrieved as part of your query. Cannot download. " + "You may need to unlock this object, add {self._obsCol} to those to retrieve, and repeat the query." + ) + + obslist = [] + if subset is not None: + if not isinstance(subset, pd.core.series.Series): + raise ValueError("Subset parameter must be a pandas series") + obslist = self._results.loc[subset][self._obsCol].tolist() + else: + obslist = self.results[self._obsCol].tolist() + + # # DEBUG LINE: + # print(f"Downloading obs: {obslist}") + # return + + downloadObsData(obslist, silent=self.silent, verbose=self.verbose, **kwargs) + + \ No newline at end of file diff --git a/swifttools/ukssdc/data/download.py b/swifttools/ukssdc/data/download.py index d5863ea1..62b38b3e 100644 --- a/swifttools/ukssdc/data/download.py +++ b/swifttools/ukssdc/data/download.py @@ -882,15 +882,15 @@ def _getLightCurve( if isinstance(incbad, bool): if incbad: - incbad="yes" + incbad = "yes" else: - incbad="no" + incbad = "no" if isinstance(nosys, bool): if nosys: - nosys="yes" + nosys = "yes" else: - nosys="no" + nosys = "no" sendData = {"type": type, "objectID": objectID, "incbad": incbad, "nosys": nosys} @@ -1041,9 +1041,9 @@ def _saveLightCurveFromDict( if suff is None: if asQDP: - suff='qdp' + suff = "qdp" else: - suff='dat' + suff = "dat" for c in theseCurves: fname = f"{destDir}/{prefix}{c}" diff --git a/swifttools/ukssdc/query/GRB.py b/swifttools/ukssdc/query/GRB.py index 73696ed0..c9f89d07 100644 --- a/swifttools/ukssdc/query/GRB.py +++ b/swifttools/ukssdc/query/GRB.py @@ -679,10 +679,10 @@ def getBurstAnalyser(self, subset=None, byName=False, byID=False, returnData=Fal found when this query was executed. Of course, this means you have to have executed the query first! - The light curve data will be stored in the ``lightCurves`` + The burst analyser data will be stored in the ``burstAnalyser`` variable of this object, and optionally returned as well. The - light curves files can also be downloaded directly from the - website to disk. + data files can also be downloaded directly from the website to + disk. Parameters ---------- @@ -699,12 +699,12 @@ def getBurstAnalyser(self, subset=None, byName=False, byID=False, returnData=Fal Requires that column to have been retrieved by your query. returnData : bool, optional - Whether the light curve data should be returned by this - function, as well as saved in the "lightCurves" + Whether the burst analyser data should be returned by this + function, as well as saved in the "burstAnalyser" variable. **kwargs : dict, optional - Parameters to pass to ukssdc.data.GRB.getSpectra() + Parameters to pass to ukssdc.data.GRB.getBurstAnalyser() """ if not self.haveResults: From 5536c235c90b4f97d36dffe0cf4a280c12495e3d Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Tue, 7 May 2024 16:32:09 +0100 Subject: [PATCH 03/22] Pulled in some small documentation fixes --- swifttools/ukssdc/APIDocs/index.php | 2 +- swifttools/ukssdc/APIDocs/ukssdc/query.md | 7 ------- .../APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md | 2 +- .../ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md | 10 +++------- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/swifttools/ukssdc/APIDocs/index.php b/swifttools/ukssdc/APIDocs/index.php index 1d646005..88919280 100644 --- a/swifttools/ukssdc/APIDocs/index.php +++ b/swifttools/ukssdc/APIDocs/index.php @@ -24,7 +24,7 @@

The swifttools API

Version 3.0 of swifttools - was released on XXXX. This major release introduces the swifttools.ukssdc module.

+ was released on 2022 August 31. This major release introduces the swifttools.ukssdc module.

Various aspects of working with Swift can now be done via the swifttools Python module. This is available through pip:

diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query.md b/swifttools/ukssdc/APIDocs/ukssdc/query.md index 2986876a..dc8c81fa 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/query.md @@ -934,17 +934,10 @@ myTargs = (81445, 45767, 81637) subset=q.results['target_id'].isin(myTargs) q.results.loc[q.results['target_id'].isin(myTargs)] # q.downloadObsData(destDir='/tmp/APIDemo_download4', - -[Jupyter notebook version of this page](query.ipynb) # subset=q.results['target_id'].isin(myTargs), - -[Jupyter notebook version of this page](query.ipynb) # instruments=('XRT',), - -[Jupyter notebook version of this page](query.ipynb) # getAuxil=False) -[Jupyter notebook version of this page](query.ipynb) ``` diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md index 656da86a..255c43bc 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md @@ -1,6 +1,6 @@ # swifttools.ukssdc.xrt_prods v1.10 Release Notes -Version 1.10 of the `xrt_prods` module was released as part of `swifttools v3.0` on XXXXXX +Version 1.10 of the `xrt_prods` module was released as part of `swifttools v3.0` on 2022 August 31. This release features a number of important changes mainly relating to the way in which data products are returned and formatted. We have made significant efforts to support backwards compatibility, so **all of your diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md index fe4cdb91..295769a6 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md @@ -332,12 +332,8 @@ Global parameters are set/retrieved with the `setGlobalPars()` and `getGlobalPar | getT0 | No** | bool | Whether to ask the server to complete the `T0` field automatically | False | | getCoords | No*** | bool | Whether to ask the server to complete the `RA` and `Dec` fields automatically | False | -*: The targetID(s) must be supplied; these tell the server which sets of data to include in your products, and it can be -a comma-separated list if more than one targetID corresponds to your object. You can either supply the targetIDs in the -`targ` field, or set `getTargs=True`. In the latter case, the server will do a cone search around the position -you supplied in the `RA` and `Dec` fields and select all targets within 12′ of the position. If you did -not supply a position it will attempt to determine it by resolving the supplied name. If the name cannot be resolved, -than instead all targetIDs in the database where the object name matches that in the `name` field, will be selected. +*: The targetID(s) must be supplied; these tell the server which sets of data to include in your products, and it can be a comma-separated list if more than one targetID corresponds to your object. You can either supply the targetIDs in the `targ` field, or set `getTargs=True`. In the latter case, the server will select all targetIDs in the database where the object name matches that in the `name` field, **and** targets in the database where the XRT field of view will overlap the position in the (`RA`, `Dec`) fields. If `getCoords=True` then the targetID determination is carried out **after** the name has been resolved +into a position. **: A start time is not mandatory, but is helpful, particularly to zero the time axis on light curve, but it can also be used as a reference point for all other input times. You can either supply it in the `T0` field, or set `getT0=True`. In the latter case the server will try to work it out, either as the trigger time (if the object is a GRB), or as the start time of the first observation of the object. In this case `T0` will be set in the [data returned by the server](ReturnData.md) @@ -447,7 +443,7 @@ an absorbed "APEC+blackbody". #### If defining specific sub-spectra -If timeslice=="user" then you must define the spectra you wish to create, giving each one a label (alphanumeric characters only) and a GTI interval, as defined in [the product generator documentation](https://www.swift.ac.uk/user_objects/docs.php). You can specify between 1 and 4 spectra, using these parameters: +If timeslice=="user" then you must define the spectra you wish to create, giving each one a label (alphanumeric characters only) and a GTI interval, as defined in [the product generator documentation](https://www.swift.ac.uk/user_objects/docs.php#timeformat). You can specify between 1 and 4 spectra, using these parameters: | Parameter | Mandatory? | Type | Description | Default | | :---- | :----: | :---: | :----- | :----: | From dc348b0e7f70b841340ae42eb07b75518e8c4d54 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Tue, 7 May 2024 15:59:38 -0400 Subject: [PATCH 04/22] Add deploy GitHub workflow --- .github/workflows/deploy.yml | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..b99b8357 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,61 @@ +name: Deploy to PyPI +on: + push: + workflow_dispatch: + +jobs: + build: + name: Build distribution 📦 + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + + - name: Build a binary wheel and a source tarball + run: python3 -m build + + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/swifttools + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 From 6c1ce25b066722a6e5d7ac4f8d0e9ea232852db7 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Tue, 7 May 2024 16:10:40 +0100 Subject: [PATCH 05/22] Modifying this to be the latest version of docs --- swifttools/ukssdc/APIDocs/index.php | 65 ------------------ swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md | 12 ++++ swifttools/ukssdc/APIDocs/ukssdc/README.md | 29 +++++--- swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip | Bin 95423 -> 0 bytes .../APIDocs/ukssdc/xrt_prods/ChangeLog.md | 5 +- .../ukssdc/APIDocs/ukssdc/xrt_prods/README.md | 3 +- 6 files changed, 33 insertions(+), 81 deletions(-) delete mode 100644 swifttools/ukssdc/APIDocs/index.php create mode 100644 swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md delete mode 100644 swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip diff --git a/swifttools/ukssdc/APIDocs/index.php b/swifttools/ukssdc/APIDocs/index.php deleted file mode 100644 index 88919280..00000000 --- a/swifttools/ukssdc/APIDocs/index.php +++ /dev/null @@ -1,65 +0,0 @@ -setStyle("/style/LSXPS_docs.css"); - $head=$page->Head(); - $head->Title("UKSSDC | Swifttools API"); - $head->Description("Documentation supporting the swifttools API"); - $head->Keywords("Swift, GRB, XRT, light curve, documentation, API, swifttools"); - $head->AddScriptRef("/scripts/centre.js"); - $head->AddScriptRef("/scripts/jquery.min.js"); - $head->AddScriptRef("/scripts/jquery_scrollto.js"); - $head->AddScriptRef("/scripts/autoproc_base.js"); - $head->AddScriptRef("/scripts/jquery-ui.js"); - - $page->PageFoot()->W3C( True ); - $page->PageFoot()->WAI( 2 ); - $page->Begin(); - print "\n"; -// print "\n"; -?> -

The swifttools API

- -

Version 3.0 of swifttools - was released on 2022 August 31. This major release introduces the swifttools.ukssdc module.

- -

Various aspects of working with Swift can now be done via the swifttools Python module. This is available through pip:

- -
-  pip install [--upgrade] swifttools
-  
- -

(The --upgrade is only needed if you have an older version of this module installed).

- -

This module has 2 sub-modules:

- -
- -
swifttools.swift_too [Docs]
-
This module includes access to Swift data, the ability to query observability of a source, - get an object's observing history, and submit ToO requests. It is maintained by Jamie Kennea, and is fully - documented on the PSU web site.
- -
swifttools.ukssdc [Docs]
-
This module provides access to Swift data, the XRT GRB products, the - LSXPS catalogue, the on-demand data-analysis tools and more. It is - maintained by Phil Evans and is fully documented here.
- - -
- -
- -

Although we recommend installing via pip the source code is also available via - a public GitLab repository, which is also where you - can report issues / bugs.

- - - -End(); - - diff --git a/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md new file mode 100644 index 00000000..d0bbbdf8 --- /dev/null +++ b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md @@ -0,0 +1,12 @@ +# Change history for the `swifttools.ukssdc` module + +Changes made to this module after its original release will be documented here. + +* 2023 May 24: Modification to the back end, light curve `dict` now includes an "Exposure" column in the hard/soft/ratio datasets. +* 2022 September 06. v1.0.3 relased as part of `swifttools` v3.0.5 + * Further minor bugfixes in `mergeUpperLimits()`. + * Fixed an issue only affecting Python >=3.9: the `math.factorial()` function now requires the argument + to be an integer, it does not accept floats such as "3.0". This caused en error in `bayesRate()` as called + by `mergeUpperLimits()` and `mergeLightCurveBins()`. +* 2022 September 01. v1.0.2 relased as part of `swifttools` v3.0.2 + * Minor bugfixes in `mergeUpperLimits()` \ No newline at end of file diff --git a/swifttools/ukssdc/APIDocs/ukssdc/README.md b/swifttools/ukssdc/APIDocs/ukssdc/README.md index aa237c4c..f7013238 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/README.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/README.md @@ -1,6 +1,6 @@ # The `swifttools.ukssdc` module. -Quick links: [`xrt_prods`](xrt_prods.md) | [`data`](data.md) | [`query`](query.md) | [Set of Jupyter notebooks](ukssdc.zip) +Quick links: [Usage policy](#usage) | [`xrt_prods`](xrt_prods.md) | [`data`](data.md) | [`query`](query.md) | [ChangeLog](ChangeLog.md) | [Set of Jupyter notebooks](ukssdc.zip) ## Quick start @@ -55,20 +55,29 @@ lots of output. The demarcation between non-silent, and verbose output is somewh ## Usage -This Python module is provided free to use, but please do remember that in any publication you should cite the source of any data obtained. -References are given in the various product documentation pages, and please always include, in the acknowledgements: +This Python module is provided free to use, but if you use it for work leading to a publication, +please do acknowledge this with a footnote pointing to (https://www.swift.ac.uk/API). The standard usage +policy for any of the tools accessed via the API remains in force as well. That is, please incude this +in your acknowledgements: + +
This work made use of data supplied by the UK Swift Science Data Centre at the University of Leicester. +
+ +and cite the appropriate paper(s) relating to the data source or algorithms you accessed through the API. +Details for each of these can be found on their relevant pages; below are links to those pages and +the citations requested. -The most common references to use are -* GRB light curves: [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract), +* [GRB light curves](/xrt_curves/docs.php#usage): [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* GRB enhanced positions: [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* GRB spectra: [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* The burst analyser: [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract) -* On-demand products: [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* LSXPS: Evans et al., (2022) +* [GRB enhanced positions](/xrt_positions/docs.php#usage): [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) +* [GRB spectra](/xrt_spectra/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) +* [The burst analyser](/burst_analyser/docs.php#usage): [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract) +* [On-demand products](/user_objects/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract): **please see the [documentation](/user_objects/docs.php#usage) for citations for the different products**. +* [2SXPS](/2SXPS/docs.php#access): [Evans et al., (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJS..247...54E/abstract) +* [LSXPS](/LSXPS/docs.php#access): [Evans et al., (2023)](https://ui.adsabs.harvard.edu/abs/2023MNRAS.518..174E/abstract) ## About this documentation diff --git a/swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip b/swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip deleted file mode 100644 index f8fb79dc01d4f6e0f692a558d0ab46da22b0acf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95423 zcmZUaLv$`o(4dnW+qP}nPTu6kwr$(CZQHhOh8!)l*L^$bf*N z0s#R*0-agmtCaBRMeiU00cFww0YL-d0vQ`P8_-+Wd)OMPC_@2(pn$*|HKBqVxwyjs z0fW+l00DuH|95vp)5d9w9r-6$FTlYR&?u2=@=oTksZCx}$G3A#pTn8cx@1fn4oWsm zIsjCRrc?U+ovY)|;Al)E#pjm6-XPU|MemyD<2n$BFbpVOT)yT->JvabAo+Qfr=L~U{RS=Percd|Jf(tQreIVF}eQ~x%Vs>UZ!(V zOdTz@RoCIdkPyk_K+A}zQtpB33eR~d>*>L0RIWu`o8-1}bnxNCg$q`xNCb=spBCyU zYZJgpczI>5db}xUD`Pcl*mzD2x`-i#u&r^c0){s$%Tbvllbs4#14E%Q#s;apYVMWK zM5|@VY8{(U$y6f^5a29i2m3NNcr*h-C94P>Dri|B249Dc){lA|`vnXyGqOu)8j?LG zXuf`W6IrC#+G>afy%#IT$o=hgsJjJA|R0A)Qub5oG-tJds?CnJy3roQ4 zKk=?b#O5=hsu4(ofjq{xa5AqPUrdN&AocAP9^Z|r?aA%EoR5KgVak%fd`~+0#C&fB zjX?Uf*Gp-Ol^iOCmPv8b-0FWT&ta5=Q+w6iS(ZNHl4FJ|noX8)6MMD)8M85YHNHW- zKUmA7x5PR>r4w%SONi$_&9jIMzawOOlpijfm+2?=Be=E%mw~t-pA*9k+a++ECK$r1D_@fpq9*ey8;{BKXkZ&&nNnR8%BE zk%u6;HyC)ogdY>w-_z&oMfmF(m0+v6pXv|8Z|lg(_|6&2kT=wa77KwAqkiEyV5XXB zD4?=NtuvNg@K%C<8M7Qi>37D6<&g$ZH5P<5A&KH2DUxI(UN-`?@*ujq8o3+Y0DHZF zB9}larx300*OwGlz>gOwHsMCfdHw|b9So;TL7$2o&YTX?PJ}h@+m*}k0&=V%U|>P0X^3E=lWn zprA<|J>YdxYGj}pFF&iti`M2xTC0z%0E!IuHKspJhDZk2>v<=M=;K@lA)Z4`jq7P4 zP9Z5B3As-ncQfS6~6&>?uhV|Yix(|ZN5P@4O7cIRPl4$lsp1E=msu` z_p7G;dfO!7kh^%3r#*uOA4leAQRsws^*>d?5{hYGRg`^wsZDi1Dg_Bh_E#Sl<3Msu z3`U~XEw#eSi(Og~G@s;xNkNxNNrdD4CKhE)e4Zw}J###Sat&Oq|Gkf>z@#rRm&!ro z)L7lR#uNTmnGB_?Eenb9tb>aV>Rc4f4-b=oKNeB%@7IX_ zw7x4!B*CA6E{&p5ArXrc_M1c25vcq~_)wyTXToc2y8@J)>?MB}X9Ee+7dA-L{AO%^ z?DcK#Ubu`6_60$M$*%prazj^t$w`yYf9t5?%|F86A0|NbSh?ei{x@ighS=i=`Mc_4 zL75B#bBmbj19iwYOkG%W#39b0lwkhS3FRe>V*gCn%gA+QR!9(qL4&|F4bc>hx$C6c z`I0ye4~NX^px{jZy5N_n$kGf+7+mgubHB;PNE{X!znI~6M}~30?c&6Y#*K}QQTTuD z=1wp*iUH))8c3P+nQ^~^uMjkQp2^5dY?ZV;b=oaQOnuB4Eg$MD9 zt=yr#y-5~(^?8qvi|5V*ojPG5Co1OX&TdOQUA1WU>~C81)5E#yV!!@yv>+bg^z(NZ zV>b;-gZr35Q-S~G7cC$iqz2S&v5QIf(APDmyQZ4Cr|bGPksO`Xgggv?lXlfL#-s7@n2)aHRafHFy6SS zD(jP=6;V#56Hj<$|A>ieG@=H@IMEn39izcQ1?FKt%k*NjifBn-QuEoC+&e*4Q(ipS z8TOlYi0(%wR=6r2U4Fe5^NHFc(iF}BdZmDxofQSy$$lNSzN<#lIbcZQhTJfwKq}gy zrGFaCI+H0RAceKcM-vUw#wn@)NGwXgdn-v=v81U{ozWNOHLx|?oQ>n)?Ga4x@+o)F zdYSpilh@xk1G8uW02neB1Dg8z2u z5cjHuT_-nZ_qL)5V@%Yq#%?zr+tpH2+D5n$5A*5=iilTIf=m&ufgwz7Jc%;au%QP( zVjQHcmND$r$0KD#xnrQ%;e7y`fEigh4n+6~K@Y{)<;&oR$+m~Lkk_Bd{y`3R8-&B8 z`bXZIOD>~eRaR`BuBclK5dGU3Lx(IPG?p)6riXJ9x?h`;gN009+D?85o(G}T4Zjhq z13@A0O5#T7C&vQv6>zw*>OeK*$Uz#MaOTAUd)jZy``lTCY_x;t!d6`!2MjX!l549z zI}lna2(3bDG=VwAshjtn=ulH_J(~>{2JLR|2>3hsBi_m3`LVzp%T-*FEo+I@UYVZn zsl=(c$+iD7DD1iWSsQs8*>0u3=*%x`-X0w>vdQiafS5EY2K#PZtZikyrKex;F15;$ z4y*^>Fh75O&8cn?j~AeG%rw1KHp}N`;$o1>5)`N%nGpNa`ulOg2C>g_zX)Dpu6`}L zFeq4r*s-e;FsOXMkMb&GmzdNb9c$+HMAI9&8$A{l2Z&#Rza^*0#;Rc$%YE^xm$s|# zGLbHi@=Dnn^ln8gyKg<$`Pc4rw6<88)TEB*16AWWBbR^++DT`q^^+rM17(!}{jvga zG@ZTiX+zau^kG3U;PH0Wef2I}c7&X=g#FC*XAc3i5CZfpqV)Cf6ErbI>x8Y^p(-B* zTHCl8XFr9e;;N1cI#E2Jpdd#yrzG+1ySD6G8_a(|L8A!tq|QS6JR+v8HWQ(Ydy=v` zlb#$5!zyjzf@Ne8xa`BuwX%wX2PD8R=T|N0yn3*xN~S6vq>zKBtKi2lF&tUjqr&{K ze@(Wn%ekL*xk({ZMpaG30CWy%B^mXC!as^9jqCgG6M9G88QiMe^;y&wd&tD_@baQo}z3zsyb`y=wc6ZE4;3JoM%PGl2&@;lC!dEwIc;@ zcT(JECWMFD<)J{m)8x0at@&MWD zy3uWCH>aYP=dv_SUi@P7uGEpaCWr-&qW6)XGDnfqiQ?Kfi^F-r;*UB2G0rnDZsguYX{*FRew+JyWr|mS7<*6<>{@9kd_Q++8#_6aBYPT3 zH?>p?#Ov0h9J8SSQKWn6Ekso*FnZmsc)G&~R#m$rAQj~CNR1g4b z>`L)eOZ^bRJ`YqDnxp~@hyIU3X*kt$z-JzF;m-L>@3MGme&WJm(ooY8jBR_x-*+R* zSeE_(a_OyeblYxY5){Y68}WRr$Sd-tS{)z6SDhN32!RWsrN{h-{9G-bZNAt1!-Pyc zau$y{+*T38Ebp^MMNJ zqK>!wG-S!1%Lyh0>93l`KVm9(1;I_oYUe~wPIJV!zKIGHzQp0#bx>O($a`>UzqxM- zxY4k*{ay9};j(`^sQvnuwcXiwoFYSG%>NiH#*RfH%O zl`H+jbRaa^9E~ZZTqg-CRJ+(V3svw)0_8?%gMs6pM1>xH(g}9eK4?@3ND$9HY7o&m z!v^MUkdX;iN^1Q}QyCpWB}YO!mC-2ACkBPuaXyIzw=+$B5g}`De8a%8MIWwK)X3$+ zl2sM9?AClyx*duS^@{1?xD`siQhq)N&n>gksVmo(#2@2-d%C=->qEC*HC7f@cF04I zV;j2tvq^!r|1s$|aM$sw_N?=5Od%sSLVYU$eC5-OJUg5GPIuve14 zdEd}Ufg|&hd2x?8leEGMGKRwL%QFO~Z+q`S@thnvenLnK-KE$nQ5-~{l$s**g$D#V zonJNHb6M{_*fNiFO}S=i1ht_{r?=pIm25_i zw2>^RP0v4yt!h`z*<+_((vI3~50suCkB=MYw;~SmCcKurdoI z0tGEj!VU32K3(eu3(Chu5nMo(LjaoR6o<0=XcQ}mZpUa-_KHHanyQDRq)Ua|cKR!H z2q5lFcm}&RJb0KxW8##eXh4wL&(sXUqM@TWBOV!|HCdFePW|XXcKTKMPiiTS5F! zLeQ=KA0ZG={ORp81iTAQN-PkQU5sehO-3+K**IotU)5}>3PLGXr|l?P%ly+XEi2Q@^~L)6p=iO#;%)O{Snd*_dgl2~pdh%s-ke|RNvWkz za#XsCd26B_7)pA(dg25izuKEv=A3CRN%Z~Z@iv+$jf~34^!b(S(#K?O$)&EkXq>oi zYini=AIis6v^up=Cy{g9?l4={eV=bMBaA{(W2Yo$dBsHt^MPL5Wlza*zcg^JV4gu&Yv^!jb1`3(B6s{8xJxj&+^D?E#m)LsFE5n zRxnF1<#{SmX9tW-3#ZzwUN%$}hBebnRcUIdV5~E~06>KH`%TmB`>kOAs}oV{q#zsuf6MBeQ!gQLDc@pZ zBCJ)j9QLrjdd8b%hK6?c>$uwY;|_-q`(m1&Tf<*g?3<>|L1N<~q_c=?&)+~ut~ZA@ zGDk2IS%*iyk>t1iMk6AfY-$=9`mfrVNtI%2eh}+LGV>K^bc`>_#~^vFXqBS-WW*$- z!rBx`cW+$yueI$~$g9$1g-ooI)?80n(^Bp)xrG0iFnE^ee&cTZ?%zYO%A2a#hKqA! zW`JJ$>m;-i6Wis3G-Fs*(#%9-yP0{t4%O{FI1_~O4mf@6G?=i<`mI%t-+YDm|Qh_rmjmfX;QC@UY;{we6m+|nmg{G1w|sJOB0(S-V>g)%t=`H z0ABV`J-oLB3t&i@b-E*H>PW(CkX3&V&zsWEq-M90#Fi3OU8Zd#&4@?#D5JDeNncl& zK@Uf-o5l{zbl)+A8S=U`(~65Jx=yWOTVx_PqXFU(H4XoLre;-KP^CrRiq3bmP^lF) z@^Fvqjb#Y0Xx6#X|7+-0qRCd=5A_~OtHw=@qdu1iUzLO5wM#ovZHtFC44s8kbcCC> zSvii$7*{Mb-E*dHJC43G7_G0cw-R*9L7y%YPWsv&NC*xxhDMTlH@!l}B zkT8iZrqNNu;?VrAKJ+KyiaF?aBc6s-N*aga1>((1Fz>(4hA`zy%bwL$3Z7)|=2d!1 z%;7Zy+Jk(>cqSaaP#|byt6zK(n$4JW9KND#9L*r4yBIHVjA?dnT?UzUlYX4cE#nY1 z!hm)BzScxExTq$p(tjdsc^`#=alXPeR=v)G&?z)nxM>Z1Z|uU00&WKchpS-LBTJhS$iPcNGLN*tuc3AtFPqbn{73cKRL9*aA8APn@aS(J%f5J#sp z=#qaO%&DMa&*X!wTFt#{rg@ujfxv4Qk_3?f=3-EmwCr{!N8Sm;3*#5a_sCfb1lX8~ zA?`X`p4_%R0f7{3R3<-NChu+G;nSHUg+$ngtFvrppRJ|%4#CHLW6>LWH|$`Ryq}~C zgRA@@3tmo1*lYidUd8P=jBxKq!DLq(U+vS-9Syhhf5G8ZquPUD-L7ZEVONS9z3+YN zxDU)L!2!Fxf*ce#1WMJx!8?tb>{f$PBu*-0Ww?5d@$LAWrwldGqG8Ql6W!eO|9;3G zV8tV4@x!vack?``X&?u?*GYri7xm17r&En$wvv9=PQu;R5XliSe)bRL>tDWW# za@c>Nk1tyJ!+l^f0S>XWT*wlV=3o8;%<7y>Zu9e{%i5>YgUYleit?cIE$_6y-9#ouE&4C#4X8m7+ z#6%?^`G$9%!6~1XlCWncXyuZUiVnvxXXG!)GDaFG{LER#XQpl^;?g0glL3c^Nyf-N z9l|>Og7NXrtGR8Iwf3j74QO~Q4FcpDYV_2zv@!zzUh2&97i1u<& zMdIAOxK&ZCpc0X^Iz$#gza9N#Kxp%#m>k4FKMyu5=^Y54JC>OL>RqOHvxn=eF&ni_ zKuJf?$4J*+B6f}^mPwZ$jULOB2)cR_K=s}Jn{ov9hZKw#ASD78Ofso^^3QGr@X2u( zHZ-`oGPg&=(R;G$dU;kwGr%Ac@t_c3l+N z^^7GY2ACPn)FP(Aq8E}$58~JN!M4(;d|F^=$e;;X0EG$z%Utf2?# zcFT6oKqL(sB;Z-`4x0-m0hD0|t=4TulmERJ5qZF6!ql0u^}3e#oI_tX7lMA?quZMP zkJ6B^2vS@|4nJTvEr5a>iC(X0ReUx$~!>_X)oLiII%d1f-o9(-5>4VDJ3mxU`X^I zCtr?S;TgPVZQT#T1iiv?+6eNV0a_ZxVbDo)#ToHAB}7|{h+@VO81x5cR~oENTCgQSc^+Ma&rKTf zKLh;h*aBCXt;oOla-#Tbd493b`K#XfVT}s8)?G3Vdr@Yjc>;iHJ@~zsdc_a}lU0q$ z1?L{cY65A*VUvX?L~@6~;DKB>YD0AVJYICC(Cm%hj!3u5vhSLUUxx6@=gGvol%uO7 zf|i@mB4|b@t7%R3Ze%-iaqSA2b3Subv}jzC2PpzW=l-m1WBW|%a~L8Iu!#R@;>161 zC>*U(VE=nWYVxt;66-8<=#06Jxw|;?ffOW$C0` zG9lR|@Q$|cdypChM{j3W{*EE-y+7I7Y!{~Sn|U;6F$KJ-E@aGxr#G5xM{&jWJVqvrj_2vDRXwkySEL4)vP_wzVhzZ(qPxd z*@R|6k7KgEWq5?k+)MX;w2K#(j1kjr!>b(AD+WTypN@A!aTgd$aN@&zq*hl`MMFag zmXX!13=pi-neO#05F-m!)%g63fjk+2nJdc9uDk9r$b9QCTEIY^^w(n7QCm%%EHezBWVVR^ zJ`?O+!sjaW!FV*eXn{(i$iY93A8mB9XakM#RNLq%`xqGXhZ>s0o;{SmBzR7g#nW}` zE5wSFQ`8gCPw3@D%G!a)Zg%uPpId5mWaxzo;n8BnNe)gxRP_sn*{U}ikKaCO=Pbrt ziS?CR<45kRs)2nb4_lUuhN!Rm9Ekok!A|_*=L|gM`(I1mdutV2^ClW65ksmpLdRhC z@b4sNT7i?mb*ygcQ9o;zy`>)ERFs6?dJF9yX&KSV&65cyW|*(T>PNH@2xSW!hxOy@ z*OvvZ`*)OF{5>T5%R#{zTqlp*2rAqn6Alft_~33j@PE8_-SoVtmgx+dtI z(xmcupz&Yg4mHJn4xAp%B;`Dc!n4G)s*t}kh0KVDeE$ zStL=D>fL0Lij)10TkCluxhPB2NCvz=o?5iUkjkxTPDf>up2$PJOY1p?9_ehx zS6G+!=*a$GEYw}y0&DL^cD<7p#|2}g)(ni{&S+Sk#y&EAU#il&cfGfJe$B=x_MWH? zs1eC$#-<8`o<*VK8>vcmm#GWonaB_^mr5P8(EMTHB9I5_*Kj#`rrWW2eZ-G~*n2iU zNbE$J8_-cto-)nw9uN;sT@J7LQ@L6noQR}Yr?J|_XVU6}N}z)J26E!G%9|O#=5CWh z^NJLMbawrHsl3RDZWK?Q8Ll>2)ayJpPweXE5tHdFjoK=%9I`h1WpipSX6s?5~fVm`#O#aPCW;KTKeg5g>QEn-O zq{k!%juHWsRiYl22^6Ozdso-w+_AWW@l0z8KI`{~%PM&6o}Yay6oF=CjEoHawO7iO zZ&UCThkp=az`~Z2_adV`MK?lO*2x`|{sFsg4s5`bQO$aTfO~A?AW0rfJ=LR&lQkq> zQ_)deEOg3S!u5-o+!&;PEBQk6dOw z5L!sn$m%f|)@s3xFj9b2K^R7HrW!6lr=7Oy92#sq*9j$?@+wnAhALYUd>MoPDEt97 zMXZ?jFV?1GN!yZFNz~2_OE3|UNSxXFC9i`K`1C76Iv_p?P7(r@!*Byjbp z|3P8^cIVMr6l3L{7dlAEh2&G3>1{c0I&H(%~yE=p4-6sFcj%dUp>ouaGRXzB!DLb~W_c5d>{Zw5N zcLL>lya7O$q`}o32!0*ZH|-(41lM1Opb~mHqR_g@;gEuc4EJ)UT9KgxO*plZh43>> zcy2^QoMyeN6Td;afX!3GGrEXUGT9r@T6op=qv|WzhMTA|+XybH>4o==cRaFa1_$3R z{nAASilw6a`zzz%r-7-U!=igsVJ z!Dj!5r`d>&C@FHUKycy5rT0p|FHz4V?=|i|U@2QtOdjM-r$Y+#o0BeC*Y1eCgfu0s z)<%xp)x@`&(hZAPFDKD`j0{A$6tC-Boai*g6`Q|aY(j3-L!>kZMyNn}8jk8Zaep&h zyPq&a&yv|^1C1jiU;gH$mAmw6(;IWp9@bT!crNCa4!`QQLq0v!YI=Ld*dDcIDX1+W zQ#C8KP=kjcATM%ey~cG9-`YMISG^{)`ER4=Icek^VDTwIRiOKY@SK;Ik1ytF zgRe7Z&(VXsK1m5(N`h6n%zkXOw2Ydr1KIU{z}@7AT=RgTiDt?~Rr|*NTH%_^ZH;9_ zN3M3{+=}h3f)>3lCfPmw9^}{yaZR!q%_aTpBPR8`wS0Lrm1}lv+Kl@LF#Fi6hKS&yj?K+>&7>1m>f_@ah48_Xz-^J?&IA$=*IFd-XET{}Hc- z1M7h&P?$F^a9ij@_>vpIDED#y$Fa_?tyI46Sz=2`R7Z_s&)kuuu?-f~R)V!}HQaUS zs`$v|WV6qRdes9tOHpd0(7HqGjx}Hu=ZeRwC3Rb(6(^>Sm}Bc(0yr8{V3v?xr6AW4 z8Up?+H8`C&voh~7$PU-Vu(yeLMgJ&F`|!Bw7cyL{91i+; z-P`m)o36u@q=nwwrqgvJZ=A4hC#B|0tShy+A4D^x(n>qq&ZKK51tk4|25wWgC&x#1 z^kH}G8J0k|guY@SNvefi``!Q+EY8>BpPgNDa`i@~2za-LK;kROE}q!f&*1Na00;{= z)zpA$-|`Id=z!XYGf8o3K=CZ;(x@;J|3%^6!kd3UaWL~FzX|wP3(C?vk@v2-{Fe2(bKzv#Xl z_i#?jFDG9nFZPb;X3_-O0-t?1#yuqdgU6F0_TNzkU(B;FM|hiqtvim1rk^vPY7oM2 zM^!6Aw?#4AqSUK-?PYRDzFOyhYRCE|dDVT1D9_G9Af||t{5WxBK{wWa1e=WS+As`B z3Y8I}r*pjCN+a=iyd*+$l>d1odI_awZo0@ZNLM(=1e3p{V?|=gC{p@%p~5l2^`p~U zDfsgNk{(NjKcJ6xbR}5z((_W&Frf;O1PA-hD^>~D^Av~o3&TDiawPzWGSl$k=eAj& zmizA7eH&VQwL{EiF%AwgXR*RYV&Ge1T~vsvoN%dTF75HTARc%dZQWLN>Ns9oCfkYr zF-~RNrtoa;Z)hKCNmNZUVploUu*w^DK|0e^pSsB{1@=)cV6b~PZU+Rn(5Xqy z)w>YWympoCR`;HJbIO)9fNMoxqPP9s?~IO>)21r+&#qoUCyUjcJ_adb{OE=QeV0L| zRA)rj4j_@K)4&QgkwysFwCvYs&Wq<15|kPdv91@Yiad-w8@|Mnb&Xh7(QBB3v& z-AF_u@sn8%r` ze9?^D^CN3hi|Wnn`-+Z-_wP)h%n!f7y#CuS$*YL-TEm2u;a~TBk$Z|cd%*dHXJwo~ zIB!gEn71b@SW~lgO?MzR=k!5V8;(CJl~I7*0}rwmr}8jyij=rBia7`m&V`7(*5lL~ zTIJ)Htz?Nme5#Jowprjl7-DlSy?h6|(2n$6W6y=9uY`P3IK*AC2fIU$;=?e*DnqS4 z$saS^vITG1+E+|(J899~dcw@3el_8fpDTdP)Qmmylu8@J65}{$Ep+M3Gesv3|47-IpLqq>CPLH|eZDBe-@k;^$xxg6w*_588mdPWuq*0mKtyu$ zj3DV~pj*pCwRg&siczLaGO_K&JkP~Iq?-ZPrM7)*kA>z8Q@NqL4zoF<_i2W{CA_b= z7o7d{zJ)*M%|x$$Ung|h?$tjITi2eTAa#o!k9(Fjmk`g2xOQH{XrOdv&*-~s?qf-v zILpjKWq4~=H3GS<2tHcZCkPU=n{vFoou>^SA(;gI@++SiaX;Sa_%U67yKvmx=8H&x z(I!-dw-!*Df#OE`bdk6vP7C=lfJ_8Q3V*^M4)`5MUl#cT*i;6W8W{LhZ~1DuT;hPL z??mNzpm&JXJLUpmkxh}G62%#dMyjOZjE}l?E!SHTO}5+D^s_kgM>qdvwiMxPL`Z~h zGOr=T0SMM7q3_%eha`wT@$Zzs^%5P$`|fPlKPo;I{MxQVdu+R9P5?Ajyi_)X3@N)B z_DvST>fkMe?0_3LSJ8fnc_wRWvFc@#g*n3AF-NO;dxEGl{|6UJ8#<~>aB?C~PA(f` zDuu*C;79f8fquyTcS#r+PoZSB3_?^QIzClqagBS#(LWX}wvi#6a{dLT1+Zgg?guF; zUB&7*_e&UQeLe2?AL^!2er46fx*_%{zQBJgvJ;%-J$`Vb8-5|F{X`0Z7Xv)*XNb*r z0tnzKw(O61Jl?JS*(_5MS{ic1V@!?2R&94Lx5R+<5>m{$Zc}6}E9(5Z$JW%6_n9sS z4<%+BnDErqTmqh_m{@32nKhR^8EE}if&jg zZU!jR^Yh$q;6HB2%G;2J@va{3vxcGXF2LAL8i3XXoIN3_Skzlh&p40W+-{;u~LvGuoWAt8;?X; z=%-G%$*1H_I5e47VB}R9vg>r+yIqebHdEA!^7N{84}1DN-=DK&%5Gk6*YGDl+iw~@ zM=vB&RSKR3Gcd7eeh>i`GIWfFc?R?~O49hrscH0LZch=+e)mAJba^nC5avCW7%wJw zQm2g8UIl?GhPi_Vq!A1Q+AWkczZHUa!mnnUb zvPbpJu{Yz}D8hs#g_8_svl9~x>v%#`Yx#om2<}$Peio!IgV;Eu$9rGq4%K}ETwGTN zWJ$A(_iYYwJ9^&@6RRHO?Kki{?JOI>!F2W|Xz7VUWEM~{3fhoUOw#3&{(1c9Z#hWa zC=-OS2{A}R^9?iO5wk3^n+Pb16Llta(4&^yaR#<+2*Hk=@>|y7ZPE##;qM+=c z$0TW5i&$n^^c5BXhyNEw;#KHx1C^PpPl_u~FN-zLUf?a}j*aUqOP6VWq%L1%UycYT zaK^1uTmIg)JZ9(HItKm8QQ9 z8Nvb8rP$!O&89Q8Tj}Csv@LWMtB&scyp6IO!QJn)<-xv@SgvR_5m|c>xGNqW7=Gqn zfNwRSMl+lsrJ4sLD2y_k#SK@`Y<4NmcJLB@QO~;faFF4PfVb-MYQSmn(xMdZ z<=R)8N7xKV4B;_zL)y?y+N`mHt{*@+F@{>5L!U-bV{8>#%%XM0r;1QDuT}OnQuK*W zV{jy1FK^15%`Vd~w+KrZvMTl@e;mGvOw4Q~hyP`M*$l>!o?bCL>U=2f7;aw50Pi` z3OKu94}~NgR0<)_;^hWVp@D*sainFxY?zP~RG)_y zM!L)Bnp}FUR;iIW>3RA_$11-fN8G7muBV36 zQ!VCQLA8c%sdc*$E>Jp$A5?)tTpFP$RoN8cmW8x$IHTA&Ho)^=iFc+q8(@}Q-OYr- zmrJTf3lpff(XVPZME3?3dB|G%Ga7K~S#I;iW%g3_I?8Z+H7t< zO*!Hfo-cScM(Z?g?*No!M_>prUZY~e+ES>hQMAuyDbMD|h;xOGZa2dhxvc*@zAPr7 zL}17E`3w9foVjPa7D0>T^UehDS=16J;&Z|;%Yo}|?==CO1+X@*-wcr7cnl!HSm>AHZl5yhs8lb?o3J=iy&l2)WEe_SAnwy3j z(@mfhlFfiKn&$Eh?~l$8;2$wL(_5W9BQ3b|3ykln*_cn%Dr^8AS-Vj5OiJK9#)AeU zS!T0#z;|g%INH=fOEWMUu&b%%y|V=yln#6>%;?s|usF5)gK)6OwGcD=A?X6Xq2!6m z5x6KwtUJu|Qxl1#R?U=(`nTm{WO~vT-u;V7dUWXn;Np==F4BwyfUZH|35T=|p!?C| zKh{r3qGI{`@y#pQWJ2V0*yOms6i=*Z3C5F=(=KbOyrI$;MsFbG)h1lXf!^67iVQ>M zc`HrG|DuvZv#Fe7%}rQD)6Q}8QB^D=32_IBlYS&bY1bL;o2NZIJl><0tlG|53O(TX zk1eLV40gs9WufgpJut#hT{BoVP0Ek?JO4J00INWVo2 zEN1H1&}nz1`}0%ObT$=H--PYZH*6|~iVJZn?9%vTfp-Qw;$s`8N;iI-8SI;ygM%s) zjyI@AW-}e>r@)YDmrvAh-B@p3x;mRc_Uv!J;nMo{ReJxjxf}1&`nhqlv9?=&B%2Rm zGMP~nTdPd5*^cG6qPLE$j59~wk<9Rym|!E{!&-_HdxG9;%0b{^M%}b|>8|etD5YFW zTeGR7)RbGuO6oH3$Y>$=WcG8_R+=&IHaC$;?>lMkDr*iHy40~lkhP)FI}9{`Q?&I# z$x!~*Ry2ZfE6CYXrnoKZ<-|r9?-#=Yck4L^lu%rxh90J`F@K3IkC%oB=VoGGV%lU& zXqNz;W<|0wz@kFE=QDX%(_+hZ3$cOxD9_LcgWnw;seW&>F*_#KGg&j+Yj@t=MKvlV(Pt|@o&tQF*JY?SW( z$Ls%y>bQCmd4=b$Q)Zz(IA1~@kI1jkSc^FP5(>3LuDE7u9IA{4ke`|fLwQAyLXYDP zxEWoBBSUAwV&@KWu3O;gDCUm&ql-i2sro{PnNAkouTbywbEsj+f zyx?>PV2tFp5ipGD_9*Z2yKDotY+mW9;Z!Vf%5iCPQCy1j4_;acZ*S4&I>2A;gwz4d zG}pfP!a;g}xuWIBM28A6n$~WdzkYH7|0<)^Y#Bo~e6#0ne7oOFp0uy8X*#N!Y7Ed< zrO=AiW^u;k8R&@?>ax!dPg?CNhuDjgUPag{~GwQb-ye0rTPSyu42?n1c*J?#? zoI(h|Zh8 zv2N(@$_hQZKzlM%dMqyCpC{uboXV}>qA|d#IO``tgiy&5!^+UU$+m%0%!^J^5+qGi zursPxrQ!iDgMWNQBX}dVQ>}6u?vtUurNQr$Ef1lt71jd)e+mprYGyX8s}u~1B*d_x zg?l23m?pqW(8kLOgw?4TYx4dZowFtnE8=+mmH<08Do@?h<_Wh3G+|o5xHV7eWVF>l zQ7J0AKvnoT*7vWz(b#lZC1j=B9hOq3W>nW0>z_)I9d)8}iO+;E(C-WcC!!n|-}o5_ zJ(#icv4?0zXX);TiI60{by8keX$(4;S#X4VLU^lA;!i?S^%c*=bpgF)oTwwPeF+!VhZ;WIU2iwzy>U36JZlfd0BYq z9IL=X#f?f|>81|JBYtwpI9F!mf6H{94p=&3opI=RQ9B-$T^#ckQQ}v9QNT-04 z2vP*iXKgAj&*5USV=xa{ZPez4*f*9eM*lzrHpP0gC!(vYqQc^o_=qsx$->GfUEk)f^Ks@ZNSd zB~r)mc~R`+ebBab-2hrE)i#$(%plt_SiqD13cgdmw$~&xVjEi2 zwPJsy#=pm}q?M=8!Z&bcZPUVSP{M$&w@8&29cI(NZ+{lX)1pkEa(-0)qi;bdmkNva z07Mr-2J+Ak)3q3&GD{-*dzG zkYw8}LU0ZP|0uH)1RuNAG?@;7<;EgmIy{RO%qfp;$(;wQCn_IcR~959#v*DreyDNw zl#2%#LGuo`hmG35&1*n0x{a10z>$B^u}f24>TIH}D}+HQD1SsVskYZt;%Pv}l+jP8 znielQC2O|N*bK>R(dfrlL&n@=#Yn@NMBfhmkl03ekKQD1iqP58f3CDgi*M??vF`(E zn2gU$5wMB_#+5h=EQw7DJr_@ejS!*T`QQj(>KTw~S#ymeu8S%(tH@;eGq~wf$#xd| z_?+X-5Jz|FFK@|qHNbLsVy?#;-rHt4cyHR&sv$npml(X8voTKBqSdx59JSP8v*WHl z-ByHhYl3{$*B}c+UVKmMzNXcbRd&?i3#&VOhGo3%(ik!I?LjR5W(gVyWom%s=7mIL zbZD)F#-;^NqyxBM0v&e6sC2sA&8YT=J@}oWT;xHx0ocDBb7Hl*z%7+4>IEfR#WB-d zq`$*}qj)7t4qszGo-t6|R(i6`90ZBZL5#Zq6tnqrS1-D)pq%T{A+z1(MB2qcywRz{(EF(|CYCE#bj_f@H*JFkne>T$jp^H$%`I?k$?r+MWZB zcqV}nzXzX3Ul81hwC4|eQYzPBqSs`?LGXT5DB{GEp_Qz`YQ|2dED(2~v4>EGnaR?k z>mFCV4UJs|J;4sSSgI@$-LP7A5hvY>LYmsYl0jZ3;Q=0;Y!}=7-}@UhXcf$%E315f7H>6+Qu9t{rCm4ov1LSZcf$dh9vzBI}F{!lbvRI;*p)eVp21;63_%;jmPT$TjNzcOoROL(T-KA&}w z4kDE8Pu!D66Mq_%N@GnZ4yyJ2BKR3Qs{~I1VEXzH_0XlKr>P^R6fc*bs;rC(>wmRM zB3*vRtv1l1TK4tc?U`>dB0E2fbofL3&-^KSXtz zv}+#q49hkZ50n$9#8ffiRlKw)Ia91uYlX{(x!aTm&ZVH?_WG&RvInH7o)JZ|df92j zNo$kgljUK-f^hfZEO;sv#6ovNB5(5(e=a9jS7;hm(S8CeVcEfgBO|b>N}ILsT5j+40lf|Ql7UFKreDrN1e?wWL$X|bZ7%~l8Sa= zPAOG`43?`NRCZp%P3kX*A+RDQmRTTHp7JEk19WKAp*HF=`b~y1y;ei*X;B#*4XRs_ z&gv%6NBt$5E+70YWOitAJ+&aw$Ba{im_pb_Fs?$ zufpERPGGBPK_XOIIvX+$3nw$Zu6ol^{>Hm~ES$k6N_=5mKtum9nz)Yebj(@DVgedF zcW}e-kBVII@AxwHoydvwr{`j{qM{ZMwu1PFe5hfd_HceIy!yAGXCwn*bZ}Jq+XYXy zjQA~+;wOqI{}%vhK$gGm=j}ni1cojjjKTlxw`ueM{%QOCJ1LUrtmD+$`Tn~@`F;KM z3mcKP4x;r|z?F1<)j5@huJOL0>n~fT>x+LXa;0GbUPpmWtjlIE0q*P93RIOQ*KoOE zo+ydPSB{`XmHzxAE z5@d_e<Cum{1Eb*4Zb(lIhin+!a8-1ZM!3M%T|YeP!z@-Ej}-PVaFI z^;Gv@(k*RlDwQL=B2b1q{>NWGfBxYe+nTedG|3Ws)DGVD#ZP*rZg7v%Sd}l}r$DJ( zbTa^4{{?|uWjGD0OTy6+O0`G=q1OgNG#1fB07h#c&^R&Z4hCdoMxar=HSpvO zped9Hj|HYB$F|b;iH6hjLt%dxxIx(6*Y2V$m*&;sA;shV+{9r&mqzyI`eq+Dw1WHI z7oT-++gmcg5UDB|M;n!*m&8%AMnJq7-(EcsTT{gAsE3UMsbyQOR(+OFhJY3hZ;SE@ zpSgmu`{Hu%AAh}qr!VhL3_BD*AO!z{*8AXQ4`l(V$&EUM@N-4R-d~hidWiGGG*=*6 z+X6Vz;oCPGqCdP2+|Vj5&0!!*SPCBTOTaXjt(H$Zl7WH)maJjOe$X+|EiM4|%0QzF zEjXs-GK2GmW44ZK>z>g#wmub+)dFDc0DLrxe1n9uMbz1PAK$7_y4Z>8B~|g1-oP+D zD{}J8-K4Mj?nc4*{T{BMiwMy^dOF$d^Lj;-kb}tASv9zn0z_9UR4yjupS9dwLr5elLGZ@Hx)}T z#d{J@+zlBDt_RUjSD5BuG?Ycoy8v~Ca4_JIAy%Z)Iz~1(wu}4b=8My~o2WRzt~3Yi z{4YOB20>~ zN@Zc}0ivh2LDQ;JhoLmYCO`y+=@l4+wF>lTcs6TJe>s;#pPljPB5Vv4h2+G=5m*C^ znX^QjtoA5VrG!mlYAtSLT9Ilm_x>}w+=FAm7>bFvgpa<5PxKP7PpOMPi!T~4;4x2GKg9_f)7a^rd0%D z5lI5h2t7p-9gGH}0p8Lo2M=dT#twY(2|z>@Av`IrbG(413Ku!SR@xV%x8#rF4l@D2i9|45tho_ z4D8K%aDzj&ELp)Ty>6ocvLbTl2GetV+$hOQGBdz>Hu0?W8_v|x{nMA-pHieubv z6&k8RFo}+a&r)a~q<)&>qOlywK@>kC!v>d6R~V^Jdz+eo5KWOCsAH1pf-O{1IOOSX zhzCzh;zQ!GV{;vbBN-0|5Ab;3s9p0!9&sE7qj30$T8X2K0IJQKp#mK@q+*B%!FME_ zk>Q|5RDtMUBi_`2 z^r_Qpa)q1!Ys?=T43PeGrWUu z9AyB0DpDb3MCWz#;>aSKKM`^-w*XKA2M(~PHLCp^E@&p#h!9p9Q3gl2wTBq8F%b%a z#SdABD1r+_eAyi~xGxG+OpZnl!4tCrSTT*<~}9HwSO}fBs0bQzEETdIxfL3ecjE`h?c$aHA@Jc2pMza zb@1#Q{Kg~d$mmAqp^7?~doFYhSt(%!qHlcIe zg3NP`(FC)Ezf{Y0sR){*g&Rxk^2Yn(-!|~8+hvN!n9!jWNGTvifmrI6|DveT6a9e}mJs50T5~U{`wXhe!cO@i5fpT1|?{ zFR?xR2zLblELUmd1B(Vvl^~De#LCeD1*(RUKC^U8n`7BH>NvBpy&j#*%PY*|J7nYd z8?Ml?f;r%dB&OX5oCfTR(_+o?GW zN{}NP`H-rM6r{VCdjYbn6rkWKX=?I@&c`92z3%5Ghzsl{V;6!Ws!7`iu&HSQBcF z1tWm_g)zUN-4Ts|kvNm=;C;!w1|r0>lRl_xE?D;0~~=fSIK4xN(uo!Dv}$^t6Sf+;uXkqUk6M zA8@$e68U8my(a;?-Hx@VPET~cgMW?oHxd>Y2{EGEdD+?c*i=E5l--TbtlFOzNRCJ2 z>B%H|AX~f30fbb5#U8Op#o(+EJXQf-APDf5`=gRGUe;WT=FLG{VV)YsQ;p%AAK{`k z<+8@%X_`JMsnQ_JkZ!&$RDY%g4O{m`l$fx>GE_@4veEo>r>TMkH~TvViUTEwgVU2| zCBZD(nLpr*PE(Pcuj1@XP^SB|laGhfco;+vVA*Yfx*ykYuz3<<1^{jYJyeF*7z=qb zC(TTt*YAlLTypyX`ATY@OWYw_|IAx!XLOyREr}jNpJA=696JDlHo# zpnxaPaTeqy$@5s}Rl#52gXEheU{BBHNo{ zoJv5wZGP(~+gNNHuV5{>E)k`tRAa#Kh9ziMMCYm>Ji$maQjl9jc;zT@7QTr&S1K~E z2?HT3Q`)vf>`@S|X#go0*Vs%YNNVS?jSr0`AL?jhh{+6Bs|U6@qpWUQI;GmWPuu(B zl2kPc26uW$diESMqtYA1z;onCu_BH2@oR*Ch5BQ{$yhFxu^|>0TBx(F0y;RWfNjss z=TIhSu_zlQPViW%LO_q6FaQCst<*`e-Sy4{yzg$eMRHkMZql-eJ-aWzBiFUZB~OQi zYQf)l9t_j9s#xlKT2JeuvSt0L)(FanbA*VP$YQ-Q@fU9R%Mzo!9Z~Z?GlH+UPb%`*y7cpQ>G&fc<4Q4l{O7SgH;?i^Co($fD@I1^5np5x6l-xxg@7i0mNgg^v;6L1frD0~sTesOQag<6VpIMzp_2 zm@YO_wPUeP<0T8%w{^E81ML(2YggS+Qm?D|0vSM4>Lu|x*w?UMn{lKvO){QihKDcH z)P;dN76PZ0+9~-c@E+Jf)bKzbHjIH^&LLAyld?>e?~@H5$Tz_s_gbszu7NxDuX1b& z2zS&_V~-z#`@`NE+#T8!+``xvsjw`ecARxgf=Lh`pX{`g`$?s@CoX}t$Ko@VS+jp|;cDM_*^F$r0X)MDq zegx2;)obt1Ttm)g`Hwn5*;(?-Z&abP{q-CNW?=RKc*)soOZ*vXmqf27ZOO<28SE7$ zmP-~JtQ$c(Q)hMCm+L;7tqo7PR<=n~Kh+ll4d0(~|VVkMJW!->lpZ(UC zq?ws*hUAED$AUw)qTdnLPlrv9^|&@sZ37|o5ato)PTUv^Ga%rjWEV`^1usOdu#r}6 zynK1?y3i;?fBDisMrk_Ugedael|Bjgvb=Uk5sxdA5z%)FI}H+(QM*x&@1@n98_d$8 zb=>KoA#y-TWG3B~K&!+wV>4^Y!D1_NGJZwFXiRO0x#Wil5f^F+X})iW)vn3JmNea- zZFayP5`Wx|*O9sQA(nyK`*Zg?9O{x&4xZKl5V>5Y2@8xO-LZfjdzq6sK9-aCnT5wD z{kTazKF!&Lr;!|=9zDX@*sEsM>iA|pC{D)E0W=W_0T$yUYTFgswXTmQ)05HjxI5=K z8Y&mv&W9@d`Q2OE5bFN>Gx+!Lsmh<645#w+=n>s0=dr8cH#Dda`aGU z@`#Y|ta_asjgAMS@Da_`EgT%2W;apU%88XQE3l6agxnZEs_? z>(bfqbR4KiO`qFi1>60^B6eF}AAe@y^yqkeIygRk1opv$porM$afDkL(F?Vxd|CQ% zg)JqJNBNh69YTeRs~cr_=8H3G>_w)+(u{cE9e$H-#xu_B8!hgYm?}?oc#Qx+Bc3*m zD!wb_AZJ0}&`So*znIZ{3B7j)TJyI$wqa7i8(sl05x!C2l0enr=_VjK*#rj8q$ zd>_s+hGU6rCQ3x8W6B!HtxzR|bZ$^46D7;M=66(7*6326p~~Lr6nug#=GDFLcOuP> zi4kH(yE|vW^tWATi%x>O6zZ-mXmB)~M5oVBpW1pdYNYSJ-7&*-6i(!1^vu>1i!sSB zKF8KiN0Ts=lSc%g54!X~1?hkEZRcW+7B;KJnGw~!QZ8-aPHX%++C(8KCKS77@q%Ea zR2)2jp?8Tr?qTktV5*KEc)P0YHKqg-ITg9q^gg9&;HF)ZF6tY?09-8JxJUc&f7A%w z0zE+oH2YE{^Q-e8X59W~X`FW3A2N%u|$PF~&juCZk2MR6Nf532O~-l7^U=<+1`n^d^lbNAvN z|COpnTRfG3{0s4x8r7~PpI(sWNgSzl(P(fgt5O$VK=dvgXFEEB)?Yfs!jjiAAXaZX!GBq}tt{i7HO)nsJf`sY|>d05u|R0myc9QvUCA z)_X5VkP??XjLTKFBrF$qIqP@6^FK5ai~*Bz$OZ=IJ}G?ufjteaA$5>`&5{`y2ZBAu zG)EjDOT5r0RVfJ-M9{R!XbedZ7MRYpMkw7a&70tRuUh-grqPlwdFgpTl;`1sQ!z>? z_cy1>luQnV51r{mIY#`rngB9Sk%VBqQXD@Rn=Z$-yX6PiVhEKl33othQ{%`O>Rl2T zUI36a@#vhjI360VSee=TDHN&>I)6yK_Nrw0Y!qoF$1dMOK{qB+a8|q@{J-)tY#a!t zDpM^6&cQeQahCG&bhXenoA;Ix(qtj(#Mam=wuB?5wDT7(ndiE4L`kZL<5Ubp5XOVM#l936reEW zbDFq#uZ!|GEf+@})nHS(1~;h&>x2H9bV*GqJRdDz1aE^XL|%dTDsVM972}n`?k9e< z;KVw9KFS;4s{oX6XZj)^81uMsjE2It@P@IM;mEi3lOk9Au(5d!@c5yccN}JG`Iu*N!AG>Q)Y=Env5_pHbDqA9U`Gb z$W(y(UdR}oEKI$4q%d~|<_lb6GzaCASXu@p1{APrS;Hlwy(tZ)D%dUWb{uxQoNM9{FoERG6enjcIr+G(%!q1qSEBCkNe>e1_$-6Va%l>(2S@Nbl=>vMad~0W zVcv7QF0%!ikjnr{idj&V5S*I4d61Y2m`UlRf@#^Bi5c6HNhy!$IFpmc+kjt zPLY>R319JTo)II*uf<s4970 zcKN1E)Iy*(mUfCH5EH)k;3(HvY)TM|lGj4AD>4)nH7?=~tFYv-=p3N^QOh<4Vh<+d zcXmO4e~F(9YfrP#5}DTu+{SDLx@TK3Z_i*jJ(X9Zx@WU89O*?P-W#*E+u0SgB17ZZ z!X~O1uK9PLGJA;|cIVVdv<6%R(&dC?LQ=~HAD{8FJCR~gJMssK9WGKa&53DRu!SCt ziCUQW5(+b6s?jDYlDU)|7sN4@CPbohZkT?zKNyF}X`#h)lNHo}fi_Bmc4YDEqf#&C zpKA(?yfD!wSUh{UczBq>zk(I{S2bz&D=BnW=EeaW<_tJCyn364?Pwoe@D2l*EyKD} zSD)^-)`O$nKL@h{;n`$G#cNyawncI6*t{60O9uc$GhcaQV}L`&sE{(CC?XMP0Y9Iy z?0$LX>g8e79%RGtG|nEBcQj)k=#wu&7RM%$7`m19$Kx2r*+KL@cM>ER@i<0e<<80Q zl!$;QLNVC16O-!!6~xzG<_qGoN?N!a4EQo0C|H73R>lf9247g@ON6P5X#^?_q?eCo zArpkSuqZk{`lsjhS8OG=##+HVNkEUMGjz&Z!D!-EVMJ~N`AyDcu;rHKX)g4&7RrIa z2C^=kaRFCi&;)ldmXnIv81)%POc`Oy5vjo;jJft&G>H7?-}~RnFWXJO`TnQ7H|!|A zVO|>KX1|#x-@+KK=|R`@plf{2xwDL&G#d}%>?8_usp|LEv;G0Ge$I_(dVcaO(*z3z`iaV-rbs9%fz2t(!5vip&Y{FD zkqhZ6aSR%aoNb+8G|^GS5E&Zmt(&KtGcQc9-y0^w(`Z$;599ry9Yta1(`kn?OT%$C zhz?OtnJrHlRTZaZ;V}auwW+q$N1Yw9s*&P|W}h-B`4ssBxif^B43k5#eg6I70q0+GHF|k#*@?({uEw~sue<!6eum_*xC$~78_%zr`Z%Eg{=~erZT57uNV0WY3f(#3M8rs zm>MDAWgr^~>@Fk$rr zOABp;AE;Q(&b)(e!*-%OZ| zDt3Z&!L}BpV2Mg>XbG-|BC3UU3I5I@1abAn4z7^rkfCjt6>VlIi{Snd4j15U0gWlp zHlQ#aQ7o($ycgn>>Y*`JT8n`EmPo%i**AG1AoAXU%u;(eTy$Q%g$*)CDMsOEp1ilb8OQIeL+k9jeo^u54d zP3a>nI3HsZJ@jQR*@OA-80NVqocb+Iqcl%yRg*p@semsugiC%Bh>@MPI!9NP9&@6U z8LN^fYRgh!UM4T8brZZttJ8I}(cqG45R4wedE)fNOBO!o_^POvz#t4vhp#V&M^S} z_``>NG3X%p3>z~oIF`5Xx^fl63r>Q`wr_9Sop@iV>~>={Y^^q#Z--rXQQBIYdI5J; z?r3=^)_dOPT8)I)x+Osflt%Yn6}?5tW-vD4rSubDcTd+V1sw(Y_kB*G$~zzV|0@uka!n%QwBd zZ2bM_M-DxpjQ#0D<%hripg&&ied5>2{OM((xz?+WZEX;SS*JfdphE0@N%n~c6x|kX z8)%i&C0xFg^oU^e*0haAS^gv#4f9p=9MUR=O(H57GRI_vbjEwx+gFtM9ac9QcEe;4?!olE zd>*ovAp)_ZPsP9KXdG}p4m~7?1w7I?Y)7{z)f4WWm%_1yZ_Zx6 z(0qmK%*)q}k%R1A@GtoA#@ohtZD#zfjkiH#YMxLMHGX~ZDV__P9UauW9rU|yl!@gpX34@dzJXa^6{$-HYXyfs zi$rR^tT)E~GGAhkQEf6o)E!<^^3k|bu*EHjo+b9h!VdQ{-r>PV;U)?ziJT=^+fFZ4 z3CR!+wyklf>)O)8&gQNfoYL&J-MSLj!ByBnlsp(N;{&Znq2>|P)ijnZGZ0E7{z^O*&HtHJIMDNfgOZT7hoNIYs6<#R!l zK1<=&tXP!7SOFJSA_(w1Z*mg_ZwVM zSZ!V;LBq3w8`T~uiVmlPW5=kKO)V^7DnMw=SFm1`?$*%x@-va1&m0}G;zIDh?2Ztm zlt7rairujRS|YMd+%ZOZJ=iG$N<6eU!=&*d^yvtod~RZ|DDK@32Pc}(uqd*wm)n%3 z-xmdPgZ?hM8Saqim1x_JYj(#W@XhUAs#4lNNGh=Y8dwk59zUC6?^QU+J5(wsMJl@C zxE+q8J*Go#L9dljnAxGaZiB43Qja7(1FJYSk(gf&mv z+LWfZg$HZrGLsz0mlicH3oIk>pEx7@J7zx%v3X&xuWK?AyQvw|t9p%lsWXqAG`%)*APZ>C0x1S+B$n9I z65;4}B5s|$!U-yMZ&+(*TiD7G@a!2CMQitl9w~RG1`(NdcXl z(~hJrt_(6O#-Q`-GXg9~{Z=|&W3hA7ETv$Z5Bui;eaLd3h`#EM`~4(3fY9N4hnXua z+c-0-nBblVwVIxWgVU!l2u`W$q?3YX6b>&)`BtWk3R6&f^O-=WS(?V75ZVQrB-QMt zI$Q!rGZNg|VlxxrynL^az&56L$s%FC$ji^?j^B0TEE)_?Ev1Jssv_O5@b)<1F6@6< z7xoALy&Vm_pU9inpQ3gcHCnqpMAfHz9X_W}Pf4#1jOirpJvosb#%By(V2$DV-HO}l zn#sCMi{r-B?{zv^Iy?YX@JG`t3^5$04+L4`4}^w)MBw%6nW_<*tRo{uF?$l zwmYC`iqV(8mk$LesqU1*BwfK`=F(}H%x89?oZg_iB{oUP-W9%S)aYbTpPMOR7>?@ zO%PX3`Dq%Go^`FyVm&EckC-Nn#x0ar)ha&_!gx%m3`1R5F97hrh=(=YT`Dl=MkB|) z&LGO#C(=yD{wWdz=*V0{r`=7W=m1In-fa$_@7CcxJ~Ot&%gdj?&&q8#i^lPZkU|`C zRUYYaez@3mp4$%>FN8j~GW!*s-VQ^U@s|?&uTRidQ}i!fx+>G3iunRo4_u({(KmS2 zZLZ%KeiK%vwLa~?<7jfk+jbU3-Od5#xUwG1Yb(Bf5>KcWIe_b-u2Ly6P!W~NY*PtN zm9`3qn^szqOa>2nu_DI92o2XX_)LaIYpY6v>5@G{1q`6M5ecxGIKx<-dET{yDbq+Y+{)x5)kWe zDEPAB)(4mD)=zV=7HKtczhtYKgfz(r=(+ZMKeN9|eE~%|Vt$mVTS(je;R>TDcK@9j z2Hr_J>9E~B(S)?4d#%eG#kfo<3XuUC36u^<#4ik^FI366Zuf9t&Y{LasEN3m0 zlmLZ=9a>AMb%b?lN=+lRj}TRYdF&IgKQaF`ClQ3gK}xu(Je)Wvledn60*hFXV!|V$ zz->mo*F4?^g>N$Z2#X;NAqZU*{1>SjBsQTjzsdQVIkdYb+}FJ76;#3)XT%`w-%LTd zPK!&zF*|+4gVy2vTMLByB!{0wT)NRY49K8lA1l&2Bok$Qchc< zwWUidyg`x@oyX-z4S#!N6Rt-SwWbjc5Z6kAqxLEH(t#u060{qaPNGAF<#^cD)b(|s z*TLo0qrg@J0pn(K-qBwwm}t!rpolIjj4Nl}^zD9XqTb*{a%1?oA9+sq*v|Oe-DdOl zK8IUo)YIj`!C>5glUZF$firo;X|CedvStIcTaKu^|?@@oc28H-3X#-OJw z2q<*p!D_fJCzyBCM3UiqXIx2=6(&#g6_zlGf?1_m&}m0*!atj55w4Ulmt%pK!g5aX zI7Qh$;l{t?F;Rozfw;1Ku5KVnC8ZU$dSNJK#GHC@OgMLb`uzHuVWcgI8FQ5~~~x4Ec|WAHXD$jXo?uv@O?LU%@$dqs{#=ufGV0hOt65<9dT}x4wUXoM^EY zEQtKxL&e@nK|(^!Nzjm|I$RVSN`t~ia}$qxlas@5y?yshfTyXlhv3&2&!F4V3C^MX zCb)pLT1?FP5f20ILa8=VB=yA1o>rg{@J|c+ll(!i=U;X%bhq0(9*=1PR|!M7k~5hde4CM zm+^Q$gLf_Aso38pEw(OMte@!AKTbg272Z&Firi2yIyZxx=zHl^^#uK)8R z7}RroSnr!(zxh8|AA>gl03F}}01yBi0C9C?a(ORPSWr_g zX>fUNVpUWL00VR_4rA^U4r6tA3jhHGECT=l1jqmY0L)zJbK6Fi|2}_3ul)g(il!(^ zq$F?TDmxk{oK514?QCXCc7+X~LG}m-jRr(>Gx^`=9o;xch&ofdQ#B3+Hu`w?`@Prv z{V(EKpyJp(dnqpeBE;|Te}3{?v0f^8`Ye(8RhX^PXMOrkhxpLO;72Aa^FY~Y;qT|-<3fpsxzL3X zQJDsX&QclcVl9eH%=NVrCQFp~?){q=W~l-l=|E&l9AyS#T8KnmDI-dwc5MP_WRIF; zVHqph2|hi*r*1c}qKxBgCDvJqe<1neOojpn2^=n#IlLd>Ub9?e{y!CeF4Iu>Ye8p& z(+s+3IZa`IK)QLULnWp{CSso9(P5<^yH+`EAD606N)w7m$)e2Rm=cyT3%fz0i~1^) zWvqw=_yrP86|~pB@XLbNNwdPPrBgm6trdd!=(u9vT%{_n7B%a%kYD>E<9*qK3mF8; z)T{y~yrmJD!87G5f^%5%F*jHMwpov7xt!IU>1Iio1!baOC%9NAS>Nj16nzn^!UM@n zmRVlV%FO6Khz6b;v;wG*Co)C?<^`}XdHD=y_zeG$?sVv2kcG;sn)<4O5;yT%0NgI% z+q8`1mTpQFo{jyZn{_iQi)C4m&VTOKR=fWj)JRB>^e?yWy3=w*>h#DD!X3NYvK-IF z&vLFnHd9d2qSHR?i_1<$@Rc zD0(5ZAG=nt!x~f#ZzOAV!f^YDC>ps;jYiplAqD=?HAyw9v1D=6iW|%+ZevbyD|3qb zGpD!@bBb-|z|glP+FO!A4S%2hr%1(#od^pqY%bIgq6@m?K#oV~RgqReN8r{rdJ)nvh*4e}3QFi0S1H6vI>HAYY$#U{ z;Blw|UgGaNqZNok-2nL58Kd;wL9Vr09iSmKDh3G$(!q)8z%&`_{z?}M7JK+|MqkY6 zo0+(lxu)IIQ=Bo<@BxH~X>NmH&^hpDJP}S}nzNW0YR?l3`e}4}d^C}digT7Z1fsp@ z)3Xl^;;=ZtG+zer*B_srHO%Jr3v@slOrQSv^eOFr`|n zkYgT@Rvf@^GgvN`&p+ZxN7n7J2VP3x_-zx=Ae>@FW>iDVF+O~Td22|G=7>G1syJAoH?eb_AFpXes_mxhY;>5_$cpNltiy1y!Z(t(Ct|s z$g3#uk5qK{FkY1@ou|P3X$5ONF(c08(P$j_;Zw-+`A^^g{%mSOJQm6E@Z|2)d@C41NLDknEqnzz~st??voLP zC{q@S){xu26JM&0ZmtYQ2$c9!$iX?Gws=A_3b*gu9V!8f6c|>(KKs@myX;k^B%|;x zGGWzk0Aw%(y-5MWfvk}gy3OR9qmyU@|JA1KUh!PHLg1l2P`U%|Te7(EE*+LJMovIA zzmQ0febM7#wxW^Bgu<3Gh3Sei1tPLzgPB!oG=*F-oC?0dVhLuy5jZ%`)Pc|jt(Gpp zw?!1ktwjQ9xLV{o8_ zUr?%yt5<WWF}|V_2n4~Ifgo`3#1=3Zsk5V_KMxoBvojtU zhTj5pKM-+yD3IqK6LQy_xeRq_&JRaB#*`@t=D7#+0=u^seXCKfdzao{En|KGGwAh& z_v#$}9S?`Y&zpWX(M7NI%&ObNT)}u8hT$aI;k!2ZgrzV_hg@Y%`iR%q!W?LDnUrV; zS@anzmDZA=usejU#-xgD`2zBD728oQtf^u4ldxMKEx=2Ld=uPl!o1HJq_yyMC%*f= zyhgPPWS%3skdYCxic6uor1M^bfx@#u%2t7bwrEI6lvQY|rm|NEolk1jQwQRI&~1?~ zEYMukBj89t0t7j7!^m;vCbX(k1?TV z=%Y>`0?_9gKe${-8NQSnk~EB>3`H5D2M3>uP=_?Zckkbry_dUdz=F_*%6kwQ{k20K znzY!8d+01ZsxeUY0G`eEFp5wJXT<1IueG!ZOp}*DCZ)->8FcA150YP-D9s-L9X4hL`nZ$N=Y|6ZGsp9y0{PM{|-jSb>%3 zAx45Wk^?rY<0Nq$F9S9Ast~W)GqAIpgQDInE%JT*mH=8GD7Fz){W+Z?%6#QRX$TrAMX+1~jE zCnSmT*S10oPk<}GTDxSF+UQ83F#85DPUj#fPNbcTv_ou!iDV-`XzsR=6_Bs!H^7Da zq(?V~usD$~jSg(!N%7$|uIKt-K(WBtT+6sl7PR3|6^PzxrDk6Sz}jz+N>o^T)k8jk zG7{(d_N;wT>ug^*b?%G({i_um$n4F!=3bG#oVh_WN@!%dZkYU5W&mxQ*B&?K0l8Z&2m2-=-5{QZx8;A53kk?{a ze!Yi|-z*_SyFEkbuEd_6$m654d$i!Zc=2LKQ0LCOU9-2{GhOe#u00^XU(*&sj5y1# z5L7NIAbEGdbO+Z@g4kkKX(}+c5;#mNn7cAgGUGx#@Vf&+-kqW<3>;Wg@-nR=XY0R9 z8(>uQVwksKUKpJz@85{wWOy<cGCvX$!|{{~@Lk2cuw9FmZ|^%O+|}>2s=5myOs2MN+ z;fcQkqQ0GB$@;?wutvQKBDwT_R5E=RMX%vJuhV&~ywBf^_QbzhW8g^M=U2lA>S!>Y z1nMvx-9twWS$RwgCFVzcaT$>{p^gkT{}4&!$aHxUduF4p>;!44hQ8-d;*`D zVX5$5|8(J1$vR;ef|ntc+(!Y}$Lu9Ul(FtT+YC=zkZ@;wgr$lK`gPz5ZNt{dE%IH* zL-gFa=m!QKXQ#v9G>Ud;_)UF**oL_sU$_9n!Cal4!TStMd?ahyYV0R{qP8Ut^~r|r z@8A9YL4}(tJ2!9OIVdv4oiRyUVQhuNta8FFxX-{IwO{uj5{#G% zmFI{LNunD$6)EV#2@te`P(mH1UO}nbV1sL}!IlZXpnYQZBhZJkt|$gI^AVv^)apXJ zwrwc6qW$yT8wWo%Oa$>?su+NQY zBe`u04-%^3!>Q0dB&IG5B@PH)!raAEckmvbo}C@59ha-_5(sVS38(kxAMSg zSPT||YJy%++yd#aGX}p24m1T~+cog%+Eto|tLym#4DC0%-1-*xBwcfOi(TF=)^|O< zurohLEkT>@YP1%Hr*pWKY7_5j8WpnB1bIoyX3oYsBCyYa({Njz0c|5KCc8KVyMr}rr~gl8 zKaMAnlrp?WOSQco0J(SHqK`!`nQ3*j)n*&Bi7;&9EA^F90}#oREv^t;Fs=JN>F%Mz z++)L$x1@$e%hqA$Ss{85JYgV)AS8!mjh)|Mk<56(`5i!fl_+QR z{ZqpiQ2)G~`B!BX*RZ#uzBtVYdTSrt+>B~Hy)b+Qy`4b!AaSGmV^wZz>N#4lcfR=n zrfzDt>$}5L)X=qlG8ZnE;+gGxvyH-Xr_I{mpVF;p45iS(?(bJ&0CkdUy8drEGt>$O zq{JB0m42fQ-{sk8Vz*%f*M3pm^T$loHDRE~Un-jhVbugeje&TBVODKexewAfi1&sv zZ578+41&O+O}87wb)kcvrx=;qpahhJZc*4=n7br86ZMjlFN$uvf1Ds{qb_drl#Y427G zzHQ6xejaAblsZ#kG`UY6HJ3W&`ahODH-8|a(LjP_3aSrw;$XLSXYl3wU6NQw+|{pe z!1QSU$(%Im{R!;(2KcSr3W6n>FcVe%ohvGYJ3O@rpPi1*M#D!T2{--}1ZS&Y^un5G zWjJtT0+GQ?u(u&Xmt1EQe;ZClhoi~h?8Rg>IyyY@K3{qn5Aiac8QeLrS4yWP)#%BsK*GaXLNiiM z#6d$!U%R$&+8*$BAimF1U^rbxWNbrMSvNNC)5R?dts)qqU{m41RYzNh$yNYVHTN<6 z2%DfwfOt(2XjSc17Y_{l4x{l%`u8axyTC6FcEo*g#`b^a~%~(wT10 zkjP57Ea{e7=FScahz#H2&jS_b-j9E~*lCx1o#M|+oTJ&?Iq;#m*JKi@Xn2om-D~^% z7gL9JQWROWf;v#J(LhJ22IUk4ZPa+D+=$5gViwm7nLTw=&B-`Aiw++tG>r{DD(O|j zQ{~IiJqZ7?GQQLsn6w-2HoDu%09rt$zx_5Ck|Y7>;UP6Amh7-z|ipxnr< zzztG0rZb|VnR((pEM?<$a7Ml8QKaa;WRKj|o>Nk)P!+Ds2rg=kmRs=8hq(*EH8R>1y%ywFCAhX7x_Psp z-Q+%Nn-ok5Z7I+GfJr;pq{3Wd|2|<8jo(A4m}b-lpsQ0>Li9@nVvsuVRjL~j+c&>_ zv<{pu4`2lYhztqtJ6j&cZ~)afCQo)A9IhN6`0w>!)7b*-w4Ec=LEa^*~cO9tOT2 z9o++B2abaXG=3<^YOdWS{mdX%lOsAE{fZ7tp}P}hoIE#_z}8i4Lrt%}z?D)iVgYi&~7+Ops8uh6xt zhVCvJ3jqS0@l82#Cvg(TE_b_TQnt%|^q^Z~KnxP@W^U!b&)JXj5C}COIk`18{*p9E zLOMEUpZ!>SE$VfUHVSJ}*kMvMa19r;M`7d#!*P>Yl)`vyWKND>4pkYH%{kwgZw7W9 zqS!pm#S{RkSvtvETcmFFR!KZ^{cbp@cn6y!PSZ)l!vOb&$wiTCqAmEc>2yUXg2)SN z+QcK=u*00ik>7KBesg#E-gC#JLHFbPmZC;%eLNC;90{*Gjt`(<03aauNiSohyy0Rz z)Y;Np{e?b`@=k6EQVLi8hC*yg&XNZO4vDmScj`n+ZGVN?5{Yi#_j?r&`_*^Qztm&^ z2DaU>r3k~pFdlmc)SWKD!`D<1cgEdAi$+Yr(xf>mTNuQU)}b8=X3^K7`xr@kHxgb2 z!uKy*quwwaPXhk{axI`q0b(jlQN3(w6o^Wg;o`(`Ctrq3{EQx_SfY@kp)dlV1sv*> zx)hHwe?-mJ_UUEE{%+$wi_rnMq;9aiZ6*bD@k!+=H`e9pHHHC-IBvNjZ>{7oLNpPe zQ2&xSrdT2rRA?Zpg+>e#rv>~>W?GQAq;UB_$7gBNx(d)Sexe-}e6yyTC`P@p-0h=A zK?pnDEg6mnGGNA6*q9skua-UA;h|MNi#@li4y>w6{Q7;jy2EM}$Wyb8!7m(gY~nBU zz1SO8;4ru95%Ni&W1XWd$YSU{#(UygcJTm4o8NM}0lPGEal_atCQuD;)0BLgE$c?9OAWj%^fP8? ztMGu$oQVU&pA|3+h*jWrNluO6M=I$g?IUGI@Xkzev$OHk*T_+kLEmq)&Q>((#$j9+ zo4fIfs^`B~U6xY|;6d^k#~$2gC$y74P%^XzU#|TtSo@z~CS?^aSBYtq_6%>&d5pkd zN(e9n7fMmkw*^~}E`k98+I*g_GiMebnGzX=Za4`K2=;KJe=|*Kciv~K2Tqk`tlJLW z(`Lv7G1m)usqzhXH*KSQ_fM#<`~+A-)APyg<KqY*kMoP0gD{h7p-I zc$$s3zFw^1%xir#v5FZy0L}q|0=Vn{@FdXKGbd*3tJ_ zT@L^G?$_!)*7_Hw(@fseKMNOn+FBAzlfK;3Rxn+~tv~j>>Ux^q;O2U4_dst1EAapV zKk)N!6}iy@cwpr&Ajh2d195*1smqsEl>j1cg<@o?fu_YoJlBgvzgA`JfoU`L+&y23 zywEea*nUTg`)A_*Uui<#EaP~-H6=y(`G`4bj+X;+B-CK!_50$GSYJU6z=%FT;zfov zBmIkAE?t)7E8=cO7m5x>nqx5&|0CA{U`^*>P}_=*K@pok-arv<(EPXPBWz41!bzd? z0)7x7T2w$Rwq&cF!n%*PsfejETnH$w{4S172Mt?zCO|gzYm3BOaZKRhR77J|Cb}u8 zG|2mhS~W5;4Lhnw5BGh$&S5Z$Y~I^n_m} z3qOqFzB?S$%<%CYs_fp889g@~^uy=?k6ZQmVA>Q2mzFU!7^9OK-!HgJx7a`k*?3^A zVf=w64UkI#=%8TX;jgrdtq90n#~luFbZWx};LL>CZj3!}-fL?^zm)CewG&G}sKbHR zE-!bkE^`i7DNTym8Ai*hRpG)ba7AREiXD!USW_Anh7WLJg$ z=wIwS6MmFbv#RrJnBfld>Vu>YAm9C!SXSoI%95TK*B@H~5Z$pqIiRaa^I*&>K%NN> zN_k(qb%5b6D>x%63r@K>%+xLBH|OMzs8eB4FD z#LcFYQ&w?+Dc4RG6N9aF5pozWWAtj!q?Hsi6SxRx^X?7oPzyuV7@}i1zI;a|4V@3biQd@ zcghsr$DxKTeYwBM~uOXP90j zu%`kvCi8H*0=^o+av;y5^tl&lM)d7WOAjkIBg~-9kH0~VFQX0JnX70546Ijkts6Q+ zr*#j_!)mR_A66u`t?!re7GA;Awb+|cCAVUP6|8meut~H6n1AN+-Q;}cra1XH$VYOA zF#RQBz6uXy$fF4bYPbUB&G0LLw?QukS(>m*g4-?_8A)WFTLQwI-s?E8<(b&FFUK}a zGIaYeN|XLm%GBEN7p?7MUm9U)*ntCATye)kn|ctoj^1nlsf7K*uYq5sf@WykQr9{PvXu^$GKi43w4fl#x;`+DV%F++G!QSQm&inS|?}a^g(4s7?97geCJcG zcLt7HfCwZMafk@F$-PNML4YJ1htt z2zS(vYvmsI5i+)Ro&P7s+=Cd?eD;^H_j&#PXy^?O1i68>3Q}V*jF%?{E{nywg9Bvf zVu+BJjzzDyptjPcU4ojc5$JYpSv~lWf_S+)8g6cKhl6f3u9bT#dvILCt0U9M<8aUw zhjjP8h1j``rkCspnJHu(f?OqZ-=#~+jxuTn2=)vh$862G5tfo%pE=i2O4~Ncv7Fe1 z)%Y##Z+Xo2jf)z^Tj7H}`9$_Wo(b+xG7PHkk| znNL0X*stFs%0Xt=>M3Q+iOvYPH1xP`p5km1S+%N0Czt}(@O>YS4Y zX3uusHt0r(Xu*)Vb9aS|!hkk;u4bLQ9*WCmmTZM|$1#kCA~6fdU5 zya7)drgJ%dTo1d3f%pN~=C3o;`yt~~nk>YK3&&^J0cs#={K#aM%MtchWCzS7RC5)o zz%v4wY!mUxY4*Ly^&C?}a-#UhPhpD0-0X(14yIttQadYD5Mu###C)mw7B@AKuW#e& z;zVv^`f45PCZO>M#vI#&a$>D!Haj^zR`Yob-n@`s>}=7Zv>h(3-K_}Z|8O4`yU)~n z+^~>q(DQ{~#pSwBfT~OdzoApNv*)^Qr85CM zp!4+p860@zZrbY&$C0N8d{5oqDd<)8HVhk0emO7M)@+!HPL|995p^!|Cd?YI_;jgLvrSrTSpWAaBZ6dNy`aih4D(5VN^ zto)5Tv4n^!EvJjTDhYWJ6b7tszA~vLQsIym>MkHkE(a*vm$NhNNvU?=Y|0cBDkX*ld+bd}M0FrxZ7IQqwp18;Ex9f7_GCjmp?eazkcUj{_^t8n~N81=k3MiyH^*^PZw`5 z%8Bl{ny*wdbP5ldys0XEf;^l_p@^+gdP#oTE|^1fA+Wdxgi>mRpwflxIuF+Wb<&U` zO{=sI7vQ2kb}3tTqiAC-`nq>$Cix>1{_Jy7)h%|TRq4eVp^Y|yHsnSnghw+8e9FC=lO3 zyCdd+3PfFKkYPFP@&+vptHQ$;3jzYz27oQlMkEVMP1q`1$SJ@}*kM*Q9S0VE0fIt9 zm}bGqIK)nEh*ab40bb;eoc(POtxoyY8sO;d&Woxl7dONIEd^;~4;&?LS_Azk{f6&%LHxfulF<58bNww52L;oM^+yL92m1EV+(= zB3py)Z>5{;oIy$I43=v;U14qn%|#2!otoZUzQGnh@`n%aSMi4rA6sqKi~fQPtqv-t ziF_(RgbNCosCv*Y&V4Xzu$twf{fwg0Z^SLa8WWUOj&PkxTPSTA5|Opp1q-I&qRnEt zwB~E{)X+?U&gUKQ2j|~5T+r|%zc(4wq#63U8)mON83u>Q&i^V6Ri;FtApeLQLXdzo zwN(?^^74gBc0`k01zYly@H49U8YK^t>uVg=J_?3G-5~d&H}Ssp@=0&ddvcS7Uf(-5 z@Cx1jxZfWfKnKN}qYk>1&^RY2>CqG#&5C7^JN9?6{(JfMhER=!K++0ToED_}aNnd3 zZ|NWavdV!Ch$-`Q*B`!M%uZ7t+TiP&-j~Kv&mG3y3S7pa`_j=XOZ0p%_QON8#Z+%1 zRuM42)goF38T_`ihJiOm6Md$3ys_DEZ-cw5${C=5#LPhl_N?#1Hw^_JM7EskVp8~1& z`Fu4C;$?Q8PR{=e*luwh!b|1E36*tK<`ze07Keix~f!0%QW8SIQ>r2**|EkXJ^7Sc{FfuCsZTtdwrC*p+A zCre}#mEOdXYC9%#N|Gz3UimAMNUL~DXwU37O<{tvmeSQsvA$$vj0t53Pb9}DiebP5 ziq_r}dKm6}hf=8mvXpY?$;o}{oG{-S`uRytY^zIHsNXtRI{g+zs9Q6O{hP&;;FgK@ z4AfQO#i+6nVqY|C8s(3;aF^_=pI%w);b5^hsbu+lLZGlbgQ^1FvHS_F5IIh^vQ$QR z?<6Pf=@QEIGv~W_onjE1;D$p9HuUV#aBWuExHV;mq!VvB(t1KtAD#pvp^gLMF4_*r z1w=Y85Ia7lm2IgzvrZ;&Z7A{>1JUa@_#F7isY<^waa`sQ37)(-z#kK6E7!oJhLdT1 z-|u$4ux9wh(3Bz&dI^;M&;GjuBT`&IC#_e%f4X?}{N=Bop1*kU_TsV{BV+#U!B}4W zw-Oumw*#c8G0 zVgC8~kEc#S`Ne$wrSwyI(vQ>`lFVL$-AHgRlr+@6Yo;>r{?+a&{6=BY7 zIPWwbMx*|uKHKxARNh_&XWtA_)kSs-=e(!h=p&=X*!M(#_n?WFCp56o?(InpKeMW-|@x> z%}{WhXRKkvjDl_`#7NYlyX8KCop19WL)UeWdy~VFH|~YvfaZI(+wByU=S|6Zv0GLC zs#prk6jz)KPL@ghCDs@-ekmHmbbijO*wB1%Nd&wZY*SOG+{J;j5H~#0CUK0EmQ?eG zv`?NO7ak;h+6KL-o2sx!9@NU$3*$hgTn~O~Nhb(wfL}S=VmB%~XQ(eBgQXbu>F(hU zOLJK?zUA6ljF?!)vlvr^=pe|AWQv`2cZT7MDMeJ1qdLezYks4zKdK=K;BWlfejuZ1`rpY%@+Q?fINq;mL^tC1VEA^L7H5ZFtyDNI2AgV3kCNi zU70>hi`9pL##$NN3V1rsZ(3>irOB}%E|hp@sy5nECm&lWUA*eiT%oHeFW3wb|hSFc#zG*BUFm^5G&@lOd#wIS^Csi79d zK4f||a-8r)u!Sugs}?-3BAkXlbvG=4GEo)fE@lEjt^g{LGvXvwIyPyc=M!_`VlqLl zgIf{?=UPb9Y{c(kftwnzJfBPxJ?3 zbU>@cbB3!aXdy2&bRA)>L|glz1+CfG2o?7(w252#^YYTAOVW5rTa+W^-qLRNk>?qD zFfiUF+kS3SYlDBW)1%0U4&oH|dUI}8AUEOE0#$@Qo;;TK&+y6v*i01n8*!ohAE7fT zaFLYpnQZzun$VBe2{GNINnlzBop5glEM7f! z%6))3y>tygI$l0*ykwzpzNBI1a4sx#IJgg=F^3A>t?a-UzXF8sj|0nVysWH^9EvCgH+tH3tIc<5!O<}yMVvQL6(MMyp<_iB z${9+8Xeu`?y@kvhlvsyQ8ACg{*}X5xEraQ&Ps&43yGu2I9&QS*x&N#1kkYTE>>hT^ z>c>Nm6@OWV!qTFDkT!5G{mvQOtUBz&BbC~bQ}=sV6v;#yXt={?-KNdzsOJub<6b?j zt*V@|${d?fke^j$h&8iB@zE8*ME4(eY0eq)AH6HSzBud z8MJm9X}wJ~Ic|xJ1;Y5e^m3if&h{D(Z#8{0g%KyJ zGrSN+SeCkwu`CTOEQx{a^t3<+U4D}U4b(g&tR~aOTwk`9F&J?*qj-$M3!+D1Bwb-c zWBH|qW%NWp^ulpXaQl{s&jC#{4`REt`VKxrWd*XZESi#7_D17y5{wV$>&wfy&x=c= zip5sC6-v4)B?$f(A(sVkpsi-|7a;|QKuRQ!U1KhX%6r>pQ_;v8v#Gec?^JNl2}wdw zb)28ULh5C+J;O6A9!v`j%GdD6l^jv&R<>Rw8CLe!u#Nv9CtnL}aO=A?zipK+-8aZ@ zY z^KK5TX{hW}=v`OhN&INV*5BiaogVaJ{uEj#`)Lki)j5#pdFW02QOyz)@++?wm)p<9Q}U~klaBCmC9TW~Cc`+hVzfCg3VY_(b_p(JS$ zV4hoTSys(igLoclk=zJC{_dQ}A81<_)fZ%w&)tf;w+f4G@f;Utj8fEsD)q{3L;A6`I8#24(y{kxK9fjudLwkpr)&Nd{?jK>aXam>c@c_4(ru1 z8}8|8I4T|t$K$S7OHLXe>V41sSk@ABob8+VUN?;61HAg`x+rkwlnXI9KU$*YNUeh< zkg{t*KPfhsb638q`Y2Z&xk<3sbg_u%QF2fHGNv;{U_~xtM*AD6(4ouEvUh00;F|=s z&j4>^Fk_Ybs_F=SZ$J2DR{PkuwQkl9r=}II9*|qVEXd{|Tic*}2k@BzOf5Vrgj-{SgFk2*z^}_kBP5)>c;Ei}(=3^%a07$C{qeW| z1yD-^1QY-O00;oRXdYF4bgRc@Qvd+UKmh;_02}~hVRT_HM^ZvAX>fUNVpUWL00Slc z4P(6i4P$k93jhHGECT=l1jqmY0Nh;pbK6Fi{(b(6j$O4RT@)ozw=f0sn~FxU^nv*YQ3`*2}O9qCfjSHJyRKC*dL zgvJd^zduzMbE9TPhp{)2HfPwV;)( z`@X%HC)T~JmPLM@M8>KpDNI-@9frnQRpzRs>EF{lcqFRA(yFj9x-^l}C4c$zKi<82 z^ZMg(UY1Mye0O)XT1|Ml6CKiGcRv^9C;#qfth7}tq7(kb+Y6&Jo@b%6WLc#&GCp6Z zp_yDxRAGW7%aZI;rOD;IRAE(I8#|h)R|X$0by2E3mPKwMk|)r|;bgMQR7;lYEEuVz zR73#UbaI)|cH+EHl{IQLH-%B_yiz))oknYQmE|k?I0@&92r;xH{&uCa5(~dHrNXcn zV4}`r{xZt5K`Aemb6t*kbn{s+mZ|ByZA}V#^Ogf`X&rfn-;zRwb6x1LG(}>|B(!6B zFCp4xhMubr=3kX5*6mI52fOr@+v%k5^q)PuNS4o@sb6W(P-hBTr7?*jOMPjSEep*Q zFXu&GUC!y?vWQo(&}+Z?u?i|)I}wat)GE!1rWsLoqTb~yiJPqwc`90Yer<~D#H_}C zJ|4U#o2LpBZVuw*L`wX%ISI=gXKt4!OyVRQtMdUdVRW5XAt^oU2Bptq zG~@GSx`laGnv9q^O|mQdarhLQ-tnJ~FxEe-#j-^PkpydaNMVnH=b_D8h(nU~()ri= z&~iFO!|cFA#Z|^*Sx~HO$)cu*19M5Au99-DX2f7KHDtDQLPKm%%k=WY^a z_&7<8<4Q14k`Wo`t0m4Et7M@d0_Eyj7l{t&>9!d3QtKs+vsmUu2}W4viLe8ajv36+ z4pN(k)q-fC`82?LK`yMQUcNma;|>0$t+<#c1Cv6SjhsoT)2sSbVlME*l~KQ3fEIMx zYueI+<_MK3ioCGGN>x^vlsDAYO1$4OpBcu}*_x?O+g+wQT0PkvdON$M z8J<7EfFJ39Y?OM8TS(M#Hq3lBVTHwi3iB!}>D!DLpl$T_+$U&bW&H6gp&rct8wuU)1|DN^8chlO#? ziwGy-w2C`!g+*c`%%r_%OcwnuM)sPjSF@}4=dA@X%ZB-4K|cm&uCEi?3u!!F^r-XN zcP$%BPcU;ki`zV>6g~Nqx6AYx+vx5Gc|ga zm?<<%(l&x|VAtHtG?pQYy%x*M>Nn0)Voux3o!>(Hz`Q_biJe~G1Wq^XrZNdIgbLOU z`DA)Q%1U%0{aWgRY~cBu34@g=7YQ4ZO@kq1rOA>QUX3t@t(MDlEej^-Of&yLDG$D1Fa?3_zx445n-D&frHSVnC(koJW+`n67w@O1)5F7~v6@czPmYd0(kpG4*Wd;077$eZ zSDr@Ra$~=2cLYvodY;d~!;qB$Xd&h^1ct*a5Sg_nJxNCRs+Z|51eWEEyf}V_`eh91 zu?SXK7(&OGx&X$d)kYwofFUT=g4mmw60Eq&8={WAY+P!rrXwL>6X%0qnjPmmZR4Eg zz|8Z0^*tl7Ru$PBc3yVWFfy^O(o)U7)u}bJ5f)?kaEXu}>Gm*0%MzXL`R zOjeWsh3v|ETD*bU-;xbL z&(iCjEX4Qj=(iq&8;HhcKZQgrWh|4!@@OyQ|1l@~nJm8+M$zoz65Txhbt3>t#>Bs7e6}DnEg>5#|8+xqX8PJ_i>2cZMxIB1TOBbZ7hD| z=4r83_MVif!vPqDBqsPLyZCTEY8kRqcPu>#Byurkzr+l$;vlFiMt&kW;8y1X9pYvDfmOgukG(_J)U3u#s5xRFbH1)WHP z*24j{SZ4`{R;{fi@RObCzcTCAL$U-Q{%>Fw+~NYmLj!twrZ>z?;_HJx4qV-k1lTio ztnzFJ0(pBjmYp`s6R08d;1&9Hcqf`S_qA7>5^`ve3A0JrTK|~9iA*97sq;7eknsW} z1VLd`7Qj)Y?-muAT5B@VrWi#Bh{S}YXjNETHxXlnFg3amiKf>K2aAZ=7CE7?ALsH8 z0}i5GTotTy=+xs%SbD68eTj9UJ2=$9%yLr&F>3+W2p>V~vJ&)9R#^(epTPOn?j_N% z)?-ZYs*&)C>_A4CL({M_=rGN$`Aoa^=Ta+B&~pWf*TTE*SuJr-=CpAWqRTS}MBNqy znM~Acp+@wL`X12MMfMWM-EuB`9y~9|Tq5+kOf`V^7#ozJ1BdD+4|Ax3_{>ZX_Vm3S zs+ObL<5so3+ARRGU^s>VY!VW4L9Pk|V6bMMg2lpCg#&KJd}QlF5`Lr;EOUxw(F7B> z0HQ4(|5Npj+&>ybI0LH_q|_S(LKw%}2ACjXmU1(G&S1A<|D8sIXh$4oWJ6U#0#@93xXfA0UZ#M7&DWYCZ6p%SsWu-=$JW17Vq?Uw9Ihv^NnDpRfh{t0`u8ybC;n`vMdm>j=bUE1|ZNKw1Zih&2 z{OM5b_D3V~Z{8%ui@|QWSnj@jd;W$r;U}8vyVv`JFU&$vF)Lp%7#uxJh{NfrIXO5A z?k>dVUd0nT;nYP%&SWj*w#RX;ye-fi$NHfQo16e4c+#QEr2$KSEjT@f1Oubwtb)@# zze38YmnxEpG0vcC+k$DmLU~hINJl2nt-R7zp_jtY&P(h*kH@`?vg*Xt?~`&Pti3&@ zMyubI_~))n@GXrtSwv51nno9nTx5j!AuN(5YXs+~A|Rhvw(Z|`GC6D^*!I+65`C?c zlw;mRE0=D;7Z??iD(C14BT3Ucs%)MTiHtaulk%Ca$F7#sb=-?K-bK-R8bk#vScTTb_ z4r26hDXi0|rT`*nG*IJ`XV}U!NaNSg(tOqe zfV}Lm`WNLzKtt414Wl2M??Q8SxUbL7{_peM!JYX|RJt(U19a&RtyROd~$ZXjK5(DB#+!R0lnI~ zUr`IBvc~7yf#V5WT_)laHiOo7zp*Y@H{$mK7@Q%Kjnt(X$zvn!;OO|w92}qiQKD7n zFNTu07^{Q5{ZtJCo5`jNR= zpBx0C(YFLL{uo2?2;6-1|ACvslfC%J?BC+I-vBWk0K*Oq2QAnIfwr#G07NBGnoZ8K zaZ2FBv98v(VMs(a`FCP$Mk_T-G74(hfelARa-{6~tjr+txl4Mwj=8hf^FULm<5S|% zdk~33QHV^sNO?D;lr?2u+kJ-xe0(x1nbh`iboxeIAKs4ypXeEvI(u()K_qSpSG?=d zlLF`q=&P6XU}wQ~Fg94DSW5y4&M&)JO6sdfB~+`uV^2p~g(c!+;)b2G??vB#c+w6N z%ECxO>ncic+Iy}7tIN+Uv)!G%{(4#qub)=D!?W%1Y&)2&zv#0Gb!IMyfW)OwC4wd? zyTo-;iNlT@E0d zK+yylGIUxXKgy-p*Y)X>ZsZzaYt+XiTpdc_q?*-wbr+I^LG%A#Fj%j zj0sYM!_fn|B$}F&;Pmhw7;|{;GL5{3(agn&z2*JyYPm)kW?{H6mg!2I;i{0bgqF0H zVx&6T`w2Ukx%BpkOL5w@>(Erlb%_}$oO`s$t?M#?5(Dn!j48dy7J4%ubW7+=%5(Xx zaBf_NoAa%Vak=1N;qsSBhWcEo3+`v`OOu&G5>}7xjvenEp9KfE9MzZkh9!9ze|Yjr zDn;pC;iC5g_sQVnzdt-_6=l|MpgQzh-hAt&=ELpp?X{oZljk0oi#$6#-4Eki9MaAG zzRLjx>z0q32#GU{v~yQphKhlTvuRRafzmycE6^iq2=nZmsis(;OTTp?5o;j7kxQ`? zWrSDo6utfq8cuzmSPSO)d#aa^q6;o>as@w0=8EQErnAtf@Be;SUEc>u|_&}SZ1CRvnRCs9TCM(Up& zvTN@H6)C=E1$j;Y^}4i$nF=a}HH23=tkTn*9vOm=0fXTYYYC6)T3Wu91$ZDxkB$xw zgE)%r&W22!H?l29pu}>8nv0u;bOwNZ@S&w01M6*rWoK!PwMc{Ti`?H4na|+Wb|M3l zu%8qFaU;a*=*+zZc$kl;&eKA_v(U6K@pFz|U2 zNljG!^)DAY8$Cf*4U-$)evAX5Owd}y%{DOIEk(Migzl(CX|Kg6SCzM$hM(v)J6X<` zPh0Eh*OpzqSvS75DwdrP7l=RGu(DRF^8Y-YKjbPClI%z-Rai*`MSf*6_7DovE9Xq# z5uf1=Wo;1{XFG`Cf>YXK;ufw0Zx;?UkwXb{JBE5>ZFFpo_xBINTO6XhYooWXAF78= zqW$3L`0yTX*-%_6)OfueLBM`qx}OWne{+ZpMb9+(??gsVHiyRynl@A8qV2lCD_Udu0%QM1DOx(>ooA z=E^apYPrmpg47<*SUmzyKonKMg9Nd21E8v&Gg6| z{z-x7Ry0Hc(I75bc%uj`XxcM)3^5Ba7&=!E!?rC|qssMEUe6ooO+%jYCZ zpJ)EtANNeKbNY@BENQ3CWep^iU~3d{K)!xdbYp67e&9z-))`x`#~cO zgO9(D2h7pI@zk8&Qgm=ri35|N_YbPayCY588P>Mudj2%-*TkY;=Q?UK8%+n;kVtiS zaCk6Q(h4?mjcLqFY!xnDe~_fK*`eTYm{XzR{MshvY@2>HbAn!AwyCaJ7Uyk#1~tN5+6``h{4*WhbX*6 zKWf!@Hh84R(MDB+3zzgPsoNjl#N^KFOBGzXK2mQIUaL#tSZTSmU7zm44Hb+ONXwy5 zhO6%C)L)Cl?Q~n`jK(4-*G3`+e20qkdB)23v~`U>gInmc$F7OnKb%Iv=`E(>%NmTU zx^HB@{5=BQvuSuTPA-Xvp9wH*oL-+Uyo-(JOqu|W7sJ6X7oWsTkfTai*#B}Ti19hi zFYiz=Eq~Vj;V(b?&@@RLgwyHCEisDC6Zutc@n9~8L0rO-idwxS?X;`YXp)HZC7!V+ z1D%M4h7ZY(-)yP?ZU}r#(i<5AGzh59VJ3#wWsZF6p>!Uyfe1kQ{o5pL3ACV zpBlFnIQ`Rrj157b`DCmWsd24y4~&_e#*seW3+_?E2ptUPnB=mExN2la8X2U|p{8)3 zO8$lQafnb02(!vn9$a&AHFuBD)_XcI*eX81r9;FuPOxjyx<6&2UUe_rX;#ef$fRV@ z#|k#duJiO7ojS%KVXG4yenDy((HgrR*(7~9pB|qH|^FfEwr#9 z9JzCKS)m!q1&v%w)GFVhF?RIMT&FSd%*qF^m@7P9wcNQYtT{lT1>e#mYjA9I4);s?ml?GL9B1-eBY4SB$_<{9bl{m9e`Ij@Z;i&t@Smx82A9_Qp-JLJ%`t% z;0R&U&Z5@qW!;(yhaZC=4sqjBtETHg;*POdx!9%4x%Ex!ZzRHd+wZ{`cN4{SNf^)6 zHzBmxk_(1h&59K*!E-%(W}iJHqgoX@9jo^JPaCJjxglC}MV^@JbZuSj)Jr(N!^5|6 zurzmXZfNT&zJ9^MD9nsA+wpju6nBvr1bMmx)IGyrNq@TB^M=SA_OQxbdr7PIq;>yV ztFy7)mh4&^nHze#)qZi8I=LXGOV_q$;El+Q6JNSX!c_xVk1b_nNNWLz*X~v0zHnP# z-83ufJmq=zZPwi5;V#T+KCF2auWlDN#E!*)u)?h`GH|^nGv(S&y|ahLI8q6%4&g;} z#f4{zO&}}mpR&m!G9ei#7tplRl3xffzsn^ZxpAgj1b5rr7fpuZVl#p2SoP~xpJv@A zy~z-pQKA}RMBQ627%jmu#EdU}*|SdTm|}f>jUDu^hHD7wq5z+#;J2GA7dG=R95Zx) zs(YEs#zlB6nm*x%MRfa)n&rP3&{Kn%zo(?zPq}f?hilWqhKVf+>d!b`8fxY;e}BY{ z{B@^Ks@Yqse$nDW9@Or3+n8~AT^p1BPkUe5Gr`Q5*wM8z0D2O7k_kWbG+iL^vN zS<;Zy&<;|;&RWo2B7o{Z0c`f0@PD7YYi1P+Xl(S1!^5y8u^Xt$%F4WXmvhgdhSRBQ zh*wCumuN+@Gk?w+I62y3wc?g@uV;yFpuN;^LAZDsH=i!ucU&O}&%rz3%JcYKsB~Ps zEM_(6yRbzhF8=u(?%f&itMmAVgs?*@sdQ!j)hYYro>840?A3U2uXFR@x7pCA}xGyB+dKf}Yq=;_4+ zo+7!-9VH1I$wZ2X%Y{9fE1n4PmQy-#^Q=%MbeM^mJ(A9iI}C^Y@o;idz5 z_1Q6#K{QNGVqJt~Sw6jK%|5U^b~G3aA8IggM_S!a9W{fRx6U8T|CEzRKfEfWk=$P& zx{!To-_)KyG-$uIVfROm+#-|Spwk;hhcvD^iYdU`AQpimQo|nZM{GxU|LE-v75`(w zX}h-YD{mt05p!jygM((*4~O+72XBc)!#Cg?@bd zyy=W_3B*Il!B6eF17I#LXUP)PTsA|^MN?~=s95-M>O55G>-7bK7%LXCVi;mIMzJuI4Q=IIk31_ZR z2eU>i>L8U~l1bdHO^)L0q&(k3kt5cka=pziTn*7WoeOmvGG)6DN1`eLj+5A zBTJMKT&pz4$4;?V9&1&*{g_n>mC%(O--QFcQOM+H0Jn05!ZQE)ryu{05t>I}$F)|% z%qm+{-gv4gV@C<@Gdw1M6gI){S*R46c6wlEBMYKXV`JS6b`l1#XcM9cDaItYpx2E9^P6 zY$0)D!Cr@yhaHO@pK%=WCdXqGUc+Di@|>Ca3Ep5nr+YNw9p=IHy74fMPV(UXl7%my zbAVCacO@Dk2`Kh+{OzUwnyhZVso6SVeq4+)IhicAUO*yZJlYC45A2H6g*~MWh}7;u zJbx*IeKA{-xPqUwCF1M6(%KB0Xa;vf9KB3ekQpoD)zRKTXhZ$bMzF%Je!!HquqElJ z<2_!vvz|+t0t^XlAsixv+V+1*w$5kwg0+=$A$HXwbATO?xEI+%I&dia?qV*Vd5udt z=PsB}rpah*P9l+kU4?x-=zYDm4(K|s<2yRGMJnpYT@xLEA1M}uXi4P`;J5-7R^kEW zG85J|5bTQy78}$qg84n1arR-sZO0YEUz>aDHpj;cYRk3Y#zTeWAYhb!PJ08EdYv!1g0yK!vHaB@K3 z#7nLkO3@Y8i?D}b5P>d~^%Qz-ZFj=g3HWd7a9XB7YY9JOAd;P}bXH&yYZiQ>1uO$- zui(V+)WFASAmAP`7lyn5fIxr0xb0dU@(D;}{VDh!*vP)xQq`82#?SgzR=Dj4U$I55 z_OSWLU>@`Qw0C$$xbPhk8>>`v~2$y%|gK=P5MJ3T3)kWQ+M$w?5KnCr1he(Q$z0h1*+ zRVC*xK_iQ$MGkqqbJsCy;yhvaYLCSx*e$R(9#sKuIL92Pu>z+cua_qDksThI#jbtB zl^(OuyeZ>3>caKe^h0eZKH`kr*ZBOOui1r2K1Z{4o?>uwRy)=2CvmSg>Yg*vUa?1T z9`|%(`Oi@qbN*9KoVzcd4L#m#camwhe?SZCvl5M)goKsQZ|;5mn8=2QWIK?)mB2g< zIY&TO@MkXiH(y{m<>P#ldmr4>0%8(wL7CBAsz5Hr=VXX z$G3!XzzLegWac3<`G3Eg`nV<5 z=|`P*G&lfi_w#=Rw>QaM^+6|=*6Sfe-hb7v5#U=9O(<*D{@)JSu2!<8a2QJ#&sw0MibUcD}}-k|-c)@Q`XI{SaYlonro3K-k3-Osvm3rxV6^!pPBzRiaV7 zt)?G6FPLRP5RB)3vy*t->kUqSv*!#UyX~;s3!_80uNDZgvW6CYpeF^F()z#~!-Ja4 zMu^kg;e29l(qF%f-D6*>oWo{NW)j^j80N#SA%>1&k}T9UOkzirxOo}ou;<%AJ4BAe zN62JfD&V?YAQ~Bw@uN-^z2>e|BR4LlByd!$k`&pmSir&Fcw1HJ$gpujDZP%*S!c>2>i8*&;$Gl$va(K*cEqi;rO&%A)q8*KsXh&Lu>9& zrc;88BbS%tSFkv!_BXFNxGQA#5XFRFB)DznWh07lc zc1R3tC2?fCTWlC#-4vBq9a&pl=+Q3B+`jHSU(fmMfN%`1)z~1-rcPbaMg+1B4=&H5 zMkTy_Q`AX)icg-+9IW6Pu!D6Cc zfkE2?mOaG=;A1c6<~qqA_@KZ+-ajhiK|a73JG;h$IN@mZz#XX5{Eb;YG~~TlEB7L* zJhlmFWJ9vzVYQYrmXiR6$w@DMbkCX;u#I zSU`SYinvv-FFK7l%pj{{E)@!r{dreu0~j?TvJOQEi96Z7Yrf+G+jNC+4f4U+CG!sn*lw4S4{qbS>Vo+q+5ed8wl zBthoGL&2=mdt#-{eNtjrS3V|0TF zYU#zmQ(C?}V;)UJW#h?4mqXwjZ&JLZ^awHeLv@2OBlSK13%xEl$h3V^BKD2m6iWG~ zGsqm+7Z}}SSuO;e@~G&TATn7_GScIe0tBgXLn8zBO=%UQ*wY4g`rE5Fgm~sFL&h#E zXEtcsv24F2jG=s-6N9CRXTWO>bx=Q!yzMRyrB{tb}VyXaq@&6H0Gf04$o2eTv8$=d`xf+X zDm-WHS!dEHZ7q>Xs5VeIV83sAOF`gUK&PE&Uh@oh zm)g_yZr7H6pGxG*Qx{Kf%;M$`@uYpRiGo({maQ48=I5fi#`6f^Q%IMlACA1 zxA42KbwBXX&d$9w>~tqle{@nq$d=QjUOrBw>0{JsciTH#JpRd{T<_oMyoh>cJeu?l z3FTOI8q3UDd@g$MO}F9cK*8D+WYiJ3d9P|R;AK)@;-;8Duu?6hVlUp2oC>|(s5dp? z;dph}Z2;uo%q$kQ1PW2czl4~8n7A*kBOsySKS4x5Zp8`$!82~o%Cr5{h{@Hgp^29sg_y1DKn(p(JBhS)NXa>nJ zJfJEKO2=0+y$%yiFu*&AE{cY;f_jW-V==*xYl@KI3SupQsHNiZ4oP5K*6%E))`ys@ zVI(aBYsDbN+X}u1wtq4(FEPKDF4#M`6A*+ER4+W7k}?yv+b{>9Zv@L!4Myu{I(bbm zKgQ;1Y8KHwkbPN$=G(S+90l6L(rL}6dC72}Wxgna!>XI~YoY9y;icItllj?ep-UK5%LZZqp^UCzv%#|(hE>nY($0J5_3L8FEi z(2OpCG=%Uv_~Q_z60I>M#v?V5jGxwvpJC@OSJZMj*!^w^TR)HI&I^s49vs>O74=PWcK@4y$ZjM&)GjV#+<}0*L zU{Jnt8;9v&m@Vqu!g7;-5_g9uAx`#uLGNncoB7-v`=(;kHiLe=1GTwX<}xMoEYzi* zm7lJc*4hp`N(O@i(mPZJCZ=(+))=me z2`nIWJr&m-@Gy_A+|Dj`tJw(qUa7TK+9)W=60q-8$ zG7FNnXI_)zf$7AdISG9J@F{St679d8m2XFbZhyE-p`pry6$MzN?c`h~Q0|rhZd%u( zgm4Kn>*t1p;`=IA;$WeJdtCZVWm$}f(8#;VO&XUAT6=T@ZGlO(lWyETz<)f2#P?b5 z@E%1(rq=U3uH8Sza5e(fW~Nv}qPdr(Yz3dL-*=@LnYB{mWR6xrdFQlTst}ep9v>}q zs1dhD?9*>(+;6V|Af0>SS#bI&%rC+HUUejmBbHwIHrFGcp5bCh zN*W04GhO8NEdJ>SjH~`QTRrWx|HInQ|Lk`D<1zd@i01hB%P#%Bf>78$$!E%{X>V0c z^6)m%a&~x+P6bMbdIoBf2Xf`wDGLcve^zCZ`*5gcXCv+)Jh z6eSFSqH^{tZJ*q+u)!Nhv=S%mWrZgCc$tu#AN;+DUfYTbg0p9~=4~v!n(*l7hq6&a zk6rMXR#xah6NJK2S;Q-EzI;ATUhOEU$4%P_0hUU5gVG6JChb%p)`Xl7qhP^aFyhO<)y zH;45u8dg;|tgEqt*1PZ0C4BwY#dS8j6tU-<_1xMBGF}&O_X|Y>XawOGa~ONxIKHuF;1F?(Bi5aGMMU9349EQq+=gauK;!d-5* zLjpRG=}y0VZw$!3Y=_Y-3&Z3g9LJupkP3BvKV41R3~lQ0x@%E7d!u4Tr*k)TkzGN6RICKrz=1uZ@`7 zl^s2fu#6reyOmB^S-wq8H5Wl>uBE6r<9Kr7{M7A+oMNzs?a6E1WTZUDnHkEj!57{? zVzFUO+n8D7DdL-9lz2^KI7s3@QR0BOlSNK*rZK9u?C@!?q5>KqRmPqVHMSs7DWEkE zhkXrJV$E*=!XGqTcL9%vauSP7hsg7=#Wi;5YpIMA{@N?z5~UKBW@h&-uf-OF%$%cP zyEf1&PoYH`jId;3$V7X9FB=Go5=*J7gh&clgJ%bXP5R^hI2!KYR@ft#ez2`-o@lJx zfB9^nn@dL(K5Omvus0ZwM|K1>+;7C$@;(1!>ztiwuiZUF)OI5~OBBV9vVq&+g`xyL zM~|vpAVNdA1ZQU<)%ay1yk?Bs1vW&DAeAdo*soIbS(s&JMJyyu1?*B&V4P8aTNk@t zXyU;z)2`NRJ{PV5R#a+`swE(rRIA+n``q}Eu9>zc(MjsYUpq3IH?oFKVl0`&?M}Zp z+)<&bWGo>UaN8Q#psDd{fXGaf#2y*VTjoQBBaJ9y`1LZ~q}T9Eb_xL;1dToOE9%7q zW4hgwbsO+8^aY)PSf(9!;_3L1*YxwUs{ATcJ?BQd4Eo*PWV(Yqa=&>~yLc8#%P@*Z zVQ<%oQ69rvrlnBJkZWUi!Lz)iE)s^@LoueX9m-{_iW~%rFUW$1Zg~5O^2~9svdIMG z1^T5Qk+#)3P#>9G6VuaBg-}jS95#w1;*g1DQVBI>Z&KG_r6ci^pe$*vtRN+%9m9mQ zbJaFPrY^+Y$&(FQV{mP45J7|{lEHB5iEd_#kOYB=XiFxCJ7z2YyppvGMRP0oHo4E( zA#N7E6nJkrLubHq&2^04h#dAp z)|4~v#C9(nj@zeMu|y*dOGqm3^sh zaJKu@nSU(=aWhC%_(@;D@p(m{(*tjrlW)4MUgOJLnO;m|kEBU;Z^ooOhW!~c|9&Nw;-|0gQbL(he7sgR#>~%*R%863>?PGho2kc!_r+s@_+Xm#v0ZWRvISa< zJ=;=nG#_L%kAX42b$fH$Xa%o{f(rDVMAZhvB@UHJkHJJNf*(>oEr@<_(LCc%qMY`d zjmgG?cb(2w#9?|1b6X5ENe~y`KWHcsHRm#!N=h3OYP_!ARbYyKtAAb4Aj7x265=|1 zVVM1O&?II6# z=5l`SIONeRq~xJ5;u$e8Xyw+F`c9h_)*s{vY|$zeFjWnNs9$`$#8O7YJq?YWbY@^Sj@)A#*sBuy1TC>;3S~E1r%UEoP3+| zAWZ&Ry|2HcfvlBu!N#ceZ-Oh`Nc?cdxF~zCRZjCn{dV>t#Q9((val`6`qqZy705zv zGSQM2BnJY-{6`;UIJktIK)q1p?`pCN0^L{?IYN^Y2PmvF?2CeRN)JxLUWv!`lr68# zBF()JNXE^j$hrQK4CMNV-}D(FIcS6E_UXSyt~O}((1zn6!0)E z()?oE1(24-FegP5D}GVRz+`)@WgupjOPU>xB4P>Fixg_655Br;dQrB+LYKCIEyj5gH|iupH{vC+(1YK6zUo{n`O&^IW(AGST9iB zeuH+XA#^3OC+Xs5nJBw-vx-^z2G5a9S~+ln!46>^#U5`U=My^6L6u=@mEJENP}Yp& z6}2&?#Gmb7gmx9OhelwZt^0##QD!$dSObN42~Qof)@f2vf5-gna=cc41HX3%D&#>{Cv(yWUtEK;(e_fGmIfDT5PlqkW4j4xlqH zdZW%=XBJ~V5Yg;o{>vJ;f@`r=mr05?Sm#81j<7dEEraO??G~|vr8Y+5-=j|Udv85* zTB{nn^{uMcs^+SQ-NPHgfm4|BQl>w#u26;ygP{$tr=b6`a6Z5jW~pX;UCj?VYxW`- zT`bZ3C^At!-6&DCkz4eN6orjNeRPElQ)IgSlc!J#AUt5<(6*k+DZnWAiQU+_o`eMp zR&BeVhHLlzWXy%bZ13B zzOH39wW;8_x=q{QwTMnHMF`3n$%Kw2F$ciKiQ*^HpE(I#ul-hXX@t)xxgMIUlu%GZ z7WRR}ko=yBayOjKU{1W7y{+h?biy;(ZK;~xc}Ac_AU5?wS6N9H7xv>97i7m)#Qgwa z%WOSo*8^Tzv+!P;ft~gsSw+^K}TpR~;+dUPGR}XFrrYbnPcy?n^0SuBvub1R{SW=Qq}Z?D8@k1;jDQ1=iHqWqopuV)_%w0Ie^ z=pNZe-5n+EaL_&gwc~QjAL1>L3?zUsB)bLylm7Y7IpD?+uJ``7Lpa?ZHPg|&x z;4`Q*@ub*7>1!2#so_-&Nhya!CUWPFO1D|z%~rM9Z_~t40;$_&Lf)kBt)cW_60_jK z795H5t&vQ8yP%HL@fhWc8DMQFV4jMc8d`JpR?F(pt>D*cx=t^~lG1p-BY#FXav(Y~ zOe9Bbca(!?p&bIU#7Q14)6mlkDrV`8!17MW&UBT&?U*GiFgws#F6fLZV$95amf@(@#uSjOKftivbHy- z=__;iJTUN0>g!u_WUYhQAexeGDUp{hwe3MUE`oP{=J=1n53d5)C&l)Pi{MB9Jl0PY zK2&UjlW>fWFj{-_G>&J9(5djdw0jM@)C!qwhYs^&ft8=1=Q=mc-^@?$D}g$i^crz6 zSGRKi-Ju$g({zPWhCyzZ7BnzL|MV5^5Gw**!@xx|*3hG4DB)7UzG}pX*u8sZ6w7eX z4?9u!BwD1n00t{&`ie}d;HP$a`!cE0pW}M3;ZL#u`bZ#C+@8kM$swdL>^Urm94q2P z2$JQ&H@SQVH9BI0D0Qm|0|9e6C;rwr_sgFSx0G+=x(>&`eydgJ&2DP*S=PCN^k zZMqj@Ov#wy#&$TTl5*3X*0>SvLa(OgS=5?q5+WjtrlQs*C1p3S_i@Hgp%HuP4M4XRN`d2y4(%f=-$xk#;(epp~l+D&F2@56T0(nxP zH3oxDju!wMh>{-4Dr@!J=Y$YW7urXKRWe$Nnm&<|OiDN!m$C+8@|0`wACu&k%V0W; zw|IKG?ytlxxrM*F0;JT1oD6M@LOZgvSad|wi46{$reeG+p==1;^*ReYbput0Wx z8FL7A!^)#>y`US2V!UVGyq)oMJV{RLfioB>Fu$rpE20PdSRdKAuHxv zE+43jd!g58mbxUe^HPga_a2@=z6+~9PiE9x0GF@>b38`6t6H4fMt2~`?oHbMS3kU> zI$bf%lCOw@dWy5NZzu%W3*_fonU93bD1u!4+&0b@wbfF80>WR4fe-V0%BLZNiZ%oS zw}od(N_T;LD6?YFyA7r(o_9Q%b)WtVo#opM2$CA!3xPt2&W!}Ta=PQ|w)PjpIhKjA zp*DQ@&r1yUG1xYK)W+TZf;=$-7U8rp9qy$E(Q(4>1Ri5&!3J~;{BSG92Om|Lp<-zj zqlAW@J$|OTItHS?UxCi0=jK!Gw3DeBb#~B_?&~I?k$G36S9h$V(22~j8;%ahrQ!Cf zb#9vz}k4xbZZTqG=Y_ETM71Y7n zo7v?myIf>|QNf+rP+PJplnfyU-4w_y9@y_6y?_4!2^rqB0p%`~J5=WPm?2Qc)P50z z0Jp70;zrY=c2JiJ;JdXJ5?v!ozALf}6DE}FG#{<~0K@g4?V!F$0G?x(KGjfET6W=)9E23 zs)dDUD)Ng#{V1Bq%@mf0^QHV%<7rTP&e&R}7(wr6qoBtrKNlmBYlZ#|!2}?@^}n*s zQ{g?j$UFlJEE&)?{b!_WsnKO0z&1L-C}ncDaIWAtd6Pmg1#DKK2w#c{MN-34SY%7^ zMY!Gwv2N#Hy!~+08x2kpov&ED@7xQ0_$)ZA$21(;2&B7XWgk0W2y0jO$DUnLJeiE9 zg99W1i|GNzA5nH1zzSR!^NT=&i;+@wa}(-;KCy#i;5mrY(FOkD(h-H|C8#)4t+csW zB!xR7V1bIMQZohccPWry2exT9mWl1&ssXTL?uDmWWxWqoab~1EU^hrDG;uAc*Vc<= zGQ(10O?v@^D#k_^F%K1_rgPZ#jo{KF(PIkJa-lMda_Z-Us`eUK3hp;B?1r{pn0;0- zn`8YgLO?S-IO#xpqLYqAZ8^v#nhX)EAQnMpNC+!v<}d@uL}Jo{#I+q*cr-tDdXC^l zB0$lW7(w~g8g|{v9^yYvRAl;vc+Lm%;HBp9$ zH3_Lh3i3v4E}3m!3K3W<+0FK7EJmMDbEvjAUKHQLdLeAQI~BU*>Jl?JVz^3PDAThS zBs?UJb-pShzu!#SFudlr#_V%biu9(VZvQm3rDCGXf9Izrz2a!x3r7b#cf>xy@P4iS zyTr3kr|B;PiJe96!kr;0det>}r8U_3lzo{QG|!v|p+-aU%<$c74%NQXFV#IC4SUuj zADqN?wB^;kqsKkcOPHE?9FC&{D6o`GB+WHvykS5n5rZD%#*3v+Y9dy{c*vBph)v8% zfEyIlG091JAlL77_N>FCI|}2I+OT(Ru2KMXX0q~bXA~zpsV7cYT3_0Eo+N|Gq_@j* zoMZ5(Inp5XHyFeRr1Zh)V1@(P$ers_5cZ|yx0jMlxu(U(rfXxnOB74b2PW*5>NE4H zX9>amg(FgZW9KnTc>sO@+)17l)CPBHCo1K9>nrIsgcBUw4Y4~xTmrnl>p5W##xm4* znTdAR0tp`?GmB8b*o}(@>a!}LGbX)Ru4|$xIcR`>vNENsH#5Jef<-|{_%djlH!%e=>rkNDHFH#zN|A?Hl55IgT z%(E`H46i!7&RilDTmyFIJU-`&VpF=xeH8ktIshQI?}t|m0nRlAOyRYp&LGJwgv%Qk6T=yJ4x1^gmKh~P6`LSB_`FV z51{3Dq#rq)_RRDUKk}vBzf?6@HtU`sP#SGUXR39l}(^-`+}v9Dc5o-QKH z&_Ml~6j}YKz@Aj4(y6Xke8+I>FkR?jQ(SpiNF}^7rJm&U#{L>P38}h`dO+{r^LWZj z7(bI^N1oCfLoxx2!4DNhFT7+0=u9_Vz5@pWtp$Ge7hC51cGyNsfW^2t(I`&S&muA| zoEZcaL;hFS)9Wh`+^Do+FX{FB zXF#R#24^4G?r@o-W4+;Ve=rP32hgseOM~nk+kBcIO}>IHO=grz43gy4FS_FcCA?S# zfS~6e$p>J%nTL=4bAtm$qe&7^&QP;}r#-RKHVh|b91h$2zaX7rzk5QqVy(an!#_=z z!2QcWKNY zwyxgQ)}Q9xqrs`|o@uvx2Y^%mp4_7C#seu=f+%{YM}}1GxZj=jItS1ggZX)F)T`WP zgnmK*JIZFPuQk7ZC-D#A@}=od36BeuzWn>cTK0CT8pz7Vn-y*$8MMxsh&@ z34p4-c&cZceReVvaJQ#%AyK%gVHOa2Ps}&QK%QpGL8aNhhlFX!*hs+x=?URm!Z#Ym@P^K zhvFRP35d1Gb#iG&AdC^xB#V5YY5Ad8bhf!UA|8z%GL`DWECRnxEv>>Wc4cki zJfpx^2!P5VxUnas3cbS9UB2N`hoy)^)Y&I&uA>tl)wOW|hxR`i% zL)8){6rEu#IbMmSL0TVT9g`=tG4G0>lfwuIAAGJZ2+Xi>?9nh5n5nQjUT6iH!}{S< zQg~)mX%tS1GiPs1x7+KP%h5dhq=A5>&TNz$| zKb(V0b4T@XA6{Cyw=V4Y=2f;YE*$GG+l7q5ghFfvX>kMt5VwI}7v~Vd5goIM$6yZi zeCfG#ZxXR^*^H2a=sM&AGU9fc0Y+LlyX0*JbqEcqkJo;1qd)BOG3g+tGtmeUFD~l{_ zkGDbGKu6sR55?yscd_IYpy2Cw?^<>J+|^n^{Ux95*w_te7w+6pHofXsQTOKmn>wza zW~1TJfI(zb-+Z{z_i*{a_lOrs(msJG3F%3&$Q*zJrv?eiy(eiD_nrODFIqc#fj}#} z|DGuaqqvYw1sIjfI39+}ExRqSEnc;F-lzuqmQ0iV2S9J9m_`yz6P{@Np6L2dNj-xu zEC(%_MD}iRnl%N;U-m~03j^GEYo=Pkx1LkzpUz0fQ4LJH1ZS~GW5j!&T}}maQK<>D zi>=~&uy)(Nw{PuRx6}W&2=>w!y3Fo)jv?;|zMX@rH3~gl%92NvP4q>y*}X(ncCNebgX^xejvo5>*Zv~?BI+$;6Hi7-`+&^Wi_gh2N>%9!N}^A|0kdmw zt~GN`h~P3M{vC*nH$U&K zg)_{cI6OMWZ znE5xDv6ZGvd`p@yl%y@x$E6g&iCTPt@EI6W-gK%q)VUFC2wL-kC?$ihAZG*wwl3AK zRNX3!#V;vgl*X3MQ+@`YvDxEj7S>8QM^WNK%5%lAf<^`vW<)AZmuB%11N%9~DY+|J zEFn>OyYg6LDMMKq2E8QuV%IxL5l&9v1TTQ`^Yj6qX}}N((++86=5yfq4M7}5lI7+@ zDJe`&V>%U~cbw5y`mH0~3)7vnM~OM0*2avDx0wxeAI zHb7Q#59NaShp)UuG|nC5A|=mGoRz>M18HT^AmMHv1`#kpK6Zw#SFje{I1osP?`4q* zhcLIK^0$R~a2n`iOUoqVU2c=)+@;~+BpOfS@T7(UJ6y+OrcbQLMh#YLd))4ICg0Z1 z3|gP``cc#woy5athkx9%?oX3m+zk&fr*FA&$q@Ar5U~>s5DRI38a!MtsGVHbg1Y{+ z_QH}}o{Ba2swvK_%(qumhH|&sO|EA!~ufDZs)|JB4q{BY7 zUqJBo`$snn2G8gAzV^eEyRwdg^yZ|5?MRk$?47%{)-;JBvD*q>5C?kfJughet#F}Z z3e;=fp_Vx^+pj?y%Pie@;Uh?$(hR01) z4#+q>192X20TjFihI}p<`dZA8X*t|u_w^K|9RaXgEXlDRLub^T+Ic^qTFD+m7FPKR zKZkjVpVe~}ZlKn@KqXmG%S&jJ5W}He+q1-L2giH^IJnT83Tb|}1!e}Kq=sPcV7$D< zhU0tY3kdp{9SFpF1BvO5Rt+Z}z!geo-K9ImK0OPOpa>)^*Eg(+<@VNg%9 z6QlqfmZLQ}AZE}oH67=&t*aN^IDB3MgWi=Bw)JYIVLxDk)&Lv5asr;B!wJ2yw=gE^j z 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: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 Date: Tue, 7 May 2024 16:27:44 +0100 Subject: [PATCH 06/22] Deleted a file that should only be on dev branch --- swifttools/ukssdc/data/datarequest_base.py | 1016 -------------------- 1 file changed, 1016 deletions(-) delete mode 100644 swifttools/ukssdc/data/datarequest_base.py diff --git a/swifttools/ukssdc/data/datarequest_base.py b/swifttools/ukssdc/data/datarequest_base.py deleted file mode 100644 index d2fc6286..00000000 --- a/swifttools/ukssdc/data/datarequest_base.py +++ /dev/null @@ -1,1016 +0,0 @@ -# import json -from . import allowedConeRadiusUnits -from .. import main as base -from ..access import downloadObsData -import pandas as pd -from .DBFilter import filter - -if base.HAS_ASTROPY: - import astropy.coordinates - - -# allowedConeRadiusUnits = ("arcsec", "arcmin", "degree", "deg") -class dataRequest: - """The base case for UKSSDC data requests. A 'virtual' class. - - This class should never in itself be instantiated, only those - dervied from it. In fact, it will contain enough functionality to - run for the most basic of cases, except that it will never select - which table / catalogue is being searched. - - MORE DOCS - maybe give a summary of the methods here. - """ - - def __init__(self, silent=True, verbose=False): - """Create a dataRequest instance. - - Parameters - ---------- - silent : bool Whether to suppress all console output - (default: True). - - verbose : bool Whether to give verbose output for everything - (default: False; overridden by silent). - - """ - - # This 'abstract' class has no table defined. - self._dbName = None - self._table = None - self._silent = silent - self._verbose = verbose - self._metadata = None - self._colsToGet = None - self._defaultCols = None - self._defaultColSets = None - self._tables = [] - self._coneRA = None - self._coneDec = None - self._coneName = None - self._coneRadius = None - self._coneUnits = "arcsec" - self._doConeSearch = False - self._filters = [] - self._sortCol = None - self._sortDir = "ASC" - self._results = None - # numRows and firstRows, if None, means no limits - self._maxRows = 5000 - self._firstRow = None - self._numRows = None - self._resolverDetails = None - self._resolvedRA = None - self._resolvedDec = None - self._locked = False - self._raw = None # DEBUG PROPERTY, DELETE LATER - self._obsCol = None - self._targetCol = None - - if self._verbose: - self._silent = False - print("Disabling silent mode, verbose mode was requested.") - - # End of __init__ - - # ----------------------------------------------------------------- - # Now set up variable access, via properties to control read/write - # etc. - - # Silent - @property - def silent(self): - """Whether to suppress output.""" - return self._silent - - @silent.setter - def silent(self, silent): - if not isinstance(silent, bool): - raise ValueError("Silent must be a bool") - self._silent = silent - - # Verbose - @property - def verbose(self): - """Whether to write extra output.""" - return self._verbose - - @verbose.setter - def verbose(self, verbose): - if not isinstance(verbose, bool): - raise ValueError("Verbose must be a bool") - self._verbose = verbose - - # make setting table unimplemented here, allowing the subclasses to - # decide whether you can change table mid query; also some classes - # may only support one table so they would want these functions not - # to work (i.e. set self._table in constructor. - # table - @property - def table(self): - return self._table - - @table.setter - def table(self, table): - self.checkLock() - if table not in self._tables: - raise ValueError(f"{table} is not a valid table. The list `tables` shows valid values.") - self._table = table - self._metadata = None - # if (self._defaultColSets is not None) and table in (self._defaultColSets): - # self._defaultCols = self._defaultColSets[table] - # If unlocking the table, we have to forget results as various results handling functions are tied to the table - # that was used. - self.reset() - if self.verbose: - print(f"Selecting table `{table}`") - - # dbName is also unset here, may not be changeable depending on sub-class. - # dbName - @property - def dbName(self): - """Which database is to be queried.""" - if self._dbName is None: - raise NotImplementedError - return self._dbName - - @dbName.setter - def dbName(self, dbName): - raise NotImplementedError - - # coneRA - @property - def coneRA(self): - """The RA on which to centre a cone search (J2000).""" - return self._coneRA - - @coneRA.setter - def coneRA(self, RA): - self.checkLock() - gotRA = False - if base.HAS_ASTROPY: - if isinstance(RA, astropy.coordinates.Angle): - RA = float(RA.deg) - gotRA = True - elif isinstance(RA, str): - tmp = astropy.coordinates.Angle(RA) - RA = float(tmp.deg) - gotRA = True - # If we don't have astropy, or it wasn't something astropy has - # parsed, then it must be int or float and we will parse it - if not gotRA: - if not isinstance(RA, (int, float)): - raise ValueError("RA must be int or float") - self._coneRA = float(RA) - - # coneDEC - @property - def coneDec(self): - """The Dec on which to centre a cone search (J2000).""" - return self._coneDec - - @coneDec.setter - def coneDec(self, Dec): - self.checkLock() - gotDec = False - if base.HAS_ASTROPY: - if isinstance(Dec, astropy.coordinates.Angle): - Dec = float(Dec.deg) - gotDec = True - elif isinstance(Dec, str): - tmp = astropy.coordinates.Angle(Dec) - Dec = float(tmp.deg) - gotDec = True - # If we don't have astropy, or it wasn't something astropy has - # parsed, then it must be int or float and we will parse it - if not gotDec: - if not isinstance(Dec, (int, float)): - raise ValueError("Dec must be int or float") - self._coneDec = float(Dec) - - # coneName - @property - def coneName(self): - """The name of the object on which to centre a cone search.""" - return self._coneName - - @coneName.setter - def coneName(self, name): - self.checkLock() - if not isinstance(name, str): - raise ValueError("Name must be a string") - self._coneName = name - - # coneRadius - @property - def coneRadius(self): - """The Radius on which to centre a cone search.""" - return self._coneRadius - - @coneRadius.setter - def coneRadius(self, Radius, Units=None): - self.checkLock() - if not isinstance(Radius, (int, float)): - raise ValueError("Radius must be int or float") - self._coneRadius = float(Radius) - if Units is not None: - self.coneUnits = Units - - # coneUnits - @property - def coneUnits(self): - """The units of the cone-search radius.""" - return self._coneUnits - - @coneUnits.setter - def coneUnits(self, Units): - self.checkLock() - if not isinstance(Units, str): - raise ValueError(f"Units must be a string, one of: {', '.join(allowedConeRadiusUnits)}") - if Units in allowedConeRadiusUnits: - self._coneUnits = Units - else: - raise ValueError(f"Units must be one of: {', '.join(allowedConeRadiusUnits)}") - - # maxRows - @property - def maxRows(self): - """How many rows to retrieve. None=all.""" - self.checkLock() - return self._maxRows - - @maxRows.setter - def maxRows(self, num): - if not isinstance(num, (int, float)) and (num is not None): - raise ValueError("num must be a number or None") - self._maxRows = num - - # firstRow - @property - def firstRow(self): - """The first row to retrieve. None=auto.""" - return self._firstRow - - @firstRow.setter - def firstRow(self, num): - self.checkLock() - if (not isinstance(num, (int, float))) and (num is not None): - raise ValueError("num must be a number or None") - self._firstRow = num - - # --------------------------- - # And some which we want to be read-only when accessed as a variable. - - # doConeSearch - @property - def doConeSearch(self): - """Whether a cone search will be done.""" - return self._doConeSearch - - # allowedConeRadiusUnits - @property - def allowedConeRadiusUnits(self): - """The allowedConeRadiusUnits for with the selected table. Read-only.""" - return allowedConeRadiusUnits - - # metadata - @property - def metadata(self): - """The metadata for with the selected table. Read-only.""" - # Need to get metadata if we don't have it: - if self._metadata is None: - if not self.silent: - print("Need to get the metadata.") - self.getMetadata() - - return self._metadata - - # colsToGet - @property - def colsToGet(self): - """The columns selected for retrieval.""" - return self._colsToGet - - # filters - @property - def filters(self): - """The filters that will be applied.""" - return self._filters - - # Results - @property - def results(self): - """The results of the query.""" - return self._results - - # haveResults - @property - def haveResults(self): - """Whether we have results from this query.""" - return self._results is not None - - @property - def locked(self): - """Is this object locked?""" - return self._locked - - @property - def numRows(self): - """The number of rows returned by the query.""" - return self._numRows - - @property - def resolverDetails(self): - """The ouput of the name resolver.""" - return self._resolverDetails - - @property - def resolvedRA(self): - """The RA returned by the name resolver.""" - return self._resolvedRA - - @property - def resolvedDec(self): - """The dec returned by the name resolver.""" - return self._resolvedDec - - @property - def tables(self): - """The tables that can be selected.""" - return self._tables - - @property - def defaultCols(self): - """The columns that are retrieved if no selection is created.""" - - if self._defaultCols is None: - # self._checkMetaData() - if "Class" in self.metadata.columns: - # Can do this all in one line, but it's a bit hard to read, so lets be nice: - # First, filter the metadata on cases where Class has "BASIC" in it - tmp = self.metadata.loc[self.metadata["Class"].str.contains("BASIC")] - # Now get the columns in this: - self._defaultCols = tmp["ColName"].tolist() - - return self._defaultCols - - @property - def cols(self): - """Columns in this table.""" - return self.metadata["ColName"].values - - @property - def obsColumn(self): - """Which column contains the observation identifier.""" - return self._obsCol - - @property - def targetColumn(self): - """Which column contains the target identifier.""" - return self._targetCol - - # --------------------------------------------------------------- - # Functions. First some standard things: - - def __str__(self): - str = f"PRINTING AN {type(self)} object." - return str - - def __repr__(self): - str = f"I AM AN {type(self)} object." - return str - - def checkLock(self): - """Check if this object is locked.""" - if self._locked: - raise RuntimeError("Cannot edit this request as it is locked.") - - def unlock(self): - """Unlock the object for editing.""" - self._locked = False - - # --------------------------------------------------------------- - # Metadata - def getMetadata(self): - """Retrieve the metadata for this catalogue from the server. - - This queries the server for the metadata associated with the - database/table of the current object, and saves it into the - metadata variable as a pandas object? Or a dict? TBC - - """ - self.checkLock() - sendData = {"database": self.dbName, "table": self.table} - if self.verbose: - print(f"Getting metadata for {self.dbName}.{self.table}") - - ret = base.submitAPICall("getMetadata", sendData, minKeys=["metadata"], verbose=self._verbose) - - # metadata should have two entries: 'columns' and 'data' - self._metadata = pd.DataFrame(ret["metadata"]["metadata"], columns=ret["metadata"]["columns"]) - - self._obsCol = None - self._targetCol = None - if "IsObsCol" in self._metadata: - self._metadata["IsObsCol"] = pd.to_numeric(self._metadata["IsObsCol"]) - tmp = self._metadata.loc[self._metadata["IsObsCol"] == 1]["ColName"] - if len(tmp) > 0: - self._obsCol = tmp.iloc[0] - if len(tmp) > 1 and not self.silent: - print( - "WARNING: Metadata contains TWO obs columns! This may be a bug; " - "please notify swift-help@leicester.ac.uk" - ) - - if "IsTargetCol" in self._metadata: - self._metadata["IsTargetCol"] = pd.to_numeric(self._metadata["IsTargetCol"]) - tmp = self._metadata.loc[self._metadata["IsTargetCol"] == 1]["ColName"] - if len(tmp) > 0: - self._targetCol = tmp.iloc[0] - if len(tmp) > 1 and not self.silent: - print( - "WARNING: Metadata contains TWO target columns! This may be a bug; " - "please notify swift-help@leicester.ac.uk" - ) - - # -------------------------------------------------------------- - # Misc - def reset(self): - """Remove all results - reset this object.""" - - if not self.silent: - print("Resetting query details") - self.unlock() - self.removeAllFilters() - self.removeConeSearch() - self._colsToGet = None - self._resolverDetails = None - self._resolvedRA = None - self._resolvedDec = None - self._results = None - self.sortCol = None - self._metadata = None - self._defaultCols = None - - self._raw = None # TEMP LINE - - # --------------------------------------------------------------- - # Cone search functions - # These allow a cone search to be build, or requested. - # Note that simply setting coneRA/Dec above on their own doesn't force - # a cone search to run unless doConeSearch is set. These methods are - # preferred ways of managing the cone search. - - def addConeSearch(self, ra=None, dec=None, radius=None, name=None, coords=None, units="arcsec"): - """Include a cone search filter on the dataRequest. - - This function adds a cone search to the set of filters to be - applied to your request. If a cone serach already existed, this - will overwrite it; you cannot (at present) have multiple cone - searches combined with an OR function; you will have to do - multiple cone searches instead. - - Note: name, or ra & dec must be specified, but they cannot - both be specified. If you supply both, an error will be raised. - - Parameters - ---------- - ra : Union[float, astropy.coordiantes.Angle] Central RA - of the cone, in J2000. - - dec : Union[float, astropy.coordiantes.Angle] Central Dec - of the cone, in J2000. - - name: str The name of an object to centre on. Will be resolved. - - coords : str Free-form coordinates to attempt to parse - - radius : float Radius of the cone search. - - units : str The units of coneRadius. Default 'arcsec', permitted - values are given in the allowedConeRadiusUnits variable. - - """ - self.checkLock() - # Set mainly via @property functions so that checks on values are done. - if name is not None: - if (ra is not None) or (dec is not None) or (coords is not None): - raise ValueError("You must supply name OR position, not both.") - self._coneRA = None - self._coneDec = None - self.coneName = name - elif coords is not None: - self.coneName = coords - elif (ra is None) or (dec is None): - raise ValueError("You must supply name or position.") - else: - self.coneRA = ra - self.coneDec = dec - self._coneName = None - - if radius is None: - raise ValueError("radius must be supplied") - - self.coneRadius = radius - self.coneUnits = units - - self._doConeSearch = True - - def editConeSearch(self, coneRA, coneDec, coneRadius, coneUnits="arcsec"): - """Changes the cone search. Just a wrapper to addConeSearch.""" - self.addConeSearch(coneRA, coneDec, coneRadius, coneUnits) - - def removeConeSearch(self): - """Remove the cone search from the filters to apply.""" - self.checkLock() - self._coneRA = None - self._coneDec = None - self._coneRadius = None - self._coneUnits = "arcsec" - self._doConeSearch = False - - def isValid(self): - """Whether the current request is valid and can be submitted.""" - - # Need to get metadata if we don't have it: - if self._metadata is None: - if not self.silent: - print("Need to get the metadata to check the query is valid.") - self.getMetadata() - - # This needs to check: - # All columns are permissable - # If not set, check defaults as user could have been naughty. - if self.verbose: - print("Checking requested columns...") - tmp = self._colsToGet - if tmp is None: - tmp = self.defaultCols - if tmp is None: - print("No columns selected to retrieve!") - return False - if tmp != "*": - for c in tmp: - if c not in self.metadata["ColName"].values: - if not self.silent: - print(f"Requested column {c} does not exist.") - return False - - # Now check filters - # Filters - if self.verbose: - print("Checking filters...") - for f in self._filters: - if not f.isValid(self.metadata): - return False - - # And check the cone search - if self._doConeSearch: - if self.verbose: - print("Checking cone search parameters...") - # Need a radius - if self.coneRadius is None: - if not self.silent: - print("A cone search is selected, but radius is not set") - return False - # Need name or ra AND dec - if (self.coneName is None) and ((self.coneRA is None) or (self.coneDec is None)): - if not self.silent: - print("A cone search is selected, but neither name, nor RA & Dec are set.") - return False - if (self.coneName is not None) and ((self.coneRA is not None) or (self.coneDec is not None)): - if not self.silent: - print("You should supply name OR position for a cone search.") - return False - - return True - - # --------------------------------------------------------------- - # Column functions - - def _addCol(self, colName): - """Internal function to add a column to the list. - - This is called by addCol() (which can support strings or lists) - but must recieve a string. It adds the item to _colsToGet, after - verifying it. - - The table metadata must be known, so it will retrieve it if it - doesn't exist. - - Will raise ValueError if the supplied parameter is not a string, - or is not a valid column (or '*'). - - Parameters - ---------- - - colName : str The column to add. - - """ - self.checkLock() - # First, check whether the metadata is retrieved and up to date. - if not isinstance(colName, str): - raise ValueError("colName should be a string") - - if colName == "*": - if self.verbose: - print("Setting to retrieve all columns.") - self._colsToGet = self.metadata["ColName"].values.tolist() - else: - # Is the column name valid? - if colName not in self.metadata["ColName"].values: - raise ValueError(f"`{colName}` is not a valid column name.") - # If previously we had selected all, then warn if not silent. - if self._colsToGet == "*": - if not self.silent: - print("WARNING: previously you were selecting all data; you are now requesting specific columns.") - self._colsToGet = [ - colName, - ] - - # If this is the first column, create the list - if self._colsToGet is None: - self._colsToGet = [ - colName, - ] - if self.verbose: - print(f"Will retrieve column {colName}") - else: - # This 'else' assumes a list, i.e. colsToGet is either '*', None or a list. - # If it is not, then likely this will throw an error. That's OK, since if it is - # not one of these then a user has edited this "hidden" field directly, and they - # deserve what they get. - if colName in self._colsToGet: - if not self.silent: - print(f"Cannot add column {colName}; it is already selected.") - else: - self._colsToGet.append(colName) - if self.verbose: - print(f"Will retrieve column {colName}") - - def addCol(self, colName): - """Add a column/columns to the list of those to retrieve. - - This can receive either a string, which is a column name or '*', - or a list of column names to add to the list to retrieve. - Note: '*' cannot appear in a list. - - The names are checked against valid column names, so if it has - not already been called, getMetadata() will run first. - - Parameters - ---------- - - colName : Union[str,list] The column(s) to add. - - """ - self.checkLock() - if isinstance(colName, str): - self._addCol(colName) - elif isinstance(colName, (list, tuple)): - if "*" in colName: - raise ValueError("You cannot include '*' in a list of columns.") - for c in colName: - self._addCol(c) - else: - raise ValueError("colName must be a string or list") - - if self.verbose: - print(f"Set to retrieve columns: {self._colsToGet}") - - def removeAllCols(self): - """Empty the list of defined columns to retrieve.""" - self.checkLock() - self._colsToGet = None - - def _removeCol(self, colName): - """Internal function to remove a column from the list. - - This is called by removeCol() (which supports strings or lists) - but must recieve a string. It removes the item to _colsToGet. - - Parameters - ---------- - - colName : str The column to remove. - - """ - self.checkLock() - if not isinstance(colName, str): - raise ValueError("colName should be a string") - - self.colsToGet.remove(colName) - - def removeCol(self, colName): - """Remove a column/columns to the list of those to retrieve. - - This can receive either a string, which is a column name or a - list of column names to add to the list to retrieve. - - Parameters - ---------- - - colName : Union[str,list] The column(s) to add. - - """ - self.checkLock() - if isinstance(colName, str): - self._removeCol(colName) - elif isinstance(colName, (list, tuple)): - for c in colName: - self._removeCol(c) - else: - raise ValueError("colName must be a string or list") - - if len(self._colsToGet) == 0: - self._colsToGet = None - - if self.verbose: - print(f"Will retrieve columns: {self._colsToGet}") - - # ---------------------- - # Filter functions - - # addFilter - # editFilter - # removeFilter - # showFilters (maybe with option for 'as SQL' vs as Dict) - - def removeAllFilters(self): - """Remove all defined search filters.""" - self.checkLock() - self._filters = [] - - def addFilter(self, filterDef): - """Add a filter to the query. - - This can be done two ways: - - 1) By passing a dict with the following keys -- - some are optional: - - colName: The name of the column to filter on - - filter: The filter to apply ('<', '>', 'IS NULL' etc) - - val: The value to apply to the filter, if appropriate. e.g. - if the filter is '<', val may be 3.1234. If the filter - takes no arguments (IS [NOT] NULL) this is ignored. - If the filter takes two arguments (BETWEEN) this should - be a list. - - combiner: OPTIONAL: This can be AND or OR, if this filter - has to components. - - filter2: As filter, for the second clause. - - val2: As val, for the second clause. - - 2) By passing a list, whose values are the above keys, in order, - e.g. [ - 'foo', - '<', - 3, - 'OR', - 'foo', - 'BETWEEN', - [7,10] - ] - - Parameters - ---------- - - filterDef : Union[string,list,tuple] The filter definition - - """ - self.checkLock() - # colname is - self._filters.append(filter(filterDef, self.metadata)) - - def showFilters(self): - """List all filters currently applied""" - i = 0 - for f in self._filters: - print(f"{i}:\t{f}") - i = i + 1 - - def removeFilter(self, ix): - """Remove a filter, by index. - - To see filters and their indices, call showFilters - - Parameters - ---------- - - ix : int Index of filter to remove - - """ - self.checkLock() - if not isinstance(ix, int): - raise ValueError("ix must be an int") - if (ix < 0) or (ix >= len(self._filters)): - raise ValueError(f"ix must be between 0 and {len(self._filters)-1}") - del self._filters[ix] - if not self._silent: - self.showFilters() - - # --------------------------------------------------------------- - # Actual search functions - - def submit(self): - """Submit the query.""" - self.checkLock() - - # First, check validity. Do this by function call - if not self.isValid(): - if not self.silent: - print("Cannot submit query - it is not valid.") - return False - - # Build the API request dict: - sendData = { - "database": self.dbName, - "table": self.table, - "adUnits": self._coneUnits, - "sortDir": self._sortDir, - "numRows": base.MAXROWS, - } - - # Specify columns, if we can - if self._colsToGet is not None: - sendData["cols"] = self._colsToGet - elif self.defaultCols is not None: - sendData["cols"] = self.defaultCols - - # And the sort Col: - if self._sortCol is not None: - sendData["sortBy"] = self._sortCol - - # Add filters - if len(self._filters) > 0: - sendData["constraints"] = [] - for f in self._filters: - sendData["constraints"].append(f.data) - - # Add cone information - if self._doConeSearch: - if self._coneName is None: - if (self._coneRA is None) or (self._coneDec is None): - raise RuntimeError("You have requested a cone search but without specifying name/position") - sendData["searchRA"] = self._coneRA - sendData["searchDec"] = self._coneDec - # print("SETTING CONE BY POSITION") - else: - sendData["searchName"] = self._coneName - # print("SETTING CONE BY NAME") - - sendData["searchRad"] = self._coneRadius - - # Now do the actual work. Note - the server can only return so - # many rows at once because of memory constraints. I will limit - # the max in one go to base.MAXROWS. NB, if you try to return - # more than this and the allocated memory is overrun you just - # get a 500 error. - - fR = 0 # First row from this query - if self._firstRow is not None: - fR = int(self._firstRow) - - maxRows = 1e80 # i.e. BIG - if self._maxRows is not None: - maxRows = int(self._maxRows) - # If we want < the maximum in one go, need to ammend numRows - if maxRows < base.MAXROWS: - sendData["numRows"] = maxRows - - # Create a local variable for the result for now, I don't want - # to update self._results until the query has definitely succeeded. - result = None - - done = False - while not done: - # Update the first row to get - sendData["firstRow"] = fR - - if not self._silent: - print(f"Calling DB look-up for rows {fR} -- {sendData['numRows']+fR}") - - ret = base.submitAPICall( - "queryDB", - sendData, - minKeys=["Results", "NumRows"], - verbose=self._verbose, - ) - if result is None: - result = ret - else: - result["NumRows"] = result["NumRows"] + ret["NumRows"] - result["Results"].extend(ret["Results"]) - - # Are we done? If we did not get as many rows as was requested then we are - if ret["NumRows"] < sendData["numRows"]: - done = True - if self._verbose: - print(f"Received {ret['NumRows']} rows / {sendData['numRows']} requested. Query complete.") - # If we have now hit the user-set limit, then we are done: - elif (self._maxRows is not None) and (result["NumRows"] >= self._maxRows): - done = True - if self._verbose: - print(f"{result['NumRows']} rows retrieved in total. Query complete.") - else: - # Increase the start row for the next call - fR = fR + sendData["numRows"] - # We may need to decrease the number of rows - if (self._maxRows is not None) and (self._maxRows < result["NumRows"] + sendData["numRows"]): - sendData["numRows"] = self._maxRows - result["NumRows"] - if self._verbose: - print(f"Reducing the number of rows requested to {sendData['numRows']}.") - # End of while not Done - # We now should have our results. Maybe do one sanity check: - if result["NumRows"] != len(result["Results"]): - raise RuntimeError(f"Should have {result['NumRows']} rows, but have {len(result['Results'])}!") - - if (self._doConeSearch) and (not self.silent) and ("ResolvedInfo" in result): - print(result["ResolvedInfo"]) - - self._numRows = result["NumRows"] - if (self._doConeSearch) and ("ResolvedInfo" in result): - self._resolverDetails = result["ResolvedInfo"] - self._resolvedRA = result["ResolvedRA"] - self._resolvedDec = result["ResolvedDec"] - self._results = pd.DataFrame(result["Results"]) - - useAst = None - if base.HAS_ASTROPY: - useAst = "_apy" - if not self.silent: - print(f"Received {self.numRows} rows.") - base.manageResults(self._results, self._metadata, "_s", useAst, self.silent, self.verbose) - self._locked = True - - self._raw = result # TEMPORARY LINE - - # --------------------------------------------------------------- - # Data retrieval - - def downloadObsData(self, subset=None, **kwargs): - """Download data for the observations returned by the query. - - If the excuted query returned a column which includes - observation identifiers, then this function will download the - data for those columns. This function essentially wraps - ukssdc.access.downloadObsData(), so for valid values of the - **kwargs, see the documentation for that function. - - The subset parameter is optional, and can be used to apply - a filter to the results this query has obtained. The easiest way - to generate this is using the pandas syntax for filtering on - value. - - e.g. if your datarequest object is called 'req' then: - - => req.downloadObsData(subset=req.results['xrt_expo_pc']<1000) - - would request a download of all observations that the request - found, which had a value of less than 1000 in the 'xrt_expo_pc' - column. - - Obviously, this function cannot be called before this request - has been submitted and has completed; if you try, your computer - will explode and your eyeballs will be eaten by ants (just - kidding; you'll get a RuntimeError). - - Parameters - ---------- - - subset : pandas.Series OPTIONAL: A pandas series defining a - subset of rows to download. - - """ - - if not self.haveResults: - raise RuntimeError("This query has not been executed, cannot download!") - - if self._obsCol is None: - raise RuntimeError("These is no column containing observation ID, cannot download.") - - if self._obsCol not in self.results.columns: - raise RuntimeError( - f"The column {self._obsCol} was not retrieved as part of your query. Cannot download. " - "You may need to unlock this object, add {self._obsCol} to those to retrieve, and repeat the query." - ) - - obslist = [] - if subset is not None: - if not isinstance(subset, pd.core.series.Series): - raise ValueError("Subset parameter must be a pandas series") - obslist = self._results.loc[subset][self._obsCol].tolist() - else: - obslist = self.results[self._obsCol].tolist() - - # # DEBUG LINE: - # print(f"Downloading obs: {obslist}") - # return - - downloadObsData(obslist, silent=self.silent, verbose=self.verbose, **kwargs) - - \ No newline at end of file From df7778bfc78e477e8a0145ae3f0711182f551bd0 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Wed, 8 May 2024 10:39:51 -0400 Subject: [PATCH 07/22] Change references to GitLab to GitHub (#7) Co-authored-by: Phil Evans --- setup.cfg | 2 +- swifttools/ukssdc/APIDocs/ukssdc/README.md | 4 ++-- swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 32275a5f..148afbd6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ classifiers = Topic :: Scientific/Engineering :: Astronomy Topic :: Scientific/Engineering :: Physics Natural Language :: English -url = https://gitlab.com/DrPhilEvans/swifttools +url = https://github.com/Swift-SOT/swifttools [options] zip_safe = False diff --git a/swifttools/ukssdc/APIDocs/ukssdc/README.md b/swifttools/ukssdc/APIDocs/ukssdc/README.md index f7013238..1e9463f4 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/README.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/README.md @@ -100,8 +100,8 @@ and experiment with it. Links to the notebooks appear at the top of each page, a ## Docstrings, PEP8 and so on. All functions should be fully documented via PEP 257-compliant docstrings, so the `help` command can be used to obtain -full details. If you find something missing or inaccurate, you can [open an issue on our GitLab -page](https://gitlab.com/DrPhilEvans/swifttools/-/issues) or just email me (swifthelp@leicester.ac.uk). +full details. If you find something missing or inaccurate, you can [open an issue on our GitHub +page](https://github.com/Swift-SOT/swifttools/-/issues) or just email me (swifthelp@leicester.ac.uk). While efforts have been made to ensure that the code is PEP8 compliant, eagle-eyed users will notice that function names in this module are of the form `someFunction()`, rather than the `some_function()` preferred diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/README.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/README.md index 551a00d8..8a3e306f 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/README.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/README.md @@ -35,7 +35,7 @@ The `xrt_prods` module is part of the `swifttools` package. The easiest way to i > pip3 install swifttools ``` -Alternatively, you can [download the sourcecode from Gitlab](https://gitlab.com/DrPhilEvans/swifttools) and install it manually if you prefer. +Alternatively, you can [download the sourcecode from GitHub](https://github.com/Swift-SOT/swifttools) and install it manually if you prefer. This requires Python 3.6 or higher. From 67333ebaa302986763b52b410ea0cd1fba7c0e9c Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Thu, 9 May 2024 03:43:42 -0400 Subject: [PATCH 08/22] Add linting/formatting. Take it easy on the linting for now. Small lint fixes to swift_too. (#9) Apply black style formatting with 120 char line lengths. Update ruff.toml to minimize linting issues. Fix bad noqa Fix name of action --- .github/workflows/lint.yml | 24 ++++ .pre-commit-config.yaml | 15 +++ ruff.toml | 20 +++ swifttools/__init__.py | 6 +- swifttools/swift_too/__init__.py | 5 +- swifttools/swift_too/api_common.py | 84 +++---------- swifttools/swift_too/api_daterange.py | 12 +- swifttools/swift_too/query_job.py | 8 +- swifttools/swift_too/swift_clock.py | 20 +-- swifttools/swift_too/swift_data.py | 29 +---- swifttools/swift_too/swift_guano.py | 16 +-- swifttools/swift_too/swift_instruments.py | 4 +- swifttools/swift_too/swift_obsid.py | 10 +- swifttools/swift_too/swift_planquery.py | 4 +- swifttools/swift_too/swift_requests.py | 4 +- swifttools/swift_too/swift_toorequest.py | 20 +-- swifttools/swift_too/swift_uvot.py | 36 ++---- swifttools/swift_too/swift_visquery.py | 4 +- swifttools/swift_too/version.py | 1 - swifttools/ukssdc/__init__.py | 4 +- swifttools/ukssdc/data/GRB.py | 4 +- swifttools/ukssdc/data/SXPS.py | 2 +- swifttools/ukssdc/data/__init__.py | 1 - swifttools/ukssdc/data/download.py | 1 + swifttools/ukssdc/main.py | 1 - swifttools/ukssdc/query/DBFilter.py | 4 +- swifttools/ukssdc/query/SXPS.py | 3 +- swifttools/ukssdc/query/__init__.py | 10 +- swifttools/ukssdc/xrt_prods/__init__.py | 10 +- swifttools/ukssdc/xrt_prods/docs/conf.py | 19 +-- swifttools/ukssdc/xrt_prods/prod_common.py | 69 ++++++----- swifttools/ukssdc/xrt_prods/prod_request.py | 2 +- swifttools/ukssdc/xrt_prods/productVars.py | 127 +++++++------------- swifttools/ukssdc/xrt_prods/version.py | 4 +- swifttools/version.py | 3 +- 35 files changed, 232 insertions(+), 354 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .pre-commit-config.yaml create mode 100644 ruff.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..a3fc4a47 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python Lint/Format check + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check for Lint + uses: chartboost/ruff-action@v1 + + - name: Check Formatting + uses: chartboost/ruff-action@v1 + with: + args: format --check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..39a2b612 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.2 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..8b4d505a --- /dev/null +++ b/ruff.toml @@ -0,0 +1,20 @@ +# Same as Black +line-length = 120 +indent-width = 4 + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +ignore = ["F405", "F401", "E721", "F403", "F841"] + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" diff --git a/swifttools/__init__.py b/swifttools/__init__.py index af29c8ea..a1ee6f06 100644 --- a/swifttools/__init__.py +++ b/swifttools/__init__.py @@ -2,11 +2,11 @@ Module for astronomers using the Neil Gehrels Swift Observatory. The `swifttools` module provides various features and functionality -designed to support users of the Swift satellite. The top-level -module has no contents, instead you will need to use one of the +designed to support users of the Swift satellite. The top-level +module has no contents, instead you will need to use one of the sub-modules: - * swifttools.swift_too -- Tools for submitting ToOs, and getting + * swifttools.swift_too -- Tools for submitting ToOs, and getting visibility and observing history. * xrt_prods -- Tools for the automated analysis of XRT Data. """ diff --git a/swifttools/swift_too/__init__.py b/swifttools/swift_too/__init__.py index b360194b..69e2c0aa 100755 --- a/swifttools/swift_too/__init__.py +++ b/swifttools/swift_too/__init__.py @@ -4,7 +4,7 @@ The module is split into the following main classes: -1. Swift_TOO +1. Swift_TOO This allows Target of Opportunity requests to be constructed and submitted. This includes basic validation before submission, and querying of the status of the @@ -104,7 +104,7 @@ 10. Swift_Data `Swift_Data` provides an easy interface to download archival or quick-look data -from the Swift Science Data Centers in the USA or UK. +from the Swift Science Data Centers in the USA or UK. 11. Swift_Resolve @@ -143,6 +143,7 @@ served basis. Typically processing requests takes a 10-20 seconds. Status of requests can be queried, and errors are reported back. """ + from .api_resolve import Resolve, Swift_Resolve from .query_job import QueryJob from .swift_calendar import Calendar, Swift_Calendar diff --git a/swifttools/swift_too/api_common.py b/swifttools/swift_too/api_common.py index ab371163..3c11fac9 100644 --- a/swifttools/swift_too/api_common.py +++ b/swifttools/swift_too/api_common.py @@ -14,10 +14,8 @@ # Make Warnings a little less weird formatwarning_orig = warnings.formatwarning -warnings.formatwarning = ( - lambda message, category, filename, lineno, line=None: formatwarning_orig( - message, category, filename, lineno, line="" - ) +warnings.formatwarning = lambda message, category, filename, lineno, line=None: formatwarning_orig( + message, category, filename, lineno, line="" ) # Define the API version @@ -103,9 +101,7 @@ def convert_to_dt(value, isutc=False, outfunc=datetime): ) dtvalue = dtvalue.astimezone(timezone.utc).replace(tzinfo=None) else: - raise ValueError( - "Date/time given as string should 'YYYY-MM-DD HH:MM:SS' or ISO8601 format." - ) + raise ValueError("Date/time given as string should 'YYYY-MM-DD HH:MM:SS' or ISO8601 format.") elif type(value) == date: dtvalue = outfunc.strptime(f"{value} 00:00:00", "%Y-%m-%d %H:%M:%S") elif type(value) == outfunc: @@ -113,12 +109,7 @@ def convert_to_dt(value, isutc=False, outfunc=datetime): # Strip out timezone info and convert to UTC value = value.astimezone(timezone.utc).replace(tzinfo=None) dtvalue = value # Just pass through un molested - elif ( - type(value) == swiftdatetime - and outfunc == datetime - or type(value) == datetime - and outfunc == swiftdatetime - ): + elif type(value) == swiftdatetime and outfunc == datetime or type(value) == datetime and outfunc == swiftdatetime: if type(value) == datetime and value.tzinfo is not None: # Strip out timezone info and convert to UTC value = value.astimezone(timezone.utc).replace(tzinfo=None) @@ -155,9 +146,7 @@ def _tablefy(table, header=None): tab = "" if header is not None: tab += "" - tab += "".join( - [f"" for head in header] - ) + tab += "".join([f"" for head in header]) tab += "" for row in table: @@ -197,22 +186,16 @@ def shared_secret(self): if self._shared_secret is None and self.username != "anonymous": # Try to fetch password using keyring, if available if keyring_support: - self._shared_secret = keyring.get_password( - "swifttools.swift_too", self.username - ) + self._shared_secret = keyring.get_password("swifttools.swift_too", self.username) else: - raise Exception( - "Warning: keyring support not available. Please set shared_secret manually." - ) + raise Exception("Warning: keyring support not available. Please set shared_secret manually.") elif self.username == "anonymous": return "anonymous" return self._shared_secret @shared_secret.setter def shared_secret(self, secret): - if self.username != "anonymous" and ( - self.username is not None or self.username == "" - ): + if self.username != "anonymous" and (self.username is not None or self.username == ""): # Try to remember the password using keyring if available if keyring_support: try: @@ -250,9 +233,7 @@ def _repr_html_(self): and self.status == "Rejected" and self.status.__class__.__name__ == "Swift_TOO_Status" ): - return "Rejected with the following error(s): " + " ".join( - self.status.errors - ) + return "Rejected with the following error(s): " + " ".join(self.status.errors) else: header, table = self._table if len(table) > 0: @@ -266,9 +247,7 @@ def __str__(self): and self.status == "Rejected" and self.status.__class__.__name__ == "Swift_TOO_Status" ): - return "Rejected with the following error(s): " + " ".join( - self.status.errors - ) + return "Rejected with the following error(s): " + " ".join(self.status.errors) else: header, table = self._table if len(table) > 0: @@ -297,9 +276,7 @@ def _parseargs(self, *args, **kwargs): if key in self._parameters + self._local: setattr(self, key, kwargs[key]) else: - raise TypeError( - f"{self.api_name} got an unexpected keyword argument '{key}'" - ) + raise TypeError(f"{self.api_name} got an unexpected keyword argument '{key}'") @property def too_api_dict(self): @@ -323,9 +300,7 @@ def api_data(self): elif type(value) == list or type(value) == tuple: def conv(x): - return ( - f"{x}" if not hasattr(x, "too_api_dict") else x.too_api_dict - ) + return f"{x}" if not hasattr(x, "too_api_dict") else x.too_api_dict data[param] = [conv(entry) for entry in value] elif ( @@ -387,9 +362,7 @@ def __convert_dict_entry(self, entry): mins = int(mins) secs = int(float(secs)) millisecs = int(1000.0 * secs % 1) - val = timedelta( - hours=hours, minutes=mins, seconds=secs, milliseconds=millisecs - ) + val = timedelta(hours=hours, minutes=mins, seconds=secs, milliseconds=millisecs) # Parse dates into a datetime.date match = re.match(_date_regex, str(entry)) @@ -442,17 +415,13 @@ def __read_dict(self, data_dict): for key in data_dict.keys(): if key in self._parameters or key in self._attributes: val = self.__convert_dict_entry(data_dict[key]) - if ( - val is not None - ): # If value is set to None, then don't change the value + if val is not None: # If value is set to None, then don't change the value if hasattr(self, f"_{key}"): setattr(self, f"_{key}", val) else: setattr(self, key, val) else: - if ( - not self.ignorekeys - ): # If keys exist in JSON we don't understand, fail out + if not self.ignorekeys: # If keys exist in JSON we don't understand, fail out self.__set_error(f"Unknown key in JSON file: {key}") return False return True # No errors @@ -471,9 +440,7 @@ def queue(self, post=True): return False # Make sure it passes validation checks if not self.validate(): - self.__set_error( - "Swift TOO API submission did not pass internal validation checks." - ) + self.__set_error("Swift TOO API submission did not pass internal validation checks.") return False return self.__submit_jwt(post=post) @@ -508,9 +475,7 @@ def __submit_jwt(self, post=True): try: too_api_dict = json.loads(r.text) except json.decoder.JSONDecodeError: - self.__set_error( - "Failed to decode JSON. Please check that your shared secret is correct." - ) + self.__set_error("Failed to decode JSON. Please check that your shared secret is correct.") self.__set_status("Rejected") return False @@ -606,19 +571,12 @@ def __repr__(self): def __sub__(self, other): """Redefined __sub__ to handle mismatched time bases""" if isinstance(other, swiftdatetime): - if self.isutc != other.isutc and ( - self.utctime is None or other.utctime is None - ): + if self.isutc != other.isutc and (self.utctime is None or other.utctime is None): raise ArithmeticError( "Cannot subtract mismatched time zones with no UTCF" ) # FIXME - correct exception? - if ( - self.isutc is True - and other.isutc is True - or self.isutc is False - and other.isutc is False - ): + if self.isutc is True and other.isutc is True or self.isutc is False and other.isutc is False: return super().__sub__(other) else: if self.isutc: @@ -730,9 +688,7 @@ def frommet(cls, met, utcf=None, isutc=False): dt = datetime(2001, 1, 1) + timedelta(seconds=met) if isutc and utcf is not None: dt += timedelta(seconds=utcf) - ret = cls( - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond - ) + ret = cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond) ret.utcf = utcf ret.isutc = isutc return ret diff --git a/swifttools/swift_too/api_daterange.py b/swifttools/swift_too/api_daterange.py index 0a5f7d5e..1c41494c 100644 --- a/swifttools/swift_too/api_daterange.py +++ b/swifttools/swift_too/api_daterange.py @@ -26,11 +26,7 @@ class TOOAPI_Daterange: @property def begin(self): - if ( - hasattr(self._begin, "utcf") - and self._begin.utcf is None - and self.utcf is not None - ): + if hasattr(self._begin, "utcf") and self._begin.utcf is None and self.utcf is not None: self._begin.utcf = self.utcf return self._begin @@ -38,11 +34,7 @@ def begin(self): def end(self): if self._begin is not None and self._length is not None and self._end is None: self._end = self.begin + timedelta(days=self._length) - if ( - hasattr(self._end, "utcf") - and self._end.utcf is None - and self.utcf is not None - ): + if hasattr(self._end, "utcf") and self._end.utcf is None and self.utcf is not None: self._end.utcf = self.utcf return self._end diff --git a/swifttools/swift_too/query_job.py b/swifttools/swift_too/query_job.py index 6295cd6f..5d24b2ed 100644 --- a/swifttools/swift_too/query_job.py +++ b/swifttools/swift_too/query_job.py @@ -74,9 +74,7 @@ def __init__(self, *args, **kwargs): shared secret for TOO API (default 'anonymous') """ # Call the TOOStatus constructor with fetchresult=True to that make this a QueryJob - TOOStatus.__init__( - self, *args, fetchresult=True, api_name=self.api_name, **kwargs - ) + TOOStatus.__init__(self, *args, fetchresult=True, api_name=self.api_name, **kwargs) @property def _table(self): @@ -85,9 +83,7 @@ def _table(self): table = [ [row, getattr(self, row)] for row in _parameters - if getattr(self, row) is not None - and getattr(self, row) != "" - and row != "result" + if getattr(self, row) is not None and getattr(self, row) != "" and row != "result" ] table.append(["result", self.result.__class__.__name__ + " object"]) return ["Parameter", "Value"], table diff --git a/swifttools/swift_too/swift_clock.py b/swifttools/swift_too/swift_clock.py index d2a753a9..6ac48994 100644 --- a/swifttools/swift_too/swift_clock.py +++ b/swifttools/swift_too/swift_clock.py @@ -177,30 +177,20 @@ def validate(self): else: self._isutc = True # Basic check that anything is set - if ( - self.met is not None - or self.swifttime is not None - or self.utctime is not None - ): + if self.met is not None or self.swifttime is not None or self.utctime is not None: return True def to_utctime(self): """Convert all entries to a UTC time base""" mets = [entry.met for entry in self.entries] utcfs = [entry.utcf for entry in self.entries] - self.entries = [ - swiftdatetime.frommet(mets[i], utcf=utcfs[i], isutc=True) - for i in range(len(mets)) - ] + self.entries = [swiftdatetime.frommet(mets[i], utcf=utcfs[i], isutc=True) for i in range(len(mets))] def to_swifttime(self): """Convert all entries to a Swift Time base""" mets = [entry.met for entry in self.entries] utcfs = [entry.utcf for entry in self.entries] - self.entries = [ - swiftdatetime.frommet(mets[i], utcf=utcfs[i], isutc=False) - for i in range(len(mets)) - ] + self.entries = [swiftdatetime.frommet(mets[i], utcf=utcfs[i], isutc=False) for i in range(len(mets))] # Aliases mettime = met @@ -229,9 +219,7 @@ def index_datetimes(dictionary, i=0, values=[], setvals=None): # If value is a list, recurse one by one elif isinstance(value, (list, tuple)): for j in range(len(value)): - i, values = index_datetimes( - {f"value{j}": value[j]}, i, values, setvals=setvals - ) + i, values = index_datetimes({f"value{j}": value[j]}, i, values, setvals=setvals) # If value is a datetime, record and/or update to the result from # Swift_Clock, increment the counter diff --git a/swifttools/swift_too/swift_data.py b/swifttools/swift_too/swift_data.py index 7f2f7da9..3e879568 100644 --- a/swifttools/swift_too/swift_data.py +++ b/swifttools/swift_too/swift_data.py @@ -1,15 +1,11 @@ import os import warnings from fnmatch import fnmatch -import warnings import boto3 from botocore import UNSIGNED from botocore.client import Config -import boto3 import requests -from botocore import UNSIGNED -from botocore.client import Config from .api_common import TOOAPI_Baseclass from .api_status import TOOStatus @@ -298,14 +294,7 @@ def _table(self): @property def all(self): - if ( - self.xrt - and self.uvot - and self.bat - and self.log - and self.auxil - and self.tdrss - ): + if self.xrt and self.uvot and self.bat and self.log and self.auxil and self.tdrss: return True return False @@ -334,9 +323,7 @@ def _post_process(self): while i < len(self.entries): keep = False for match in self.match: - if fnmatch( - f"{self.entries[i].path}/{self.entries[i].filename}", match - ): + if fnmatch(f"{self.entries[i].path}/{self.entries[i].filename}", match): keep = True if not keep: del self.entries[i] @@ -391,9 +378,7 @@ def download(self, outdir=None): # Index any existing files for i in range(len(self.entries)): - fullfilepath = os.path.join( - self.outdir, self.entries[i].path, self.entries[i].filename - ) + fullfilepath = os.path.join(self.outdir, self.entries[i].path, self.entries[i].filename) if os.path.exists(fullfilepath): self.entries[i].localpath = fullfilepath @@ -406,9 +391,7 @@ def download(self, outdir=None): # Don't re-download a file unless clobber=True localfile = f"{self.outdir}/{dfile.path}/{dfile.filename}" if not self.clobber and os.path.exists(localfile) and not self.quiet: - warnings.warn( - f"{dfile.filename} exists and not overwritten (set clobber=True to override this)." - ) + warnings.warn(f"{dfile.filename} exists and not overwritten (set clobber=True to override this).") elif not dfile.download(outdir=self.outdir): self.status.error(f"Error downloading {dfile.filename}") return False @@ -439,9 +422,7 @@ def download(self, *args, **kwargs): if key in params + self._local: setattr(data, key, kwargs[key]) else: - raise TypeError( - f"{self.api_name} got an unexpected keyword argument '{key}'" - ) + raise TypeError(f"{self.api_name} got an unexpected keyword argument '{key}'") # Set up and download data data.obsid = self.obsid data.username = self.username diff --git a/swifttools/swift_too/swift_guano.py b/swifttools/swift_too/swift_guano.py index b9551fa4..7840c263 100644 --- a/swifttools/swift_too/swift_guano.py +++ b/swifttools/swift_too/swift_guano.py @@ -240,15 +240,11 @@ def _table(self): return ["Parameter", "Value"], table def _calc_begin_end(self): - self.begin = self.triggertime + timedelta( - seconds=self.offset - self.duration / 2 - ) + self.begin = self.triggertime + timedelta(seconds=self.offset - self.duration / 2) self.end = self.triggertime + timedelta(seconds=self.offset + self.duration / 2) -class Swift_GUANO( - TOOAPI_Baseclass, TOOAPI_Daterange, TOOAPI_ClockCorrect, TOOAPI_TriggerTime -): +class Swift_GUANO(TOOAPI_Baseclass, TOOAPI_Daterange, TOOAPI_ClockCorrect, TOOAPI_TriggerTime): """Query BAT ring buffer dumps of event data associated with the Gamma-Ray Burst Urgent Archiver for Novel Opportunities (GUANO). @@ -374,9 +370,7 @@ def validate(self): or self.triggertype is not None ): if self.subthreshold is True and self.username == "anonymous": - self.status.error( - "For subthreshold triggers, username cannot be anonymous." - ) + self.status.error("For subthreshold triggers, username cannot be anonymous.") return False return True @@ -407,9 +401,7 @@ def _table(self): obsnum = "Pending Data" elif ent.uplinked: obsnum = "Pending Execution" - table.append( - [ent.triggertype, ent.triggertime, ent.offset, exposure, obsnum] - ) + table.append([ent.triggertype, ent.triggertime, ent.offset, exposure, obsnum]) return header, table diff --git a/swifttools/swift_too/swift_instruments.py b/swifttools/swift_too/swift_instruments.py index e69763d9..3d2dd456 100644 --- a/swifttools/swift_too/swift_instruments.py +++ b/swifttools/swift_too/swift_instruments.py @@ -65,9 +65,7 @@ def xrt_mode_setter(self, attr, mode): if mode in XRTMODES.keys(): setattr(self, f"_{attr}", mode) else: - raise ValueError( - f"Unknown mode ({mode}), should be PC (7), WT (6) or Auto (0)" - ) + raise ValueError(f"Unknown mode ({mode}), should be PC (7), WT (6) or Auto (0)") @property def xrt(self): diff --git a/swifttools/swift_too/swift_obsid.py b/swifttools/swift_too/swift_obsid.py index ddd99fb9..433c4df0 100644 --- a/swifttools/swift_too/swift_obsid.py +++ b/swifttools/swift_too/swift_obsid.py @@ -48,10 +48,7 @@ def obsnum(self): if self._target_id is None or self._seg is None: return None elif type(self._target_id) == list: - return [ - f"{self.target_id[i]:08d}{self.seg[i]:03d}" - for i in range(len(self._target_id)) - ] + return [f"{self.target_id[i]:08d}{self.seg[i]:03d}" for i in range(len(self._target_id))] else: return f"{self.target_id:08d}{self.seg:03d}" @@ -76,10 +73,7 @@ def obsnum(self, obsnum): def obsnumsc(self): """Return the obsnum in spacecraft format""" if type(self._target_id) == list: - return [ - self._target_id[i] + (self._seg[i] << 24) - for i in range(len(self._target_id)) - ] + return [self._target_id[i] + (self._seg[i] << 24) for i in range(len(self._target_id))] return self._target_id + (self._seg << 24) # Aliases diff --git a/swifttools/swift_too/swift_planquery.py b/swifttools/swift_too/swift_planquery.py index 3d3992e6..d495bffd 100644 --- a/swifttools/swift_too/swift_planquery.py +++ b/swifttools/swift_too/swift_planquery.py @@ -113,9 +113,7 @@ def exposure(self): def _table(self): _parameters = ["begin", "end", "targname", "obsnum", "exposure"] header = [self._header_title(row) for row in _parameters] - return header, [ - [self.begin, self.end, self.targname, self.obsnum, self.exposure.seconds] - ] + return header, [[self.begin, self.end, self.targname, self.obsnum, self.exposure.seconds]] class Swift_PPST( diff --git a/swifttools/swift_too/swift_requests.py b/swifttools/swift_too/swift_requests.py index 833f03ca..3ba51470 100644 --- a/swifttools/swift_too/swift_requests.py +++ b/swifttools/swift_too/swift_requests.py @@ -7,9 +7,7 @@ from .swift_toorequest import Swift_TOO_Request -class Swift_TOO_Requests( - TOOAPI_Baseclass, TOOAPI_Daterange, TOOAPI_SkyCoord, TOOAPI_AutoResolve -): +class Swift_TOO_Requests(TOOAPI_Baseclass, TOOAPI_Daterange, TOOAPI_SkyCoord, TOOAPI_AutoResolve): """Class used to obtain details about previous TOO requests. Attributes diff --git a/swifttools/swift_too/swift_toorequest.py b/swifttools/swift_too/swift_toorequest.py index 7117f8ec..983c83c7 100755 --- a/swifttools/swift_too/swift_toorequest.py +++ b/swifttools/swift_too/swift_toorequest.py @@ -327,9 +327,7 @@ def __init__(self, *args, **kwargs): self.too_id = None # TOO ID assigned by server on acceptance (int) self.timestamp = None # Timestamp that TOO was accepted (datetime) # Source name, type, location, position_error - self.source_name = ( - None # Name of the object we're requesting a TOO for (string) - ) + self.source_name = None # Name of the object we're requesting a TOO for (string) # Type of object (e.g. "Supernova", "LMXB", "BL Lac") (string) self.source_type = None self.ra = None # RA(J2000) Degrees decimal (float) @@ -499,9 +497,7 @@ def validate(self): return False if self.obs_type not in self.obs_types: - self.status.error( - f"Observation Type needs to be one of the following: {self.obs_types}" - ) + self.status.error(f"Observation Type needs to be one of the following: {self.obs_types}") return False if self.instrument not in self.instruments: @@ -518,9 +514,7 @@ def validate(self): self.exp_time_per_visit = int(self.exposure / self.num_of_visits) else: if self.exp_time_per_visit * self.num_of_visits != self.exposure: - self.status.warning( - "INFO: Total exposure time does not match total of individuals. Corrected." - ) + self.status.warning("INFO: Total exposure time does not match total of individuals. Corrected.") self.exposure = self.exp_time_per_visit * self.num_of_visits else: if not self.exposure: @@ -530,13 +524,9 @@ def validate(self): if self.monitoring_freq is not None: if HAS_ASTROPY and type(self.monitoring_freq) is u.quantity.Quantity: if self.monitoring_freq.to(u.day).value >= (1 * u.day).value: - self.monitoring_freq = ( - f"{self.monitoring_freq.to(u.day).value} days" - ) + self.monitoring_freq = f"{self.monitoring_freq.to(u.day).value} days" else: - self.monitoring_freq = ( - f"{self.monitoring_freq.to(u.hour).value} hours" - ) + self.monitoring_freq = f"{self.monitoring_freq.to(u.hour).value} hours" _, unit = self.monitoring_freq.strip().split() if unit[-1] == "s": diff --git a/swifttools/swift_too/swift_uvot.py b/swifttools/swift_too/swift_uvot.py index d79c0399..0faa50fd 100644 --- a/swifttools/swift_too/swift_uvot.py +++ b/swifttools/swift_too/swift_uvot.py @@ -66,9 +66,7 @@ def __str__(self): return self.filter_name -class Swift_UVOTMode( - TOOAPI_Baseclass, TOOAPI_Instruments, TOOAPI_SkyCoord, TOOAPI_AutoResolve -): +class Swift_UVOTMode(TOOAPI_Baseclass, TOOAPI_Instruments, TOOAPI_SkyCoord, TOOAPI_AutoResolve): """Class to fetch information about a given UVOT mode. Specifically this is useful for understanding for a given UVOT hex mode (e.g. 0x30ed), which filters and configuration are used by UVOT. @@ -139,14 +137,8 @@ def __len__(self): def __str__(self): """Display UVOT mode table""" - if ( - hasattr(self, "status") - and self.status == "Rejected" - and self.status.__class__.__name__ == "TOOStatus" - ): - return "Rejected with the following error(s): " + " ".join( - self.status.errors - ) + if hasattr(self, "status") and self.status == "Rejected" and self.status.__class__.__name__ == "TOOStatus": + return "Rejected with the following error(s): " + " ".join(self.status.errors) elif self.entries is not None: table_cols = [ "filter_name", @@ -176,12 +168,8 @@ def __str__(self): table += "The following table summarizes this mode, ordered by the filter sequence:\n" table += tabulate(table_columns, tablefmt="pretty") table += "\nFilter: The particular filter in the sequence.\n" - table += ( - "Event FOV: The size of the FOV (in arc-minutes) for UVOT event data.\n" - ) - table += ( - "Image FOV: The size of the FOV (in arc-minutes) for UVOT image data.\n" - ) + table += "Event FOV: The size of the FOV (in arc-minutes) for UVOT event data.\n" + table += "Image FOV: The size of the FOV (in arc-minutes) for UVOT image data.\n" table += "Max. Exp. Time: The maximum amount of time the snapshot will spend on the particular filter in the sequence.\n" table += "Weighting: Ratio of time spent on the particular filter in the sequence.\n" table += "Comments: Additional notes that may be useful to know.\n" @@ -191,14 +179,8 @@ def __str__(self): def _repr_html_(self): """Jupyter Notebook friendly display of UVOT mode table""" - if ( - hasattr(self, "status") - and self.status == "Rejected" - and self.status.__class__.__name__ == "TOOStatus" - ): - return "Rejected with the following error(s): " + " ".join( - self.status.errors - ) + if hasattr(self, "status") and self.status == "Rejected" and self.status.__class__.__name__ == "TOOStatus": + return "Rejected with the following error(s): " + " ".join(self.status.errors) elif self.entries is not None: html = f"

UVOT Mode: {self.uvotmode}

" html += "

The following table summarizes this mode, ordered by the filter sequence:

" @@ -260,9 +242,7 @@ def validate(self): if self.uvotmode is None: return False if not self.username or not self.shared_secret: - self.status.error( - "username and shared_secret parameters need to be supplied." - ) + self.status.error("username and shared_secret parameters need to be supplied.") return False if type(self._uvot) != int: diff --git a/swifttools/swift_too/swift_visquery.py b/swifttools/swift_too/swift_visquery.py index cee39b0b..b2831896 100644 --- a/swifttools/swift_too/swift_visquery.py +++ b/swifttools/swift_too/swift_visquery.py @@ -42,9 +42,7 @@ def length(self): @property def _table(self): - header = [ - self._header_title(row) for row in self._parameters + self._attributes - ] + header = [self._header_title(row) for row in self._parameters + self._attributes] return header, [[self.begin, self.end, self.length]] def __str__(self): diff --git a/swifttools/swift_too/version.py b/swifttools/swift_too/version.py index e75484be..daffe27c 100644 --- a/swifttools/swift_too/version.py +++ b/swifttools/swift_too/version.py @@ -1,3 +1,2 @@ version = "1.2.32" version_tuple = (1, 2, 32) - diff --git a/swifttools/ukssdc/__init__.py b/swifttools/ukssdc/__init__.py index bb958fde..29d6402b 100644 --- a/swifttools/ukssdc/__init__.py +++ b/swifttools/ukssdc/__init__.py @@ -1,7 +1,7 @@ """ukssdc module, providing API interface to UKSSDC services. This module provices an interface to various services offered by the -UK Swift Science Data Centre, which have traditionally been provided +UK Swift Science Data Centre, which have traditionally been provided via the website https://www.swift.ac.uk. This README will be updated later. @@ -10,4 +10,4 @@ __all__ = ["APIURL"] from .main import APIURL, plotLightCurve, bayesRate, mergeLightCurveBins, mergeUpperLimits # noqa -from .version import __version__, _apiVersion # noqa \ No newline at end of file +from .version import __version__, _apiVersion # noqa diff --git a/swifttools/ukssdc/data/GRB.py b/swifttools/ukssdc/data/GRB.py index a445be99..3b869bb3 100644 --- a/swifttools/ukssdc/data/GRB.py +++ b/swifttools/ukssdc/data/GRB.py @@ -7,6 +7,7 @@ This documentation will be updated when I've written the code """ + __docformat__ = "restructedtext en" @@ -1176,7 +1177,6 @@ def getBurstAnalyser( raise RuntimeError(f"Failed getting tar for {key}") if saveData or returnData: - sendData = { "targetID": t, "instruments": instruments, @@ -1471,7 +1471,7 @@ def saveSingleBurstAn( # If we don't want the bad data, filter them: if not badBATBins: # flake is wrong, is False crashes! - tmp = data["BAT"][b][d][data["BAT"][b][d]["BadBin"] == False] # noqa; + tmp = data["BAT"][b][d][data["BAT"][b][d]["BadBin"] == False] # noqa: E712 # For QDP, this gets more complicated, we need to filter the columns to get rid of the unwanted errors if asQDP: diff --git a/swifttools/ukssdc/data/SXPS.py b/swifttools/ukssdc/data/SXPS.py index d789c509..44dc529d 100644 --- a/swifttools/ukssdc/data/SXPS.py +++ b/swifttools/ukssdc/data/SXPS.py @@ -1770,7 +1770,7 @@ def saveDatasetImages( # We can just accept this result, as the only key we have to have is "OK", and # if that wasn't set we will already have hit an error. tmp = base.submitAPICall("getSXPSDatasetImages", sendData, verbose=verbose) - if 'SupersededBy' in tmp: + if "SupersededBy" in tmp: print("\n ** WARNING: This dataset is an obsolete stack ** \n") path = destDir if subDirs: diff --git a/swifttools/ukssdc/data/__init__.py b/swifttools/ukssdc/data/__init__.py index b401eec5..9845846c 100644 --- a/swifttools/ukssdc/data/__init__.py +++ b/swifttools/ukssdc/data/__init__.py @@ -15,4 +15,3 @@ """ from .download import downloadObsData, downloadObsDataByTarget # noqa - diff --git a/swifttools/ukssdc/data/download.py b/swifttools/ukssdc/data/download.py index 62b38b3e..47f3ccc4 100644 --- a/swifttools/ukssdc/data/download.py +++ b/swifttools/ukssdc/data/download.py @@ -3,6 +3,7 @@ This module provides functions for downloading data or products. """ + __docformat__ = "restructedtext en" diff --git a/swifttools/ukssdc/main.py b/swifttools/ukssdc/main.py index 2361dbca..31e15b57 100644 --- a/swifttools/ukssdc/main.py +++ b/swifttools/ukssdc/main.py @@ -1097,7 +1097,6 @@ def mergeUpperLimits( # First, do we have all of the columns? gotAll = True for c in needCols: - if f"{b}_{c}" not in tmp.columns: gotAll = False break diff --git a/swifttools/ukssdc/query/DBFilter.py b/swifttools/ukssdc/query/DBFilter.py index 34143f21..2caab86d 100644 --- a/swifttools/ukssdc/query/DBFilter.py +++ b/swifttools/ukssdc/query/DBFilter.py @@ -5,6 +5,7 @@ SQL 'WHERE' statements. """ + __docformat__ = "restructedtext en" @@ -151,7 +152,6 @@ def __init__(self, filterDef, metadata=None): # May have to do this twice, if combiner is set. suffs = ("", "2") for s in suffs: - # ------- # filter numArgs = 0 @@ -201,7 +201,7 @@ def __init__(self, filterDef, metadata=None): # Last thing is to check that we are valid, if metadata was supplied if (metadata is not None) and (not self.isValid(metadata)): - tmp = self._data['colName'] + tmp = self._data["colName"] self._data = None raise RuntimeError(f"Cannot create filter, {tmp} is not a valid column.") diff --git a/swifttools/ukssdc/query/SXPS.py b/swifttools/ukssdc/query/SXPS.py index e47bd3d7..40431558 100644 --- a/swifttools/ukssdc/query/SXPS.py +++ b/swifttools/ukssdc/query/SXPS.py @@ -1,6 +1,5 @@ -"""File with the for the SXPSQuery class. +"""File with the for the SXPSQuery class.""" -""" __docformat__ = "restructedtext en" import pandas as pd diff --git a/swifttools/ukssdc/query/__init__.py b/swifttools/ukssdc/query/__init__.py index 21f4d20c..b7ddf8ee 100644 --- a/swifttools/ukssdc/query/__init__.py +++ b/swifttools/ukssdc/query/__init__.py @@ -1,14 +1,14 @@ """ukssdc.query module, providing API interface to UKSSDC services. This module provices an interface to various services offered by the -UK Swift Science Data Centre, which have traditionally been provided +UK Swift Science Data Centre, which have traditionally been provided via the website https://www.swift.ac.uk. This README will be updated later. """ __all__ = ["ObsQuery", "SXPSQuery", "GRBQuery"] -from .dataquery_base import dataQuery # noqa -from .obsData import ObsQuery # noqa -from .SXPS import SXPSQuery # noqa -from .GRB import GRBQuery # noqa +from .dataquery_base import dataQuery # noqa +from .obsData import ObsQuery # noqa +from .SXPS import SXPSQuery # noqa +from .GRB import GRBQuery # noqa diff --git a/swifttools/ukssdc/xrt_prods/__init__.py b/swifttools/ukssdc/xrt_prods/__init__.py index 7cfe4cab..d4e5468a 100644 --- a/swifttools/ukssdc/xrt_prods/__init__.py +++ b/swifttools/ukssdc/xrt_prods/__init__.py @@ -11,10 +11,10 @@ https://www.swift.ac.uk/user_objects/register.php -This module exports a single class: XRTProductRequest. +This module exports a single class: XRTProductRequest. -There is a second class within the module - the ProductRequest, -which contains individual products (LightCurve, Spectrum) etc. +There is a second class within the module - the ProductRequest, +which contains individual products (LightCurve, Spectrum) etc. This is not intended exported, as it is not intended for public use, however if you retrieve a specific product for passing between requests then it will be an instance of this class. @@ -30,7 +30,7 @@ :: # The email address supplied must be registered for API usage - myRequest = XRTProductRequest('your_email_address') + myRequest = XRTProductRequest('your_email_address') myRequest.addLightCurve( .. some arguments ..) myRequest.addStandardPosition( .. some arguments ..) if myRequest.submit(): @@ -47,5 +47,5 @@ """ from .prod_request import XRTProductRequest -from .prod_request import listOldJobs,countActiveJobs +from .prod_request import listOldJobs, countActiveJobs from .version import __version__, _apiVersion diff --git a/swifttools/ukssdc/xrt_prods/docs/conf.py b/swifttools/ukssdc/xrt_prods/docs/conf.py index 786bf4f3..36dce900 100644 --- a/swifttools/ukssdc/xrt_prods/docs/conf.py +++ b/swifttools/ukssdc/xrt_prods/docs/conf.py @@ -12,13 +12,14 @@ # import os import sys -sys.path.insert(0, '/Users/pae9/soft/src/swift-too') + +sys.path.insert(0, "/Users/pae9/soft/src/swift-too") # -- Project information ----------------------------------------------------- -project = 'xrt_prods' -copyright = '2020, Phil Evans' -author = 'Phil Evans' +project = "xrt_prods" +copyright = "2020, Phil Evans" +author = "Phil Evans" # -- General configuration --------------------------------------------------- @@ -26,7 +27,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ "sphinx.ext.autodoc", "sphinxcontrib.napoleon", "sphinx.ext.autosectionlabel"] +extensions = ["sphinx.ext.autodoc", "sphinxcontrib.napoleon", "sphinx.ext.autosectionlabel"] napoleon_include_init_with_doc = True napoleon_google_docstring = False napoleon_numpy_docstring = True @@ -34,12 +35,12 @@ autosectionlabel_maxdepth = 3 # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -47,9 +48,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ["_static"] diff --git a/swifttools/ukssdc/xrt_prods/prod_common.py b/swifttools/ukssdc/xrt_prods/prod_common.py index f45a30ce..ddd4f817 100644 --- a/swifttools/ukssdc/xrt_prods/prod_common.py +++ b/swifttools/ukssdc/xrt_prods/prod_common.py @@ -1,45 +1,50 @@ # The variable that indicates if a parameter is required -classReqPar = {'lc': 'lc', - 'spec': 'spec', - 'psf': 'dopsf', - 'enh': 'doenh', - 'xastrom': 'doxastrom', - 'image': 'image', - 'sourceDet': 'sourceDet' - } +classReqPar = { + "lc": "lc", + "spec": "spec", + "psf": "dopsf", + "enh": "doenh", + "xastrom": "doxastrom", + "image": "image", + "sourceDet": "sourceDet", +} # Conversion from the short-form normally used to reference them, so a # full name for printing -longProdName = {'lc': 'light curve', - 'spec': 'spectrum', - 'psf': 'standard position', - 'enh': 'enhanced position', - 'xastrom': 'astrometric position', - 'image': 'image', - 'sourceDet': 'source detection'} +longProdName = { + "lc": "light curve", + "spec": "spectrum", + "psf": "standard position", + "enh": "enhanced position", + "xastrom": "astrometric position", + "image": "image", + "sourceDet": "source detection", +} # Conversion from the long name used by the Python interface, to my # abbrevations -longToShort = {'LightCurve': 'lc', - 'Spectrum': 'spec', - 'StandardPos': 'psf', - 'EnhancedPos': 'enh', - 'AstromPos': 'xastrom', - 'Image': 'image', - 'SourceDet': 'sourceDet' - } +longToShort = { + "LightCurve": "lc", + "Spectrum": "spec", + "StandardPos": "psf", + "EnhancedPos": "enh", + "AstromPos": "xastrom", + "Image": "image", + "SourceDet": "sourceDet", +} # Conversion from my abbreviations to those used by the Python # interface. -shortToLong = {'lc': 'LightCurve', - 'spec': 'Spectrum', - 'psf': 'StandardPos', - 'enh': 'EnhancedPos', - 'xastrom': 'AstromPos', - 'image': 'Image', - 'sourceDet': 'SourceDet' - } +shortToLong = { + "lc": "LightCurve", + "spec": "Spectrum", + "psf": "StandardPos", + "enh": "EnhancedPos", + "xastrom": "AstromPos", + "image": "Image", + "sourceDet": "SourceDet", +} # downloadFormats lists supported download formats: -downloadFormats = ('zip', 'tar', 'tar.gz') +downloadFormats = ("zip", "tar", "tar.gz") diff --git a/swifttools/ukssdc/xrt_prods/prod_request.py b/swifttools/ukssdc/xrt_prods/prod_request.py index 05230cd9..38a535c2 100644 --- a/swifttools/ukssdc/xrt_prods/prod_request.py +++ b/swifttools/ukssdc/xrt_prods/prod_request.py @@ -3455,7 +3455,7 @@ def plotLC(self, xlog=False, ylog=False, fileName=None, incbad=False, nosys=Fals def saveSpectralData( self, - **kwargs + **kwargs, # destDir="spec", # saveImages=True, # saveData=True, diff --git a/swifttools/ukssdc/xrt_prods/productVars.py b/swifttools/ukssdc/xrt_prods/productVars.py index e375f024..419c2388 100644 --- a/swifttools/ukssdc/xrt_prods/productVars.py +++ b/swifttools/ukssdc/xrt_prods/productVars.py @@ -39,7 +39,7 @@ "grades": "specgrades", "incHours": "specobstime", "specStem": "specstem", - "deltaFitStat": "dchi" + "deltaFitStat": "dchi", }, "psf": { "whichData": "posobs", @@ -63,34 +63,20 @@ "detOrCent": "detornot", "centMeth": "detMeth", }, - "image": { - "energies": "imen", - "useObs": "useimobs", - "whichData": "imobs" - }, - "sourceDet": { - "whichData": "detobs", - "useObs": "usedetobs", - "whichBands": "detbands" - } + "image": {"energies": "imen", "useObs": "useimobs", "whichData": "imobs"}, + "sourceDet": {"whichData": "detobs", "useObs": "usedetobs", "whichBands": "detbands"}, } deprecatedPars = { "lc": { "timeType": "timeFormat", }, - "spec": { - }, - "psf": { - }, - "enh": { - }, - "xastrom": { - }, - "image": { - }, - "sourceDet": { - } + "spec": {}, + "psf": {}, + "enh": {}, + "xastrom": {}, + "image": {}, + "sourceDet": {}, } # prodNeedGlobals lists any global parameters which are required by a @@ -102,7 +88,7 @@ "enh": (), "xastrom": (), "image": (), - "sourceDet": () + "sourceDet": (), } # prodUseGlobals shows any parameters that are shared between products, # so will be handled as globals by this API, even though the user sets @@ -121,7 +107,7 @@ "centMeth", ), "image": (), - "sourceDet": () + "sourceDet": (), } # prodNeedPars lists any parameters which are mandatory for the specific @@ -133,17 +119,19 @@ "enh": (), "xastrom": (), "image": (), - "sourceDet": ("whichData",) + "sourceDet": ("whichData",), } prodDefaults = { "lc": {}, - "spec": {"deltaFitStat": 2.706,}, + "spec": { + "deltaFitStat": 2.706, + }, "psf": {}, "enh": {}, "xastrom": {}, "image": {}, - "sourceDet": {} + "sourceDet": {}, } # prodParTypes lists all possible parameters for each product, along @@ -189,7 +177,10 @@ "redshift": (float,), "whichData": (str,), "useObs": (str,), - "incHours": (float, int,), + "incHours": ( + float, + int, + ), "timeslice": (str,), "grades": (str,), "rname1": (str,), @@ -204,24 +195,13 @@ "doNotFit": (bool,), "galactic": (bool,), "models": (list, tuple), - "deltaFitStat": (float,) + "deltaFitStat": (float,), }, "psf": {}, "enh": {}, - "xastrom": { - "useAllObs": (bool,) - }, - "image": { - "energies": (str,), - "whichData": (str,), - "useObs": (str,) - }, - "sourceDet": { - "useObs": (str,), - "whichData": (str,), - "whichBands": (str,), - "fitStrayLight": (bool,) - } + "xastrom": {"useAllObs": (bool,)}, + "image": {"energies": (str,), "whichData": (str,), "useObs": (str,)}, + "sourceDet": {"useObs": (str,), "whichData": (str,), "whichBands": (str,), "fitStrayLight": (bool,)}, } @@ -235,23 +215,18 @@ "timeType": ("m", "s", "mjd", "sec"), "timeFormat": ("m", "s", "mjd", "sec"), "whichData": ("all", "user"), - "grades": ("0", "all", "4") + "grades": ("0", "all", "4"), }, "spec": { "whichData": ("all", "user", "hours"), "timeslice": ("single", "user", "snapshot", "obsid"), - "grades": ("0", "all", "4") + "grades": ("0", "all", "4"), }, "psf": {}, "enh": {}, "xastrom": {}, - "image": { - "whichData": ("all", "user") - }, - "sourceDet": { - "whichData": ("all", "user"), - "whichBands": ("total", "all") - }, + "image": {"whichData": ("all", "user")}, + "sourceDet": {"whichData": ("all", "user"), "whichBands": ("total", "all")}, } # prodParDeps lists any parameter dependencies, i.e. where parameter b @@ -272,17 +247,15 @@ }, "spec": { "whichData": {"user": ("useObs",), "hours": ("incHours",)}, - "hasRedshift": {"True": ("redshift",), }, + "hasRedshift": { + "True": ("redshift",), + }, }, "psf": {}, "enh": {}, "xastrom": {}, - "image": { - "whichData": {"user": ("useObs",)} - }, - "sourceDet": { - "whichData": {"user": ("useObs",)} - }, + "image": {"whichData": {"user": ("useObs",)}}, + "sourceDet": {"whichData": {"user": ("useObs",)}}, } # prodParTriggers lists cases where setting parameter a to some value @@ -290,37 +263,19 @@ # the 'redshift' parmeter is given any value then hasRedshift gets set # to true prodParTriggers = { - "lc": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} - }, + "lc": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, "spec": { "redshift": {"ANY": {"hasRedshift": True, "galactic": True}, "NONE": {"hasRedshift": False}}, "hasRedshift": {True: {"galactic": True}}, "useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}, - "doNotFit": {True: {"hasRedshift": False}} - }, - "psf": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} - }, - "enh": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} - }, - "xastrom": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} - }, - "image": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} - }, - "sourceDet": { - "useObs": {"ANY": {"whichData": "user"}}, - "whichData": {"all": {"useObs": None}} + "doNotFit": {True: {"hasRedshift": False}}, }, + "psf": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, + "enh": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, + "xastrom": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, + "image": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, + "sourceDet": {"useObs": {"ANY": {"whichData": "user"}}, "whichData": {"all": {"useObs": None}}}, } # globalParTriggers lists cases where setting a global parameter to some @@ -339,7 +294,7 @@ "enh": "enh", "xastrom": "xastrom", "image": "image", - "sourceDet": "sourceDet" + "sourceDet": "sourceDet", } skipGlobals = { diff --git a/swifttools/ukssdc/xrt_prods/version.py b/swifttools/ukssdc/xrt_prods/version.py index affce641..3d387d34 100644 --- a/swifttools/ukssdc/xrt_prods/version.py +++ b/swifttools/ukssdc/xrt_prods/version.py @@ -1,2 +1,2 @@ -__version__ = '1.11' -_apiVersion = '1.11' +__version__ = "1.11" +_apiVersion = "1.11" diff --git a/swifttools/version.py b/swifttools/version.py index be8173b0..3a43c585 100644 --- a/swifttools/version.py +++ b/swifttools/version.py @@ -1,2 +1 @@ -__version__ = '3.0.21' - +__version__ = "3.0.21" From 8bf794def9f7d023a8f6929c761c1e33e6c086b4 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Thu, 9 May 2024 13:15:52 +0100 Subject: [PATCH 09/22] Fixed various minor python issues giving linting errors. (#11) * Fixed various minor python issues giving linting errors. * Re-applied formatting fixes * Reformatted prod_requst using ruff instead of black * Modified ruff.toml to include F/E otherwise vscode is not using it properly, I think * Reverted last change to ruff.toml; was a mistake * Fixed type checks to use isinstance() * Completed reversion of ruff.toml - my error * FFS, think I've finally got the formatting as ruff wants it * Remove one ruff filter --------- Co-authored-by: Jamie Kennea --- ruff.toml | 2 +- swifttools/ukssdc/data/GRB.py | 2 +- swifttools/ukssdc/data/SXPS.py | 6 +-- swifttools/ukssdc/data/download.py | 10 ++--- swifttools/ukssdc/query/dataquery_base.py | 4 +- swifttools/ukssdc/xrt_prods/__init__.py | 6 +-- swifttools/ukssdc/xrt_prods/docs/conf.py | 8 ++-- swifttools/ukssdc/xrt_prods/prod_base.py | 11 +++-- swifttools/ukssdc/xrt_prods/prod_request.py | 47 ++++++++++++--------- 9 files changed, 52 insertions(+), 44 deletions(-) diff --git a/ruff.toml b/ruff.toml index 8b4d505a..9e30a197 100644 --- a/ruff.toml +++ b/ruff.toml @@ -4,7 +4,7 @@ indent-width = 4 [lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -ignore = ["F405", "F401", "E721", "F403", "F841"] +ignore = ["F405", "F401", "E721", "F403"] [format] # Like Black, use double quotes for strings. diff --git a/swifttools/ukssdc/data/GRB.py b/swifttools/ukssdc/data/GRB.py index 3b869bb3..35b72691 100644 --- a/swifttools/ukssdc/data/GRB.py +++ b/swifttools/ukssdc/data/GRB.py @@ -897,7 +897,7 @@ def checkTimesliceStatus(JobID, silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa if not isinstance(JobID, int): raise ValueError("JobID must be an int") diff --git a/swifttools/ukssdc/data/SXPS.py b/swifttools/ukssdc/data/SXPS.py index 44dc529d..5e203b9f 100644 --- a/swifttools/ukssdc/data/SXPS.py +++ b/swifttools/ukssdc/data/SXPS.py @@ -240,7 +240,7 @@ def getSourceDetails(sourceID=None, sourceName=None, cat="LSXPS", silent=True, v """ if verbose: - silent = False + silent = False # noqa varName, varVal, single = _handleSourceArgs(sourceID, sourceName) if forceSingle: @@ -1630,7 +1630,7 @@ def getDatasetDetails(DatasetID=None, ObsID=None, cat="LSXPS", silent=True, verb """ if verbose: - silent = False + silent = False # noqa varName, varVal, single = _handleDSArgs(DatasetID, ObsID) @@ -1834,7 +1834,7 @@ def listOldTables(silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa sendData = {} tmp = base.submitAPICall("getOldSXPSTables", sendData, verbose=verbose, minKeys=("hourly", "daily")) return tmp diff --git a/swifttools/ukssdc/data/download.py b/swifttools/ukssdc/data/download.py index 47f3ccc4..638f57c5 100644 --- a/swifttools/ukssdc/data/download.py +++ b/swifttools/ukssdc/data/download.py @@ -346,7 +346,7 @@ def _getObsList(targetID, silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa sendData = {"targetID": targetID} ret = base.submitAPICall("getObsByTarg", sendData, minKeys=["obsList"], verbose=verbose) @@ -447,7 +447,7 @@ def _getFileList(obs, dirs, source, silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa # sendData = {"obsid": obs, "source": source, "dirs": dirs} @@ -488,7 +488,7 @@ def _handleLightCurve(data, oldCols=False, silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa ret = {} url = {} @@ -1122,7 +1122,7 @@ def _rebinLightCurve(type, objectID, binMeth=None, silent=True, verbose=False, * """ if verbose: - silent = False + silent = False # noqa sendData = {"type": type, "objectID": objectID, "binMeth": binMeth} sendData.update(kwargs) @@ -1166,7 +1166,7 @@ def _checkRebinStatus(JobID, silent=True, verbose=False): """ if verbose: - silent = False + silent = False # noqa if not isinstance(JobID, int): raise ValueError("JobID must be an int") diff --git a/swifttools/ukssdc/query/dataquery_base.py b/swifttools/ukssdc/query/dataquery_base.py index e3dd7797..28d35110 100644 --- a/swifttools/ukssdc/query/dataquery_base.py +++ b/swifttools/ukssdc/query/dataquery_base.py @@ -932,7 +932,7 @@ def removeFilter(self, ix): if not isinstance(ix, int): raise ValueError("ix must be an int") if (ix < 0) or (ix >= len(self._filters)): - raise ValueError(f"ix must be between 0 and {len(self._filters)-1}") + raise ValueError(f"ix must be between 0 and {len(self._filters) - 1}") del self._filters[ix] if not self._silent: self.showFilters() @@ -1019,7 +1019,7 @@ def submit(self, **kwargs): sendData["firstRow"] = fR if not self._silent: - print(f"Calling DB look-up for rows {fR} -- {sendData['numRows']+fR}") + print(f"Calling DB look-up for rows {fR} -- {sendData['numRows'] + fR}") ret = base.submitAPICall( "queryDB", diff --git a/swifttools/ukssdc/xrt_prods/__init__.py b/swifttools/ukssdc/xrt_prods/__init__.py index d4e5468a..7967b400 100644 --- a/swifttools/ukssdc/xrt_prods/__init__.py +++ b/swifttools/ukssdc/xrt_prods/__init__.py @@ -46,6 +46,6 @@ """ -from .prod_request import XRTProductRequest -from .prod_request import listOldJobs, countActiveJobs -from .version import __version__, _apiVersion +from .prod_request import XRTProductRequest # noqa +from .prod_request import listOldJobs, countActiveJobs # noqa +from .version import __version__, _apiVersion # noqa diff --git a/swifttools/ukssdc/xrt_prods/docs/conf.py b/swifttools/ukssdc/xrt_prods/docs/conf.py index 36dce900..400e1666 100644 --- a/swifttools/ukssdc/xrt_prods/docs/conf.py +++ b/swifttools/ukssdc/xrt_prods/docs/conf.py @@ -10,10 +10,10 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os -import sys +# import os +# import sys -sys.path.insert(0, "/Users/pae9/soft/src/swift-too") +# sys.path.insert(0, "/Users/pae9/soft/src/swift-too") # -- Project information ----------------------------------------------------- @@ -27,7 +27,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinxcontrib.napoleon", "sphinx.ext.autosectionlabel"] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosectionlabel", "sphinx_markdown_builder"] napoleon_include_init_with_doc = True napoleon_google_docstring = False napoleon_numpy_docstring = True diff --git a/swifttools/ukssdc/xrt_prods/prod_base.py b/swifttools/ukssdc/xrt_prods/prod_base.py index bd1520c5..daad8a78 100644 --- a/swifttools/ukssdc/xrt_prods/prod_base.py +++ b/swifttools/ukssdc/xrt_prods/prod_base.py @@ -93,7 +93,7 @@ def complete(self): # Setter @complete.setter def complete(self, isComplete): - if type(isComplete) != bool: + if not isinstance(isComplete, bool): raise ValueError(f"complete must be a bool, not {type(isComplete)}") self._complete = isComplete @@ -190,7 +190,10 @@ def setPars(self, **prodPars): val = val.lower() if val.lower() not in self._specificParValues[ppar]: raise ValueError( - f"'{val}' is not a valid value for {ppar}. Options are: {','.join(self._specificParValues[ppar])}." + ( + f"'{val}' is not a valid value for {ppar}. " + "Options are: {','.join(self._specificParValues[ppar])}." + ) ) # OK if we got here then we can set it: if not self.silent: @@ -275,7 +278,7 @@ def updatePars(self, parList, fromServer=False): # Is it a actually a parameter in this product? if par in self._parTypes: # If the parameter was a bool then it has come back as an int - if (bool in self._parTypes[par]) and (type(val) != bool): + if (bool in self._parTypes[par]) and (not isinstance(val, bool)): val = val == 1 or val == "yes" or val == "1" # Otherwise, check the type and try to cast it: @@ -527,7 +530,7 @@ def getJSONDict(self): for par in self._pars: val = self._pars[par] # Bools need converting to 0/1 - if type(val) == bool: + if isinstance(val, bool): if val: val = 1 else: diff --git a/swifttools/ukssdc/xrt_prods/prod_request.py b/swifttools/ukssdc/xrt_prods/prod_request.py index 38a535c2..009a3432 100644 --- a/swifttools/ukssdc/xrt_prods/prod_request.py +++ b/swifttools/ukssdc/xrt_prods/prod_request.py @@ -6,7 +6,6 @@ import os import re import warnings -import numpy as np import pandas as pd from distutils.version import StrictVersion from .version import _apiVersion @@ -810,7 +809,7 @@ def isValid(self, what="all"): status = True report = "" - if type(what) == str: + if isinstance(what, str): if what == "all": what = self._productList.keys() else: @@ -1775,7 +1774,7 @@ def getJSONDict(self): for gPar in self.globalPars: val = self.globalPars[gPar] # Bools need converting to 0/1 - if type(val) == bool: + if isinstance(val, bool): if val: val = 1 else: @@ -2047,7 +2046,7 @@ def _getWhatList(self, what): A list of the products with the internal keys. """ - if type(what) == str: + if isinstance(what, str): if what == "all": what = self._productList.keys() else: @@ -2397,7 +2396,7 @@ def downloadProducts(self, dir, what="all", silent=True, format="tar.gz", stem=N If the product requested is not complete. """ - if stem is not None and type(stem) != str: + if (stem is not None) and (not isinstance(stem, str)): raise TypeError(f"Stem must be str or None, cannot be {type(stem)}") if format not in downloadFormats: @@ -2412,7 +2411,7 @@ def downloadProducts(self, dir, what="all", silent=True, format="tar.gz", stem=N retDict = dict() for prod in reqWhat: if not (self._productList[prod].complete): - if what == "all": #  OK, just skip: + if what == "all": # OK, just skip: retDict[shortToLong[prod]] = None else: raise ValueError(f"The {longProdName[prod]} is not complete, so can't be downloaded") @@ -2900,17 +2899,23 @@ def retrieveLightCurve(self, nosys="no", incbad="no", returnData=None): raise RuntimeError(f"`{key}` contains no column information.") c = returnedData[tmpKey]["columns"] c = [ - "T_+ve" - if x == "TimePos" - else "T_-ve" - if x == "TimeNeg" - else "Ratepos" - if x == "RatePos" - else "Rateneg" - if x == "RateNeg" - else "Rate" - if x == "UpperLimit" - else x + ( + "T_+ve" + if x == "TimePos" + else ( + "T_-ve" + if x == "TimeNeg" + else ( + "Ratepos" + if x == "RatePos" + else "Rateneg" + if x == "RateNeg" + else "Rate" + if x == "UpperLimit" + else x + ) + ) + ) for x in c ] returnedData[tmpKey]["columns"] = c @@ -3211,7 +3216,7 @@ def _updateGlobals(self, parList, fromServer=False): # Is it a actually a global? parList may contain product-specific pars if par in XRTProductRequest._globalTypes: # If the parameter was a bool then it has come back as an int - if (bool in XRTProductRequest._globalTypes[par]) and (type(val) != bool): + if (bool in XRTProductRequest._globalTypes[par]) and not isinstance(val, bool): val = val == 1 or val == "yes" or val == "1" # Otherwise, check the type and try to cast it: @@ -3275,10 +3280,10 @@ def setFromJSON(self, JSONVals, fromServer=False): If 'JSONVals' is not a string or dictionary. """ - if type(JSONVals) == str: # It's literally a json string + if isinstance(JSONVals, str): # It's literally a json string JSONVals = json.loads(JSONVals) - if type(JSONVals) != dict: # Oh oh + if not isinstance(JSONVals, dict): # Oh oh raise ValueError("JSON should be a JSON string or a dict; it is not.") # Reset almost everything @@ -3307,7 +3312,7 @@ def setFromJSON(self, JSONVals, fromServer=False): for par, prod in parProd.items(): if par in JSONVals: val = JSONVals[par] # Just for ease - if ((type(val) == bool) and val) or (type(val) == int and (val == 1)): + if (isinstance(val, bool) and val) or (isinstance(val, int) and (val == 1)): # We do want this product self.addProduct(prod) From 8aa583581ab678bd8413bcba087cd5ddde9539e4 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Fri, 10 May 2024 08:13:31 +0100 Subject: [PATCH 10/22] Modified the details of the main README.md, and links in the ChangeLog, to reflect the creation of the ukssdc package. (#13) ALso removed scripts and files which are specific to the rendering of these Markdown documents on the swift.ac.uk website. --- ChangeLog.md | 11 ++-- README.md | 16 ++++-- swifttools/ukssdc/APIDocs/fixHTMLMD.pl | 62 ---------------------- swifttools/ukssdc/APIDocs/renderMD.php | 72 -------------------------- 4 files changed, 18 insertions(+), 143 deletions(-) delete mode 100755 swifttools/ukssdc/APIDocs/fixHTMLMD.pl delete mode 100644 swifttools/ukssdc/APIDocs/renderMD.php diff --git a/ChangeLog.md b/ChangeLog.md index 0300e328..850a55d0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,8 +1,11 @@ # Change history for the swifttools package. -Changes to this package will be summarised here, with links to the -change log for the specific modules modified. +Changes to the main `swifttools` package: -[xrt_prods changes](swifttools/xrt_prods/ChangeLog.md) +* 2024 May 09. Migrated the sourcecode from GitLab to GitHub. -[swift_too changes](swifttools/swift_too/ChangeLog.md) \ No newline at end of file +Package-specific changes: + +* [ukssdc](swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md) +* [ukssdc.xrt_prods](swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ChangeLog.md) +* [swift_too](swifttools/swift_too/ChangeLog.md) diff --git a/README.md b/README.md index 39045673..326d1bad 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ -# Swifttools python package. +# Swifttools python package This module presents tools to enable access from Python to some of the web-based tools supporting the Swift satellite. -In the this release there are a two modules available: [xrt_prods](swifttools/xrt_prods/README.md) and [swift_too](swifttools/swift_too/README.md). +In the this release there are a two modules available: [ukssdc](swifttools/ukssdc/APIDocs/ukssdc/README.md) and +[swift_too](swifttools/swift_too/README.md). -`xrt_prods` provides a Python interface to the [tools to analyse XRT data](https://www.swift.ac.uk/user_objects). - -`swift_too` provides a Python interface for submitting Swift Target of Opportunity Requests, calculating visibility of targets to Swift, querying observations that Swift has performed and will perform, and other Swift related tools. Documentation for the `swift_too` module can be found here: [https://www.swift.psu.edu/too_api/index.php](https://www.swift.psu.edu/too_api/index.php). +`ukssdc` provides a Python interface to various services from the [UK Swift Science Data +Centre](https://www.swift.ac.uk). This includes the [tools to build XRT data products](https://www.swift.ac.uk/user_objects), +interfaces to query catalogues such as [the LSXPS catalogue](https://www.swift.ac.uk/LSXPS) and tools to download +data, including [XRT GRB data products](https://www.swift.ac.uk/xrt_products) or [Swift obs data](https://www.swift.ac.uk/swift_live). +`swift_too` provides a Python interface for submitting Swift Target of Opportunity Requests, calculating visibility of +targets to Swift, querying observations that Swift has performed and will perform, and other Swift related tools. +Documentation for the `swift_too` module can be found here: +[https://www.swift.psu.edu/too_api/index.php](https://www.swift.psu.edu/too_api/index.php). diff --git a/swifttools/ukssdc/APIDocs/fixHTMLMD.pl b/swifttools/ukssdc/APIDocs/fixHTMLMD.pl deleted file mode 100755 index fbb075fc..00000000 --- a/swifttools/ukssdc/APIDocs/fixHTMLMD.pl +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env perl -use strict; -use File::Basename; - -my $infile = shift @ARGV; -my $outfile = shift @ARGV; - -my $NB = basename($infile); -$NB=~s/md$/ipynb/; -print "$NB\n"; - -open (my $OUT, ">", $outfile); -open (my $IN, "<", $infile); -my $inDiv=0; -my $doneNB = 0; -while (<$IN>) -{ - chomp; - s/ipynb/md/g; - s/(\w+)_(\w+)\.md/$1\/$2.md/g; - s/testtools/swifttools/g; - if ($inDiv) - { - print $OUT "$_"; - if (m/\/div\>/) - { - $inDiv=0; - } - } - else - { - if (/\]*\>/) - { - $inDiv=1; - print $OUT "
"; - } - else - { - print $OUT "$_\n"; - } - } - if (m/^# / and not $doneNB) # First line - { - print $OUT "\n[Jupyter notebook version of this page]($NB)\n"; - $doneNB=1; - } - -} - - - - -exit(0); -my $in=`cat $infile`; - -$/=""; -$in=~s/\>\s*\n\s*/>/g; -$/="\n"; - - -print $OUT $in."\n"; -close($OUT); diff --git a/swifttools/ukssdc/APIDocs/renderMD.php b/swifttools/ukssdc/APIDocs/renderMD.php deleted file mode 100644 index df54cda3..00000000 --- a/swifttools/ukssdc/APIDocs/renderMD.php +++ /dev/null @@ -1,72 +0,0 @@ -setStyle("/style/markdown.css"); - $head=$page->Head(); - $head->Title("UKSSDC | Swifttools API documentation | $file"); - $head->Description("Documentation supporting the swifttools Python module"); - $head->Keywords("Swift, XRT,Python, API, swifttools"); - - $head->Append( - " - - - " - ); - - $hasSB=0; - $bread = "API"; - - - - $page->Begin(); - print "\n"; - - $Parsedown = new ParsedownExtra(); - print "
\n"; - $txt= $Parsedown->text(file_get_contents($file)); - $txt = str_replace("
", "
", $txt); - print $txt; - print "
\n"; - $page->End(); From 52645046c4a1599ccdd467a5a57b45811d5674d4 Mon Sep 17 00:00:00 2001 From: rini21 Date: Fri, 24 May 2024 17:17:22 +0100 Subject: [PATCH 11/22] Updated description for checkProductStatus method --- swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/JobStatus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/JobStatus.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/JobStatus.md index cc450827..a000b48e 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/JobStatus.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/JobStatus.md @@ -26,7 +26,7 @@ True ## Querying the product status -To query the product status, use the `checkProductStatus()` method. This takes a single, optional argument detailing which product(s) to cancel. This can either be the string 'all' (to cancel all requested products) or a tuple/list of the product names. e.g. +To query the product status, use the `checkProductStatus()` method. This takes a single, optional argument detailing which product(s) to query. This can either be the string 'all' (to query all requested products) or a tuple/list of the product names. e.g. ```python In [2]: prodStatus = myReq.checkProductStatus(('LightCurve', 'StandardPos')) # Poll only the light curve and standard position From 46a019c9c2f0300e7cbf3f640a2080a3334a741d Mon Sep 17 00:00:00 2001 From: Vatsalya Maddi Date: Wed, 29 May 2024 12:09:35 +0100 Subject: [PATCH 12/22] Updates to fix #14 (#18) * updated link for users to create a new GitHub issue from #14 * updated documentation notebooks zip link from #14 * fixed GitHub markdown by updating citation title links from #14 - NEEDS REVIEW --- swifttools/ukssdc/APIDocs/ukssdc/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/swifttools/ukssdc/APIDocs/ukssdc/README.md b/swifttools/ukssdc/APIDocs/ukssdc/README.md index 1e9463f4..ae764de6 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/README.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/README.md @@ -70,20 +70,20 @@ Details for each of these can be found on their relevant pages; below are links the citations requested. -* [GRB light curves](/xrt_curves/docs.php#usage): [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract), +* [GRB light curves](https://www.swift.ac.uk/xrt_curves/docs.php#usage): [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* [GRB enhanced positions](/xrt_positions/docs.php#usage): [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* [GRB spectra](/xrt_spectra/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) -* [The burst analyser](/burst_analyser/docs.php#usage): [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract) -* [On-demand products](/user_objects/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract): **please see the [documentation](/user_objects/docs.php#usage) for citations for the different products**. -* [2SXPS](/2SXPS/docs.php#access): [Evans et al., (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJS..247...54E/abstract) -* [LSXPS](/LSXPS/docs.php#access): [Evans et al., (2023)](https://ui.adsabs.harvard.edu/abs/2023MNRAS.518..174E/abstract) +* [GRB enhanced positions](https://www.swift.ac.uk/xrt_positions/docs.php#usage): [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) +* [GRB spectra](https://www.swift.ac.uk/xrt_spectra/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract) +* [The burst analyser](https://www.swift.ac.uk/burst_analyser/docs.php#usage): [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract) +* [On-demand products](https://www.swift.ac.uk/user_objects/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract): **please see the [documentation](/user_objects/docs.php#usage) for citations for the different products**. +* [2SXPS](https://www.swift.ac.uk/2SXPS/docs.php#access): [Evans et al., (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJS..247...54E/abstract) +* [LSXPS](https://www.swift.ac.uk/LSXPS/docs.php#access): [Evans et al., (2023)](https://ui.adsabs.harvard.edu/abs/2023MNRAS.518..174E/abstract) ## About this documentation The documentation for this Python module is organised by package and sub-package as given in the contents list below. Many of the documentation pages can also be downloaded as Jupyter notebooks, so you can actually execute the example code -and experiment with it. Links to the notebooks appear at the top of each page, and a [zip archive of all of them is available](ukssdc.zip). +and experiment with it. Links to the notebooks appear at the top of each page, and a [zip archive of all of them is available](https://www.swift.ac.uk/API/ukssdc/ukssdc.zip). **Documentation contents:** @@ -101,7 +101,7 @@ and experiment with it. Links to the notebooks appear at the top of each page, a All functions should be fully documented via PEP 257-compliant docstrings, so the `help` command can be used to obtain full details. If you find something missing or inaccurate, you can [open an issue on our GitHub -page](https://github.com/Swift-SOT/swifttools/-/issues) or just email me (swifthelp@leicester.ac.uk). +page](https://github.com/Swift-SOT/swifttools/issues/new) or just email me (swifthelp@leicester.ac.uk). While efforts have been made to ensure that the code is PEP8 compliant, eagle-eyed users will notice that function names in this module are of the form `someFunction()`, rather than the `some_function()` preferred From 3f4fda6e8c7078a2030e1ae8cb14dc4c9e893799 Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Wed, 29 May 2024 14:01:08 +0100 Subject: [PATCH 13/22] Added the 'srcrad' parameter to spectra and lc requests, updated all API versions ready for a new release (#20) --- setup.cfg | 14 +++++++------- swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md | 4 +++- .../APIDocs/ukssdc/xrt_prods/ChangeLog.md | 4 ++++ .../APIDocs/ukssdc/xrt_prods/RequestJob.md | 19 +++++++++++-------- swifttools/ukssdc/version.py | 4 ++-- swifttools/ukssdc/xrt_prods/productVars.py | 2 ++ swifttools/ukssdc/xrt_prods/version.py | 4 ++-- swifttools/version.py | 2 +- 8 files changed, 32 insertions(+), 21 deletions(-) diff --git a/setup.cfg b/setup.cfg index 148afbd6..05c868fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] -name = swifttools -version = 3.0.21 +name = swifttools +version = 3.0.22 description = Tools for users of the Swift satellite long_description = file: README.md long_description_content_type = text/markdown @@ -26,11 +26,11 @@ packages = find: platforms = any include_package_data = True python_requires = >= 3.6 -install_requires = - requests - python-jose - pandas - tabulate +install_requires = + requests + python-jose + pandas + tabulate numpy boto3 [bdist_wheel] diff --git a/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md index d0bbbdf8..505129fa 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md @@ -2,6 +2,8 @@ Changes made to this module after its original release will be documented here. +* 2024 May 29: v1.0.5 released as part of `swifttools` v3.0.22 + * `swifttools.ukssdc.xrt_prods` v1.12 released: adds a `srcrad` parameter for light curves and spectra. * 2023 May 24: Modification to the back end, light curve `dict` now includes an "Exposure" column in the hard/soft/ratio datasets. * 2022 September 06. v1.0.3 relased as part of `swifttools` v3.0.5 * Further minor bugfixes in `mergeUpperLimits()`. @@ -9,4 +11,4 @@ Changes made to this module after its original release will be documented here. to be an integer, it does not accept floats such as "3.0". This caused en error in `bayesRate()` as called by `mergeUpperLimits()` and `mergeLightCurveBins()`. * 2022 September 01. v1.0.2 relased as part of `swifttools` v3.0.2 - * Minor bugfixes in `mergeUpperLimits()` \ No newline at end of file + * Minor bugfixes in `mergeUpperLimits()` diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ChangeLog.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ChangeLog.md index cf9ff298..4d998978 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ChangeLog.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ChangeLog.md @@ -2,6 +2,10 @@ Changes made to this module after its original release will be documented here. +* 2024 May 29: xrt_prods v1.12 with `swifttools` v3.0.22 + * This release adds a new `srcrad` parameter to light curves and spectra, which sets the maximum size the source + radius can extend to. This is only useful for rare, exteremly piled up sources where the annulus to exclude + needs to be larger than the default 30-pixel maximum radius. * 2022 August 31. xrt_prods v1.10 released, with swifttools 3.0. * This release contains many changes **including deprecating some old behaviour**. Full details are given in the [release notes](ReleaseNotes_v110.md). diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md index 295769a6..472545d5 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md @@ -3,7 +3,7 @@ The main part of requesting products be built is, well, requesting that products be built(!). This is relatively straightforward in concept and can be done with a very small number of commands; however, it does require you to know what parameters can be set, and which ones are mandatory. Fortunately, this page gives you all of that information. -**Note**: This documentation is not designed to introduce the XRT Product Generator. If you are not familiar with this +**Note**: This documentation is not designed to introduce the XRT Product Generator. If you are not familiar with this facility we strongly advise you to read [that facility's documentation](https://www.swift.ac.uk/user_objects/docs.php) first, and we also recommend that you build a few products via [the web interface](https://www.swift.ac.uk/user_objects/) web interface to familiarise yourself with the system before trying to use the Python module. @@ -68,8 +68,8 @@ Out[5]: None In [6]: myReq.getGlobalPars() # same as getGlobalPars('all') Out[6]: {'centroid': True, 'posErr': 1.5} -In [7]: myReq.getGlobalPars(showUnset=True) -Out[7]: +In [7]: myReq.getGlobalPars(showUnset=True) +Out[7]: {'name': None, 'targ': None, 'T0': None, @@ -208,7 +208,7 @@ The following problems were found: * Global parameter `targ` is not set, and nor is the alternative: `getTargs`. ``` -Ah, yes, that would explain it. I forgot to set a bunch of parameters. In this particular case the request submission failed before python even tried to submit it to the server, because some internal checks are done before submission. You can do these checks yourself before running `submit()` if you want, via the `isValid()` function. This takes one (optional) argument, which is either 'all' (the default) or a list/tuple of products to check. It returns a list with two entries: first a `bool` indicating whether the product/request is valid or not, and second a text string saying what's wrong (if it's not ready). +Ah, yes, that would explain it. I forgot to set a bunch of parameters. In this particular case the request submission failed before python even tried to submit it to the server, because some internal checks are done before submission. You can do these checks yourself before running `submit()` if you want, via the `isValid()` function. This takes one (optional) argument, which is either 'all' (the default) or a list/tuple of products to check. It returns a list with two entries: first a `bool` indicating whether the product/request is valid or not, and second a text string saying what's wrong (if it's not ready). ```python In [18]: status = myReq.isValid( ['LightCurve', 'Spectrum']) @@ -252,7 +252,7 @@ spectrum problems: When you submit a request, you don't have to supply every possible parameter, many of them have default values. And some parameters ask the server to calculate other values: for example if `getCoords` is `True` then the server will try to resolve the supplied name, to get the position. You may want to know what values the server actually used, either for sanity checking (is the resolved position what you were expecting?) or so that you can submit a later job with the same set of parameters. -Of course, you can find this out by looking at [the data returned by the server](ReturnData.md), but you then have to manage those data yourself. On the other hand, as discussed in [Advanced usage](advanced.md), we provide a simple mechanism to dump the parameters and values from one request and paste them into another: this is useful if you want to create a similar / identical request (for example, to update a product every time it is re-observed, without changing any of the parameters). For this case, it is helpful to have your request parameters updated to include those set by the server, so that if you dump or copy those parameters you know that you are copying the exact parameters. Since changing the product parameters after submission has no effect on the running jobs (and indeed, you can't manually change parameters after submission), this option is `True` by default, For more infomration see [Advanced usage](advanced.md). +Of course, you can find this out by looking at [the data returned by the server](ReturnData.md), but you then have to manage those data yourself. On the other hand, as discussed in [Advanced usage](advanced.md), we provide a simple mechanism to dump the parameters and values from one request and paste them into another: this is useful if you want to create a similar / identical request (for example, to update a product every time it is re-observed, without changing any of the parameters). For this case, it is helpful to have your request parameters updated to include those set by the server, so that if you dump or copy those parameters you know that you are copying the exact parameters. Since changing the product parameters after submission has no effect on the running jobs (and indeed, you can't manually change parameters after submission), this option is `True` by default, For more infomration see [Advanced usage](advanced.md). --- @@ -272,7 +272,7 @@ parameters are not tied to any specific product but affect all products. The product-specific parameters are, as the name implies, tied to specific parameters. -Most fields are self-explanatory. Times can be entered in any of the [formats specified in the XRT Products documentation](https://www.swift.ac.uk/user_objects/docs.php#timeformat). the `useObs` fields can contain either a list of Swift obsIDs, or a comma-separated list of *start-stop* values, where start/stop are times in the formats just described. +Most fields are self-explanatory. Times can be entered in any of the [formats specified in the XRT Products documentation](https://www.swift.ac.uk/user_objects/docs.php#timeformat). the `useObs` fields can contain either a list of Swift obsIDs, or a comma-separated list of *start-stop* values, where start/stop are times in the formats just described. --- @@ -373,6 +373,8 @@ Some of the light curve parameters are relevant for all binning methods, some on | timeFormat | No | str | The units to use on the time axis. Must be one of {'s'(=seconds), 'm'(=MJD)} | 's' | | whichData | No | str | Which observations to use for the light curve. Must be one of {'all', 'user'} | 'all' | | useObs | If `whichData='user'` | str | The specific observations to use to create the light curve [[Details](https://www.swift.ac.uk/user_objects/docs.php#useobs)] | -- | +| srcrad | No | int | The maximum radius the source extraction region can be (pixels). | -- | + @@ -432,9 +434,10 @@ Some parameters are relevant all the time, some are only needed if you are defin | galactic | No | bool | Whether to include an absorber fixed at the Galactic value, as well as a free absorber. | False | | models | No | list/tuple | A list of emission models to fit (options are 'apec', 'powerlaw' and 'blackbody'). | ('powerlaw') | | deltaFitStat | No | float | The change in fit statistic used to calculate the parameter errors on the spectral fit. | 2.706 | +| srcrad | No | int | The maximum radius the source extraction region can be (pixels). | -- | The last 3 parameters were newly introduced in v1.10 of the module (part of `swifttools` v3.0) and represent -functionality recently added to the service and also available via the website. If `galactic` is `True`, the value +functionality recently added to the service and also available via the website. If `galactic` is `True`, the value of the Galactic component is taken from [Willingale et al., (2013)](https://ui.adsabs.harvard.edu/abs/2013MNRAS.431..394W/abstract). The `models` parameter is case-insensitive and if it contains multiple elements then multiple models will be fitted. **Note** Specifying multiple models will give separate fits with the different emisssion models. i.e. If you supply @@ -492,7 +495,7 @@ The following parameters can be set for creating an image. | Parameter | Mandatory? | Type | Description | Default | | :---- | :----: | :---: | :----- | :----: | -| energies | No | str | A string comprising a comma-separated list of energy bands for your images. | '0.3-10,0.3-1.5,1.51-10' | +| energies | No | str | A string comprising a comma-separated list of energy bands for your images. | '0.3-10,0.3-1.5,1.51-10' | | whichData | No | str | Which datasets to use in the image. Must be one of {'all', 'user'} | 'all' | | useObs | If `whichData='user'` | str | The specific observations to use to create the image [[Details](https://www.swift.ac.uk/user_objects/docs.php#useobs)] | -- | diff --git a/swifttools/ukssdc/version.py b/swifttools/ukssdc/version.py index 3d05c20e..1537b1a8 100644 --- a/swifttools/ukssdc/version.py +++ b/swifttools/ukssdc/version.py @@ -1,2 +1,2 @@ -__version__ = "1.0.4" -_apiVersion = "1.0.4" +__version__ = "1.0.5" +_apiVersion = "1.0.5" diff --git a/swifttools/ukssdc/xrt_prods/productVars.py b/swifttools/ukssdc/xrt_prods/productVars.py index 419c2388..e384f43f 100644 --- a/swifttools/ukssdc/xrt_prods/productVars.py +++ b/swifttools/ukssdc/xrt_prods/productVars.py @@ -171,6 +171,7 @@ "pcHRBinTime": (float, int), "minFracExp": (float, int), "whichData": (str,), + "srcrad": (int,), }, "spec": { "hasRedshift": (bool,), @@ -196,6 +197,7 @@ "galactic": (bool,), "models": (list, tuple), "deltaFitStat": (float,), + "srcrad": (int,), }, "psf": {}, "enh": {}, diff --git a/swifttools/ukssdc/xrt_prods/version.py b/swifttools/ukssdc/xrt_prods/version.py index 3d387d34..fc9862ea 100644 --- a/swifttools/ukssdc/xrt_prods/version.py +++ b/swifttools/ukssdc/xrt_prods/version.py @@ -1,2 +1,2 @@ -__version__ = "1.11" -_apiVersion = "1.11" +__version__ = "1.12" +_apiVersion = "1.12" diff --git a/swifttools/version.py b/swifttools/version.py index 3a43c585..3df1a32d 100644 --- a/swifttools/version.py +++ b/swifttools/version.py @@ -1 +1 @@ -__version__ = "3.0.21" +__version__ = "3.0.22" From 4a90298f7c14eb79c97d31822405b85ed12a4fa2 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Mon, 9 Sep 2024 12:04:05 -0400 Subject: [PATCH 14/22] Auto generates version.py file from GitHub tags (#22) --- pyproject.toml | 4 ++++ setup.cfg | 3 ++- swifttools/version.py | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 swifttools/version.py diff --git a/pyproject.toml b/pyproject.toml index 374b58cb..e46e04bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,10 @@ [build-system] requires = [ "setuptools>=42", + "setuptools_scm[toml]>=3.4", "wheel" ] build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +write_to = "swifttools/version.py" diff --git a/setup.cfg b/setup.cfg index 05c868fd..21f09239 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,5 @@ [metadata] name = swifttools -version = 3.0.22 description = Tools for users of the Swift satellite long_description = file: README.md long_description_content_type = text/markdown @@ -26,6 +25,8 @@ packages = find: platforms = any include_package_data = True python_requires = >= 3.6 +setup_requires = + setuptools_scm install_requires = requests python-jose diff --git a/swifttools/version.py b/swifttools/version.py deleted file mode 100644 index 3df1a32d..00000000 --- a/swifttools/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "3.0.22" From a8b50f6f19a4976cd1c7c0b9a6e934b970205c8c Mon Sep 17 00:00:00 2001 From: Phil Evans Date: Tue, 29 Oct 2024 11:04:58 -0400 Subject: [PATCH 15/22] Updated ukssdc.query.GRB documentation to reflect the fact that the BAT GRB catalogue has been updated --- .../ukssdc/APIDocs/ukssdc/query/GRB.ipynb | 2 +- swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb index 2281da1b..4fba063e 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb @@ -88,7 +88,7 @@ "\n", "The Swift Data Table is curated by JD Myers at GSFC, and is essentially manually compiled from information contained in GCN circulars. This has presented the occasional challenge ingesting everything into a form that can be queried by this API. For example, how will a T90 value of \"~2\" fare with a \">\" operator? To resolve this, I strip out the non-numeric characters from columns like this, and set some warning columns [which we will look at presently](#dtcaveat).\n", "\n", - "The BAT GRB catalogue is a machine-readable catalogue without these issues and so is just ingested directly, but again there a couple of warnings. First and most importantly, at the time of writing the catalogue only includes GRBs up to mid-2020. I understand that this is due to a known technical issue and will be rectified in the coming months. Secondly, when I ingested that table I found a couple of GRBs with duplicate rows; only the first row is ingested ('name' is required as a unique column for combining catalogues).\n", + "The BAT GRB catalogue is a machine-readable catalogue without these issues and so is just ingested directly, but again there a couple of warnings. First and most importantly, at the time of writing the catalogue only includes GRBs up to the end of 2023. Secondly, when I ingested that table I found a couple of GRBs with duplicate rows; only the first row is ingested ('name' is required as a unique column for combining catalogues).\n", "\n", "Right, with those preliminaries out of the way, let's get to business.\n", "\n", diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md index 7a40f47b..87a0eda1 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md @@ -64,7 +64,7 @@ There are also a couple of warnings and caveats related to these tables. The Swift Data Table is curated by JD Myers at GSFC, and is essentially manually compiled from information contained in GCN circulars. This has presented the occasional challenge ingesting everything into a form that can be queried by this API. For example, how will a T90 value of "~2" fare with a ">" operator? To resolve this, I strip out the non-numeric characters from columns like this, and set some warning columns [which we will look at presently](#dtcaveat). -The BAT GRB catalogue is a machine-readable catalogue without these issues and so is just ingested directly, but again there a couple of warnings. First and most importantly, at the time of writing the catalogue only includes GRBs up to mid-2020. I understand that this is due to a known technical issue and will be rectified in the coming months. Secondly, when I ingested that table I found a couple of GRBs with duplicate rows; only the first row is ingested ('name' is required as a unique column for combining catalogues). +The BAT GRB catalogue is a machine-readable catalogue without these issues and so is just ingested directly, but again there a couple of warnings. First and most importantly, at the time of writing the catalogue only includes GRBs up to the end of 2023. Secondly, when I ingested that table I found a couple of GRBs with duplicate rows; only the first row is ingested ('name' is required as a unique column for combining catalogues). Right, with those preliminaries out of the way, let's get to business. @@ -299,7 +299,7 @@ print(myRow['BAT_T90_orig']) ~1.3 to ~3 -Here you can see the problem: GRB 161004A has a T90 value in the SDC_GRB table of "~1.3 to ~3". Since I want T90 to be a number, my ingestion code took "1.3" but set the warning flag. +Here you can see the problem: GRB 161004A has a T90 value in the SDC_GRB table of "~1.3 to ~3". Since I want T90 to be a number, my ingestion code took "1.3" but set the warning flag. How you choose to handle such cases is entirely up to you, and depends on your specific needs; all I'm trying to do here is to show you the tools I have provided to help you in this. @@ -337,7 +337,7 @@ q.setAuxCat('UK_XRT') The auxilliary catalogue is literally just another `GRBQuery` object, and we can access it via `q.auxCat` so anything we can do to the primary catalogue we can also do to the auxilliary catalogue. This is handy because we need to add filters to both. -**Important note** filters must be added to the correct catalogue; I have not made a mechanism to infer from the column name which catalogue you meant, because it is possible for both catalogues to share column names. So it is your reponsibility to get the right filters in the right place. +**Important note** filters must be added to the correct catalogue; I have not made a mechanism to infer from the column name which catalogue you meant, because it is possible for both catalogues to share column names. So it is your reponsibility to get the right filters in the right place. So, let's do that. FIrst we want a T90 filter, which applies to our primary catalogue: @@ -484,7 +484,7 @@ Lastly, since the `auxCat` is itself just a `GRBQuery` object, it too can have a ## GRB Products -Having identified a sample of GRBs, we may want to actually get at some of the data - recall my example above where I've been asked for the ability to *download all XRT light curves* for GRB with T90<2 s. +Having identified a sample of GRBs, we may want to actually get at some of the data - recall my example above where I've been asked for the ability to *download all XRT light curves* for GRB with T90<2 s. This is easy to do, because the `GRBQuery` class provides wrappers to all of the [`swifttools.ukssdc.data.GRB` functions](../data/GRB.md). As [already explained for `ObsQuery`](../query.md#prods), the syntactic difference is just that we don't provide the list of objects to retrieve, that is automatically taken from `q.results`, but we can provide [a subset](../query.md#subsets) of rows. @@ -519,8 +519,8 @@ print(f"\n\nI have {len(q.results)} rows in the merged table") Received 146 rows. Calling DB look-up for rows 0 -- 1000 Received 808 rows. - - + + I have 16 rows in the merged table @@ -750,7 +750,7 @@ q.saveLightCurves(whichGRBs=['GRB 051221A', 'GRB 100117A'], #### Plotting light curves -You can also plot light curves, using the [module-level `plotLightCurve()` function](https://www.swift.ac.uk/API/ukssdc/commonFunc.md#plotlightcurve), but because I'm really nice, and you really want to buy me a drink, I've added a `plotLightCurves()` function into this module to wrap around it - although it still only plots one LC at a time. +You can also plot light curves, using the [module-level `plotLightCurve()` function](https://www.swift.ac.uk/API/ukssdc/commonFunc.md#plotlightcurve), but because I'm really nice, and you really want to buy me a drink, I've added a `plotLightCurves()` function into this module to wrap around it - although it still only plots one LC at a time. If we don't try to specify the datasets to plot, we may end up in a mess (or at least, with a really messy plot), so let's pick a GRB and check what we have: @@ -791,9 +791,9 @@ q.plotLightCurves('GRB 060313', - + ![png](GRB_1.png) - + @@ -821,9 +821,9 @@ f,a = q.plotLightCurves('GRB 060313', - + ![png](GRB_2.png) - + @@ -847,13 +847,13 @@ f - + ![png](GRB_3.png) - -For reasons I don't entirely follow, if the above cell doesn't end with the `f` (i.e. just the `matplotlib.figure` object, Jupyter doesn't plot it. + +For reasons I don't entirely follow, if the above cell doesn't end with the `f` (i.e. just the `matplotlib.figure` object, Jupyter doesn't plot it. Notice above I made use of the `cols` argument to make the second GRB use difference colours to the first. @@ -901,7 +901,7 @@ q.getSpectra(subset=q.results['Err90']<1.9, This function has done a few things. Firstly, it only got spectra for rows where the "Err90" column was less than 1.9†. -Secondly, it saved the spectral data for those objects, in the form of `tar` files and images, to '/tmp/APIDemo_GRB_Spec2', but it neither extracted the `tar` files nor deleted them. And lastly, it saved the spectral data to the internal variable `q.spectra` (this last point was not requested explicitly, it *always* happens). +Secondly, it saved the spectral data for those objects, in the form of `tar` files and images, to '/tmp/APIDemo_GRB_Spec2', but it neither extracted the `tar` files nor deleted them. And lastly, it saved the spectral data to the internal variable `q.spectra` (this last point was not requested explicitly, it *always* happens). († To find out what Err90 is, you'd have to read the metadata, or in this case, the auxCat metadata. To save you the bother: it's the XRT 90% confidence radial position error, in arcsec.) From b5ba0e57ba203a29115989ce047aa42e213a9313 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Tue, 29 Oct 2024 12:36:27 -0400 Subject: [PATCH 16/22] LINTING: Fix use of `!= None` and `== False` (#24) * Auto-fixes * More fixes * Fix formatting of notebooks * Fix formatting of UKSSDC notebooks * Update pre-commit * Fix as per Phil * Commit as per Phil * Mask correct use of == False/True as noqa: E712 * Modified the documentation for ukssdc.query.GRB to remove the == False and instead use .astype(bool) --------- Co-authored-by: Phil Evans --- .pre-commit-config.yaml | 4 +- ...t Observation Query Example Notebook.ipynb | 24 +- .../Swift Plan Query Example Notebook.ipynb | 2 +- examples/Swift SAA Example Notebook.ipynb | 12 +- .../Swift TOO Requests Example Notebook.ipynb | 4 +- examples/Swift TOO submission Example.ipynb | 14 +- examples/Swift_Clock Example Notebook.ipynb | 18 +- examples/Swift_Data Example Notebook.ipynb | 12 +- examples/Swift_Resolve example notebook.ipynb | 2 +- ...Target Visibility Calculator Example.ipynb | 10 +- examples/UVOT_mode example notebook.ipynb | 2 +- ruff.toml | 2 +- swifttools/ukssdc/APIDocs/ukssdc/data.ipynb | 27 +- .../ukssdc/APIDocs/ukssdc/data/GRB.ipynb | 358 +++++----- .../ukssdc/APIDocs/ukssdc/data/SXPS.ipynb | 638 +++++++++--------- swifttools/ukssdc/APIDocs/ukssdc/query.ipynb | 95 ++- .../ukssdc/APIDocs/ukssdc/query/GRB.ipynb | 164 ++--- swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md | 4 +- .../ukssdc/APIDocs/ukssdc/query/SXPS.ipynb | 340 ++++------ 19 files changed, 787 insertions(+), 945 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39a2b612..33a77f10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.7.1 hooks: # Run the linter. - id: ruff diff --git a/examples/Swift Observation Query Example Notebook.ipynb b/examples/Swift Observation Query Example Notebook.ipynb index 7d9e402c..2ec3c15e 100644 --- a/examples/Swift Observation Query Example Notebook.ipynb +++ b/examples/Swift Observation Query Example Notebook.ipynb @@ -304,7 +304,7 @@ "metadata": {}, "outputs": [], "source": [ - "query = ObsQuery(name='SS433',radius=5*u.arcmin)" + "query = ObsQuery(name=\"SS433\", radius=5 * u.arcmin)" ] }, { @@ -399,12 +399,8 @@ ], "source": [ "plt.figure()\n", - "plt.plot(\n", - " [float(entry.ra) for entry in query], [float(entry.dec) for entry in query], \"+\"\n", - ")\n", - "plt.plot(\n", - " [entry.ra_object for entry in query], [entry.dec_object for entry in query], \"X\"\n", - ")\n", + "plt.plot([float(entry.ra) for entry in query], [float(entry.dec) for entry in query], \"+\")\n", + "plt.plot([entry.ra_object for entry in query], [entry.dec_object for entry in query], \"X\")\n", "plt.xlabel(\"RA(J2000)\")\n", "plt.ylabel(\"Dec(J2000)\")\n", "_ = plt.title(f\"{query.name} pointing scatter\")" @@ -458,10 +454,10 @@ "from astropy.coordinates import SkyCoord\n", "import astropy.units as u\n", "\n", - "sc = SkyCoord([entry.skycoord for entry in query if entry.ra_point != None])\n", + "sc = SkyCoord([entry.skycoord for entry in query if entry.ra_point is not None])\n", "scp = SkyCoord(\n", - " [entry.ra_point for entry in query if entry.ra_point != None],\n", - " [entry.dec_point for entry in query if entry.ra_point != None],\n", + " [entry.ra_point for entry in query if entry.ra_point is not None],\n", + " [entry.dec_point for entry in query if entry.ra_point is not None],\n", " frame=\"fk5\",\n", " unit=(u.deg, u.deg),\n", ")" @@ -565,9 +561,7 @@ } ], "source": [ - "print(\n", - " f\"Target ID = {query[0].targetid}, segment = {query[0].seg}, ObservationID = {query[0].obsnum}\"\n", - ")" + "print(f\"Target ID = {query[0].targetid}, segment = {query[0].seg}, ObservationID = {query[0].obsnum}\")" ] }, { @@ -1024,9 +1018,7 @@ } ], "source": [ - "print(\n", - " f\"XRT mode = {query.observations['00035190015'].xrt}, UVOT mode = {query.observations['00035190015'].uvot}\"\n", - ")" + "print(f\"XRT mode = {query.observations['00035190015'].xrt}, UVOT mode = {query.observations['00035190015'].uvot}\")" ] }, { diff --git a/examples/Swift Plan Query Example Notebook.ipynb b/examples/Swift Plan Query Example Notebook.ipynb index 338526a7..574133fb 100644 --- a/examples/Swift Plan Query Example Notebook.ipynb +++ b/examples/Swift Plan Query Example Notebook.ipynb @@ -564,7 +564,7 @@ } ], "source": [ - "query = PlanQuery(obsid='03102995005')\n", + "query = PlanQuery(obsid=\"03102995005\")\n", "query.clock_correct()\n", "query" ] diff --git a/examples/Swift SAA Example Notebook.ipynb b/examples/Swift SAA Example Notebook.ipynb index 562ad7c9..928eec4e 100644 --- a/examples/Swift SAA Example Notebook.ipynb +++ b/examples/Swift SAA Example Notebook.ipynb @@ -57,7 +57,7 @@ } ], "source": [ - "saa = SAA('2022-03-30')\n", + "saa = SAA(\"2022-03-30\")\n", "saa" ] }, @@ -92,7 +92,7 @@ } ], "source": [ - "SAA('2022-03-29','2022-03-30')" + "SAA(\"2022-03-29\", \"2022-03-30\")" ] }, { @@ -124,7 +124,7 @@ } ], "source": [ - "SAA('2022-03-29',length=1)" + "SAA(\"2022-03-29\", length=1)" ] }, { @@ -161,8 +161,8 @@ "from astropy.time import Time\n", "import astropy.units as u\n", "\n", - "t = Time(59800,format='mjd')\n", - "SAA(t, t + 1*u.day)" + "t = Time(59800, format=\"mjd\")\n", + "SAA(t, t + 1 * u.day)" ] }, { @@ -196,7 +196,7 @@ } ], "source": [ - "saa = SAA('2022-03-29',bat=True)\n", + "saa = SAA(\"2022-03-29\", bat=True)\n", "saa" ] }, diff --git a/examples/Swift TOO Requests Example Notebook.ipynb b/examples/Swift TOO Requests Example Notebook.ipynb index c3c98707..1b77d53f 100644 --- a/examples/Swift TOO Requests Example Notebook.ipynb +++ b/examples/Swift TOO Requests Example Notebook.ipynb @@ -51,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "toos = TOORequests(begin='2022-01-01',length=10)" + "toos = TOORequests(begin=\"2022-01-01\", length=10)" ] }, { @@ -243,7 +243,7 @@ "source": [ "mytoo = TOORequests()\n", "mytoo.username = \"myuser\"\n", - "mytoo.shared_secret = 'mysharedsecret'\n", + "mytoo.shared_secret = \"mysharedsecret\"\n", "mytoo.detail = True\n", "mytoo.too_id = 16832\n", "mytoo.submit()" diff --git a/examples/Swift TOO submission Example.ipynb b/examples/Swift TOO submission Example.ipynb index ad844e45..d28389ef 100755 --- a/examples/Swift TOO submission Example.ipynb +++ b/examples/Swift TOO submission Example.ipynb @@ -175,7 +175,8 @@ "outputs": [], "source": [ "from astropy.coordinates import SkyCoord\n", - "too.skycoord = SkyCoord(l=302.86232855, b=-44.69366259, frame='galactic', unit='deg')" + "\n", + "too.skycoord = SkyCoord(l=302.86232855, b=-44.69366259, frame=\"galactic\", unit=\"deg\")" ] }, { @@ -194,6 +195,7 @@ "outputs": [], "source": [ "import astropy.units as u\n", + "\n", "too.ra = 0.868226666 * u.hourangle\n", "too.dec = -72.4345 * u.deg" ] @@ -305,9 +307,7 @@ "metadata": {}, "outputs": [], "source": [ - "too.immediate_objective = (\n", - " \"Track the X-ray evolution and pulsar period in this new outburst of SMC X-3\"\n", - ")" + "too.immediate_objective = \"Track the X-ray evolution and pulsar period in this new outburst of SMC X-3\"" ] }, { @@ -597,9 +597,7 @@ "metadata": {}, "outputs": [], "source": [ - "too.uvot_mode = (\n", - " \"I think I want all UV filters for this, whatever the UVOT team recommends.\"\n", - ")" + "too.uvot_mode = \"I think I want all UV filters for this, whatever the UVOT team recommends.\"" ] }, { @@ -626,7 +624,7 @@ "if too.validate():\n", " print(\"Good to go!\")\n", "else:\n", - " print(f\"Something is wrong! {[e for e in too.status.errors]}\")\n" + " print(f\"Something is wrong! {[e for e in too.status.errors]}\")" ] }, { diff --git a/examples/Swift_Clock Example Notebook.ipynb b/examples/Swift_Clock Example Notebook.ipynb index f73888e3..eb78812b 100644 --- a/examples/Swift_Clock Example Notebook.ipynb +++ b/examples/Swift_Clock Example Notebook.ipynb @@ -22,7 +22,7 @@ "outputs": [], "source": [ "from swifttools.swift_too import Clock, ObsQuery, VisQuery\n", - "from datetime import datetime,timedelta" + "from datetime import datetime, timedelta" ] }, { @@ -100,7 +100,7 @@ } ], "source": [ - "cc = Clock(swifttime='2022-01-01 00:00:00')\n", + "cc = Clock(swifttime=\"2022-01-01 00:00:00\")\n", "cc" ] }, @@ -159,8 +159,8 @@ } ], "source": [ - "dt1 = datetime(2016,12,31,23,59,59)\n", - "dt2 = datetime(2017,1,1,0,0,0)\n", + "dt1 = datetime(2016, 12, 31, 23, 59, 59)\n", + "dt2 = datetime(2017, 1, 1, 0, 0, 0)\n", "dttd = dt2 - dt1\n", "print(f\"datetime time difference = {dttd.total_seconds():.1f}s\")" ] @@ -193,8 +193,8 @@ "from astropy.time import Time\n", "import astropy.units as u\n", "\n", - "at1 = Time(dt1,format='datetime',scale='utc')\n", - "at2 = Time(dt2,format='datetime',scale='utc')\n", + "at1 = Time(dt1, format=\"datetime\", scale=\"utc\")\n", + "at2 = Time(dt2, format=\"datetime\", scale=\"utc\")\n", "atd = at2 - at1\n", "print(f\"Time difference = {atd.to_value(u.s):.1f}s\")" ] @@ -235,7 +235,7 @@ ], "source": [ "cc = Clock()\n", - "cc.utctime = '2022-01-01 00:00:00', '2021-01-01 00:00:00', '2020-01-01 00:00:00'\n", + "cc.utctime = \"2022-01-01 00:00:00\", \"2021-01-01 00:00:00\", \"2020-01-01 00:00:00\"\n", "cc.submit()\n", "cc" ] @@ -472,7 +472,7 @@ } ], "source": [ - "obs = ObsQuery(name='RR Lyrae')\n", + "obs = ObsQuery(name=\"RR Lyrae\")\n", "obs" ] }, @@ -638,7 +638,7 @@ } ], "source": [ - "vis = VisQuery(266,-29,hires=True,length=1)\n", + "vis = VisQuery(266, -29, hires=True, length=1)\n", "vis" ] }, diff --git a/examples/Swift_Data Example Notebook.ipynb b/examples/Swift_Data Example Notebook.ipynb index b81fd1f8..a3b19f11 100644 --- a/examples/Swift_Data Example Notebook.ipynb +++ b/examples/Swift_Data Example Notebook.ipynb @@ -477,7 +477,7 @@ } ], "source": [ - "oq[4].download(xrt=True,uvot=True, outdir=\"~/Downloads\")" + "oq[4].download(xrt=True, uvot=True, outdir=\"~/Downloads\")" ] }, { @@ -630,7 +630,7 @@ } ], "source": [ - "newdata = obsid=oq[2].download(uvot=True, auxil=False, outdir=\"~/Downloads\", clobber=True)" + "newdata = obsid = oq[2].download(uvot=True, auxil=False, outdir=\"~/Downloads\", clobber=True)" ] }, { @@ -669,7 +669,7 @@ "source": [ "from swifttools.swift_too import GUANO\n", "\n", - "g = GUANO(begin='2021-01-01',limit=1)\n", + "g = GUANO(begin=\"2021-01-01\", limit=1)\n", "g" ] }, @@ -702,7 +702,7 @@ } ], "source": [ - "g[0].download(bat=True,fetch=False)" + "g[0].download(bat=True, fetch=False)" ] }, { @@ -734,7 +734,7 @@ } ], "source": [ - "g[0].download(bat=True,fetch=False,match='*bev*.evt.gz')" + "g[0].download(bat=True, fetch=False, match=\"*bev*.evt.gz\")" ] }, { @@ -766,7 +766,7 @@ } ], "source": [ - "g[0].download(bat=True,fetch=False,match=['*bev*.evt.gz','*/auxil/*'])" + "g[0].download(bat=True, fetch=False, match=[\"*bev*.evt.gz\", \"*/auxil/*\"])" ] }, { diff --git a/examples/Swift_Resolve example notebook.ipynb b/examples/Swift_Resolve example notebook.ipynb index 8543bfbc..df45a889 100644 --- a/examples/Swift_Resolve example notebook.ipynb +++ b/examples/Swift_Resolve example notebook.ipynb @@ -389,7 +389,7 @@ } ], "source": [ - "too = TOO(source_name='Crab')\n", + "too = TOO(source_name=\"Crab\")\n", "print(f\"RA/Dec(J2000) = {too.skycoord.to_string(style='hmsdms')}\")" ] }, diff --git a/examples/Target Visibility Calculator Example.ipynb b/examples/Target Visibility Calculator Example.ipynb index 441a4272..bf58b446 100644 --- a/examples/Target Visibility Calculator Example.ipynb +++ b/examples/Target Visibility Calculator Example.ipynb @@ -166,9 +166,7 @@ } ], "source": [ - "print(\n", - " f\"SkyCoord should be the equivalent of RA/Dec(J2000) = {query.ra:.4f}, {query.dec:.4f}\"\n", - ")" + "print(f\"SkyCoord should be the equivalent of RA/Dec(J2000) = {query.ra:.4f}, {query.dec:.4f}\")" ] }, { @@ -242,7 +240,7 @@ "import astropy.units as u\n", "\n", "query.length = 1 * u.week\n", - "print(f\"{query.length=}\") # Note if this crashes, you're probably using Python 3.6." + "print(f\"{query.length=}\") # Note if this crashes, you're probably using Python 3.6." ] }, { @@ -338,9 +336,7 @@ } ], "source": [ - "print(\n", - " f\"Time between start of first window and end of last window = {query[-1][1] - query[0][0]}\"\n", - ")" + "print(f\"Time between start of first window and end of last window = {query[-1][1] - query[0][0]}\")" ] }, { diff --git a/examples/UVOT_mode example notebook.ipynb b/examples/UVOT_mode example notebook.ipynb index 3470e67a..48fe65aa 100644 --- a/examples/UVOT_mode example notebook.ipynb +++ b/examples/UVOT_mode example notebook.ipynb @@ -225,7 +225,7 @@ } ], "source": [ - "UVOTMode(name='Vega',uvotmode=0x30ed)" + "UVOTMode(name=\"Vega\", uvotmode=0x30ED)" ] }, { diff --git a/ruff.toml b/ruff.toml index 9e30a197..5733402d 100644 --- a/ruff.toml +++ b/ruff.toml @@ -4,7 +4,7 @@ indent-width = 4 [lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -ignore = ["F405", "F401", "E721", "F403"] +ignore = ["F405", "F401", "E721", "F403", "E402"] [format] # Like Black, use double quotes for strings. diff --git a/swifttools/ukssdc/APIDocs/ukssdc/data.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/data.ipynb index 5b9e52fb..3d0c0b12 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/data.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/data.ipynb @@ -74,9 +74,7 @@ "metadata": {}, "outputs": [], "source": [ - "ud.downloadObsData('00282445000',\n", - " destDir='/tmp/APIDemo_download1',\n", - " silent=False)" + "ud.downloadObsData(\"00282445000\", destDir=\"/tmp/APIDemo_download1\", silent=False)" ] }, { @@ -108,12 +106,7 @@ "metadata": {}, "outputs": [], "source": [ - "ud.downloadObsData(221755001,\n", - " instruments=('XRT',),\n", - " source='us',\n", - " destDir='/tmp/APIDemo_download2',\n", - " silent=False\n", - " )" + "ud.downloadObsData(221755001, instruments=(\"XRT\",), source=\"us\", destDir=\"/tmp/APIDemo_download2\", silent=False)" ] }, { @@ -160,11 +153,7 @@ "metadata": {}, "outputs": [], "source": [ - "ud.downloadObsData((221755001,282445000),\n", - " instruments=(),\n", - " destDir='/tmp/APIDemo_download3',\n", - " silent=False\n", - " )" + "ud.downloadObsData((221755001, 282445000), instruments=(), destDir=\"/tmp/APIDemo_download3\", silent=False)" ] }, { @@ -198,10 +187,7 @@ "metadata": {}, "outputs": [], "source": [ - "ud.downloadObsDataByTarget(282445,\n", - " instruments=(),\n", - " destDir='/tmp/APIDemo_download4',\n", - " silent=False)" + "ud.downloadObsDataByTarget(282445, instruments=(), destDir=\"/tmp/APIDemo_download4\", silent=False)" ] }, { @@ -221,10 +207,7 @@ "metadata": {}, "outputs": [], "source": [ - "ud.downloadObsDataByTarget((282445,20014),\n", - " instruments=(),\n", - " destDir='/tmp/APIDemo_download5',\n", - " silent=False)" + "ud.downloadObsDataByTarget((282445, 20014), instruments=(), destDir=\"/tmp/APIDemo_download5\", silent=False)" ] }, { diff --git a/swifttools/ukssdc/APIDocs/ukssdc/data/GRB.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/data/GRB.ipynb index 7947ed6c..9ee48d85 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/data/GRB.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/data/GRB.ipynb @@ -107,10 +107,7 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getLightCurves(GRBName=\"GRB 060729\",\n", - " saveData=True,\n", - " destDir='/tmp/APIDemo_GRBLC1',\n", - " silent=False)" + "udg.getLightCurves(GRBName=\"GRB 060729\", saveData=True, destDir=\"/tmp/APIDemo_GRBLC1\", silent=False)" ] }, { @@ -135,10 +132,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=(\"GRB 060729\",\"GRB 080319B\"),\n", - " destDir='/tmp/APIDemo_GRBLC2',\n", - " silent=False\n", - " )" + "lcData = udg.getLightCurves(GRBName=(\"GRB 060729\", \"GRB 080319B\"), destDir=\"/tmp/APIDemo_GRBLC2\", silent=False)" ] }, { @@ -157,12 +151,9 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=(\"GRB 060729\",\"GRB 080319B\"),\n", - " destDir='/tmp/APIDemo_GRBLC3',\n", - " subDirs=False,\n", - " silent=False,\n", - " verbose=True\n", - " )" + "lcData = udg.getLightCurves(\n", + " GRBName=(\"GRB 060729\", \"GRB 080319B\"), destDir=\"/tmp/APIDemo_GRBLC3\", subDirs=False, silent=False, verbose=True\n", + ")" ] }, { @@ -183,11 +174,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(targetID=(221755, 306757),\n", - " destDir='/tmp/APIDemo_GRBLC4',\n", - " silent=False,\n", - " verbose=True\n", - " )" + "lcData = udg.getLightCurves(targetID=(221755, 306757), destDir=\"/tmp/APIDemo_GRBLC4\", silent=False, verbose=True)" ] }, { @@ -208,11 +195,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=(\"GRB 060729\",),\n", - " destDir='/tmp/APIDemo_GRBLC5',\n", - " silent=False,\n", - " verbose=True\n", - " )" + "lcData = udg.getLightCurves(GRBName=(\"GRB 060729\",), destDir=\"/tmp/APIDemo_GRBLC5\", silent=False, verbose=True)" ] }, { @@ -244,11 +227,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=\"GRB 220427A\",\n", - " incbad=\"both\",\n", - " nosys=\"both\",\n", - " saveData=False,\n", - " returnData=True)" + "lcData = udg.getLightCurves(GRBName=\"GRB 220427A\", incbad=\"both\", nosys=\"both\", saveData=False, returnData=True)" ] }, { @@ -320,7 +299,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData['Datasets']" + "lcData[\"Datasets\"]" ] }, { @@ -344,7 +323,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData['PC']" + "lcData[\"PC\"]" ] }, { @@ -365,9 +344,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=(\"GRB 220427A\",\"GRB 070616\"),\n", - " saveData=False,\n", - " returnData=True)" + "lcData = udg.getLightCurves(GRBName=(\"GRB 220427A\", \"GRB 070616\"), saveData=False, returnData=True)" ] }, { @@ -396,7 +373,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(lcData['GRB 070616'].keys())" + "list(lcData[\"GRB 070616\"].keys())" ] }, { @@ -429,11 +406,8 @@ "outputs": [], "source": [ "from swifttools.ukssdc import plotLightCurve\n", - "fig, ax = plotLightCurve(lcData['GRB 070616'],\n", - " whichCurves=('WT_incbad', 'PC_incbad'),\n", - " xlog=True,\n", - " ylog=True\n", - " )" + "\n", + "fig, ax = plotLightCurve(lcData[\"GRB 070616\"], whichCurves=(\"WT_incbad\", \"PC_incbad\"), xlog=True, ylog=True)" ] }, { @@ -482,9 +456,9 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getLightCurves(GRBName=(\"GRB 220427A\",\"GRB 070616\", \"GRB 080319B\", \"GRB 130925A\"),\n", - " saveData=False,\n", - " returnData=True)" + "lcData = udg.getLightCurves(\n", + " GRBName=(\"GRB 220427A\", \"GRB 070616\", \"GRB 080319B\", \"GRB 130925A\"), saveData=False, returnData=True\n", + ")" ] }, { @@ -503,13 +477,14 @@ "metadata": {}, "outputs": [], "source": [ - "udg.saveLightCurves(lcData,\n", - " destDir='/tmp/APIDemo_GRBLC6',\n", - " whichGRBs=('GRB 070616', 'GRB 080319B'),\n", - " whichCurves=('WTHR_incbad', 'PCHR_incbad'),\n", - " sep=';',\n", - " verbose=True,\n", - " )" + "udg.saveLightCurves(\n", + " lcData,\n", + " destDir=\"/tmp/APIDemo_GRBLC6\",\n", + " whichGRBs=(\"GRB 070616\", \"GRB 080319B\"),\n", + " whichCurves=(\"WTHR_incbad\", \"PCHR_incbad\"),\n", + " sep=\";\",\n", + " verbose=True,\n", + ")" ] }, { @@ -548,9 +523,7 @@ "metadata": {}, "outputs": [], "source": [ - "JobID = udg.rebinLightCurve(GRBName=\"GRB 070616\",\n", - " binMeth='obsid',\n", - " timeFormat='MJD')" + "JobID = udg.rebinLightCurve(GRBName=\"GRB 070616\", binMeth=\"obsid\", timeFormat=\"MJD\")" ] }, { @@ -613,9 +586,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcData = udg.getRebinnedLightCurve(JobID,\n", - " saveData=False,\n", - " returnData=True)\n", + "lcData = udg.getRebinnedLightCurve(JobID, saveData=False, returnData=True)\n", "list(lcData.keys())" ] }, @@ -718,15 +689,16 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getSpectra(GRBName=\"GRB 130925A\",\n", - " saveData=True,\n", - " saveImages=True,\n", - " destDir=\"/tmp/APIDemo_GRB_Spec1\",\n", - " extract=True,\n", - " removeTar=True,\n", - " silent=False,\n", - " verbose=True\n", - " )" + "udg.getSpectra(\n", + " GRBName=\"GRB 130925A\",\n", + " saveData=True,\n", + " saveImages=True,\n", + " destDir=\"/tmp/APIDemo_GRB_Spec1\",\n", + " extract=True,\n", + " removeTar=True,\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -747,16 +719,17 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getSpectra(GRBName=\"GRB 130925A\",\n", - " saveData=True,\n", - " saveImages=True,\n", - " spectra=('late_time',),\n", - " destDir=\"/tmp/APIDemo_GRB_Spec2\",\n", - " extract=True,\n", - " removeTar=True,\n", - " silent=False,\n", - " verbose=True\n", - " )" + "udg.getSpectra(\n", + " GRBName=\"GRB 130925A\",\n", + " saveData=True,\n", + " saveImages=True,\n", + " spectra=(\"late_time\",),\n", + " destDir=\"/tmp/APIDemo_GRB_Spec2\",\n", + " extract=True,\n", + " removeTar=True,\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -779,15 +752,16 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getSpectra(GRBName=(\"GRB 130925A\", \"GRB 071020\"),\n", - " saveData=True,\n", - " saveImages=True,\n", - " destDir=\"/tmp/APIDemo_GRB_Spec3\",\n", - " extract=True,\n", - " removeTar=True,\n", - " silent=False,\n", - " verbose=True\n", - " )" + "udg.getSpectra(\n", + " GRBName=(\"GRB 130925A\", \"GRB 071020\"),\n", + " saveData=True,\n", + " saveImages=True,\n", + " destDir=\"/tmp/APIDemo_GRB_Spec3\",\n", + " extract=True,\n", + " removeTar=True,\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -820,11 +794,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData = udg.getSpectra(GRBName=\"GRB 130925A\",\n", - " saveData=False,\n", - " saveImages=False,\n", - " returnData=True\n", - " )" + "specData = udg.getSpectra(GRBName=\"GRB 130925A\", saveData=False, saveImages=False, returnData=True)" ] }, { @@ -843,7 +813,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData['late_time']['PC']['PowerLaw']" + "specData[\"late_time\"][\"PC\"][\"PowerLaw\"]" ] }, { @@ -862,7 +832,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData['late_time']['PC']['PowerLaw']['Gamma']" + "specData[\"late_time\"][\"PC\"][\"PowerLaw\"][\"Gamma\"]" ] }, { @@ -881,11 +851,12 @@ "metadata": {}, "outputs": [], "source": [ - "specData = udg.getSpectra(GRBName=[\"GRB 060729\", \"GRB 070616\", \"GRB 130925A\"],\n", - " returnData=True,\n", - " saveData=False,\n", - " saveImages=False,\n", - " )\n", + "specData = udg.getSpectra(\n", + " GRBName=[\"GRB 060729\", \"GRB 070616\", \"GRB 130925A\"],\n", + " returnData=True,\n", + " saveData=False,\n", + " saveImages=False,\n", + ")\n", "specData.keys()" ] }, @@ -905,7 +876,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData['GRB 130925A']['late_time']['PC']['PowerLaw']['Gamma']" + "specData[\"GRB 130925A\"][\"late_time\"][\"PC\"][\"PowerLaw\"][\"Gamma\"]" ] }, { @@ -924,11 +895,12 @@ "metadata": {}, "outputs": [], "source": [ - "specData = udg.getSpectra(GRBName=(\"GRB 060729\",),\n", - " returnData=True,\n", - " saveData=False,\n", - " saveImages=False,\n", - " )\n", + "specData = udg.getSpectra(\n", + " GRBName=(\"GRB 060729\",),\n", + " returnData=True,\n", + " saveData=False,\n", + " saveImages=False,\n", + ")\n", "specData.keys()" ] }, @@ -966,26 +938,28 @@ "outputs": [], "source": [ "# Get the data for 3 GRBs in to the `specData` variable:\n", - "specData = udg.getSpectra(GRBName=[\"GRB 060729\", \"GRB 070616\", \"GRB 130925A\"],\n", - " returnData=True,\n", - " saveData=False,\n", - " saveImages=False,\n", - " )\n", + "specData = udg.getSpectra(\n", + " GRBName=[\"GRB 060729\", \"GRB 070616\", \"GRB 130925A\"],\n", + " returnData=True,\n", + " saveData=False,\n", + " saveImages=False,\n", + ")\n", "\n", "# In real code there would probably be some stuff here that leads us to deciding\n", "# that we only want the interval0 spectra and only some of the above GRBs, but for this demo\n", "# it's just hard coded.\n", "\n", - "udg.saveSpectra(specData,\n", - " destDir='/tmp/APIDemo_GRBspec3',\n", - " whichGRBs=('GRB 060729', 'GRB 130925A'),\n", - " spectra=('interval0',),\n", - " saveImages=True,\n", - " verbose=True,\n", - " clobber=True,\n", - " extract=True,\n", - " removeTar=True\n", - " )" + "udg.saveSpectra(\n", + " specData,\n", + " destDir=\"/tmp/APIDemo_GRBspec3\",\n", + " whichGRBs=(\"GRB 060729\", \"GRB 130925A\"),\n", + " spectra=(\"interval0\",),\n", + " saveImages=True,\n", + " verbose=True,\n", + " clobber=True,\n", + " extract=True,\n", + " removeTar=True,\n", + ")" ] }, { @@ -1031,11 +1005,11 @@ "outputs": [], "source": [ "slices = {\n", - " 'early': ['100-800', 'WT'],\n", - " 'mixed': '100-300,500-1000',\n", + " \"early\": [\"100-800\", \"WT\"],\n", + " \"mixed\": \"100-300,500-1000\",\n", "}\n", "\n", - "JobID = udg.timesliceSpectrum(targetID='00635887', slices=slices, verbose=True)" + "JobID = udg.timesliceSpectrum(targetID=\"00635887\", slices=slices, verbose=True)" ] }, { @@ -1054,7 +1028,7 @@ "metadata": {}, "outputs": [], "source": [ - "#udg.cancelTimeslice(JobID)" + "# udg.cancelTimeslice(JobID)" ] }, { @@ -1113,16 +1087,17 @@ "metadata": {}, "outputs": [], "source": [ - "specData = udg.getSpectra(JobID = JobID,\n", - " returnData=True,\n", - " saveData=True,\n", - " saveImages=True,\n", - " destDir=\"/tmp/APIDemo_slice_spec\",\n", - " extract=True,\n", - " removeTar=True,\n", - " silent=False,\n", - " verbose=True,\n", - " )" + "specData = udg.getSpectra(\n", + " JobID=JobID,\n", + " returnData=True,\n", + " saveData=True,\n", + " saveImages=True,\n", + " destDir=\"/tmp/APIDemo_slice_spec\",\n", + " extract=True,\n", + " removeTar=True,\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -1141,7 +1116,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData['early']['WT']" + "specData[\"early\"][\"WT\"]" ] }, { @@ -1163,8 +1138,8 @@ "outputs": [], "source": [ "# Uncomment the line you want to try:\n", - "#JobID = udg.timesliceSpectrum(targetID='00635887', slices=slices, redshift=2.3)\n", - "#JobID = udg.timesliceSpectrum(targetID='00635887', slices=slices, redshift='NONE')\n" + "# JobID = udg.timesliceSpectrum(targetID='00635887', slices=slices, redshift=2.3)\n", + "# JobID = udg.timesliceSpectrum(targetID='00635887', slices=slices, redshift='NONE')\n" ] }, { @@ -1241,9 +1216,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = udg.getBurstAnalyser(GRBName=\"GRB 201013A\",\n", - " returnData=True,\n", - " saveData=False)" + "data = udg.getBurstAnalyser(GRBName=\"GRB 201013A\", returnData=True, saveData=False)" ] }, { @@ -1281,7 +1254,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['Instruments']" + "data[\"Instruments\"]" ] }, { @@ -1306,7 +1279,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(data['BAT'].keys())" + "list(data[\"BAT\"].keys())" ] }, { @@ -1329,7 +1302,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT']['HRData']" + "data[\"BAT\"][\"HRData\"]" ] }, { @@ -1348,7 +1321,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT']['Binning']" + "data[\"BAT\"][\"Binning\"]" ] }, { @@ -1369,7 +1342,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT']['SNR4'].keys()" + "data[\"BAT\"][\"SNR4\"].keys()" ] }, { @@ -1390,7 +1363,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT']['SNR4']['Datasets']" + "data[\"BAT\"][\"SNR4\"][\"Datasets\"]" ] }, { @@ -1400,7 +1373,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT']['SNR4']['Density']" + "data[\"BAT\"][\"SNR4\"][\"Density\"]" ] }, { @@ -1427,7 +1400,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(data['BAT_NoEvolution'].keys())" + "list(data[\"BAT_NoEvolution\"].keys())" ] }, { @@ -1446,7 +1419,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT_NoEvolution']['ECFs']" + "data[\"BAT_NoEvolution\"][\"ECFs\"]" ] }, { @@ -1465,7 +1438,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(data['BAT_NoEvolution']['SNR4'].keys())" + "list(data[\"BAT_NoEvolution\"][\"SNR4\"].keys())" ] }, { @@ -1475,7 +1448,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT_NoEvolution']['SNR4']['Datasets']" + "data[\"BAT_NoEvolution\"][\"SNR4\"][\"Datasets\"]" ] }, { @@ -1485,7 +1458,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['BAT_NoEvolution']['SNR4']['Density']" + "data[\"BAT_NoEvolution\"][\"SNR4\"][\"Density\"]" ] }, { @@ -1515,7 +1488,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(data['XRT'].keys())" + "list(data[\"XRT\"].keys())" ] }, { @@ -1534,7 +1507,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['XRT']['HRData_PC']" + "data[\"XRT\"][\"HRData_PC\"]" ] }, { @@ -1555,7 +1528,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['XRT']['Density_PC_incbad']" + "data[\"XRT\"][\"Density_PC_incbad\"]" ] }, { @@ -1576,7 +1549,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['UVOT'].keys()" + "data[\"UVOT\"].keys()" ] }, { @@ -1597,7 +1570,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['UVOT']['uvm2']" + "data[\"UVOT\"][\"uvm2\"]" ] }, { @@ -1664,10 +1637,12 @@ "metadata": {}, "outputs": [], "source": [ - "udg.saveBurstAnalyser(data,\n", - " destDir='/tmp/APIDemo_burstAn1',\n", - " # verbose=True,\n", - " badBATBins=True)" + "udg.saveBurstAnalyser(\n", + " data,\n", + " destDir=\"/tmp/APIDemo_burstAn1\",\n", + " # verbose=True,\n", + " badBATBins=True,\n", + ")" ] }, { @@ -1686,13 +1661,14 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getBurstAnalyser(GRBName=\"GRB 201013A\",\n", - " saveData=True,\n", - " returnData=False,\n", - " destDir='/tmp/APIDemo_burstAn2',\n", - " badBATBins=True,\n", - " #verbose=True\n", - " )" + "udg.getBurstAnalyser(\n", + " GRBName=\"GRB 201013A\",\n", + " saveData=True,\n", + " returnData=False,\n", + " destDir=\"/tmp/APIDemo_burstAn2\",\n", + " badBATBins=True,\n", + " # verbose=True\n", + ")" ] }, { @@ -1722,14 +1698,15 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getBurstAnalyser(GRBName=\"GRB 201013A\",\n", - " saveData=False,\n", - " returnData=False,\n", - " downloadTar=True,\n", - " extract=True,\n", - " removeTar=True,\n", - " destDir='/tmp/APIDemo_burstAn3',\n", - " )" + "udg.getBurstAnalyser(\n", + " GRBName=\"GRB 201013A\",\n", + " saveData=False,\n", + " returnData=False,\n", + " downloadTar=True,\n", + " extract=True,\n", + " removeTar=True,\n", + " destDir=\"/tmp/APIDemo_burstAn3\",\n", + ")" ] }, { @@ -1748,14 +1725,15 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getBurstAnalyser(GRBName=\"GRB 201013A\",\n", - " saveData=True, ### This line has changed compared to the last cell\n", - " returnData=False,\n", - " downloadTar=True,\n", - " extract=True,\n", - " removeTar=True,\n", - " destDir='/tmp/APIDemo_burstAn4',\n", - " )" + "udg.getBurstAnalyser(\n", + " GRBName=\"GRB 201013A\",\n", + " saveData=True, ### This line has changed compared to the last cell\n", + " returnData=False,\n", + " downloadTar=True,\n", + " extract=True,\n", + " removeTar=True,\n", + " destDir=\"/tmp/APIDemo_burstAn4\",\n", + ")" ] }, { @@ -1788,7 +1766,7 @@ "metadata": {}, "outputs": [], "source": [ - "pos = udg.getPositions(GRBName='GRB 080319B')" + "pos = udg.getPositions(GRBName=\"GRB 080319B\")" ] }, { @@ -1819,8 +1797,7 @@ "metadata": {}, "outputs": [], "source": [ - "pos = udg.getPositions(GRBName=('GRB 080319B', 'GRB 101225A'),\n", - " positions=('Enhanced', 'SPER'))\n", + "pos = udg.getPositions(GRBName=(\"GRB 080319B\", \"GRB 101225A\"), positions=(\"Enhanced\", \"SPER\"))\n", "pos" ] }, @@ -1856,11 +1833,14 @@ "metadata": {}, "outputs": [], "source": [ - "udg.getObsData(GRBName=\"GRB 201013A\",\n", - " instruments=['XRT',],\n", - " destDir='/tmp/APIDemo_downloadGRB',\n", - " silent=False,\n", - " )" + "udg.getObsData(\n", + " GRBName=\"GRB 201013A\",\n", + " instruments=[\n", + " \"XRT\",\n", + " ],\n", + " destDir=\"/tmp/APIDemo_downloadGRB\",\n", + " silent=False,\n", + ")" ] }, { diff --git a/swifttools/ukssdc/APIDocs/ukssdc/data/SXPS.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/data/SXPS.ipynb index 94180dab..257d1533 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/data/SXPS.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/data/SXPS.ipynb @@ -118,7 +118,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul = uds.getUpperLimits(position='334.502058 -8.256481', cat='LSXPS')" + "ul = uds.getUpperLimits(position=\"334.502058 -8.256481\", cat=\"LSXPS\")" ] }, { @@ -154,7 +154,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul['ULData']" + "ul[\"ULData\"]" ] }, { @@ -179,10 +179,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul = uds.getUpperLimits(position='77.969042, -62.324167',\n", - " cat='LSXPS',\n", - " bands='all',\n", - " whichData='all')" + "ul = uds.getUpperLimits(position=\"77.969042, -62.324167\", cat=\"LSXPS\", bands=\"all\", whichData=\"all\")" ] }, { @@ -200,7 +197,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul['ULData']" + "ul[\"ULData\"]" ] }, { @@ -240,7 +237,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul['DetData']" + "ul[\"DetData\"]" ] }, { @@ -267,10 +264,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul = uds.getUpperLimits(position='77.969042, -62.324167',\n", - " cat='LSXPS',\n", - " whichData='all',\n", - " detectionsAsRates=True)" + "ul = uds.getUpperLimits(position=\"77.969042, -62.324167\", cat=\"LSXPS\", whichData=\"all\", detectionsAsRates=True)" ] }, { @@ -290,7 +284,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul['ULData']" + "ul[\"ULData\"]" ] }, { @@ -308,7 +302,7 @@ "metadata": {}, "outputs": [], "source": [ - "ul['ULData'].columns.tolist()" + "ul[\"ULData\"].columns.tolist()" ] }, { @@ -429,11 +423,7 @@ "metadata": {}, "outputs": [], "source": [ - "merged = uk.mergeUpperLimits(ul['ULData'],\n", - " verbose=True,\n", - " conf=0.95,\n", - " rows=ul['ULData']['ObsID']<\"10000000000\"\n", - " )\n", + "merged = uk.mergeUpperLimits(ul[\"ULData\"], verbose=True, conf=0.95, rows=ul[\"ULData\"][\"ObsID\"] < \"10000000000\")\n", "merged" ] }, @@ -467,10 +457,7 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getFullTable(table='sources',\n", - " saveData=True,\n", - " destDir='/tmp/APIDemo_SXPS_cat',\n", - " silent=False)" + "uds.getFullTable(table=\"sources\", saveData=True, destDir=\"/tmp/APIDemo_SXPS_cat\", silent=False)" ] }, { @@ -492,11 +479,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getFullTable(table='sources',\n", - " returnData=True,\n", - " saveData=False,\n", - " silent=False,\n", - " verbose=True)" + "data = uds.getFullTable(table=\"sources\", returnData=True, saveData=False, silent=False, verbose=True)" ] }, { @@ -537,13 +520,15 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getFullTable(table='datasets',\n", - " destDir='/tmp/APIDemo_SXPS_cat',\n", - " saveData=True,\n", - " subset='ultra-clean',\n", - " format='fits',\n", - " silent=False,\n", - " verbose=True, )" + "data = uds.getFullTable(\n", + " table=\"datasets\",\n", + " destDir=\"/tmp/APIDemo_SXPS_cat\",\n", + " saveData=True,\n", + " subset=\"ultra-clean\",\n", + " format=\"fits\",\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -593,7 +578,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(oldTabs['hourly'].keys())[0:5]" + "list(oldTabs[\"hourly\"].keys())[0:5]" ] }, { @@ -603,7 +588,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(oldTabs['daily'].keys())[0:5]" + "list(oldTabs[\"daily\"].keys())[0:5]" ] }, { @@ -623,14 +608,16 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getFullTable(table='sources',\n", - " destDir='/tmp/APIDemo_SXPS_cat',\n", - " saveData=True,\n", - " subset='ultra-clean',\n", - " epoch='2022-08-17',\n", - " format='fits',\n", - " silent=False,\n", - " verbose=True, )" + "data = uds.getFullTable(\n", + " table=\"sources\",\n", + " destDir=\"/tmp/APIDemo_SXPS_cat\",\n", + " saveData=True,\n", + " subset=\"ultra-clean\",\n", + " epoch=\"2022-08-17\",\n", + " format=\"fits\",\n", + " silent=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -670,11 +657,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getSourceDetails(sourceID=37562,\n", - " cat='LSXPS',\n", - " silent=True,\n", - " verbose=False\n", - " )\n", + "data = uds.getSourceDetails(sourceID=37562, cat=\"LSXPS\", silent=True, verbose=False)\n", "data.keys()" ] }, @@ -685,7 +668,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['newerSources']" + "data[\"newerSources\"]" ] }, { @@ -722,7 +705,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getSourceDetails(sourceID=11375, cat='LSXPS')" + "data = uds.getSourceDetails(sourceID=11375, cat=\"LSXPS\")" ] }, { @@ -762,7 +745,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['Detections'].keys()" + "data[\"Detections\"].keys()" ] }, { @@ -780,7 +763,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['Detections']['NumStacks']" + "data[\"Detections\"][\"NumStacks\"]" ] }, { @@ -790,7 +773,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['Detections']['Observations']" + "data[\"Detections\"][\"Observations\"]" ] }, { @@ -808,7 +791,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['LSXPS_ID']" + "data[\"LSXPS_ID\"]" ] }, { @@ -828,8 +811,8 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getSourceDetails(sourceID=106107, cat='LSXPS')\n", - "data['CrossMatch']" + "data = uds.getSourceDetails(sourceID=106107, cat=\"LSXPS\")\n", + "data[\"CrossMatch\"]" ] }, { @@ -849,8 +832,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getSourceDetails(sourceName=('LSXPS J163700.6+073914', 'LSXPS J163647.0+074206'),\n", - " cat='LSXPS')" + "data = uds.getSourceDetails(sourceName=(\"LSXPS J163700.6+073914\", \"LSXPS J163647.0+074206\"), cat=\"LSXPS\")" ] }, { @@ -878,7 +860,7 @@ "metadata": {}, "outputs": [], "source": [ - "data['LSXPS J163700.6+073914']" + "data[\"LSXPS J163700.6+073914\"]" ] }, { @@ -903,7 +885,7 @@ "metadata": {}, "outputs": [], "source": [ - "what = uds.getObsList(sourceName='LSXPS J073006.9-193710', useObs='allCat')\n", + "what = uds.getObsList(sourceName=\"LSXPS J073006.9-193710\", useObs=\"allCat\")\n", "what" ] }, @@ -938,10 +920,10 @@ "source": [ "for sid in (209851, 209220):\n", " print(f\"Source {sid}:\")\n", - " what = uds.getObsList(sourceID=sid, useObs='blind')\n", - " if len(what['obsList']) == 0:\n", + " what = uds.getObsList(sourceID=sid, useObs=\"blind\")\n", + " if len(what[\"obsList\"]) == 0:\n", " print(\"No blind detections found, getting allCat\")\n", - " what = uds.getObsList(sourceID=sid, useObs='allCat')\n", + " what = uds.getObsList(sourceID=sid, useObs=\"allCat\")\n", " print(f\" * Targets: {what['targList']}\\n * Observations: {what['obsList']}\\n\\n\")" ] }, @@ -963,11 +945,8 @@ "outputs": [], "source": [ "import swifttools.ukssdc.data as ud\n", - "ud.downloadObsData(what['obsList'],\n", - " instruments=('xrt',),\n", - " destDir='/tmp/APIDemo_SXPS_data',\n", - " silent=False\n", - " )" + "\n", + "ud.downloadObsData(what[\"obsList\"], instruments=(\"xrt\",), destDir=\"/tmp/APIDemo_SXPS_data\", silent=False)" ] }, { @@ -1013,13 +992,9 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName='LSXPS J062131.8-622213',\n", - " cat='LSXPS',\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " saveData=False,\n", - " returnData=True\n", - " )" + "lcs = uds.getLightCurves(\n", + " sourceName=\"LSXPS J062131.8-622213\", cat=\"LSXPS\", binning=\"obsid\", timeFormat=\"MJD\", saveData=False, returnData=True\n", + ")" ] }, { @@ -1077,7 +1052,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Datasets']" + "lcs[\"Datasets\"]" ] }, { @@ -1097,7 +1072,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Total_rates']" + "lcs[\"Total_rates\"]" ] }, { @@ -1115,7 +1090,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Total_UL']" + "lcs[\"Total_UL\"]" ] }, { @@ -1136,14 +1111,15 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName='LSXPS J221755.4-082100',\n", - " cat='LSXPS',\n", - " binning='snapshot',\n", - " timeFormat='TDB',\n", - " bands=('total', 'hard', 'HR1'),\n", - " saveData=False,\n", - " returnData=True\n", - " )" + "lcs = uds.getLightCurves(\n", + " sourceName=\"LSXPS J221755.4-082100\",\n", + " cat=\"LSXPS\",\n", + " binning=\"snapshot\",\n", + " timeFormat=\"TDB\",\n", + " bands=(\"total\", \"hard\", \"HR1\"),\n", + " saveData=False,\n", + " returnData=True,\n", + ")" ] }, { @@ -1215,15 +1191,16 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName='LSXPS J051152.3-621925',\n", - " cat='LSXPS',\n", - " bands=('total',),\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " saveData=False,\n", - " returnData=True,\n", - " getAllTypes=True\n", - " )" + "lcs = uds.getLightCurves(\n", + " sourceName=\"LSXPS J051152.3-621925\",\n", + " cat=\"LSXPS\",\n", + " bands=(\"total\",),\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " saveData=False,\n", + " returnData=True,\n", + " getAllTypes=True,\n", + ")" ] }, { @@ -1233,7 +1210,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Datasets']" + "lcs[\"Datasets\"]" ] }, { @@ -1253,16 +1230,17 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName='LSXPS J051152.3-621925',\n", - " cat='LSXPS',\n", - " bands=('total',),\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " saveData=False,\n", - " returnData=True,\n", - " retroAsUL = True,\n", - " groupULs = False \n", - " )" + "lcs = uds.getLightCurves(\n", + " sourceName=\"LSXPS J051152.3-621925\",\n", + " cat=\"LSXPS\",\n", + " bands=(\"total\",),\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " saveData=False,\n", + " returnData=True,\n", + " retroAsUL=True,\n", + " groupULs=False,\n", + ")" ] }, { @@ -1280,7 +1258,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Datasets']" + "lcs[\"Datasets\"]" ] }, { @@ -1317,17 +1295,18 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName='LSXPS J051152.3-621925',\n", - " cat='LSXPS',\n", - " bands=('total',),\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " saveData=False,\n", - " returnData=True,\n", - " retroAsUL = True,\n", - " groupULs = True, ## This is the default, but I'm being explicit \n", - " )\n", - "lcs['Datasets']" + "lcs = uds.getLightCurves(\n", + " sourceName=\"LSXPS J051152.3-621925\",\n", + " cat=\"LSXPS\",\n", + " bands=(\"total\",),\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " saveData=False,\n", + " returnData=True,\n", + " retroAsUL=True,\n", + " groupULs=True, ## This is the default, but I'm being explicit\n", + ")\n", + "lcs[\"Datasets\"]" ] }, { @@ -1345,7 +1324,7 @@ "metadata": {}, "outputs": [], "source": [ - "len(lcs['Total_UL'])" + "len(lcs[\"Total_UL\"])" ] }, { @@ -1377,9 +1356,7 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = plotLightCurve(lcs, whichCurves=('Total_rates',),\n", - " ylog=True,\n", - " verbose=True)" + "fig, ax = plotLightCurve(lcs, whichCurves=(\"Total_rates\",), ylog=True, verbose=True)" ] }, { @@ -1397,12 +1374,9 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = plotLightCurve(lcs, whichCurves=('Total_UL',),\n", - " ylog=True,\n", - " verbose=True,\n", - " fig=fig,\n", - " cols={\"Total_UL\": \"blue\"},\n", - " ax=ax)\n", + "fig, ax = plotLightCurve(\n", + " lcs, whichCurves=(\"Total_UL\",), ylog=True, verbose=True, fig=fig, cols={\"Total_UL\": \"blue\"}, ax=ax\n", + ")\n", "fig" ] }, @@ -1433,14 +1407,15 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getLightCurves(sourceName='LSXPS J062131.8-622213',\n", - " cat='LSXPS',\n", - " saveData=True,\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " destDir='/tmp/APIDemo_SXPS_LC',\n", - " verbose=True,\n", - " )" + "uds.getLightCurves(\n", + " sourceName=\"LSXPS J062131.8-622213\",\n", + " cat=\"LSXPS\",\n", + " saveData=True,\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " destDir=\"/tmp/APIDemo_SXPS_LC\",\n", + " verbose=True,\n", + ")" ] }, { @@ -1467,15 +1442,16 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getLightCurves(sourceName='LSXPS J062131.8-622213',\n", - " cat='LSXPS',\n", - " saveData=True,\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " destDir='/tmp/APIDemo_SXPS_LC2',\n", - " verbose=True,\n", - " getAllTypes=True\n", - " )" + "uds.getLightCurves(\n", + " sourceName=\"LSXPS J062131.8-622213\",\n", + " cat=\"LSXPS\",\n", + " saveData=True,\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " destDir=\"/tmp/APIDemo_SXPS_LC2\",\n", + " verbose=True,\n", + " getAllTypes=True,\n", + ")" ] }, { @@ -1493,15 +1469,16 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getLightCurves(sourceName=('LSXPS J062131.8-622213','LSXPS J051152.3-621925'),\n", - " cat='LSXPS',\n", - " saveData=True,\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " destDir='/tmp/APIDemo_SXPS_LC3',\n", - " verbose=True,\n", - " subDirs=True\n", - " )" + "uds.getLightCurves(\n", + " sourceName=(\"LSXPS J062131.8-622213\", \"LSXPS J051152.3-621925\"),\n", + " cat=\"LSXPS\",\n", + " saveData=True,\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " destDir=\"/tmp/APIDemo_SXPS_LC3\",\n", + " verbose=True,\n", + " subDirs=True,\n", + ")" ] }, { @@ -1521,15 +1498,16 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getLightCurves(sourceName=('LSXPS J062131.8-622213','LSXPS J051152.3-621925'),\n", - " cat='LSXPS',\n", - " saveData=True,\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " destDir='/tmp/APIDemo_SXPS_LC4',\n", - " verbose=True,\n", - " subDirs=False\n", - " )" + "uds.getLightCurves(\n", + " sourceName=(\"LSXPS J062131.8-622213\", \"LSXPS J051152.3-621925\"),\n", + " cat=\"LSXPS\",\n", + " saveData=True,\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " destDir=\"/tmp/APIDemo_SXPS_LC4\",\n", + " verbose=True,\n", + " subDirs=False,\n", + ")" ] }, { @@ -1564,15 +1542,16 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName=('LSXPS J051152.3-621925','LSXPS J221755.4-082100', 'LSXPS J062131.8-622213'),\n", - " cat='LSXPS',\n", - " bands='all',\n", - " binning='obsid',\n", - " timeFormat='MJD',\n", - " saveData=False,\n", - " returnData=True,\n", - " getAllTypes=True\n", - " )" + "lcs = uds.getLightCurves(\n", + " sourceName=(\"LSXPS J051152.3-621925\", \"LSXPS J221755.4-082100\", \"LSXPS J062131.8-622213\"),\n", + " cat=\"LSXPS\",\n", + " bands=\"all\",\n", + " binning=\"obsid\",\n", + " timeFormat=\"MJD\",\n", + " saveData=False,\n", + " returnData=True,\n", + " getAllTypes=True,\n", + ")" ] }, { @@ -1600,7 +1579,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['LSXPS J051152.3-621925']['Datasets']" + "lcs[\"LSXPS J051152.3-621925\"][\"Datasets\"]" ] }, { @@ -1618,13 +1597,14 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveLightCurves(lcs,\n", - " whichSources=('LSXPS J051152.3-621925','LSXPS J062131.8-622213'),\n", - " whichCurves=('Total_blind_rates', 'Hard_nondet_UL'),\n", - " destDir='/tmp/APIDemo_SXPS_LC5',\n", - " subDirs=True,\n", - " verbose=True\n", - " )" + "uds.saveLightCurves(\n", + " lcs,\n", + " whichSources=(\"LSXPS J051152.3-621925\", \"LSXPS J062131.8-622213\"),\n", + " whichCurves=(\"Total_blind_rates\", \"Hard_nondet_UL\"),\n", + " destDir=\"/tmp/APIDemo_SXPS_LC5\",\n", + " subDirs=True,\n", + " verbose=True,\n", + ")" ] }, { @@ -1684,13 +1664,14 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getSpectra(sourceName='LSXPS J221755.4-082100',\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_spec',\n", - " verbose=True,\n", - " extract=True,\n", - " removeTar=True\n", - " )" + "uds.getSpectra(\n", + " sourceName=\"LSXPS J221755.4-082100\",\n", + " cat=\"LSXPS\",\n", + " destDir=\"/tmp/APIDemo_SXPS_spec\",\n", + " verbose=True,\n", + " extract=True,\n", + " removeTar=True,\n", + ")" ] }, { @@ -1708,13 +1689,14 @@ "metadata": {}, "outputs": [], "source": [ - "uds.getSpectra(sourceName=('LSXPS J221755.4-082100', 'LSXPS J033112.0+435414'),\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_spec2',\n", - " verbose=True,\n", - " extract=True,\n", - " removeTar=True\n", - " )" + "uds.getSpectra(\n", + " sourceName=(\"LSXPS J221755.4-082100\", \"LSXPS J033112.0+435414\"),\n", + " cat=\"LSXPS\",\n", + " destDir=\"/tmp/APIDemo_SXPS_spec2\",\n", + " verbose=True,\n", + " extract=True,\n", + " removeTar=True,\n", + ")" ] }, { @@ -1743,12 +1725,13 @@ "metadata": {}, "outputs": [], "source": [ - "specData = uds.getSpectra(sourceName='LSXPS J221755.4-082100',\n", - " cat='LSXPS',\n", - " saveData=False,\n", - " saveImages=False,\n", - " returnData=True,\n", - " )" + "specData = uds.getSpectra(\n", + " sourceName=\"LSXPS J221755.4-082100\",\n", + " cat=\"LSXPS\",\n", + " saveData=False,\n", + " saveImages=False,\n", + " returnData=True,\n", + ")" ] }, { @@ -1766,7 +1749,7 @@ "metadata": {}, "outputs": [], "source": [ - "specData['interval0']['PC']['APEC']" + "specData[\"interval0\"][\"PC\"][\"APEC\"]" ] }, { @@ -1794,12 +1777,13 @@ "metadata": {}, "outputs": [], "source": [ - "specData = uds.getSpectra(sourceName=('LSXPS J051152.3-621925','LSXPS J221755.4-082100', 'LSXPS J062131.8-622213'),\n", - " cat='LSXPS',\n", - " saveData=False,\n", - " saveImages=False,\n", - " returnData=True,\n", - " )" + "specData = uds.getSpectra(\n", + " sourceName=(\"LSXPS J051152.3-621925\", \"LSXPS J221755.4-082100\", \"LSXPS J062131.8-622213\"),\n", + " cat=\"LSXPS\",\n", + " saveData=False,\n", + " saveImages=False,\n", + " returnData=True,\n", + ")" ] }, { @@ -1809,13 +1793,14 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveSpectra(specData,\n", - " whichSources=('LSXPS J051152.3-621925','LSXPS J221755.4-082100'),\n", - " destDir='/tmp/APIDemo_SXPS_spec3',\n", - " verbose=True,\n", - " extract=True,\n", - " removeTar=True\n", - " )" + "uds.saveSpectra(\n", + " specData,\n", + " whichSources=(\"LSXPS J051152.3-621925\", \"LSXPS J221755.4-082100\"),\n", + " destDir=\"/tmp/APIDemo_SXPS_spec3\",\n", + " verbose=True,\n", + " extract=True,\n", + " removeTar=True,\n", + ")" ] }, { @@ -1837,10 +1822,7 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveSourceImages(sourceName='LSXPS J051152.3-621925',\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_image',\n", - " verbose=True)" + "uds.saveSourceImages(sourceName=\"LSXPS J051152.3-621925\", cat=\"LSXPS\", destDir=\"/tmp/APIDemo_SXPS_image\", verbose=True)" ] }, { @@ -1862,11 +1844,13 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveSourceImages(sourceName=('LSXPS J051152.3-621925','LSXPS J221755.4-082100'),\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_image2',\n", - " subDirs=False,\n", - " verbose=True)" + "uds.saveSourceImages(\n", + " sourceName=(\"LSXPS J051152.3-621925\", \"LSXPS J221755.4-082100\"),\n", + " cat=\"LSXPS\",\n", + " destDir=\"/tmp/APIDemo_SXPS_image2\",\n", + " subDirs=False,\n", + " verbose=True,\n", + ")" ] }, { @@ -1944,12 +1928,7 @@ "metadata": {}, "outputs": [], "source": [ - "myReq = uds.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " cat='LSXPS',\n", - " sourceID=17092,\n", - " useObs='all',\n", - " silent=False\n", - " )\n", + "myReq = uds.makeProductRequest(\"MY_EMAIL_ADDRESS\", cat=\"LSXPS\", sourceID=17092, useObs=\"all\", silent=False)\n", "myReq.getGlobalPars()" ] }, @@ -1991,14 +1970,15 @@ "metadata": {}, "outputs": [], "source": [ - "myReq = uds.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " cat='LSXPS',\n", - " sourceID=17092,\n", - " T0='firstBlindDet',\n", - " useObs='blind',\n", - " addProds=['LightCurve', 'StandardPos'],\n", - " silent=True, \n", - " )\n", + "myReq = uds.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " cat=\"LSXPS\",\n", + " sourceID=17092,\n", + " T0=\"firstBlindDet\",\n", + " useObs=\"blind\",\n", + " addProds=[\"LightCurve\", \"StandardPos\"],\n", + " silent=True,\n", + ")\n", "print(myReq)\n", "print(f\"Globals: {myReq.getGlobalPars()}\\n\")\n", "for p in myReq.productList:\n", @@ -2040,7 +2020,7 @@ "metadata": {}, "outputs": [], "source": [ - "myReq.setSpectrumPars(whichData='user', useObs = myReq.getLightCurvePars()['useObs'])\n", + "myReq.setSpectrumPars(whichData=\"user\", useObs=myReq.getLightCurvePars()[\"useObs\"])\n", "for p in myReq.productList:\n", " print(f\"{p}:\\t{myReq.getProductPars(p)}\")" ] @@ -2060,20 +2040,17 @@ "metadata": {}, "outputs": [], "source": [ - "info = uds.getSourceDetails(sourceID=17092,\n", - " cat='LSXPS',\n", - " silent=True,\n", - " verbose=False\n", - ")\n", + "info = uds.getSourceDetails(sourceID=17092, cat=\"LSXPS\", silent=True, verbose=False)\n", "\n", - "myReq = uds.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " sourceDetails=info,\n", - " sourceID=17092,\n", - " T0='firstBlindDet',\n", - " useObs='blind',\n", - " addProds=['LightCurve','Spectrum', 'StandardPos'],\n", - " silent=True, \n", - " )\n", + "myReq = uds.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " sourceDetails=info,\n", + " sourceID=17092,\n", + " T0=\"firstBlindDet\",\n", + " useObs=\"blind\",\n", + " addProds=[\"LightCurve\", \"Spectrum\", \"StandardPos\"],\n", + " silent=True,\n", + ")\n", "print(myReq)\n", "print(f\"Globals: {myReq.getGlobalPars()}\\n\")\n", "for p in myReq.productList:\n", @@ -2097,22 +2074,19 @@ "metadata": {}, "outputs": [], "source": [ - "data = uds.getSourceDetails(sourceID=(17092,),\n", - " cat='LSXPS',\n", - " silent=True,\n", - " verbose=False\n", - ")\n", + "data = uds.getSourceDetails(sourceID=(17092,), cat=\"LSXPS\", silent=True, verbose=False)\n", "\n", - "rlist = uds.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " sourceDetails=info,\n", - " sourceID=(17092,128791),\n", - " cat='LSXPS', # This is actually the default, but explicit is good\n", - " T0='firstBlindDet',\n", - " useObs='blind',\n", - " addProds=['LightCurve'],\n", - " silent=False, \n", - " verbose=False\n", - " )" + "rlist = uds.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " sourceDetails=info,\n", + " sourceID=(17092, 128791),\n", + " cat=\"LSXPS\", # This is actually the default, but explicit is good\n", + " T0=\"firstBlindDet\",\n", + " useObs=\"blind\",\n", + " addProds=[\"LightCurve\"],\n", + " silent=False,\n", + " verbose=False,\n", + ")" ] }, { @@ -2152,11 +2126,11 @@ "source": [ "for sourceID in rlist.keys():\n", " print(f\"\\n{sourceID}\\n======\")\n", - " rlist[sourceID].silent=True\n", + " rlist[sourceID].silent = True\n", " print(f\"{rlist[sourceID]}\")\n", " print(f\"Globals: {rlist[sourceID].getGlobalPars()}\\n\")\n", " for p in rlist[sourceID].productList:\n", - " print(f\"{p}:\\t{rlist[sourceID].getProductPars(p)}\")\n" + " print(f\"{p}:\\t{rlist[sourceID].getProductPars(p)}\")" ] }, { @@ -2208,7 +2182,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo = uds.getDatasetDetails(ObsID='00282445000', cat='LSXPS')" + "dsInfo = uds.getDatasetDetails(ObsID=\"00282445000\", cat=\"LSXPS\")" ] }, { @@ -2238,7 +2212,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo['Sources']" + "dsInfo[\"Sources\"]" ] }, { @@ -2256,7 +2230,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo['Sources']['Total_DetectionDetails'][0]" + "dsInfo[\"Sources\"][\"Total_DetectionDetails\"][0]" ] }, { @@ -2274,7 +2248,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo['Sources']['Total_DetectionDetails'][0]['SNR']" + "dsInfo[\"Sources\"][\"Total_DetectionDetails\"][0][\"SNR\"]" ] }, { @@ -2292,8 +2266,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo = uds.getDatasetDetails(ObsID=('00282445000','00015231001'),\n", - " cat='LSXPS')\n", + "dsInfo = uds.getDatasetDetails(ObsID=(\"00282445000\", \"00015231001\"), cat=\"LSXPS\")\n", "dsInfo.keys()" ] }, @@ -2317,11 +2290,7 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveDatasetImages(ObsID='00282445000',\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_Im', \n", - " verbose=True,\n", - " getRegions=True)" + "uds.saveDatasetImages(ObsID=\"00282445000\", cat=\"LSXPS\", destDir=\"/tmp/APIDemo_SXPS_Im\", verbose=True, getRegions=True)" ] }, { @@ -2351,13 +2320,15 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveDatasetImages(ObsID=('00282445000','00221755001'),\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_Im2', \n", - " types=('image','backgroundmap'),\n", - " bands=('total', 'soft'),\n", - " verbose=True,\n", - " getRegions=True)" + "uds.saveDatasetImages(\n", + " ObsID=(\"00282445000\", \"00221755001\"),\n", + " cat=\"LSXPS\",\n", + " destDir=\"/tmp/APIDemo_SXPS_Im2\",\n", + " types=(\"image\", \"backgroundmap\"),\n", + " bands=(\"total\", \"soft\"),\n", + " verbose=True,\n", + " getRegions=True,\n", + ")" ] }, { @@ -2393,11 +2364,7 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveDatasetImages(ObsID='10000000668',\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_Im3', \n", - " verbose=True,\n", - " getRegions=True)" + "uds.saveDatasetImages(ObsID=\"10000000668\", cat=\"LSXPS\", destDir=\"/tmp/APIDemo_SXPS_Im3\", verbose=True, getRegions=True)" ] }, { @@ -2415,11 +2382,7 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveDatasetImages(ObsID='10000000189',\n", - " cat='LSXPS',\n", - " destDir='/tmp/APIDemo_SXPS_Im3', \n", - " verbose=True,\n", - " getRegions=True)" + "uds.saveDatasetImages(ObsID=\"10000000189\", cat=\"LSXPS\", destDir=\"/tmp/APIDemo_SXPS_Im3\", verbose=True, getRegions=True)" ] }, { @@ -2439,7 +2402,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo = uds.getDatasetDetails(ObsID='10000000668', cat='LSXPS')\n", + "dsInfo = uds.getDatasetDetails(ObsID=\"10000000668\", cat=\"LSXPS\")\n", "dsInfo.keys()" ] }, @@ -2458,7 +2421,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo['SupersedingStacks']" + "dsInfo[\"SupersedingStacks\"]" ] }, { @@ -2478,8 +2441,8 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo = uds.getDatasetDetails(DatasetID=229018, cat='LSXPS')\n", - "dsInfo['SupersedingStacks']" + "dsInfo = uds.getDatasetDetails(DatasetID=229018, cat=\"LSXPS\")\n", + "dsInfo[\"SupersedingStacks\"]" ] }, { @@ -2499,7 +2462,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsInfo = uds.getDatasetDetails(DatasetID=228895, cat='LSXPS')\n", + "dsInfo = uds.getDatasetDetails(DatasetID=228895, cat=\"LSXPS\")\n", "dsInfo[\"IsObsoleteStack\"]" ] }, @@ -2614,13 +2577,14 @@ "metadata": {}, "outputs": [], "source": [ - "lcs = uds.getLightCurves(sourceName=('Swift J073006.8-193709', 'Swift J175737.4-070600'),\n", - " transient=True,\n", - " cat='LSXPS',\n", - " binning='counts',\n", - " saveData=False,\n", - " returnData=True,\n", - " )\n", + "lcs = uds.getLightCurves(\n", + " sourceName=(\"Swift J073006.8-193709\", \"Swift J175737.4-070600\"),\n", + " transient=True,\n", + " cat=\"LSXPS\",\n", + " binning=\"counts\",\n", + " saveData=False,\n", + " returnData=True,\n", + ")\n", "lcs.keys()" ] }, @@ -2639,7 +2603,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Swift J073006.8-193709']['Datasets']" + "lcs[\"Swift J073006.8-193709\"][\"Datasets\"]" ] }, { @@ -2661,7 +2625,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcs['Swift J073006.8-193709']['PC_incbad']" + "lcs[\"Swift J073006.8-193709\"][\"PC_incbad\"]" ] }, { @@ -2697,14 +2661,15 @@ "metadata": {}, "outputs": [], "source": [ - "specSet = uds.getSpectra(sourceID=(30, 576),\n", - " destDir='/tmp/APIDemo_transSpec2',\n", - " transient=True,\n", - " silent=False,\n", - " specType='both',\n", - " verbose=True,\n", - " returnData=True\n", - " )" + "specSet = uds.getSpectra(\n", + " sourceID=(30, 576),\n", + " destDir=\"/tmp/APIDemo_transSpec2\",\n", + " transient=True,\n", + " silent=False,\n", + " specType=\"both\",\n", + " verbose=True,\n", + " returnData=True,\n", + ")" ] }, { @@ -2758,7 +2723,7 @@ "metadata": {}, "outputs": [], "source": [ - "specSet[30]['Discovery'].keys()" + "specSet[30][\"Discovery\"].keys()" ] }, { @@ -2768,7 +2733,7 @@ "metadata": {}, "outputs": [], "source": [ - "specSet[30]['Full'].keys()" + "specSet[30][\"Full\"].keys()" ] }, { @@ -2801,11 +2766,13 @@ "metadata": {}, "outputs": [], "source": [ - "uds.saveSourceImages(sourceName='Swift J073006.8-193709',\n", - " transient=True,\n", - " bands=('soft', 'expmap'),\n", - " destDir='/tmp/APIDemo_SXPS_image2',\n", - " verbose=True)" + "uds.saveSourceImages(\n", + " sourceName=\"Swift J073006.8-193709\",\n", + " transient=True,\n", + " bands=(\"soft\", \"expmap\"),\n", + " destDir=\"/tmp/APIDemo_SXPS_image2\",\n", + " verbose=True,\n", + ")" ] }, { @@ -2830,13 +2797,16 @@ }, "outputs": [], "source": [ - "myReq = uds.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " transient=True,\n", - " sourceID=576,\n", - " T0='Discovery',\n", - " useObs='new',\n", - " addProds=['LightCurve',]\n", - " )\n", + "myReq = uds.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " transient=True,\n", + " sourceID=576,\n", + " T0=\"Discovery\",\n", + " useObs=\"new\",\n", + " addProds=[\n", + " \"LightCurve\",\n", + " ],\n", + ")\n", "print(myReq)\n", "print(f\"Globals: {myReq.getGlobalPars()}\\n\")\n", "for p in myReq.productList:\n", diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/query.ipynb index 211e2604..d74ce905 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/query.ipynb @@ -96,7 +96,7 @@ "outputs": [], "source": [ "q.verbose = True\n", - "q.verbose = False # Turn it off again!" + "q.verbose = False # Turn it off again!" ] }, { @@ -145,7 +145,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.table = 'swiftxrlog'" + "q.table = \"swiftxrlog\"" ] }, { @@ -163,9 +163,7 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.ObsQuery(table='swiftbalog',\n", - " silent=False\n", - " )\n", + "q = uq.ObsQuery(table=\"swiftbalog\", silent=False)\n", "q.table" ] }, @@ -196,7 +194,7 @@ "outputs": [], "source": [ "q = uq.ObsQuery(silent=False)\n", - "q.addConeSearch(name='GK Per', radius=300, units='arcsec')\n", + "q.addConeSearch(name=\"GK Per\", radius=300, units=\"arcsec\")\n", "q.submit()" ] }, @@ -316,13 +314,14 @@ "metadata": {}, "outputs": [], "source": [ - "q.addConeSearch(ra=123.456, dec=-43.221, radius=1, units='deg')\n", - "q.addConeSearch(position='12 13 15, -15 16 17', radius=12, units='arcmin')\n", + "q.addConeSearch(ra=123.456, dec=-43.221, radius=1, units=\"deg\")\n", + "q.addConeSearch(position=\"12 13 15, -15 16 17\", radius=12, units=\"arcmin\")\n", "\n", "from astropy.coordinates import Angle\n", - "ra = Angle('12h 13m 14s')\n", - "dec = Angle('-13d 14m 15s')\n", - "q.addConeSearch(ra=ra, dec=dec, radius=300, units='arcsec')" + "\n", + "ra = Angle(\"12h 13m 14s\")\n", + "dec = Angle(\"-13d 14m 15s\")\n", + "q.addConeSearch(ra=ra, dec=dec, radius=300, units=\"arcsec\")" ] }, { @@ -423,7 +422,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.addCol('name')\n", + "q.addCol(\"name\")\n", "q.colsToGet" ] }, @@ -461,7 +460,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.removeCol('name')\n", + "q.removeCol(\"name\")\n", "q.colsToGet" ] }, @@ -472,7 +471,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.removeCol(('obsid', 'stop_time'))\n", + "q.removeCol((\"obsid\", \"stop_time\"))\n", "q.colsToGet" ] }, @@ -483,7 +482,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.addCol(['cycle', 'soft_version']+q.defaultCols)" + "q.addCol([\"cycle\", \"soft_version\"] + q.defaultCols)" ] }, { @@ -554,16 +553,9 @@ "metadata": {}, "outputs": [], "source": [ - "filter1 = ('xrt_exposure', '>', 1000, 'OR', '<', 200)\n", + "filter1 = (\"xrt_exposure\", \">\", 1000, \"OR\", \"<\", 200)\n", "\n", - "filter2 = {\n", - " \"colName\": \"ra\", \n", - " \"filter\": \">\", \n", - " \"val\": 123,\n", - " \"combiner\": \"and\" ,\n", - " \"filter2\": \"<\",\n", - " \"val2\": 200\n", - "}" + "filter2 = {\"colName\": \"ra\", \"filter\": \">\", \"val\": 123, \"combiner\": \"and\", \"filter2\": \"<\", \"val2\": 200}" ] }, { @@ -614,17 +606,11 @@ "source": [ "q.removeAllFilters()\n", "\n", - "q.addFilter ( ('xrt_exposure', '<', 2000))\n", + "q.addFilter((\"xrt_exposure\", \"<\", 2000))\n", "\n", - "q.addFilter ( ('ra', 'BETWEEN', [100,200]))\n", + "q.addFilter((\"ra\", \"BETWEEN\", [100, 200]))\n", "\n", - "q.addFilter ({\n", - " 'colName': 'target_id',\n", - " 'filter': 'IS NULL',\n", - " 'combiner': 'or',\n", - " 'filter2': '<',\n", - " 'val2': 10000 \n", - "})" + "q.addFilter({\"colName\": \"target_id\", \"filter\": \"IS NULL\", \"combiner\": \"or\", \"filter2\": \"<\", \"val2\": 10000})" ] }, { @@ -700,9 +686,9 @@ "metadata": {}, "outputs": [], "source": [ - "q=uq.ObsQuery(silent=False)\n", - "q.addConeSearch(name='GK Per', radius=12, units='arcmin')\n", - "q.addFilter( ('xrt_exposure', '>', 3000))\n", + "q = uq.ObsQuery(silent=False)\n", + "q.addConeSearch(name=\"GK Per\", radius=12, units=\"arcmin\")\n", + "q.addFilter((\"xrt_exposure\", \">\", 3000))\n", "q.isValid()" ] }, @@ -804,8 +790,8 @@ "outputs": [], "source": [ "q.unlock()\n", - "q.sortCol='xrt_exposure'\n", - "q.sortDir='DESC'\n", + "q.sortCol = \"xrt_exposure\"\n", + "q.sortDir = \"DESC\"\n", "q.submit()\n", "q.results" ] @@ -832,7 +818,7 @@ "outputs": [], "source": [ "q.unlock()\n", - "q.maxRows=3\n", + "q.maxRows = 3\n", "q.submit()" ] }, @@ -903,7 +889,7 @@ "outputs": [], "source": [ "q = uq.ObsQuery(silent=False)\n", - "q.addConeSearch(name='GRB 210205A', radius=300)\n", + "q.addConeSearch(name=\"GRB 210205A\", radius=300)\n", "q.submit()\n", "q.results" ] @@ -923,9 +909,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.downloadObsData(destDir='/tmp/APIDemo_download1',\n", - " instruments=('BAT', 'XRT'),\n", - " getTDRSS=True)" + "q.downloadObsData(destDir=\"/tmp/APIDemo_download1\", instruments=(\"BAT\", \"XRT\"), getTDRSS=True)" ] }, { @@ -955,9 +939,9 @@ "outputs": [], "source": [ "q = uq.ObsQuery(silent=False)\n", - "q.addConeSearch(name='GK Per', radius=300)\n", - "q.sortCol = 'xrt_exposure'\n", - "q.sortDir='DESC'\n", + "q.addConeSearch(name=\"GK Per\", radius=300)\n", + "q.sortCol = \"xrt_exposure\"\n", + "q.sortDir = \"DESC\"\n", "q.submit()\n", "q.results" ] @@ -977,10 +961,9 @@ "metadata": {}, "outputs": [], "source": [ - "q.downloadObsData(destDir='/tmp/APIDemo_download2',\n", - " subset=q.results['xrt_exposure']>6000,\n", - " instruments=('XRT',),\n", - " getAuxil=False)" + "q.downloadObsData(\n", + " destDir=\"/tmp/APIDemo_download2\", subset=q.results[\"xrt_exposure\"] > 6000, instruments=(\"XRT\",), getAuxil=False\n", + ")" ] }, { @@ -998,7 +981,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.results.loc[q.results['xrt_exposure']>6000]" + "q.results.loc[q.results[\"xrt_exposure\"] > 6000]" ] }, { @@ -1020,7 +1003,7 @@ "metadata": {}, "outputs": [], "source": [ - "subset = (q.results['xrt_exposure']>6000)&(q.results['xrt_exposure']<7000)\n", + "subset = (q.results[\"xrt_exposure\"] > 6000) & (q.results[\"xrt_exposure\"] < 7000)\n", "q.results.loc[subset]\n", "# Uncomment the following if you want:\n", "# q.downloadObsData(destDir='/tmp/APIDemo_download3',\n", @@ -1045,8 +1028,8 @@ "outputs": [], "source": [ "myTargs = (81445, 45767, 81637)\n", - "subset=q.results['target_id'].isin(myTargs)\n", - "q.results.loc[q.results['target_id'].isin(myTargs)]\n", + "subset = q.results[\"target_id\"].isin(myTargs)\n", + "q.results.loc[q.results[\"target_id\"].isin(myTargs)]\n", "# q.downloadObsData(destDir='/tmp/APIDemo_download4',\n", "# subset=q.results['target_id'].isin(myTargs),\n", "# instruments=('XRT',),\n", @@ -1064,7 +1047,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.1 64-bit", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1078,7 +1061,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.12.6" }, "vscode": { "interpreter": { diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb index 4fba063e..3b5ec4c4 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.ipynb @@ -156,9 +156,8 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.GRBQuery(cat='BAT_GRB',\n", - " silent=False)\n", - "q.cat = 'SDC_GRB'" + "q = uq.GRBQuery(cat=\"BAT_GRB\", silent=False)\n", + "q.cat = \"SDC_GRB\"" ] }, { @@ -198,7 +197,7 @@ "metadata": {}, "outputs": [], "source": [ - "list(q.metadata['ColName'])" + "list(q.metadata[\"ColName\"])" ] }, { @@ -223,8 +222,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.addFilter(('BAT_T90', '<', 2))\n", - "q.addCol('*')\n", + "q.addFilter((\"BAT_T90\", \"<\", 2))\n", + "q.addCol(\"*\")\n", "q.submit()\n", "q.results" ] @@ -244,7 +243,7 @@ "metadata": {}, "outputs": [], "source": [ - "subset=q.results['BAT_T90_warn'] == False" + "subset = ~q.results[\"BAT_T90_warn\"].astype(bool)" ] }, { @@ -252,7 +251,7 @@ "id": "f16f1bdc", "metadata": {}, "source": [ - "(Pythonic note, you must use `== False` here, `is False` will not give the correct result).\n", + "(Quick Python note: to build the subset on a boolean column we don't need a comparison, so `q.results['some_col']` returns the indices where `some_col` is `True`, the `~` above returns instead where it is `False`. The 'boolean' columns in the database are actually integers with values 1 or 0 (blame MariaDB) hence the `astype()` call).\n", "\n", "I could pass this subset to any of the product functions that we will come to later, or just take a look at it:" ] @@ -282,12 +281,12 @@ "metadata": {}, "outputs": [], "source": [ - "subset=q.results['BAT_T90_warn'] == True\n", + "subset = q.results[\"BAT_T90_warn\"].astype(bool)\n", "myFrame = q.results.loc[subset]\n", "myRow = myFrame.iloc[0]\n", - "print(myRow['Name'])\n", - "print(myRow['BAT_T90'])\n", - "print(myRow['BAT_T90_orig'])" + "print(myRow[\"Name\"])\n", + "print(myRow[\"BAT_T90\"])\n", + "print(myRow[\"BAT_T90_orig\"])" ] }, { @@ -318,8 +317,7 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.GRBQuery(cat='BAT_GRB',\n", - " silent=False)" + "q = uq.GRBQuery(cat=\"BAT_GRB\", silent=False)" ] }, { @@ -337,7 +335,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.setAuxCat('UK_XRT')" + "q.setAuxCat(\"UK_XRT\")" ] }, { @@ -361,7 +359,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.addFilter(('T90', '<', 2))" + "q.addFilter((\"T90\", \"<\", 2))" ] }, { @@ -379,7 +377,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.auxCat.addFilter( ('NumLCBreaks', '>=', 1))" + "q.auxCat.addFilter((\"NumLCBreaks\", \">=\", 1))" ] }, { @@ -417,8 +415,8 @@ "metadata": {}, "outputs": [], "source": [ - "print (len(q.results))\n", - "print (len(q.auxCat.results))\n" + "print(len(q.results))\n", + "print(len(q.auxCat.results))" ] }, { @@ -504,8 +502,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.addCol('*')\n", - "q.auxCat.addCol('*')" + "q.addCol(\"*\")\n", + "q.auxCat.addCol(\"*\")" ] }, { @@ -580,11 +578,10 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.GRBQuery(cat='BAT_GRB',\n", - " silent=False)\n", - "q.setAuxCat('UK_XRT')\n", - "q.addFilter(('T90', '<', 2))\n", - "q.auxCat.addFilter( ('NumLCBreaks', '>=', 1))\n", + "q = uq.GRBQuery(cat=\"BAT_GRB\", silent=False)\n", + "q.setAuxCat(\"UK_XRT\")\n", + "q.addFilter((\"T90\", \"<\", 2))\n", + "q.auxCat.addFilter((\"NumLCBreaks\", \">=\", 1))\n", "q.submit(merge=True)\n", "print(f\"\\n\\nI have {len(q.results)} rows in the merged table\")" ] @@ -766,10 +763,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.saveLightCurves(whichGRBs=['GRB 051221A', 'GRB 100117A'],\n", - " destDir='/tmp/APIDemo_GRB_LC',\n", - " header=True,\n", - " subDirs=True)" + "q.saveLightCurves(whichGRBs=[\"GRB 051221A\", \"GRB 100117A\"], destDir=\"/tmp/APIDemo_GRB_LC\", header=True, subDirs=True)" ] }, { @@ -792,7 +786,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.lightCurves['GRB 060313']['Datasets']" + "q.lightCurves[\"GRB 060313\"][\"Datasets\"]" ] }, { @@ -810,10 +804,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.plotLightCurves('GRB 060313',\n", - " whichCurves=('WT_incbad', 'PC_incbad', 'PCUL_incbad'),\n", - " xlog=True,\n", - " ylog=True)" + "q.plotLightCurves(\"GRB 060313\", whichCurves=(\"WT_incbad\", \"PC_incbad\", \"PCUL_incbad\"), xlog=True, ylog=True)" ] }, { @@ -831,10 +822,7 @@ "metadata": {}, "outputs": [], "source": [ - "f,a = q.plotLightCurves('GRB 060313',\n", - " whichCurves=('WT_incbad', 'PC_incbad', 'PCUL_incbad'),\n", - " xlog=True,\n", - " ylog=True)" + "f, a = q.plotLightCurves(\"GRB 060313\", whichCurves=(\"WT_incbad\", \"PC_incbad\", \"PCUL_incbad\"), xlog=True, ylog=True)" ] }, { @@ -844,14 +832,15 @@ "metadata": {}, "outputs": [], "source": [ - "f,a = q.plotLightCurves('GRB 160501A',\n", - " whichCurves=('WT_incbad', 'PC_incbad', 'PCUL_incbad'),\n", - " xlog=True,\n", - " ylog=True,\n", - " fig = f,\n", - " ax = a,\n", - " cols = {'WT':'cyan', 'PC': 'magenta'}\n", - " )\n", + "f, a = q.plotLightCurves(\n", + " \"GRB 160501A\",\n", + " whichCurves=(\"WT_incbad\", \"PC_incbad\", \"PCUL_incbad\"),\n", + " xlog=True,\n", + " ylog=True,\n", + " fig=f,\n", + " ax=a,\n", + " cols={\"WT\": \"cyan\", \"PC\": \"magenta\"},\n", + ")\n", "f" ] }, @@ -885,12 +874,14 @@ "metadata": {}, "outputs": [], "source": [ - "q.getSpectra(subset=q.results['Err90']<1.9,\n", - " saveData=True,\n", - " destDir='/tmp/APIDemo_GRB_Spec2',\n", - " extract=False,\n", - " removeTar=False,\n", - " saveImages=True)" + "q.getSpectra(\n", + " subset=q.results[\"Err90\"] < 1.9,\n", + " saveData=True,\n", + " destDir=\"/tmp/APIDemo_GRB_Spec2\",\n", + " extract=False,\n", + " removeTar=False,\n", + " saveImages=True,\n", + ")" ] }, { @@ -934,10 +925,17 @@ "metadata": {}, "outputs": [], "source": [ - "q.saveSpectra(destDir='/tmp/APIDemo_GRB_Spec3',\n", - " whichGRBs=('GRB 051221A', 'GRB 060218', 'GRB 060313', 'GRB 061201',),\n", - " extract=True,\n", - " removeTar=True)" + "q.saveSpectra(\n", + " destDir=\"/tmp/APIDemo_GRB_Spec3\",\n", + " whichGRBs=(\n", + " \"GRB 051221A\",\n", + " \"GRB 060218\",\n", + " \"GRB 060313\",\n", + " \"GRB 061201\",\n", + " ),\n", + " extract=True,\n", + " removeTar=True,\n", + ")" ] }, { @@ -964,11 +962,13 @@ "metadata": {}, "outputs": [], "source": [ - "q.getBurstAnalyser(subset=q.results['Err90']<1.5,\n", - " downloadTar=True,\n", - " extract=False,\n", - " removeTar=False,\n", - " destDir='/tmp/APIDemo_GRB_burstAn')" + "q.getBurstAnalyser(\n", + " subset=q.results[\"Err90\"] < 1.5,\n", + " downloadTar=True,\n", + " extract=False,\n", + " removeTar=False,\n", + " destDir=\"/tmp/APIDemo_GRB_burstAn\",\n", + ")" ] }, { @@ -1004,13 +1004,16 @@ "metadata": {}, "outputs": [], "source": [ - "q.saveBurstAnalyser(destDir=\"/tmp/APIDemo_GRB_burstAn2\",\n", - " whichGRBs=['GRB 060218', 'GRB 200324A'],\n", - " header=True,\n", - " subDirs=True,\n", - " usePropagatedErrors=True,\n", - " instruments=['XRT',]\n", - " )\n" + "q.saveBurstAnalyser(\n", + " destDir=\"/tmp/APIDemo_GRB_burstAn2\",\n", + " whichGRBs=[\"GRB 060218\", \"GRB 200324A\"],\n", + " header=True,\n", + " subDirs=True,\n", + " usePropagatedErrors=True,\n", + " instruments=[\n", + " \"XRT\",\n", + " ],\n", + ")" ] }, { @@ -1031,7 +1034,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getPositions(byName=True, subset=q.results['Image_position_err']>1.8)" + "q.getPositions(byName=True, subset=q.results[\"Image_position_err\"] > 1.8)" ] }, { @@ -1059,7 +1062,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.positions['GRB 060218']" + "q.positions[\"GRB 060218\"]" ] }, { @@ -1077,9 +1080,7 @@ "metadata": {}, "outputs": [], "source": [ - "pointlessVar = q.getPositions(byName=True,\n", - " subset=q.results['Image_position_err']>1.8,\n", - " returnData=True)\n", + "pointlessVar = q.getPositions(byName=True, subset=q.results[\"Image_position_err\"] > 1.8, returnData=True)\n", "pointlessVar.keys()" ] }, @@ -1101,10 +1102,13 @@ "metadata": {}, "outputs": [], "source": [ - "q.getObsData(destDir=\"/tmp/APIDemo_GRBdata\",\n", - " subset=q.results['GRBname']=='GRB 200324A',\n", - " instruments=['XRT',],\n", - " )" + "q.getObsData(\n", + " destDir=\"/tmp/APIDemo_GRBdata\",\n", + " subset=q.results[\"GRBname\"] == \"GRB 200324A\",\n", + " instruments=[\n", + " \"XRT\",\n", + " ],\n", + ")" ] }, { @@ -1120,7 +1124,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1134,7 +1138,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.12.6" } }, "nbformat": 4, diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md index 87a0eda1..f10fc89f 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md +++ b/swifttools/ukssdc/APIDocs/ukssdc/query/GRB.md @@ -267,7 +267,7 @@ I could now define a subset of only those with no BAT_T90_warning value set, so subset=q.results['BAT_T90_warn'] == False ``` -(Pythonic note, you must use `== False` here, `is False` will not give the correct result). +(Quick Python note: to build the subset on a boolean column we don't need a comparison, so `q.results['some_col']` returns the indices where `some_col` is `True`, the `~` above returns instead where it is `False`. The 'boolean' columns in the database are actually integers with values 1 or 0 (blame MariaDB) hence the `astype()` call). I could pass this subset to any of the product functions that we will come to later, or just take a look at it: @@ -286,7 +286,7 @@ This only has one fewer row than the original query. It may be more informative ```python -subset=q.results['BAT_T90_warn'] == True +subset=q.results['BAT_T90_warn'].astype(bool) myFrame = q.results.loc[subset] myRow = myFrame.iloc[0] print(myRow['Name']) diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query/SXPS.ipynb b/swifttools/ukssdc/APIDocs/ukssdc/query/SXPS.ipynb index 497ffb50..19610b60 100644 --- a/swifttools/ukssdc/APIDocs/ukssdc/query/SXPS.ipynb +++ b/swifttools/ukssdc/APIDocs/ukssdc/query/SXPS.ipynb @@ -112,11 +112,9 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(silent=False,\n", - " cat='LSXPS',\n", - " table='datasets')\n", - "q.cat='2SXPS'\n", - "q.table='sources'" + "q = uq.SXPSQuery(silent=False, cat=\"LSXPS\", table=\"datasets\")\n", + "q.cat = \"2SXPS\"\n", + "q.table = \"sources\"" ] }, { @@ -155,7 +153,7 @@ "outputs": [], "source": [ "print(f\"The current subset is {q.subset}\")\n", - "q.subset = 'Clean'\n", + "q.subset = \"Clean\"\n", "print(f\"Now it is {q.subset}\")" ] }, @@ -174,7 +172,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.subset=None\n", + "q.subset = None\n", "print(f\"Now it is {q.subset}\")" ] }, @@ -196,13 +194,9 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(silent=False,\n", - " cat='LSXPS',\n", - " table='datasets')\n", - "q.addConeSearch(name='GK Per',\n", - " radius=12,\n", - " units='arcmin')\n", - "q.addFilter(['ExposureUsed', '>=', 5000])\n", + "q = uq.SXPSQuery(silent=False, cat=\"LSXPS\", table=\"datasets\")\n", + "q.addConeSearch(name=\"GK Per\", radius=12, units=\"arcmin\")\n", + "q.addFilter([\"ExposureUsed\", \">=\", 5000])\n", "q.submit()\n", "q.results" ] @@ -255,13 +249,9 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(cat='LSXPS',\n", - " table='sources',\n", - " silent=False)\n", - "q.addConeSearch(name='GRB 060729',\n", - " radius=2,\n", - " units='arcmin')\n", - "q.addFilter(('DetFlag', '=', 0))\n", + "q = uq.SXPSQuery(cat=\"LSXPS\", table=\"sources\", silent=False)\n", + "q.addConeSearch(name=\"GRB 060729\", radius=2, units=\"arcmin\")\n", + "q.addFilter((\"DetFlag\", \"=\", 0))\n", "q.submit()" ] }, @@ -298,7 +288,7 @@ "metadata": {}, "outputs": [], "source": [ - "mySS = (q.results['MeanOffAxisAngle']<2) | (q.results['HR1']>0)" + "mySS = (q.results[\"MeanOffAxisAngle\"] < 2) | (q.results[\"HR1\"] > 0)" ] }, { @@ -335,9 +325,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getSourceDetails(byName=True,\n", - " subset = mySS\n", - " )" + "q.getSourceDetails(byName=True, subset=mySS)" ] }, { @@ -369,9 +357,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getDetails(byName=True,\n", - " subset = mySS\n", - " )\n", + "q.getDetails(byName=True, subset=mySS)\n", "q.sourceDetails.keys()" ] }, @@ -393,11 +379,9 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('sourceDetails')\n", + "q.clearProduct(\"sourceDetails\")\n", "print(f\"sourceDetails is: {q.sourceDetails}\\n\\n\")\n", - "q.getDetails(byID=True,\n", - " subset = mySS\n", - " )\n", + "q.getDetails(byID=True, subset=mySS)\n", "q.sourceDetails.keys()" ] }, @@ -433,12 +417,9 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('sourceDetails')\n", + "q.clearProduct(\"sourceDetails\")\n", "\n", - "q.getObsList(byName=True,\n", - " subset=mySS,\n", - " useObs='allDet'\n", - " )\n", + "q.getObsList(byName=True, subset=mySS, useObs=\"allDet\")\n", "\n", "q.sourceObsList" ] @@ -461,11 +442,13 @@ "outputs": [], "source": [ "import swifttools.ukssdc.data as ud\n", - "ud.downloadObsData(q.sourceObsList['LSXPS J062126.9-622317']['obsList'],\n", - " instruments=('xrt',),\n", - " destDir='/tmp/APIDemo_SXPSq_data',\n", - " silent=False\n", - " )" + "\n", + "ud.downloadObsData(\n", + " q.sourceObsList[\"LSXPS J062126.9-622317\"][\"obsList\"],\n", + " instruments=(\"xrt\",),\n", + " destDir=\"/tmp/APIDemo_SXPSq_data\",\n", + " silent=False,\n", + ")" ] }, { @@ -494,11 +477,12 @@ "metadata": {}, "outputs": [], "source": [ - "q.getLightCurves(byID=True,\n", - " subset = q.results['MeanOffAxisAngle']<2,\n", - " timeFormat='MJD',\n", - " binning='obs',\n", - " )" + "q.getLightCurves(\n", + " byID=True,\n", + " subset=q.results[\"MeanOffAxisAngle\"] < 2,\n", + " timeFormat=\"MJD\",\n", + " binning=\"obs\",\n", + ")" ] }, { @@ -538,11 +522,12 @@ "metadata": {}, "outputs": [], "source": [ - "q.getLightCurves(byID=True,\n", - " subset = q.results['MeanOffAxisAngle']>2,\n", - " timeFormat='TDB',\n", - " binning='obs',\n", - " )" + "q.getLightCurves(\n", + " byID=True,\n", + " subset=q.results[\"MeanOffAxisAngle\"] > 2,\n", + " timeFormat=\"TDB\",\n", + " binning=\"obs\",\n", + ")" ] }, { @@ -562,13 +547,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('lightCurves')\n", - "q.getLightCurves(byID=True,\n", - " subset = mySS,\n", - " timeFormat='MJD',\n", - " binning='obs',\n", - " bands=('total', 'soft')\n", - " )" + "q.clearProduct(\"lightCurves\")\n", + "q.getLightCurves(byID=True, subset=mySS, timeFormat=\"MJD\", binning=\"obs\", bands=(\"total\", \"soft\"))" ] }, { @@ -596,7 +576,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.lightCurves[209851]['Datasets']" + "q.lightCurves[209851][\"Datasets\"]" ] }, { @@ -614,12 +594,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getLightCurves(byID=True,\n", - " subset = mySS,\n", - " timeFormat='MJD',\n", - " binning='obs',\n", - " bands=('HR1',)\n", - " )" + "q.getLightCurves(byID=True, subset=mySS, timeFormat=\"MJD\", binning=\"obs\", bands=(\"HR1\",))" ] }, { @@ -637,7 +612,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.lightCurves[209851]['Datasets']" + "q.lightCurves[209851][\"Datasets\"]" ] }, { @@ -661,17 +636,18 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('lightCurves')\n", - "q.verbose=True\n", - "q.getLightCurves(byID=True,\n", - " subset = q.results['MeanOffAxisAngle']<2,\n", - " timeFormat='MJD',\n", - " binning='obs',\n", - " bands=('total', 'soft'),\n", - " saveData=True,\n", - " destDir='/tmp/APIDemo_SXPSq_LC1',\n", - " subDirs=True\n", - " )" + "q.clearProduct(\"lightCurves\")\n", + "q.verbose = True\n", + "q.getLightCurves(\n", + " byID=True,\n", + " subset=q.results[\"MeanOffAxisAngle\"] < 2,\n", + " timeFormat=\"MJD\",\n", + " binning=\"obs\",\n", + " bands=(\"total\", \"soft\"),\n", + " saveData=True,\n", + " destDir=\"/tmp/APIDemo_SXPSq_LC1\",\n", + " subDirs=True,\n", + ")" ] }, { @@ -691,12 +667,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.saveLightCurves(destDir='/tmp/APIDemo_SXPSq_LC2',\n", - " subDirs=False,\n", - " whichSources=(209851,),\n", - " whichCurves=('Total_rates',)\n", - " )\n", - "q.verbose = False # I want to turn this off again because it annoys me" + "q.saveLightCurves(destDir=\"/tmp/APIDemo_SXPSq_LC2\", subDirs=False, whichSources=(209851,), whichCurves=(\"Total_rates\",))\n", + "q.verbose = False # I want to turn this off again because it annoys me" ] }, { @@ -716,10 +688,9 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = q.plotLightCurves(209851,\n", - " whichCurves=('Total_rates', 'Total_UL'),\n", - " cols = {'Total_rates':'red', 'Total_UL':'blue'},\n", - " ylog=True)" + "fig, ax = q.plotLightCurves(\n", + " 209851, whichCurves=(\"Total_rates\", \"Total_UL\"), cols={\"Total_rates\": \"red\", \"Total_UL\": \"blue\"}, ylog=True\n", + ")" ] }, { @@ -737,12 +708,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = q.plotLightCurves(209920,\n", - " fig=fig,\n", - " ax=ax,\n", - " whichCurves=('Total_rates', 'Total_UL'),\n", - " cols = {'Total_rates':'green', 'Total_UL':'black'},\n", - " ylog=True)\n", + "fig, ax = q.plotLightCurves(\n", + " 209920,\n", + " fig=fig,\n", + " ax=ax,\n", + " whichCurves=(\"Total_rates\", \"Total_UL\"),\n", + " cols={\"Total_rates\": \"green\", \"Total_UL\": \"black\"},\n", + " ylog=True,\n", + ")\n", "fig" ] }, @@ -765,9 +738,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getSpectra(byID=True,\n", - " subset = mySS\n", - " )" + "q.getSpectra(byID=True, subset=mySS)" ] }, { @@ -809,14 +780,9 @@ "metadata": {}, "outputs": [], "source": [ - "q.verbose=True\n", - "q.saveSpectra(whichSources=(209851,),\n", - " destDir='/tmp/APIDemo_SXPSq_spec',\n", - " saveImages=True,\n", - " extract=True,\n", - " removeTar=True\n", - " )\n", - "q.verbose=False" + "q.verbose = True\n", + "q.saveSpectra(whichSources=(209851,), destDir=\"/tmp/APIDemo_SXPSq_spec\", saveImages=True, extract=True, removeTar=True)\n", + "q.verbose = False" ] }, { @@ -839,10 +805,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.verbose=True # So we see what's going on\n", - "q.saveImages(subset = mySS,\n", - " byName=True,\n", - " destDir='/tmp/APIDemo_SXPSq_images')" + "q.verbose = True # So we see what's going on\n", + "q.saveImages(subset=mySS, byName=True, destDir=\"/tmp/APIDemo_SXPSq_images\")" ] }, { @@ -868,15 +832,12 @@ "outputs": [], "source": [ "import swifttools.ukssdc.query as uq\n", - "q = uq.SXPSQuery(cat='LSXPS',\n", - " table='sources',\n", - " silent=False)\n", - "q.addConeSearch(name='GRB 060729',\n", - " radius=2,\n", - " units='arcmin')\n", - "q.addFilter(('DetFlag', '=', 0))\n", + "\n", + "q = uq.SXPSQuery(cat=\"LSXPS\", table=\"sources\", silent=False)\n", + "q.addConeSearch(name=\"GRB 060729\", radius=2, units=\"arcmin\")\n", + "q.addFilter((\"DetFlag\", \"=\", 0))\n", "q.submit()\n", - "mySS = (q.results['MeanOffAxisAngle']<2) | (q.results['HR1']>0)" + "mySS = (q.results[\"MeanOffAxisAngle\"] < 2) | (q.results[\"HR1\"] > 0)" ] }, { @@ -927,11 +888,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.doSIMBADSearch(byName=True,\n", - " subset = mySS,\n", - " radius=20*au.arcsec,\n", - " epoch='J2000',\n", - " equinox=2000)" + "q.doSIMBADSearch(byName=True, subset=mySS, radius=20 * au.arcsec, epoch=\"J2000\", equinox=2000)" ] }, { @@ -969,7 +926,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.SIMBAD['LSXPS J062131.8-622213']" + "q.SIMBAD[\"LSXPS J062131.8-622213\"]" ] }, { @@ -991,10 +948,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.doVizierSearch(byName=True,\n", - " subset = mySS,\n", - " radius=20*au.arcsec\n", - " )" + "q.doVizierSearch(byName=True, subset=mySS, radius=20 * au.arcsec)" ] }, { @@ -1014,7 +968,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.Vizier['LSXPS J062131.8-622213'].keys()" + "q.Vizier[\"LSXPS J062131.8-622213\"].keys()" ] }, { @@ -1036,12 +990,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('Vizier') # Let's lose that mass of data we downloaded just now\n", - "q.doVizierSearch(byName=True,\n", - " subset = q.results['MeanOffAxisAngle']<2,\n", - " radius=20*au.arcsec,\n", - " catalog = 'GSC'\n", - " )" + "q.clearProduct(\"Vizier\") # Let's lose that mass of data we downloaded just now\n", + "q.doVizierSearch(byName=True, subset=q.results[\"MeanOffAxisAngle\"] < 2, radius=20 * au.arcsec, catalog=\"GSC\")" ] }, { @@ -1059,7 +1009,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.Vizier['LSXPS J062131.8-622213'].keys()" + "q.Vizier[\"LSXPS J062131.8-622213\"].keys()" ] }, { @@ -1077,7 +1027,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.Vizier['LSXPS J062131.8-622213']['I/353/gsc242']" + "q.Vizier[\"LSXPS J062131.8-622213\"][\"I/353/gsc242\"]" ] }, { @@ -1134,14 +1084,15 @@ "metadata": {}, "outputs": [], "source": [ - "q.silent=True\n", - "rlist=q.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " subset=mySS,\n", - " byID=True,\n", - " T0='firstBlindDet',\n", - " useObs='blind',\n", - " addProds=['LightCurve','Spectrum', 'StandardPos']\n", - " )" + "q.silent = True\n", + "rlist = q.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " subset=mySS,\n", + " byID=True,\n", + " T0=\"firstBlindDet\",\n", + " useObs=\"blind\",\n", + " addProds=[\"LightCurve\", \"Spectrum\", \"StandardPos\"],\n", + ")" ] }, { @@ -1183,9 +1134,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getSourceDetails(byID=True,\n", - " subset = q.results['MeanOffAxisAngle']<2\n", - " )\n", + "q.getSourceDetails(byID=True, subset=q.results[\"MeanOffAxisAngle\"] < 2)\n", "q.sourceDetails[209920]" ] }, @@ -1220,12 +1169,8 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(cat='LSXPS',\n", - " table='datasets',\n", - " silent=False)\n", - "q.addConeSearch(name='GK Per',\n", - " radius=10,\n", - " units='arcmin')\n", + "q = uq.SXPSQuery(cat=\"LSXPS\", table=\"datasets\", silent=False)\n", + "q.addConeSearch(name=\"GK Per\", radius=10, units=\"arcmin\")\n", "q.submit()" ] }, @@ -1244,7 +1189,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.results['ExposureUsed'].tolist()" + "q.results[\"ExposureUsed\"].tolist()" ] }, { @@ -1262,7 +1207,7 @@ "metadata": {}, "outputs": [], "source": [ - "mySS = q.results['ExposureUsed']>7000" + "mySS = q.results[\"ExposureUsed\"] > 7000" ] }, { @@ -1285,8 +1230,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getDetails(byObsID=True,\n", - " subset=mySS)" + "q.getDetails(byObsID=True, subset=mySS)" ] }, { @@ -1316,9 +1260,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.getDatasetDetails(byDatasetID=True,\n", - " subset=q.results['ExposureUsed']<300)\n", - " \n", + "q.getDatasetDetails(byDatasetID=True, subset=q.results[\"ExposureUsed\"] < 300)\n", + "\n", "q.datasetDetails.keys()" ] }, @@ -1343,10 +1286,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.verbose=True # So we see what's going on\n", - "q.saveImages(subset=mySS,\n", - " byObsID=True,\n", - " destDir='/tmp/APIDemo_SXPSq_images2')" + "q.verbose = True # So we see what's going on\n", + "q.saveImages(subset=mySS, byObsID=True, destDir=\"/tmp/APIDemo_SXPSq_images2\")" ] }, { @@ -1376,8 +1317,8 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(cat='LSXPS', table='transients', silent=False)\n", - "q.addFilter(('Classification', '=', 1))\n", + "q = uq.SXPSQuery(cat=\"LSXPS\", table=\"transients\", silent=False)\n", + "q.addFilter((\"Classification\", \"=\", 1))\n", "q.submit()\n", "q.results" ] @@ -1389,7 +1330,7 @@ "metadata": {}, "outputs": [], "source": [ - "mySS = q.results['TransientID']<100" + "mySS = q.results[\"TransientID\"] < 100" ] }, { @@ -1436,10 +1377,11 @@ "metadata": {}, "outputs": [], "source": [ - "q.getLightCurves(byName=True,\n", - " subset=mySS,\n", - " binning='counts',\n", - " )\n", + "q.getLightCurves(\n", + " byName=True,\n", + " subset=mySS,\n", + " binning=\"counts\",\n", + ")\n", "q.lightCurves.keys()" ] }, @@ -1463,8 +1405,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.getSpectra(byID=True,\n", - " specType='discovery')\n", + "q.getSpectra(byID=True, specType=\"discovery\")\n", "q.spectra.keys()" ] }, @@ -1496,11 +1437,9 @@ "metadata": {}, "outputs": [], "source": [ - "q.verbose=True # So we see what's going on\n", - "q.saveImages(byName=True,\n", - " subset=mySS,\n", - " destDir='/tmp/APIDemo_SXPSq_timages')\n", - "q.verbose=False # So we can stop seeing what's going on" + "q.verbose = True # So we see what's going on\n", + "q.saveImages(byName=True, subset=mySS, destDir=\"/tmp/APIDemo_SXPSq_timages\")\n", + "q.verbose = False # So we can stop seeing what's going on" ] }, { @@ -1523,11 +1462,7 @@ "outputs": [], "source": [ "# import astropy.units as au # Uncomment if you didn't do this earlier\n", - "q.doSIMBADSearch(byName=True,\n", - " subset=mySS,\n", - " radius=20*au.arcsec,\n", - " epoch='J2000',\n", - " equinox=2000)\n", + "q.doSIMBADSearch(byName=True, subset=mySS, radius=20 * au.arcsec, epoch=\"J2000\", equinox=2000)\n", "q.SIMBAD.keys()" ] }, @@ -1538,7 +1473,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.SIMBAD['Swift J102732.5-643553']" + "q.SIMBAD[\"Swift J102732.5-643553\"]" ] }, { @@ -1548,11 +1483,8 @@ "metadata": {}, "outputs": [], "source": [ - "q.clearProduct('Vizier') # Let's lose that mass of data we downloaded just now\n", - "q.doVizierSearch(byName=True,\n", - " radius=20*au.arcsec,\n", - " catalog = 'USNO-B1'\n", - " )\n", + "q.clearProduct(\"Vizier\") # Let's lose that mass of data we downloaded just now\n", + "q.doVizierSearch(byName=True, radius=20 * au.arcsec, catalog=\"USNO-B1\")\n", "q.Vizier.keys()" ] }, @@ -1563,7 +1495,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.Vizier['Swift J102732.5-643553'].keys()" + "q.Vizier[\"Swift J102732.5-643553\"].keys()" ] }, { @@ -1573,7 +1505,7 @@ "metadata": {}, "outputs": [], "source": [ - "q.Vizier['Swift J102732.5-643553']['I/284/out']" + "q.Vizier[\"Swift J102732.5-643553\"][\"I/284/out\"]" ] }, { @@ -1594,14 +1526,17 @@ "metadata": {}, "outputs": [], "source": [ - "q.silent=True\n", - "rlist=q.makeProductRequest('MY_EMAIL_ADDRESS',\n", - " byID=True,\n", - " subset=mySS,\n", - " T0='Discovery',\n", - " useObs='new',\n", - " addProds=['LightCurve',]\n", - " )" + "q.silent = True\n", + "rlist = q.makeProductRequest(\n", + " \"MY_EMAIL_ADDRESS\",\n", + " byID=True,\n", + " subset=mySS,\n", + " T0=\"Discovery\",\n", + " useObs=\"new\",\n", + " addProds=[\n", + " \"LightCurve\",\n", + " ],\n", + ")" ] }, { @@ -1648,9 +1583,10 @@ "metadata": {}, "outputs": [], "source": [ - "q = uq.SXPSQuery(cat='LSXPS',\n", - " table='obssources',\n", - " )\n", + "q = uq.SXPSQuery(\n", + " cat=\"LSXPS\",\n", + " table=\"obssources\",\n", + ")\n", "q.getFullTable()\n", "q.fullTable" ] From f694660eceb8944450c8721786147f5edc9d2f59 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Wed, 13 Nov 2024 10:52:51 -0500 Subject: [PATCH 17/22] Fix for crash reading in textual UVOT mode (#26) --- swifttools/swift_too/swift_instruments.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/swifttools/swift_too/swift_instruments.py b/swifttools/swift_too/swift_instruments.py index 3d2dd456..b03a4a57 100644 --- a/swifttools/swift_too/swift_instruments.py +++ b/swifttools/swift_too/swift_instruments.py @@ -1,4 +1,7 @@ # Lookup table for XRT modes +import re + + XRTMODES = { 0: "Auto", 1: "Null", @@ -42,6 +45,11 @@ class TOOAPI_Instruments: def uvot_mode_setter(self, attr, mode): if type(mode) == str and "0x" in mode: """Convert hex string to int""" + uvot_mode = re.match(r"0x([0-9a-fA-F]+)", mode) + if uvot_mode is not None: + setattr(self, f"_{attr}", int(uvot_mode.group(0), 16)) + else: + setattr(self, f"_{attr}", mode) setattr(self, f"_{attr}", int(mode.split(":")[0], 16)) elif type(mode) == str: """Convert decimal string to int""" From 7ebadf8d1d462910d8fd28a8a12ed8e8d44e462a Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Fri, 15 Nov 2024 09:09:53 +0100 Subject: [PATCH 18/22] Update swift_too changelog and version number (#28) --- swifttools/swift_too/ChangeLog.md | 4 ++++ swifttools/swift_too/version.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/swifttools/swift_too/ChangeLog.md b/swifttools/swift_too/ChangeLog.md index 0d39a3e2..a18dae3d 100644 --- a/swifttools/swift_too/ChangeLog.md +++ b/swifttools/swift_too/ChangeLog.md @@ -6,6 +6,10 @@ #### Author: Jamie A. Kennea (Penn State) +## `swifttools` 3.0.23` / `swift_too` 1.2.33 + +** Nov 13, 2024 **: Fix bug that caused crash when reading in UVOT modes in +`TOORequests` when `detail = True`. ## `swifttools` 3.0.21` / `swift_too` 1.2.32 diff --git a/swifttools/swift_too/version.py b/swifttools/swift_too/version.py index daffe27c..db6e7ee0 100644 --- a/swifttools/swift_too/version.py +++ b/swifttools/swift_too/version.py @@ -1,2 +1,2 @@ -version = "1.2.32" -version_tuple = (1, 2, 32) +version = "1.2.33" +version_tuple = (1, 2, 33) From cf4208ea12bb086cbf75b8f277bcde4bd7848a22 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Sat, 21 Mar 2026 14:36:47 -0400 Subject: [PATCH 19/22] fix: add missing endpoint parameters --- swifttools/swift_too/base/constants.py | 2 +- swifttools/swift_too/swift/calendar.py | 10 ++++++++++ swifttools/swift_too/swift/guano.py | 10 +++++++++- swifttools/swift_too/swift/obsquery.py | 10 ++++++++++ swifttools/swift_too/swift/planquery.py | 8 ++++++++ swifttools/swift_too/swift/requests.py | 7 +++++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/swifttools/swift_too/base/constants.py b/swifttools/swift_too/base/constants.py index 2a51e008..8b0fabd6 100644 --- a/swifttools/swift_too/base/constants.py +++ b/swifttools/swift_too/base/constants.py @@ -40,7 +40,7 @@ # Submission URL # Default to production API; allow explicit override for local/dev testing. -API_URL = os.getenv("SWIFT_TOO_API_URL", f"https://www.swift.psu.edu/api/v{API_VERSION}") +API_URL = os.getenv("SWIFT_TOO_API_URL", f"http://localhost:8000/api/v{API_VERSION}") # Magic strings STATUS_PENDING = "Pending" diff --git a/swifttools/swift_too/swift/calendar.py b/swifttools/swift_too/swift/calendar.py index 992f141e..490e9277 100755 --- a/swifttools/swift_too/swift/calendar.py +++ b/swifttools/swift_too/swift/calendar.py @@ -22,7 +22,12 @@ class SwiftCalendarGetSchema(BaseModel): dec: AstropyAngle | None = None too_id: int | None = None radius: AstropyAngle | None = 12 / 60.0 + target_id: int | None = None targetid: int | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None status: TOOStatus = Field(default_factory=TOOStatus) @@ -122,7 +127,12 @@ class SwiftCalendarSchema(BaseSchema, TOOAPIReprMixin): ra: AstropyAngle | None = None dec: AstropyAngle | None = None radius: AstropyAngle | None = None + target_id: int | None = None targetid: int | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None entries: list[SwiftCalendarEntry] = [] status: TOOStatus = TOOStatus() diff --git a/swifttools/swift_too/swift/guano.py b/swifttools/swift_too/swift/guano.py index cdab2929..e20ef20e 100644 --- a/swifttools/swift_too/swift/guano.py +++ b/swifttools/swift_too/swift/guano.py @@ -197,7 +197,11 @@ class SwiftGUANOGetSchema(OptionalBeginEndLengthSchema): triggertime: datetime | None = None limit: int | None = None page: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None triggertype: str | None = None + queue_num: int | None = None model_config = ConfigDict(extra="ignore") @@ -209,7 +213,6 @@ def validate_parameters(cls, values): return if not isinstance(values, dict): values = values.__dict__ - print(values) for key in cls.model_fields.keys(): if key in values: good = True @@ -221,10 +224,15 @@ def validate_parameters(cls, values): class SwiftGUANOSchema(BaseSchema): begin: datetime | None = None end: datetime | None = None + username: str = Field(default="anonymous") subthreshold: bool = False successful: bool = True triggertime: datetime | None = None limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None + queue_num: int | None = None triggertype: str | None = None lastcommand: datetime | None = None guanostatus: bool | None = None diff --git a/swifttools/swift_too/swift/obsquery.py b/swifttools/swift_too/swift/obsquery.py index a0733bea..e5e50d16 100644 --- a/swifttools/swift_too/swift/obsquery.py +++ b/swifttools/swift_too/swift/obsquery.py @@ -150,6 +150,11 @@ class SwiftAFSTGetSchema(BaseModel): radius: AstropyAngle | None = None target_id: int | list[int] | None = None obs_id: ObsIDSDC | list[ObsIDSDC] | None = None + gw_event: str | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None model_config = ConfigDict(extra="ignore") @@ -172,6 +177,11 @@ class SwiftAFSTSchema(OptionalCoordinateSchema, OptionalBeginEndLengthSchema): radius: AstropyAngle | None = None target_id: int | list[int] | None = None obs_id: ObsIDSDC | list[ObsIDSDC] | None = None + gw_event: str | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None afstmax: datetime | SwiftDateTimeSchema | None = None entries: list[SwiftAFSTEntry] = [] status: TOOStatus = TOOStatus() diff --git a/swifttools/swift_too/swift/planquery.py b/swifttools/swift_too/swift/planquery.py index ac29c353..34fdd783 100644 --- a/swifttools/swift_too/swift/planquery.py +++ b/swifttools/swift_too/swift/planquery.py @@ -18,6 +18,10 @@ class SwiftPPSTGetSchema(OptionalBeginEndLengthSchema, OptionalCoordinateSchema) radius: AstropyAngle | None = None target_id: int | list[int] | None = None obs_id: ObsIDSDC | list[ObsIDSDC] | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None model_config = ConfigDict(extra="ignore") @@ -122,6 +126,10 @@ class SwiftPPSTSchema(OptionalBeginEndLengthSchema, OptionalCoordinateSchema): radius: AstropyAngle | None = None target_id: int | list[int] | None = None obs_id: ObsIDSDC | list[ObsIDSDC] | None = None + limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None ppstmax: datetime | None = None entries: list[SwiftPPSTEntry] = Field(default_factory=list) status: TOOStatus = Field(default_factory=TOOStatus) diff --git a/swifttools/swift_too/swift/requests.py b/swifttools/swift_too/swift/requests.py index 844a037d..4393d0e4 100644 --- a/swifttools/swift_too/swift/requests.py +++ b/swifttools/swift_too/swift/requests.py @@ -11,7 +11,11 @@ class SwiftTOORequestsGetSchema(OptionalBeginEndLengthSchema, OptionalCoordinateSchema): + username: str | None = None limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None page: int | None = None year: int | None = None detail: bool = False @@ -48,6 +52,9 @@ class SwiftTOORequestsSchema(BaseSchema): begin: datetime | None = None length: float | None = None limit: int | None = None + offset: int | None = None + sort_by: str | None = None + order: str | None = None year: int | None = None detail: bool = False too_id: int | None = None From 4ce18ec4429fc02e0ff9aa872ef27e2984c5678b Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Sat, 21 Mar 2026 14:57:22 -0400 Subject: [PATCH 20/22] fix: align endpoints with openapi.json --- swifttools/swift_too/swift/calendar.py | 3 +-- swifttools/swift_too/swift/guano.py | 3 +-- swifttools/swift_too/swift/requests.py | 1 + swifttools/swift_too/swift/uvot.py | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/swifttools/swift_too/swift/calendar.py b/swifttools/swift_too/swift/calendar.py index 490e9277..41cff091 100755 --- a/swifttools/swift_too/swift/calendar.py +++ b/swifttools/swift_too/swift/calendar.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel from ..base.common import TOOAPIBaseclass from ..base.repr import TOOAPIReprMixin @@ -28,7 +28,6 @@ class SwiftCalendarGetSchema(BaseModel): offset: int | None = None sort_by: str | None = None order: str | None = None - status: TOOStatus = Field(default_factory=TOOStatus) class SwiftCalendarEntry(BaseSchema, TOOAPIClockCorrect, TOOAPIReprMixin): diff --git a/swifttools/swift_too/swift/guano.py b/swifttools/swift_too/swift/guano.py index e20ef20e..e45fdd71 100644 --- a/swifttools/swift_too/swift/guano.py +++ b/swifttools/swift_too/swift/guano.py @@ -201,7 +201,7 @@ class SwiftGUANOGetSchema(OptionalBeginEndLengthSchema): sort_by: str | None = None order: str | None = None triggertype: str | None = None - queue_num: int | None = None + queue: int | None = None model_config = ConfigDict(extra="ignore") @@ -232,7 +232,6 @@ class SwiftGUANOSchema(BaseSchema): offset: int | None = None sort_by: str | None = None order: str | None = None - queue_num: int | None = None triggertype: str | None = None lastcommand: datetime | None = None guanostatus: bool | None = None diff --git a/swifttools/swift_too/swift/requests.py b/swifttools/swift_too/swift/requests.py index 4393d0e4..d206b4af 100644 --- a/swifttools/swift_too/swift/requests.py +++ b/swifttools/swift_too/swift/requests.py @@ -32,6 +32,7 @@ def validate_at_least_one_param(cls, data): if isinstance(data, dict): # Exclude model_config and other class attributes params = [ + "username", "begin", "end", "length", diff --git a/swifttools/swift_too/swift/uvot.py b/swifttools/swift_too/swift/uvot.py index 540e7434..b59f1b4d 100644 --- a/swifttools/swift_too/swift/uvot.py +++ b/swifttools/swift_too/swift/uvot.py @@ -50,7 +50,7 @@ class SwiftUVOTModeEntry(BaseSchema, TOOAPIReprMixin): comment on special modes """ - uvot_mode: int = 0 + uvot_mode: int filter_num: int | None = None min_exposure: int | None = None filter_pos: int | None = None @@ -62,7 +62,7 @@ class SwiftUVOTModeEntry(BaseSchema, TOOAPIReprMixin): weight: int | None = None special: int | None = None comment: str | None = None - filter_name: str | None = None + filter_name: str class SwiftUVOTModeSchema(BaseSchema): @@ -99,7 +99,7 @@ class SwiftUVOTMode(TOOAPIBaseclass, TOOAPIInstruments, SwiftUVOTModeSchema, TOO def _post_process(self): if len(self.entries) == 0: - self.entries = [SwiftUVOTModeEntry(uvot_mode=self.uvot_mode or 0)] + self.entries = [SwiftUVOTModeEntry(uvot_mode=self.uvot_mode or 0, filter_name="")] def __getitem__(self, index): return self.entries[index] From 093a750dd9fcbf227659b919b1f798de2c4345d6 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Sat, 21 Mar 2026 15:52:55 -0400 Subject: [PATCH 21/22] fix: tests for constants --- swifttools/swift_too/base/constants.py | 2 +- tests/swift_too/base/common/test_constants.py | 12 +++++ tests/swift_too/swift/schemas/test_schemas.py | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/swift_too/base/common/test_constants.py create mode 100644 tests/swift_too/swift/schemas/test_schemas.py diff --git a/swifttools/swift_too/base/constants.py b/swifttools/swift_too/base/constants.py index 8b0fabd6..2a51e008 100644 --- a/swifttools/swift_too/base/constants.py +++ b/swifttools/swift_too/base/constants.py @@ -40,7 +40,7 @@ # Submission URL # Default to production API; allow explicit override for local/dev testing. -API_URL = os.getenv("SWIFT_TOO_API_URL", f"http://localhost:8000/api/v{API_VERSION}") +API_URL = os.getenv("SWIFT_TOO_API_URL", f"https://www.swift.psu.edu/api/v{API_VERSION}") # Magic strings STATUS_PENDING = "Pending" diff --git a/tests/swift_too/base/common/test_constants.py b/tests/swift_too/base/common/test_constants.py new file mode 100644 index 00000000..27e6fa27 --- /dev/null +++ b/tests/swift_too/base/common/test_constants.py @@ -0,0 +1,12 @@ +import importlib + + +def test_default_swift_too_api_url(monkeypatch): + monkeypatch.delenv("SWIFT_TOO_API_URL", raising=False) + + import swifttools.swift_too.base.constants as constants + + reloaded = importlib.reload(constants) + expected = f"https://www.swift.psu.edu/api/v{reloaded.API_VERSION}" + + assert reloaded.API_URL == expected diff --git a/tests/swift_too/swift/schemas/test_schemas.py b/tests/swift_too/swift/schemas/test_schemas.py new file mode 100644 index 00000000..14a61909 --- /dev/null +++ b/tests/swift_too/swift/schemas/test_schemas.py @@ -0,0 +1,45 @@ +from datetime import datetime, timedelta + +from swifttools.swift_too.swift.schemas import SwiftObservationSchema + + +class TestSwiftObservationSchema: + def test_settle_field_is_declared(self): + schema = SwiftObservationSchema( + begin=datetime(2023, 1, 1, 12, 0, 0), + settle=datetime(2023, 1, 1, 12, 1, 0), + end=datetime(2023, 1, 1, 12, 10, 0), + ) + + assert schema.settle == datetime(2023, 1, 1, 12, 1, 0) + + def test_time_properties_are_none_safe(self): + schema = SwiftObservationSchema( + begin=datetime(2023, 1, 1, 12, 0, 0), + end=datetime(2023, 1, 1, 12, 10, 0), + ) + + assert schema.exposure is None + assert schema.slewtime is None + + def test_table_uses_none_when_deltas_unavailable(self): + schema = SwiftObservationSchema( + begin=datetime(2023, 1, 1, 12, 0, 0), + end=datetime(2023, 1, 1, 12, 10, 0), + target_name="Test Target", + obs_id="00012345001", + ) + + _, data = schema._table + assert data[0][4] is None + assert data[0][5] is None + + def test_time_properties_when_complete(self): + schema = SwiftObservationSchema( + begin=datetime(2023, 1, 1, 12, 0, 0), + settle=datetime(2023, 1, 1, 12, 1, 0), + end=datetime(2023, 1, 1, 12, 10, 0), + ) + + assert schema.exposure == timedelta(minutes=9) + assert schema.slewtime == timedelta(minutes=1) From 9c3bb8266dca7397df242a02828bf88f454edb97 Mon Sep 17 00:00:00 2001 From: Jamie Kennea Date: Sat, 21 Mar 2026 15:53:49 -0400 Subject: [PATCH 22/22] fix: broken unit tests --- tests/swift_too/swift/uvot/conftest.py | 12 ++++++------ tests/swift_too/swift/uvot/test_uvot.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/swift_too/swift/uvot/conftest.py b/tests/swift_too/swift/uvot/conftest.py index 0fe401ab..0da51a05 100644 --- a/tests/swift_too/swift/uvot/conftest.py +++ b/tests/swift_too/swift/uvot/conftest.py @@ -34,15 +34,15 @@ def uvot_mode_with_entries(uvot_mode_empty): @pytest.fixture def uvot_entry(): - return SwiftUVOTModeEntry(uvot_mode=0x30ED) + return SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name="") @pytest.fixture def sample_uvot_entries(): """List of sample SwiftUVOTModeEntry objects for testing.""" return [ - SwiftUVOTModeEntry(uvot_mode=0x30ED), - SwiftUVOTModeEntry(uvot_mode=0x30ED), + SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name=""), + SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name=""), ] @@ -57,9 +57,9 @@ def uvot_mode_with_two_entries(uvot_mode_empty, sample_uvot_entries): def uvot_mode_with_three_entries(uvot_mode_empty): """SwiftUVOTMode instance with three entries.""" entries = [ - SwiftUVOTModeEntry(uvot_mode=0x30ED), - SwiftUVOTModeEntry(uvot_mode=0x30ED), - SwiftUVOTModeEntry(uvot_mode=0x30ED), + SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name=""), + SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name=""), + SwiftUVOTModeEntry(uvot_mode=0x30ED, filter_name=""), ] uvot_mode_empty.entries = entries return uvot_mode_empty diff --git a/tests/swift_too/swift/uvot/test_uvot.py b/tests/swift_too/swift/uvot/test_uvot.py index 35b83bd2..5d41e8a9 100644 --- a/tests/swift_too/swift/uvot/test_uvot.py +++ b/tests/swift_too/swift/uvot/test_uvot.py @@ -44,7 +44,7 @@ def test_init_uvot_mode(self, uvot_entry): assert uvot_entry.uvot_mode == 0x30ED def test_init_filter_name(self, uvot_entry): - assert uvot_entry.filter_name is None + assert uvot_entry.filter_name == "" def test_init_eventmode(self, uvot_entry): assert uvot_entry.eventmode is None
{head}{head}