def donation_delete(self, msg, donation_id: str) -> str: """ As an admin, delete a donation either because its spam or needs to be re-submitted """ with synchronized(CONFIRMATION_LOCK): try: to_be_confirmed = self["to_be_confirmed"] to_be_confirmed.pop(donation_id) self["to_be_confirmed"] = to_be_confirmed return f"Removed pending donation {donation_id}" except KeyError: pass with synchronized(RECORDED_LOCK): try: to_be_recorded = self["to_be_recorded"] to_be_recorded.pop(donation_id) self["to_be_recorded"] = to_be_recorded return f"Removed recorded donation {donation_id}" except KeyError: pass with synchronized(DONOR_LOCK): try: donations = self["donations"] donations.pop(donation_id) self["donations"] = donations return ( f"Removed pr'd donation {donation_id}. This won't remove the donation from the page until a pr" f"is redone with new donations list. You can do this with ./rebuild donations list" ) except KeyError: return f"Donation {donation_id} is not found." return f"Donation {donation_id} is not in our donations lists"
def donation_change(self, msg, donation_id: str, amount: str) -> str: """ As an admin, change a donation amount """ if "$" not in amount: return ( "Error: Please include your amount as a $##.##. i.e. $20.99. You can also use whole numbers like " "$20") amount_float = float(amount.replace("$", "")) if amount_float <= 0: return "Error: Donation amount has to be a positive number." with synchronized(CONFIRMATION_LOCK): to_be_confirmed = self["to_be_confirmed"] donation = to_be_confirmed.pop(donation_id, None) if donation is None: return f"Error: {donation_id} is not in our donation database." self["to_be_confirmed"] = to_be_confirmed self._add_donation_for_confirmation( donation_id=donation_id, amount=amount_float, file_url=donation["file_url"], user=donation["user"], make_public=donation["user"] is not None, ) return ( f"Donation {donation_id} has been updated. You can now confirm it with " f"`./donation confirm {donation_id}`")
def donation_confirm(self, msg, donation_id: str) -> str: """ As an admin, confirm a donation """ with synchronized(CONFIRMATION_LOCK): to_be_confirmed = self["to_be_confirmed"] donation = to_be_confirmed.pop(donation_id, None) if donation is None: return f"Error: {donation_id} is not in our donation database." self["to_be_confirmed"] = to_be_confirmed with synchronized(RECORDED_LOCK): to_be_recorded = self["to_be_recorded"] to_be_recorded[donation_id] = donation self["to_be_recorded"] = to_be_recorded return f"Donation {donation_id} confirmed. Be on the look out for a PR updating the website"
def _record_donations(self, force: bool = False) -> None: """ Poller that records a donation and turns it into a PR """ if len(self["to_be_recorded"].keys()) == 0 and not force: return timestamp = int(datetime.now().timestamp()) with synchronized(RECORDED_LOCK): to_be_recorded = self["to_be_recorded"] self["to_be_recorded"] = dict() with synchronized(DONOR_LOCK): new_donations = {**self["donations"], **to_be_recorded} self["donations"] = new_donations self["donation_total"] = self._total_donations() branch_name = f"new-donations-{timestamp}" with self.website_plugin.temp_website_clone( checkout_branch=branch_name) as website_clone: file_list = self._update_blog_post(website_clone, new_donations, self["donation_total"]) pr = self.website_plugin.open_website_pr( website_clone, file_list, f"updating with new donations {timestamp}", f"Donation Manager: New Donations {timestamp}", "New donations", ) self.send(self.config["DM_CHANNEL_IDENTIFIER"], text=f"New donation PR:\n" f"{pr}") self.log.debug(self.config["DM_REPORT_CHANNEL_ID"]) self._bot.api_call( "conversations.setTopic", { "channel": self.config["DM_REPORT_CHANNEL_ID"], "topic": f"Total Donations in SA Dev's Season of Giving: ${self['donation_total']:.2f}", }, )
def activate(self): super().activate() with synchronized(CONFIRMATION_LOCK): try: self["to_be_confirmed"] except KeyError: self["to_be_confirmed"] = dict() with synchronized(RECORDED_LOCK): try: self["to_be_recorded"] except KeyError: self["to_be_recorded"] = dict() with synchronized(DONOR_LOCK): try: self["donations"] except KeyError: self["donations"] = dict() self["donation_total"] = self._total_donations() self.website_plugin = self.get_plugin("SADevsWebsite") self.start_poller(self.config["DM_RECORD_POLLER_INTERVAL"], self._record_donations)
def list_donations(self, msg, _) -> str: """Lists all the donations we have""" yield "*Donations still needing confirmation*:" with synchronized(CONFIRMATION_LOCK): for id, donation in self["to_be_confirmed"].items(): yield f"{id}: {donation['user']} - {donation['amount']} - {donation['file_url']}" yield "*Donations waiting to be recorded:*" with synchronized(RECORDED_LOCK): yield "\n".join([ f"{id}: {donation['user']} - {donation['amount']}" for id, donation in self["to_be_recorded"].items() ]) yield "*Confirmed Donations*:" with synchronized(DONOR_LOCK): yield "\n".join([ f"{id}: {donation['user']} - {donation['amount']}" for id, donation in self["donations"].items() ])
def activate(self): super().activate() # setup our on disk log with synchronized(CAL_LOCK): try: self["channel_action_log"] except KeyError: self["channel_action_log"] = { datetime.now().strftime("%Y-%m-%d"): list() } self.start_poller( self.config["CHANMON_LOG_JANITOR_INTERVAL"], self._log_janitor, args=(self.config["CHANMON_LOG_DAYS"]), )
def _log_janitor(self, days_to_keep: int) -> None: """Prunes our on-disk logs""" first_key = next(iter(self["channel_action_log"])) if UTC.localize(datetime.utcnow()) - parse(first_key) > timedelta( days=days_to_keep): with synchronized(CAL_LOCK): cal_log = self["channel_action_log"] cal_log.pop(first_key, None) self["channel_action_log"] = cal_log cal_log = self["channel_action_log"] today = datetime.now().strftime("%Y-%m-%d") for key in cal_log.keys(): if len(cal_log[key]) == 0 and key != today: cal_log.pop(key) self["channel_action_log"] = cal_log
def _log_channel_change(self, channel_name: str, user_name: str, action: str, timestamp: str) -> None: """Logs a channel change event""" log = self._build_log(channel_name, user_name, action, timestamp) if self.config["CHANMON_CHANNEL_ID"] is not None: self._send_log_to_slack(log) with synchronized(CAL_LOCK): chan_log = self["channel_action_log"] today = datetime.now().strftime("%Y-%m-%d") try: chan_log[today].append(log) except KeyError: chan_log[today] = list() chan_log[today].append(log) self["channel_action_log"] = chan_log
def _write_router_status(self): self._modelChanged.wait() with synchronized(self): def neq(i, v): s = self.read_state return len(s) > i and v != s[i] diff = [(i, v) for i, v in enumerate(self.goal_state) if neq(i, v)] sends = diff try: for i, v in sends: self.read_state[i] = v self.send_set_items(sends) except OSError: self._send_disconnect() except Exception as e: print(e) self._modelChanged.clear()
def _add_donation_for_confirmation( self, donation_id: str, amount: float, file_url: str, user: str, make_public: bool, ) -> None: """ Adds a donation to be confirmed """ if not make_public: user = None else: if type(user) != str: user = self._get_user_real_name(user) with synchronized(CONFIRMATION_LOCK): try: to_be_confirmed = self["to_be_confirmed"] except KeyError: to_be_confirmed = dict() if donation_id in to_be_confirmed: raise KeyError( "Donation is not unique. Did you already add this donation? If this is in error, " "reach out to the admins") to_be_confirmed[donation_id] = { "amount": amount, "file_url": file_url, "user": user, } self["to_be_confirmed"] = to_be_confirmed self.send( self.config["DM_CHANNEL_IDENTIFIER"], text=f"New donation:\n" f"Amount: ${amount:.2f}\n" f"File URL: {file_url}\n" f"User: {user}\n\n" f"To approve this donation run `./donation confirm {donation_id}`\n" f"To change this donation run `./donation change {donation_id} [new amount]`", )
def report_metrics(): """Report the current batch of metrics to InfluxDB. """ global data_points # Set aside the current batch of metrics and initialize the list # used to collect metrics to empty again. with wrapt.synchronized(lock): pending_data_points = data_points data_points = [] # Report the complete batch of metrics to InfluxDB in one go. if pending_data_points: try: client.write_points(pending_data_points) except Exception: traceback.print_exc()
def wrapper(*args, **kwargs): use_cached = kwargs.pop(optional, default) if optional else True if method: self = args[0] c = cache(self) if single_lock: cache_lock = lock else: cache_lock = lock( self) if lock is not True else synchronized(self) else: c = cache cache_lock = lock if c is None: return func(*args, **kwargs) kargs, kkwargs = split_arg_vals_with_defaults( sig, *args, **kwargs) k = key(*kargs, **kkwargs) if use_cached: with cache_lock: try: val = c[k] except KeyError: pass # key not found else: if isinstance(val, Exception) and exc: raise val return val if key_lock: do_exec = False with cache_lock: klock = key_locks.get(k, None) if klock is None: # The func is not already being executed with these args do_exec = True klock = key_locks[k] = lock_type( ) if lock is not True else RLock() # Acquire before releasing cache_lock to prevent the wrong thread from getting it first klock.acquire() if do_exec: try: try: val = func(*args, **kwargs) except Exception as e: if exc: val = e else: raise e with suppress( ValueError ): # raised if the value is too large with cache_lock: c[k] = val finally: with cache_lock: klock.release() del key_locks[ k] # Allow future runs if key disappears from cache (e.g., TTLCache) if isinstance(val, Exception) and exc: raise val return val else: with klock: # Block until the executing thread completes with cache_lock: try: val = c[k] except KeyError: pass # Just in case something goes wrong; falls thru to execute/store/return else: if isinstance(val, Exception) and exc: raise val return val try: val = func(*args, **kwargs) except Exception as e: if exc: val = e else: raise e with suppress(ValueError): # raised if the value is too large with cache_lock: c[k] = val if isinstance(val, Exception) and exc: raise val return val