class SendEmail(object):
    def __init__(self):
        self.mail_host = ReadData().get_sendEmail_info()['mail_host']
        self.mail_port = ReadData().get_sendEmail_info()['mail_port']
        self.mail_account = ReadData().get_sendEmail_info()['mail_account']
        self.mail_pwd = ReadData().get_sendEmail_info()['mail_pwd']
        self.mail_sender = ReadData().get_sendEmail_info()['mail_sender']
        self.mail_receiver = ReadData().get_sendEmail_info()['mail_receiver']
        self.test_report = self.get_filepath()
        self.logger = Logger().get_log()

    @staticmethod
    def get_filepath():
        # 获取上一级目录
        parent_path = os.path.dirname(os.path.dirname(__file__))
        # 定位到目录 interfaceTest\report
        test_report = parent_path + '/report'
        return test_report

    def get_filename(self):
        lists = os.listdir(self.test_report)  # 列出目录的下所有文件和文件夹保存到lists
        # print(lists)
        lists.sort(key=lambda fn: os.path.getmtime(self.test_report + "\\" + fn
                                                   ))  # 按时间排序
        file_name = os.path.join(self.test_report,
                                 lists[-1])  # 获取最新的文件保存到file_new
        print(file_name)
        return file_name

    def send_report(self):
        # 获取实例化对象
        message = MIMEMultipart()
        # 邮件主题
        subject = 'SaaS接口自动化测试报告'
        message['Subject'] = Header(subject, 'utf-8')
        message["from"] = self.mail_sender
        message["to"] = self.mail_receiver
        # 邮件正文有三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码
        message.attach(MIMEText('最新测试报告如下:', 'plain', 'utf-8'))
        # 构造附件
        att = MIMEText(
            open(self.get_filename(), 'rb').read(), 'base64', 'utf-8')
        att['Content-Type'] = 'application/octet-stream'
        att['Content-Disposition'] = 'attachment;filename=interface_report.html'
        message.attach(att)
        try:
            mail_service = smtplib.SMTP_SSL(self.mail_host, self.mail_port)
            mail_service.login(self.mail_account, self.mail_pwd)
            mail_service.sendmail(self.mail_sender, self.mail_receiver,
                                  message.as_string())
            mail_service.quit()
            print('邮件发送成功')
        except smtplib.SMTPException as e:
            self.logger.warning('send failed, reason is %s' % e)
            print('邮件发送失败,reason is %s' % e)
Exemple #2
0
class JobScheduler:
    def __init__(self):
        self.logger = Logger("setting_manager")
        self.jobs = []
        self.job_id_index = 0

    def inject(self, registry):
        pass

    def start(self):
        pass

    def check_for_scheduled_jobs(self, timestamp):
        while self.jobs and self.jobs[0]["time"] <= timestamp:
            try:
                job = self.jobs.pop(0)
                job["callback"](job["time"], *job["args"], **job["kwargs"])
            except Exception as e:
                self.logger.warning("Error processing scheduled job", e)

    def delayed_job(self, callback, delay, *args, **kwargs):
        return self.scheduled_job(callback,
                                  int(time.time()) + delay, *args, **kwargs)

    def scheduled_job(self, callback, scheduled_time, *args, **kwargs):
        job_id = self._get_next_job_id()
        new_job = {
            "id": job_id,
            "callback": callback,
            "args": args,
            "kwargs": kwargs,
            "time": scheduled_time
        }

        self._insert_job(new_job)
        return job_id

    def cancel_job(self, job_id):
        for index, job in enumerate(self.jobs):
            if job["id"] == job_id:
                return self.jobs.pop(index)
        return None

    def _insert_job(self, new_job):
        for index, job in enumerate(self.jobs):
            if job["time"] > new_job["time"]:
                self.jobs.insert(index, new_job)
                return
        self.jobs.append(new_job)

    def _get_next_job_id(self):
        self.job_id_index += 1
        return self.job_id_index
Exemple #3
0
class DB:
    def __init__(self):
        self.connection = None
        self.client = None
        self.logger = Logger("MongoDB")

    def connect(self, host, name):
        self.connection = MongoClient(host)
        self.client = self.connection[name]
        # Test db connection on start up, do not remove
        self.connection.admin.command('ismaster')

    def insert(self, table, query):
        try:
            return self.client[table].insert_one(query)
        except DuplicateKeyError:
            self.logger.warning('Duplicate error')
            return False

    def update(self, table, target, update):
        return self.client[table].update_one(target, {"$set": update})

    def update_all(self, table, target, update):
        return self.client[table].update_many(target, {"$set": update})

    def find(self, table, query):
        return self.client[table].find_one(query)

    def find_and_update(self, table, target, update):
        return self.client[table].find_one_and_update(target, {"$set": update})

    def find_all(self, table, query):
        return self.client[table].find(query)

    def delete(self, table, query):
        return self.client[table].delete_one(query)

    def delete_all(self, table, query):
        return self.client[table].delete_many(query)
Exemple #4
0
class Cutout:
    def __init__(self, survey, position, radius, **kwargs):
        self.survey = survey
        self.position = position
        self.ra = self.position.ra.to_value(u.deg)
        self.dec = self.position.dec.to_value(u.deg)
        self.radius = radius
        self.basesurvey = kwargs.get('basesurvey', 'racsI')
        self.psf = kwargs.get('psf')
        self.cmap = kwargs.get('cmap', 'gray_r')
        self.color = 'k' if self.cmap == 'hot' else 'black'
        self.band = kwargs.get('band', 'g')

        level = 'DEBUG' if kwargs.get('verbose') else 'INFO'
        self.logger = Logger(__name__, kwargs.get('log'),
                             streamlevel=level).logger
        self.logger.propagate = False

        self.kwargs = kwargs

        try:
            self._get_cutout()
        except Exception as e:
            msg = f"{survey} failed: {e}"
            raise FITSException(msg)
        finally:
            if 'racs' not in self.survey and 'vast' not in self.survey:
                self.plot_sources = False
                self.plot_neighbours = False

    def __repr__(self):
        return f"Cutout({self.survey}, ra={self.ra:.2f}, dec={self.dec:.2f})"

    def _get_source(self):
        try:
            pattern = re.compile(r'\S*(\d{4}[+-]\d{2}[AB])\S*')
            selpath = SURVEYS.loc[self.survey]['selavy']
            sel = glob.glob(f'{selpath}/*components.txt')
            sel = [s for s in sel if pattern.sub(r'\1', self.filepath) in s]

            if len(sel) > 1:
                df = pd.concat([pd.read_fwf(s, skiprows=[
                    1,
                ]) for s in sel])
            else:
                df = pd.read_fwf(sel[0], skiprows=[
                    1,
                ])
            coords = SkyCoord(df.ra_deg_cont, df.dec_deg_cont, unit=u.deg)
            d2d = self.position.separation(coords)
            df['d2d'] = d2d
            sources = df.iloc[np.where(d2d.deg < 0.5 * self.radius)[0]]
            sources = sources.sort_values('d2d', ascending=True)

            if any(sources.d2d < self.pos_err / 3600):
                self.source = sources.iloc[0]
                self.neighbours = sources.iloc[1:]
                self.plot_sources = True
            else:
                self.source = None
                self.neighbours = sources
                self.plot_sources = False

            self.plot_neighbours = self.kwargs.get('neighbours', True)

            self.logger.debug(f'Source: \n {self.source}')
            if len(self.neighbours) > 0:
                nn = self.neighbours.iloc[0]
                self.logger.debug(
                    f'Nearest neighbour coords: \n {nn.ra_deg_cont, nn.dec_deg_cont}'
                )
                self.logger.debug(
                    f'Nearest 5 Neighbours \n {self.neighbours.head()}')

        except IndexError:
            self.plot_sources = False
            self.plot_neighbours = False
            self.logger.warning('No nearby sources found.')

    def _get_cutout(self):

        if not os.path.exists(cutout_cache + self.survey):
            msg = f"{cutout_cache}{self.survey} cutout directory does not exist, creating."
            self.logger.info(msg)
            os.makedirs(cutout_cache + self.survey)

        if os.path.isfile(self.survey):
            self._get_local_cutout()
        elif 'racs' in self.survey or 'vast' in self.survey or 'vlass' in self.survey:
            self._get_local_cutout()
        elif self.survey == 'skymapper':
            self._get_skymapper_cutout()
        elif self.survey == 'panstarrs':
            self._get_panstarrs_cutout()
        elif self.survey == 'decam':
            self._get_decam_cutout()
        else:
            self._get_skyview_cutout()

    def _get_local_cutout(self):
        """Fetch cutout data via local FITS images (e.g. RACS / VLASS)."""

        fields = self._find_image()
        assert len(
            fields
        ) > 0, f"No fields located at {self.position.ra:.2f}, {self.position.dec:.2f}"
        closest = fields[fields.dist_field_centre ==
                         fields.dist_field_centre.min()].iloc[0]
        image_path = SURVEYS.loc[self.survey]['images']

        if self.survey == 'vlass':
            filepath = f'{closest.epoch}/{closest.tile}/{closest.image}/{closest.filename}'
            image_path = vlass_path
        elif 'racs' in self.survey:
            pol = self.survey[-1]
            if on_system == 'ada':
                filepath = f'RACS_test4_1.05_{closest.field}.fits'
            else:
                filepath = f'RACS_{closest.field}.EPOCH00.{pol}.fits'
        elif 'vast' in self.survey:
            pattern = re.compile(r'vastp(\dx*)([IV])')
            epoch = pattern.sub(r'\1', self.survey)
            pol = pattern.sub(r'\2', self.survey)
            filepath = f'VAST_{closest.field}.EPOCH0{epoch}.{pol}.fits'
        else:
            filepath = f'*{closest.field}*0.restored.fits'

        try:
            self.filepath = glob.glob(image_path + filepath)[0]
        except IndexError:
            raise FITSException(
                f'Could not match {self.survey} image filepath: \n{image_path + filepath}'
            )

        with fits.open(self.filepath) as hdul:
            self.header, data = hdul[0].header, hdul[0].data
            wcs = WCS(self.header, naxis=2)
            self.mjd = Time(self.header['DATE']).mjd

            try:
                cutout = Cutout2D(data[0, 0, :, :],
                                  self.position,
                                  self.radius * u.deg,
                                  wcs=wcs)
            except IndexError:
                cutout = Cutout2D(data,
                                  self.position,
                                  self.radius * u.deg,
                                  wcs=wcs)
            self.data = cutout.data * 1000
            self.wcs = cutout.wcs

        if 'racs' in self.survey or 'vast' in self.survey:
            self.pos_err = SURVEYS.loc[self.basesurvey].pos_err
            self._get_source()
        else:
            # Probably using vlass, yet to include aegean catalogs
            self.plot_sources = False
            self.plot_neighbours = False

    def _get_panstarrs_cutout(self):
        """Fetch cutout data via PanSTARRS DR2 API."""
        path = cutout_cache + 'panstarrs/{}_{}arcmin_{}_{}.fits'.format(
            self.band,
            '{:.3f}',
            '{:.3f}',
            '{:.3f}',
        )
        imgpath = path.format(self.radius * 60, self.ra, self.dec)
        if not os.path.exists(imgpath):
            pixelrad = int(self.radius * 120 * 120)
            service = "https://ps1images.stsci.edu/cgi-bin/ps1filenames.py"
            url = (
                f"{service}?ra={self.ra}&dec={self.dec}&size={pixelrad}&format=fits"
                f"&filters=grizy")
            table = Table.read(url, format='ascii')

            msg = f"No PS1 image at {self.position.ra:.2f}, {self.position.dec:.2f}"
            assert len(table) > 0, msg

            urlbase = (
                f"https://ps1images.stsci.edu/cgi-bin/fitscut.cgi?"
                f"ra={self.ra}&dec={self.dec}&size={pixelrad}&format=fits&red="
            )

            flist = ["yzirg".find(x) for x in table['filter']]
            table = table[np.argsort(flist)]

            for row in table:
                self.mjd = row['mjd']
                filt = row['filter']
                url = urlbase + row['filename']
                path = cutout_cache + 'panstarrs/{}_{}arcmin_{}_{}.fits'.format(
                    filt,
                    '{:.3f}',
                    '{:.3f}',
                    '{:.3f}',
                )
                path = path.format(self.radius * 60, self.ra, self.dec)

                img = requests.get(url, allow_redirects=True)

                if not os.path.exists(path):
                    with open(path, 'wb') as f:
                        f.write(img.content)

        with fits.open(imgpath) as hdul:
            self.header, self.data = hdul[0].header, hdul[0].data
            self.wcs = WCS(self.header, naxis=2)

    def _get_skymapper_cutout(self):
        """Fetch cutout data via Skymapper API."""

        path = cutout_cache + self.survey + '/dr2_jd{:.3f}_{:.3f}arcmin_{:.3f}_{:.3f}'
        linka = 'http://api.skymapper.nci.org.au/aus/siap/dr2/'
        linkb = 'query?POS={:.5f},{:.5f}&SIZE={:.3f}&BAND=all&RESPONSEFORMAT=CSV'
        linkc = '&VERB=3&INTERSECT=covers'
        sm_query = linka + linkb + linkc

        link = linka + 'get_image?IMAGE={}&SIZE={}&POS={},{}&FORMAT=fits'

        table = requests.get(sm_query.format(self.ra, self.dec, self.radius))
        df = pd.read_csv(io.StringIO(table.text))
        assert len(
            df
        ) > 0, f'No Skymapper image at {self.position.ra:.2f}, {self.position.dec:.2f}'

        df = df[df.band == 'z']
        self.mjd = df.iloc[0]['mjd_obs']
        link = df.iloc[0].get_image

        img = requests.get(link)

        path = path.format(self.mjd, self.radius * 60, self.ra, self.dec)

        if not os.path.exists(path):
            with open(path, 'wb') as f:
                f.write(img.content)

        with fits.open(path) as hdul:
            self.header, self.data = hdul[0].header, hdul[0].data
            self.wcs = WCS(self.header, naxis=2)

    def _get_decam_cutout(self):
        """Fetch cutout data via DECam LS API."""
        size = int(self.radius * 3600 / 0.262)
        if size > 512:
            size = 512
            maxradius = size * 0.262 / 3600
            self.logger.warning(
                f"Using maximum DECam LS cutout radius of {maxradius:.3f} deg")

        link = f"http://legacysurvey.org/viewer/fits-cutout?ra={self.ra}&dec={self.dec}"
        link += f"&size={size}&layer=dr8&pixscale=0.262&bands={self.band}"
        img = requests.get(link)

        path = cutout_cache + self.survey + '/dr8_jd{:.3f}_{:.3f}arcmin_{:.3f}_{:.3f}_{}band'
        path = path.format(self.mjd, self.radius * 60, self.ra, self.dec,
                           self.band)
        if not os.path.exists(path):
            with open(path, 'wb') as f:
                f.write(img.content)

        with fits.open(path) as hdul:
            self.header, self.data = hdul[0].header, hdul[0].data
            self.wcs = WCS(self.header, naxis=2)

        msg = f"No DECam LS image at {self.position.ra:.2f}, {self.position.dec:.2f}"
        assert self.data is not None, msg

    def _get_skyview_cutout(self):
        """Fetch cutout data via SkyView API."""

        sv = SkyView()
        path = cutout_cache + self.survey + '/{:.3f}arcmin_{:.3f}_{:.3f}.fits'
        path = path.format(self.radius * 60, self.ra, self.dec)
        progress = self.kwargs.get('progress', False)

        if not os.path.exists(path):
            skyview_key = SURVEYS.loc[self.survey].sv
            try:
                hdul = sv.get_images(position=self.position,
                                     survey=[skyview_key],
                                     radius=self.radius * u.deg,
                                     show_progress=progress)[0][0]
            except IndexError:
                raise FITSException('Skyview image list returned empty.')
            except ValueError:
                raise FITSException(
                    f'{self.survey} is not a valid SkyView survey.')
            except HTTPError:
                raise FITSException('No response from Skyview server.')

            with open(path, 'wb') as f:
                hdul.writeto(f)

        with fits.open(path) as hdul:
            self.header, self.data = hdul[0].header, hdul[0].data
            self.wcs = WCS(self.header, naxis=2)

            try:
                self.mjd = Time(self.header['DATE']).mjd
            except KeyError:
                try:
                    self.epoch = self.kwargs.get('epoch')
                    msg = "Could not detect epoch, PM correction disabled."
                    assert self.epoch is not None, msg
                    self.mjd = self.epoch if self.epoch > 3000 else Time(
                        self.epoch, format='decimalyear').mjd
                except AssertionError as e:
                    if self.kwargs.get('pm'):
                        self.logger.warning(e)
                    self.mjd = None

            self.data *= 1000

    def _find_image(self):
        """Return DataFrame of survey fields containing coord."""

        survey = self.survey.replace('I', '').replace('V', '')
        try:
            image_df = pd.read_csv(aux_path + f'{survey}_fields.csv')
        except FileNotFoundError:
            raise FITSException(f"Missing field metadata csv for {survey}.")

        beam_centre = SkyCoord(ra=image_df['cr_ra_pix'],
                               dec=image_df['cr_dec_pix'],
                               unit=u.deg)
        image_df['dist_field_centre'] = beam_centre.separation(
            self.position).deg

        pbeamsize = 1 * u.degree if self.survey == 'vlass' else 5 * u.degree
        return image_df[image_df.dist_field_centre < pbeamsize].reset_index(
            drop=True)

    def _obfuscate(self):
        """Remove all coordinates and identifying information."""
        lon = self.ax.coords[0]
        lat = self.ax.coords[1]
        lon.set_ticks_visible(False)
        lon.set_ticklabel_visible(False)
        lat.set_ticks_visible(False)
        lat.set_ticklabel_visible(False)
        lon.set_axislabel('')
        lat.set_axislabel('')

    def _plot_setup(self, fig, ax):
        """Create figure and determine normalisation parameters."""
        if ax:
            self.fig = fig
            self.ax = ax
        else:
            self.fig = plt.figure()
            self.ax = self.fig.add_subplot(111, projection=self.wcs)

        if self.kwargs.get('grid', True):
            self.ax.coords.grid(color='white', alpha=0.5)
        self.ax.set_xlabel('RA (J2000)')
        self.ax.set_ylabel('Dec (J2000)')

        if self.kwargs.get('title', True):
            self.ax.set_title(SURVEYS.loc[self.survey]['name'],
                              fontdict={
                                  'fontsize': 20,
                                  'fontweight': 10
                              })
        if self.kwargs.get('obfuscate', False):
            self._obfuscate()

        if self.kwargs.get('annotation'):
            color = 'white' if self.cmap == 'hot' else 'k'
            self.ax.text(0.05,
                         0.85,
                         self.kwargs.get('annotation'),
                         color=color,
                         weight='bold',
                         transform=self.ax.transAxes)

    def _add_cornermarker(self, ra, dec, span, offset):
        color = 'white' if self.cmap != 'gray_r' else 'r'
        cosdec = np.cos(np.radians(dec))
        raline = Line2D(
            xdata=[ra + offset / cosdec, ra + span / cosdec],
            ydata=[dec, dec],
            color=color,
            linewidth=2,
            path_effects=[pe.Stroke(linewidth=3, foreground='k'),
                          pe.Normal()],
            transform=self.ax.get_transform('world'))
        decline = Line2D(
            xdata=[ra, ra],
            ydata=[dec + offset, dec + span],
            color=color,
            linewidth=2,
            path_effects=[pe.Stroke(linewidth=3, foreground='k'),
                          pe.Normal()],
            transform=self.ax.get_transform('world'))
        self.ax.add_artist(raline)
        self.ax.add_artist(decline)

    def plot(self, fig=None, ax=None):
        """Plot survey data and position overlay."""
        self.sign = self.kwargs.get('sign', 1)
        self._plot_setup(fig, ax)
        self.data *= self.sign
        absmax = max(self.data.max(), self.data.min(), key=abs)
        self.logger.debug(f"Max flux in cutout: {absmax:.2f} mJy.")
        rms = np.sqrt(np.mean(np.square(self.data)))
        self.logger.debug(f"RMS flux in cutout: {rms:.2f} mJy.")

        assert (sum((~np.isnan(self.data).flatten())) > 0 and sum(self.data.flatten()) != 0), \
            f"No data in {self.survey}"

        if self.kwargs.get('maxnorm'):
            self.norm = ImageNormalize(self.data,
                                       interval=ZScaleInterval(),
                                       vmax=self.data.max(),
                                       clip=True)
        else:
            self.norm = ImageNormalize(self.data,
                                       interval=ZScaleInterval(contrast=0.2),
                                       clip=True)

        self.im = self.ax.imshow(self.data, cmap=self.cmap, norm=self.norm)

        if self.kwargs.get('bar', True):
            try:
                self.fig.colorbar(self.im,
                                  label=r'Flux Density (mJy beam$^{-1}$)',
                                  ax=self.ax)
            except UnboundLocalError:
                self.logger.error(
                    "Colorbar failed. Upgrade to recent version of astropy ")

        if self.psf:
            try:
                self.bmaj = self.header['BMAJ'] * 3600
                self.bmin = self.header['BMIN'] * 3600
                self.bpa = self.header['BPA']
            except KeyError:
                self.logger.warning('Header did not contain PSF information.')
                try:
                    self.bmaj = self.psf[0]
                    self.bmin = self.psf[1]
                    self.bpa = 0
                    self.logger.warning(
                        'Using supplied BMAJ/BMin. Assuming BPA=0')
                except ValueError:
                    self.logger.error('No PSF information supplied.')

            rhs = self.wcs.wcs_pix2world(self.data.shape[0], 0, 1)
            lhs = self.wcs.wcs_pix2world(0, 0, 1)

            # Offset PSF marker by the major axis in pixel coordinates
            try:
                cdelt = self.header['CDELT1']
            except KeyError:
                cdelt = self.header['CD1_1']
            beamavg = (self.bmaj + self.bmin) / 2
            beamsize_pix = beamavg / abs(cdelt) / 3600
            ax_len_pix = abs(lhs[0] - rhs[0]) / abs(cdelt) / 3600
            beam = self.wcs.wcs_pix2world(beamsize_pix, beamsize_pix, 1)
            self.beamx = beam[0]
            self.beamy = beam[1]

            self.beam = Ellipse((self.beamx, self.beamy),
                                self.bmin / 3600,
                                self.bmaj / 3600,
                                -self.bpa,
                                facecolor='white',
                                edgecolor='k',
                                transform=self.ax.get_transform('world'),
                                zorder=10)
            self.ax.add_patch(self.beam)

            # Optionally plot square around the PSF
            # Set size to greater of 110% PSF size or 10% ax length
            if self.kwargs.get('beamsquare', False):
                boxsize = max(beamsize_pix * 1.15, ax_len_pix * .1)
                offset = beamsize_pix - boxsize / 2
                self.square = Rectangle(
                    (offset, offset),
                    boxsize,
                    boxsize,
                    facecolor='white',
                    edgecolor='k',
                    # transform=self.ax.get_transform('world'),
                    zorder=5)
                self.ax.add_patch(self.square)

        if self.plot_sources:
            if self.kwargs.get('corner'):
                self._add_cornermarker(
                    self.source.ra_deg_cont, self.source.dec_deg_cont,
                    self.kwargs.get('corner_span', 20 / 3600),
                    self.kwargs.get('corner_offset', 10 / 3600))
            else:
                self.sourcepos = Ellipse(
                    (self.source.ra_deg_cont, self.source.dec_deg_cont),
                    self.source.min_axis / 3600,
                    self.source.maj_axis / 3600,
                    -self.source.pos_ang,
                    facecolor='none',
                    edgecolor='r',
                    ls=':',
                    lw=2,
                    transform=self.ax.get_transform('world'))
                self.ax.add_patch(self.sourcepos)

        else:
            if self.kwargs.get('corner'):
                self._add_cornermarker(
                    self.ra, self.dec, self.kwargs.get('corner_span',
                                                       20 / 3600),
                    self.kwargs.get('corner_offset', 10 / 3600))
            else:
                self.bmin = 15
                self.bmaj = 15
                self.bpa = 0
                overlay = SphericalCircle(
                    (self.ra * u.deg, self.dec * u.deg),
                    self.bmaj * u.arcsec,
                    edgecolor='r',
                    linewidth=2,
                    facecolor='none',
                    transform=self.ax.get_transform('world'))
                self.ax.add_artist(overlay)

        if self.plot_neighbours:
            for idx, neighbour in self.neighbours.iterrows():
                n = Ellipse((neighbour.ra_deg_cont, neighbour.dec_deg_cont),
                            neighbour.min_axis / 3600,
                            neighbour.maj_axis / 3600,
                            -neighbour.pos_ang,
                            facecolor='none',
                            edgecolor='c',
                            ls=':',
                            lw=2,
                            transform=self.ax.get_transform('world'))
                self.ax.add_patch(n)

    def save(self, path, fmt='png'):
        """Save figure with tight bounding box."""
        self.fig.savefig(path, format=fmt, bbox_inches='tight')

    def savefits(self, path):
        """Export FITS cutout to path"""
        header = self.wcs.to_header()
        hdu = fits.PrimaryHDU(data=self.data, header=header)
        hdu.writeto(path)
Exemple #5
0
class CommandManager:
    PRIVATE_CHANNEL = "priv"
    ORG_CHANNEL = "org"
    PRIVATE_MESSAGE = "msg"

    def __init__(self):
        self.handlers = collections.defaultdict(list)
        self.logger = Logger("command_manager")
        self.channels = {}
        self.ignore_regexes = [
            re.compile(" is AFK \(Away from keyboard\) since ", re.IGNORECASE),
            re.compile("I am away from my keyboard right now", re.IGNORECASE),
            re.compile("Unknown command or access denied!", re.IGNORECASE),
            re.compile("I am responding", re.IGNORECASE),
            re.compile("I only listen", re.IGNORECASE),
            re.compile("Error!", re.IGNORECASE),
            re.compile("Unknown command input", re.IGNORECASE),
            re.compile("You have been auto invited", re.IGNORECASE),
        ]

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util: Util = registry.get_instance("util")
        self.access_manager: AccessManager = registry.get_instance("access_manager")
        self.bot: Mangopie = registry.get_instance("mangopie")
        self.character_manager: CharacterManager = registry.get_instance("character_manager")
        self.setting_manager: SettingManager = registry.get_instance("setting_manager")
        self.command_alias_manager = registry.get_instance("command_alias_manager")

    def pre_start(self):
        self.bot.add_packet_handler(server_packets.PrivateMessage.id, self.handle_private_message)
        self.bot.add_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message)
        self.register_command_channel("Private Message", self.PRIVATE_MESSAGE)
        self.register_command_channel("Org Channel", self.ORG_CHANNEL)
        self.register_command_channel("Private Channel", self.PRIVATE_CHANNEL)

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "command"):
                    cmd_name, params, access_level, description, help_file, sub_command = getattr(method, "command")
                    handler = getattr(inst, name)
                    module = self.util.get_module_name(handler)
                    help_text = self.get_help_file(module, help_file)
                    self.register(handler, cmd_name, params, access_level, description, module, help_text, sub_command)

    def register(self, handler, command, params, access_level, description, module, help_text=None, sub_command=None):
        command = command.lower()
        if sub_command:
            sub_command = sub_command.lower()
        else:
            sub_command = ""
        access_level = access_level.lower()
        module = module.lower()
        command_key = self.get_command_key(command, sub_command)

        if help_text is None:
            help_text = self.generate_help(command, description, params)

        if not self.access_manager.get_access_level_by_label(access_level):
            self.logger.error("Could not add command '%s': could not find access level '%s'" % (command, access_level))
            return

        for channel, label in self.channels.items():
            row = self.db.find('command_config', {"command": command, "sub_command": sub_command, 'channel': channel})

            if row is None:
                # add new command commands
                self.db.insert('command_config',
                               {'command': command, 'sub_command': sub_command, 'access_level': access_level,
                                'channel': channel, 'module': module, 'verified': 1, 'enabled': 1})

            elif 'verified' in row and row['verified']:
                if row['module'] != module:
                    self.logger.warning("module different for different forms of command '%s' and sub_command '%s'" % (
                        command, sub_command))
            else:
                # mark command as verified
                self.db.update('command_config',
                               {'module': module, 'command': command, 'channel': channel, 'access_level': access_level,
                                'sub_command': sub_command},
                               {'verified': 1})

        # save reference to command handler
        r = re.compile(self.get_regex_from_params(params), re.IGNORECASE)
        self.handlers[command_key].append(
            {"regex": r, "callback": handler, "help": help_text, "description": description, "params": params})

    def handle_private_message(self, packet: server_packets.PrivateMessage):
        # since the command symbol is not required for private messages,
        # the command_str must have length of at least 1 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 1 or not self.bot.is_ready():
            return

        for regex in self.ignore_regexes:
            if regex.search(packet.message):
                return

        if packet.message[:1] == '!':
            command_str = packet.message[1:]
        else:
            command_str = packet.message

        self.process_command(
            command_str,
            "msg",
            packet.char_id,
            lambda msg: self.bot.send_private_message(packet.char_id, msg))

    def handle_private_channel_message(self, packet: server_packets.PrivateChannelMessage):
        # since the command symbol is required in the private channel,
        # the command_str must have length of at least 2 in order to be valid,
        # otherwise it is ignored
        if len(packet.message) < 2:
            return

        symbol = packet.message[:1]
        command_str = packet.message[1:]
        if symbol == self.setting_manager.get(
                "symbol").get_value() and packet.private_channel_id == self.bot.char_id:
            self.process_command(
                command_str,
                "priv",
                packet.char_id,
                lambda msg: self.bot.send_private_channel_message(msg))

    def process_command(self, message: str, channel: str, char_id, reply):
        try:
            command_str, command_args = self.get_command_parts(message)

            # check for command alias
            command_alias = self.command_alias_manager.check_for_alias(command_str)

            if command_alias:
                command_str, command_args = self.get_command_parts(
                    command_alias + " " + command_args if command_args else command_alias)

            cmd_configs = self.get_command_configs(command_str, channel, 1)
            cmd_configs = list(cmd_configs)
            if cmd_configs:
                # given a list of cmd_configs that are enabled, see if one has regex that matches incoming command_str
                cmd_config, matches, handler = self.get_matches(cmd_configs, command_args)
                if matches:
                    if self.access_manager.check_access(char_id, cmd_config['access_level']):
                        sender = MapObject(
                            {"name": self.character_manager.resolve_char_to_name(char_id), "char_id": char_id})
                        handler["callback"](channel, sender, reply, self.process_matches(matches, handler["params"]))
                    else:
                        self.access_denied_response(char_id, cmd_config, reply)
                else:
                    # handlers were found, but no handler regex matched
                    help_text = self.get_help_text(char_id, command_str, channel)
                    if help_text:
                        reply(self.format_help_text(command_str, help_text))
                    else:
                        reply("Error! Invalid syntax.")
            else:
                reply("Error! Unknown command.")
        except Exception as e:
            self.logger.error("error processing command: %s" % message, e)
            reply("There was an error processing your request.")

    def get_help_text(self, char, command_str, channel):
        data = self.db.find_all('command_config', {'command': command_str, 'channel': channel, 'enabled': 1})
        # filter out commands that character does not have access level for
        data = filter(lambda row: self.access_manager.check_access(char, row['access_level']), data)

        def read_help_text(row):
            command_key = self.get_command_key(row['command'], row['sub_command'])
            return filter(lambda x: x is not None, map(lambda handler: handler["help"], self.handlers[command_key]))

        content = "\n\n".join(flatmap(read_help_text, data))
        return content if content else None

    def get_help_file(self, module, help_file):
        if help_file:
            try:
                help_file = "./" + module.replace(".", "/") + "/" + help_file
                with open(help_file) as f:
                    return f.read().strip()
            except FileNotFoundError as e:
                self.logger.error("Error reading help file", e)
        return None

    def format_help_text(self, topic, help_text):
        return ChatBlob("Help (" + topic + ")", help_text)

    def generate_help(self, command, description, params):
        return description + ":\n" + "<tab><symbol>" + command + " " + " ".join(map(lambda x: x.get_name(), params))

    def access_denied_response(self, char_id, cmd_config, reply):
        reply("Error! Access denied.")

    def get_command_key(self, command, sub_command):
        if sub_command:
            return command + ":" + sub_command
        else:
            return command

    def get_command_key_parts(self, command_str):
        parts = command_str.split(":", 1)
        if len(parts) == 2:
            return parts[0], parts[1]
        else:
            return parts[0], ""

    def get_matches(self, cmd_configs, command_args):
        if command_args:
            command_args = " " + command_args

        for row in cmd_configs:
            command_key = self.get_command_key(row['command'], row['sub_command'])
            handlers = self.handlers[command_key]
            for handler in handlers:
                matches = handler["regex"].match(command_args)
                if matches:
                    return row, matches, handler
        return None, None, None

    def process_matches(self, matches, params):
        groups = list(matches.groups())

        processed = []
        for param in params:
            processed.append(param.process_matches(groups))
        return processed

    def format_matches(self, command_args, matches):
        # convert matches to list
        m = list(matches.groups())
        m.insert(0, command_args)

        # strip leading spaces for each group, if they group exists
        return list(map(lambda x: x[1:] if x else x, m))

    def get_command_parts(self, message):
        parts = message.split(" ", 1)
        if len(parts) == 2:
            return parts[0].lower(), parts[1]
        else:
            return parts[0].lower(), ""

    def get_command_configs(self, command, channel=None, enabled=1, sub_command=None):
        query = {"command": command}
        if channel:
            query['channel'] = channel
        if enabled:
            query['enabled'] = enabled
        if sub_command:
            query['sub_command'] = sub_command
        return self.db.find_all('command_config', query)

    def get_handlers(self, command_key):
        return self.handlers.get(command_key, None)

    def register_command_channel(self, label, value):
        if value in self.channels:
            self.logger.error("Could not register command channel '%s': command channel already registered" % value)
            return

        self.logger.debug("Registering command channel '%s'" % value)
        self.channels[value] = label

    def is_command_channel(self, channel):
        return channel in self.channels

    def get_regex_from_params(self, params):
        # params must be wrapped with line-beginning and line-ending anchors in order to match
        # when no params are specified (eg. "^$")
        return "^" + "".join(map(lambda x: x.get_regex(), params)) + "$"
Exemple #6
0
class EventManager:
    def __init__(self):
        self.handlers = {}
        self.logger = Logger("event_manager")
        self.event_types = []
        self.last_timer_event = 0

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")

    def pre_start(self):
        self.register_event_type("timer")

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "event"):
                    event_type, description = getattr(method, "event")
                    handler = getattr(inst, name)
                    module = self.util.get_module_name(handler)
                    self.register(handler, event_type, description, module)

    def register_event_type(self, event_type):
        event_type = event_type.lower()

        if event_type in self.event_types:
            self.logger.error(
                "Could not register event type '%s': event type already registered"
                % event_type)
            return

        self.logger.debug("Registering event type '%s'" % event_type)
        self.event_types.append(event_type)

    def is_event_type(self, event_base_type):
        return event_base_type in self.event_types

    def register(self, handler, event_type, description, module):
        event_base_type, event_sub_type = self.get_event_type_parts(event_type)
        module = module.lower()
        handler_name = self.util.get_handler_name(handler).lower()

        if event_base_type not in self.event_types:
            self.logger.error(
                "Could not register handler '%s' for event type '%s': event type does not exist"
                % (handler_name, event_type))
            return

        if not description:
            self.logger.warning(
                "No description for event_type '%s' and handler '%s'" %
                (event_type, handler_name))

        row = self.db.find('event_config', {
            'event_type': event_base_type,
            'handler': handler_name
        })

        if row is None:
            # add new event commands
            self.db.insert(
                'event_config', {
                    'event_type': event_base_type,
                    'event_sub_type': event_sub_type,
                    'handler': handler_name,
                    'description': description,
                    'module': module,
                    'verified': 1,
                    'enabled': 1,
                    'next_run': int(time.time())
                })

        else:
            # mark command as verified
            self.db.update('event_config', {
                'event_type': event_base_type,
                'handler': handler_name
            }, {
                'verified': 1,
                'module': module,
                'description': description,
                'event_sub_type': event_sub_type,
            })

        # load command handler
        self.handlers[handler_name] = handler

    def fire_event(self, event_type, event_data=None):
        event_base_type, event_sub_type = self.get_event_type_parts(event_type)

        if event_base_type not in self.event_types:
            self.logger.error(
                "Could not fire event type '%s': event type does not exist" %
                event_type)
            return

        data = self.db.find_all(
            'event_config', {
                'event_type': event_base_type,
                'event_sub_type': event_sub_type,
                'enabled': 1
            })
        for row in data:
            handler = self.handlers.get(row['handler'], None)
            if not handler:
                self.logger.error(
                    "Could not find handler callback for event type '%s' and handler '%s'"
                    % (event_type, row.handler))
                return

            try:
                handler(event_type, event_data)
            except Exception as e:
                self.logger.error("error processing event '%s'" % event_type,
                                  e)

    def get_event_type_parts(self, event_type):
        parts = event_type.lower().split(":", 1)
        if len(parts) == 2:
            return parts[0], parts[1]
        else:
            return parts[0], ""

    def get_event_type_key(self, event_base_type, event_sub_type):
        return event_base_type + ":" + event_sub_type

    def check_for_timer_events(self, timestamp):
        data = self.db.find('event_config', {
            'enabled': 1,
            'event_type': 'timer',
            'next_run': {
                '$gte': timestamp
            }
        })

        if data is not None:
            for row in data:
                event_type_key = self.get_event_type_key(
                    row['event_type'], row['event_sub_type'])

                # timer event run times should be consistent, so we base the next run time off the last run time,
                # instead of the current timestamp
                next_run = row['next_run'] + int(row['event_sub_type'])

                # prevents timer events from getting too far behind, or having a large "catch-up" after
                # the bot has been offline for a time
                if next_run < timestamp:
                    next_run = timestamp + int(row['event_sub_type'])

                self.db.update('event_config', {
                    'event_type': 'timer',
                    'handler': row['handler']
                }, {'next_run': next_run})

                self.fire_event(event_type_key)
Exemple #7
0
class BuddyManager:
    BUDDY_LOGON_EVENT = "buddy_logon"
    BUDDY_LOGOFF_EVENT = "buddy_logoff"

    def __init__(self):
        self.buddy_list = {}
        self.buddy_list_size = 1000
        self.logger = Logger("Mangopie")

    def inject(self, registry):
        self.character_manager: CharacterManager = registry.get_instance(
            "character_manager")
        self.bot = registry.get_instance("mangopie")
        self.event_manager = registry.get_instance("event_manager")

    def pre_start(self):
        self.bot.add_packet_handler(server_packets.BuddyAdded.id,
                                    self.handle_add)
        self.bot.add_packet_handler(server_packets.BuddyRemoved.id,
                                    self.handle_remove)
        self.bot.add_packet_handler(server_packets.LoginOK.id,
                                    self.handle_login_ok)
        self.event_manager.register_event_type(self.BUDDY_LOGON_EVENT)
        self.event_manager.register_event_type(self.BUDDY_LOGOFF_EVENT)

    def handle_add(self, packet):
        buddy = self.buddy_list.get(packet.char_id, {"types": []})
        buddy["online"] = packet.online
        self.buddy_list[packet.char_id] = buddy
        if packet.online == 1:
            self.event_manager.fire_event(self.BUDDY_LOGON_EVENT, packet)
        else:
            self.event_manager.fire_event(self.BUDDY_LOGOFF_EVENT, packet)

    def handle_remove(self, packet):
        if packet.char_id in self.buddy_list:
            if len(self.buddy_list[packet.char_id]["types"]) > 0:
                self.logger.warning(
                    "Removing buddy %d that still has types %s" %
                    (packet.char_id, self.buddy_list[packet.char_id]["types"]))
            del self.buddy_list[packet.char_id]

    def handle_login_ok(self, packet):
        self.buddy_list_size += 1000

    def add_buddy(self, char, _type):
        char_id = self.character_manager.resolve_char_to_id(char)
        if char_id and char_id != self.bot.char_id:
            if char_id not in self.buddy_list:
                self.bot.send_packet(client_packets.BuddyAdd(char_id, "\1"))
                self.buddy_list[char_id] = {"online": None, "types": [_type]}
            else:
                self.buddy_list[char_id]["types"].append(_type)

            return True
        else:
            return False

    def remove_buddy(self, char, _type):
        char_id = self.character_manager.resolve_char_to_id(char)
        if char_id:
            if char_id not in self.buddy_list:
                return False
            else:
                if _type in self.buddy_list[char_id]["types"]:
                    self.buddy_list[char_id]["types"].remove(_type)
                if len(self.buddy_list[char_id]["types"]) == 0:
                    self.bot.send_packet(client_packets.BuddyRemove(char_id))
                return True
        else:
            return False

    def get_buddy(self, char):
        char_id = self.character_manager.resolve_char_to_id(char)
        return self.buddy_list.get(char_id, None)

    def is_online(self, char):
        char_id = self.character_manager.resolve_char_to_id(char)
        buddy = self.get_buddy(char_id)
        if buddy is None:
            return None
        else:
            return buddy.get("online", None)
Exemple #8
0
class PorkManager:
    def __init__(self):
        self.logger = Logger("pork_manager")

    def inject(self, registry):
        self.bot = registry.get_instance("mangopie")
        self.db = registry.get_instance("db")
        self.character_manager = registry.get_instance("character_manager")

    def pre_start(self):
        self.bot.add_packet_handler(server_packets.CharacterLookup.id,
                                    self.update)
        self.bot.add_packet_handler(server_packets.CharacterName.id,
                                    self.update)

    def start(self):
        pass

    def get_character_info(self, char):
        # if we have entry in database and it is less than a day old, use that
        char_info = self.get_from_database(char)
        if char_info and char_info['source'] == "chat_server":
            char_info = None
        elif char_info and char_info['last_updated'] > (int(time.time()) -
                                                        86400):
            char_info['source'] += " (cache)"
            return char_info

        char_name = self.character_manager.resolve_char_to_name(char)
        url = "http://people.anarchy-online.com/character/bio/d/%d/name/%s/bio.xml?data_type=json" % (
            self.bot.dimension, char_name)

        r = requests.get(url)
        try:
            json = r.json()
        except ValueError as e:
            self.logger.warning("Error marshalling value as json: %s" % r.text,
                                e)
            json = None
        if json:
            char_info_json = json[0]
            org_info_json = json[1] if json[1] else {}

            char_info = MapObject({
                "name":
                char_info_json["NAME"],
                "char_id":
                char_info_json["CHAR_INSTANCE"],
                "first_name":
                char_info_json["FIRSTNAME"],
                "last_name":
                char_info_json["LASTNAME"],
                "level":
                char_info_json["LEVELX"],
                "breed":
                char_info_json["BREED"],
                "dimension":
                char_info_json["CHAR_DIMENSION"],
                "gender":
                char_info_json["SEX"],
                "faction":
                char_info_json["SIDE"],
                "profession":
                char_info_json["PROF"],
                "profession_title":
                char_info_json["PROFNAME"],
                "ai_rank":
                char_info_json["RANK_name"],
                "ai_level":
                char_info_json["ALIENLEVEL"],
                "pvp_rating":
                char_info_json["PVPRATING"],
                "pvp_title":
                none_to_empty_string(char_info_json["PVPTITLE"]),
                "head_id":
                char_info_json["HEADID"],
                "org_id":
                org_info_json.get("ORG_INSTANCE", 0),
                "org_name":
                org_info_json.get("NAME", ""),
                "org_rank_name":
                org_info_json.get("RANK_TITLE", ""),
                "org_rank_id":
                org_info_json.get("RANK", 0),
                "source":
                "people.anarchy-online.com"
            })

            self.save_character_info(char_info)
            return char_info
        else:
            # return cached info from database, even tho it's old
            return char_info

    def get_character_history(self, char):
        pass

    def get_org_info(self, org_id):
        pass

    def load_character_info(self, char_id):
        char_info = self.get_character_info(char_id)
        if not char_info:
            char_info = MapObject({
                "name": "Unknown:" + str(char_id),
                "char_id": char_id,
                "first_name": "",
                "last_name": "",
                "level": 0,
                "breed": "",
                "dimension": 5,
                "gender": "",
                "faction": "",
                "profession": "",
                "profession_title": "",
                "ai_rank": "",
                "ai_level": 0,
                "pvp_rating": 0,
                "pvp_title": "",
                "head_id": 0,
                "org_id": 0,
                "org_name": "",
                "org_rank_name": "",
                "org_rank_id": 6,
                "source": "stub"
            })
            self.save_character_info(char_info)

    def save_character_info(self, char_info):
        # Remove old data
        self.db.delete('player', {'char_id': char_info.char_id})

        self.db.insert(
            'player', {
                'char_id': char_info.char_id,
                'name': char_info.name,
                'first_name': char_info.first_name,
                'last_name': char_info.last_name,
                'level': char_info.level,
                'breed': char_info.breed,
                'gender': char_info.gender,
                'faction': char_info.faction,
                'profession': char_info.profession,
                'profession_title ': char_info.profession_title,
                'ai_rank': char_info.ai_rank,
                'ai_level': char_info.ai_level,
                'org_id': char_info.org_id,
                'org_name': char_info.org_name,
                'org_rank_name': char_info.org_rank_name,
                'org_rank_id': char_info.org_rank_id,
                'dimension': char_info.dimension,
                'head_id': char_info.head_id,
                'pvp_rating': char_info.pvp_rating,
                'pvp_title': char_info.pvp_title,
                'source': char_info.source,
                'last_updated': int(time.time())
            })

    def get_from_database(self, char):
        char_id = self.character_manager.resolve_char_to_id(char)
        return self.db.find('player', {'char_id': char_id})

    def update(self, packet):
        character = self.get_from_database(packet.char_id)
        if character:
            if character['name'] != packet.name:
                self.db.update('player', {'char_id': packet.char_id},
                               {'name': packet.name})
        else:
            self.db.insert(
                'player', {
                    'char_id': packet.char_id,
                    'name': packet.name,
                    'source': 'chat_server',
                    'last_updated': int(time.time())
                })
class SettingManager:
    def __init__(self):
        self.logger = Logger("setting_manager")
        self.settings = {}

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.util = registry.get_instance("util")

    def start(self):
        # process decorators
        for _, inst in Registry.get_all_instances().items():
            for name, method in get_attrs(inst).items():
                if hasattr(method, "setting"):
                    setting_name, value, description, obj = getattr(method, "setting")
                    handler = getattr(inst, name)
                    module = self.util.get_module_name(handler)
                    self.register(setting_name, value, description, obj, module)

    def register(self, name, value, description, setting: SettingType, module):
        name = name.lower()
        module = module.lower()
        setting.set_name(name)
        setting.set_description(description)

        if not description:
            self.logger.warning("No description specified for setting '%s'" % name)

        row = self.db.find('settings', {"name": name})

        if row is None:
            self.logger.debug("Adding setting '%s'" % name)

            self.db.insert("settings",
                           {
                               "name": name,
                               "value": value,
                               "description": description,
                               "module": module,
                               "verified": 1
                           })
            # verify default value is a valid value, and is formatted appropriately
            setting.set_value(value)
        else:
            self.logger.debug("Updating setting '%s'" % name)
            self.db.update('settings', {"name": name}, {"description": description, "verified": 1, "module": module})

        self.settings[name] = setting

    def get_value(self, name):
        row = self.db.find('settings', {"name": name})
        return row['value'] if row else None

    def set_value(self, name, value):
        self.db.update('settings', {"name": name}, {"value": value})

    def get(self, name):
        name = name.lower()
        setting = self.settings.get(name, None)
        if setting:
            return setting
        else:
            return None