[#287] Migrated github from a backend to a runnable plugin.

Closes: #287
This commit is contained in:
Fabio Manganiello 2023-12-30 16:28:12 +01:00
parent 0fb1035331
commit 5eb09eab1a
7 changed files with 57 additions and 77 deletions

View file

@ -11,7 +11,6 @@ Backends
platypush/backend/camera.pi.rst platypush/backend/camera.pi.rst
platypush/backend/chat.telegram.rst platypush/backend/chat.telegram.rst
platypush/backend/foursquare.rst platypush/backend/foursquare.rst
platypush/backend/github.rst
platypush/backend/google.fit.rst platypush/backend/google.fit.rst
platypush/backend/google.pubsub.rst platypush/backend/google.pubsub.rst
platypush/backend/gps.rst platypush/backend/gps.rst

View file

@ -1,5 +0,0 @@
``github``
============================
.. automodule:: platypush.backend.github
:members:

View file

@ -0,0 +1,5 @@
``github``
==========
.. automodule:: platypush.plugins.github
:members:

View file

@ -35,6 +35,7 @@ Plugins
platypush/plugins/file.rst platypush/plugins/file.rst
platypush/plugins/file.monitor.rst platypush/plugins/file.monitor.rst
platypush/plugins/foursquare.rst platypush/plugins/foursquare.rst
platypush/plugins/github.rst
platypush/plugins/google.calendar.rst platypush/plugins/google.calendar.rst
platypush/plugins/google.drive.rst platypush/plugins/google.drive.rst
platypush/plugins/google.fit.rst platypush/plugins/google.fit.rst

View file

@ -1,32 +0,0 @@
manifest:
events:
platypush.message.event.github.GithubCommitCommentEvent: when a new commit comment
is created.
platypush.message.event.github.GithubCreateEvent: when a tag or branch is created.
platypush.message.event.github.GithubDeleteEvent: when a tag or branch is deleted.
platypush.message.event.github.GithubEvent: for any event that doesn't fall in
the above categories(``event_type`` will be set accordingly).
platypush.message.event.github.GithubForkEvent: when a user forks a repository.
platypush.message.event.github.GithubIssueCommentEvent: when new activity happens
on an issue comment.
platypush.message.event.github.GithubIssueEvent: when new repository issue activity
happens.
platypush.message.event.github.GithubMemberEvent: when new repository collaborators
activity happens.
platypush.message.event.github.GithubPublicEvent: when a repository goes public.
platypush.message.event.github.GithubPullRequestEvent: when new pull request related
activity happens.
platypush.message.event.github.GithubPullRequestReviewCommentEvent: when activity
happens on a pullrequest commit.
platypush.message.event.github.GithubPushEvent: when a new push is created.
platypush.message.event.github.GithubReleaseEvent: when a new release happens.
platypush.message.event.github.GithubSponsorshipEvent: when new sponsorship related
activity happens.
platypush.message.event.github.GithubWatchEvent: when someone stars/starts watching
a repository.
platypush.message.event.github.GithubWikiEvent: when new activity happens on a
repository wiki.
install:
pip: []
package: platypush.backend.github
type: backend

View file

@ -8,7 +8,6 @@ import requests
from sqlalchemy import create_engine, Column, String, DateTime from sqlalchemy import create_engine, Column, String, DateTime
from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm import sessionmaker, scoped_session
from platypush.backend import Backend
from platypush.common.db import declarative_base from platypush.common.db import declarative_base
from platypush.config import Config from platypush.config import Config
from platypush.message.event.github import ( from platypush.message.event.github import (
@ -29,11 +28,13 @@ from platypush.message.event.github import (
GithubSponsorshipEvent, GithubSponsorshipEvent,
GithubWatchEvent, GithubWatchEvent,
) )
from platypush.plugins import RunnablePlugin
Base = declarative_base() Base = declarative_base()
Session = scoped_session(sessionmaker()) Session = scoped_session(sessionmaker())
# pylint: disable=too-few-public-methods
class GithubResource(Base): class GithubResource(Base):
""" """
Models the GithubLastEvent table, containing the timestamp where a certain URL was last checked. Models the GithubLastEvent table, containing the timestamp where a certain URL was last checked.
@ -44,9 +45,9 @@ class GithubResource(Base):
last_updated_at = Column(DateTime) last_updated_at = Column(DateTime)
class GithubBackend(Backend): class GithubPlugin(RunnablePlugin):
""" """
This backend monitors for notifications and events either on Github user, organization or repository level. This plugin monitors for notifications and events either on Github user, organization or repository level.
You'll need a Github personal access token to use the service. To get one: You'll need a Github personal access token to use the service. To get one:
- Access your Github profile settings - Access your Github profile settings
@ -54,7 +55,7 @@ class GithubBackend(Backend):
- Select *Personal access tokens* - Select *Personal access tokens*
- Click *Generate new token* - Click *Generate new token*
This backend requires the following permissions: This plugin requires the following permissions:
- ``repo`` - ``repo``
- ``notifications`` - ``notifications``
@ -72,22 +73,21 @@ class GithubBackend(Backend):
org: Optional[str] = None, org: Optional[str] = None,
poll_seconds: int = 60, poll_seconds: int = 60,
max_events_per_scan: Optional[int] = 10, max_events_per_scan: Optional[int] = 10,
*args, **kwargs,
**kwargs
): ):
""" """
If neither ``repos`` nor ``org`` is specified then the backend will monitor all new events on user level. If neither ``repos`` nor ``org`` is specified then the plugin will monitor all new events on user level.
:param user: Github username. :param user: Github username.
:param user_token: Github personal access token. :param user_token: Github personal access token.
:param repos: List of repos to be monitored - if a list is provided then only these repositories will be :param repos: List of repos to be monitored - if a list is provided then only these repositories will be
monitored for events. Repositories should be passed in the format ``username/repository``. monitored for events. Repositories should be passed in the format ``username/repository``.
:param org: Organization to be monitored - if provided then only this organization will be monitored for events. :param org: Organization to be monitored - if provided then only this organization will be monitored for events.
:param poll_seconds: How often the backend should check for new events, in seconds (default: 60). :param poll_seconds: How often the plugin should check for new events, in seconds (default: 60).
:param max_events_per_scan: Maximum number of events per resource that will be triggered if there is a large :param max_events_per_scan: Maximum number of events per resource that will be triggered if there is a large
number of events/notification since the last check (default: 10). Specify 0 or null for no limit. number of events/notification since the last check (default: 10). Specify 0 or null for no limit.
""" """
super().__init__(*args, **kwargs) super().__init__(**kwargs)
self._last_text: Optional[str] = None self._last_text: Optional[str] = None
self.user = user self.user = user
self.user_token = user_token self.user_token = user_token
@ -95,7 +95,7 @@ class GithubBackend(Backend):
self.org = org self.org = org
self.poll_seconds = poll_seconds self.poll_seconds = poll_seconds
self.db_lock = threading.RLock() self.db_lock = threading.RLock()
self.workdir = os.path.join(os.path.expanduser(Config.get('workdir')), 'github') self.workdir = os.path.join(os.path.expanduser(Config.get_workdir()), 'github')
self.dbfile = os.path.join(self.workdir, 'github.db') self.dbfile = os.path.join(self.workdir, 'github.db')
self.max_events_per_scan = max_events_per_scan self.max_events_per_scan = max_events_per_scan
@ -103,8 +103,8 @@ class GithubBackend(Backend):
self._init_db() self._init_db()
def _request(self, uri: str, method: str = 'get') -> dict: def _request(self, uri: str, method: str = 'get') -> dict:
method = getattr(requests, method.lower()) func = getattr(requests, method.lower())
return method( return func(
self._base_url + uri, self._base_url + uri,
auth=(self.user, self.user_token), auth=(self.user, self.user_token),
headers={'Accept': 'application/vnd.github.v3+json'}, headers={'Accept': 'application/vnd.github.v3+json'},
@ -112,7 +112,7 @@ class GithubBackend(Backend):
def _init_db(self): def _init_db(self):
engine = create_engine( engine = create_engine(
'sqlite:///{}'.format(self.dbfile), f'sqlite:///{self.dbfile}',
connect_args={'check_same_thread': False}, connect_args={'check_same_thread': False},
) )
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
@ -124,7 +124,7 @@ class GithubBackend(Backend):
return datetime.datetime.fromisoformat(time_string[:-1] + '+00:00') return datetime.datetime.fromisoformat(time_string[:-1] + '+00:00')
@staticmethod @staticmethod
def _get_or_create_resource(uri: str, session: Session) -> GithubResource: def _get_or_create_resource(uri: str, session: scoped_session) -> GithubResource:
record = session.query(GithubResource).filter_by(uri=uri).first() record = session.query(GithubResource).filter_by(uri=uri).first()
if record is None: if record is None:
record = GithubResource(uri=uri) record = GithubResource(uri=uri)
@ -135,18 +135,18 @@ class GithubBackend(Backend):
def _get_last_event_time(self, uri: str): def _get_last_event_time(self, uri: str):
with self.db_lock: with self.db_lock:
record = self._get_or_create_resource(uri=uri, session=Session()) record = self._get_or_create_resource(uri=uri, session=Session()) # type: ignore
return ( return (
record.last_updated_at.replace(tzinfo=datetime.timezone.utc) record.last_updated_at.replace(tzinfo=datetime.timezone.utc)
if record.last_updated_at if record.last_updated_at # type: ignore
else None else None
) )
def _update_last_event_time(self, uri: str, last_updated_at: datetime.datetime): def _update_last_event_time(self, uri: str, last_updated_at: datetime.datetime):
with self.db_lock: with self.db_lock:
session = Session() session = Session()
record = self._get_or_create_resource(uri=uri, session=session) record = self._get_or_create_resource(uri=uri, session=session) # type: ignore
record.last_updated_at = last_updated_at record.last_updated_at = last_updated_at # type: ignore
session.add(record) session.add(record)
session.commit() session.commit()
@ -211,16 +211,17 @@ class GithubBackend(Backend):
fired_events.append(self._parse_event(event)) fired_events.append(self._parse_event(event))
for event in fired_events: for event in fired_events:
self.bus.post(event) self._bus.post(event)
if new_last_event_time:
self._update_last_event_time( self._update_last_event_time(
uri=uri, last_updated_at=new_last_event_time uri=uri, last_updated_at=new_last_event_time
) )
except Exception as e: except Exception as e:
self.logger.warning( self.logger.warning(
'Encountered exception while fetching events from {}: {}'.format( 'Encountered exception while fetching events from %s: %s',
uri, str(e) uri,
) e,
) )
self.logger.exception(e) self.logger.exception(e)
@ -229,45 +230,36 @@ class GithubBackend(Backend):
return thread return thread
def run(self): def main(self):
self.logger.info('Starting Github backend')
monitors = [] monitors = []
if self.repos: if self.repos:
for repo in self.repos: for repo in self.repos:
monitors.append( monitors.append(
threading.Thread( threading.Thread(
target=self._events_monitor( target=self._events_monitor(f'/networks/{repo}/events')
'/networks/{repo}/events'.format(repo=repo)
)
) )
) )
if self.org: if self.org:
monitors.append( monitors.append(
threading.Thread( threading.Thread(
target=self._events_monitor( target=self._events_monitor(f'/orgs/{self.org}/events')
'/orgs/{org}/events'.format(org=self.org)
)
) )
) )
if not (self.repos or self.org): if not (self.repos or self.org):
monitors.append( monitors.append(
threading.Thread( threading.Thread(
target=self._events_monitor( target=self._events_monitor(f'/users/{self.user}/events')
'/users/{user}/events'.format(user=self.user)
)
) )
) )
for monitor in monitors: for monitor in monitors:
monitor.start() monitor.start()
self.logger.info('Started Github backend')
for monitor in monitors: for monitor in monitors:
monitor.join() monitor.join()
self.logger.info('Github backend terminated')
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -0,0 +1,20 @@
manifest:
events:
- platypush.message.event.github.GithubCommitCommentEvent
- platypush.message.event.github.GithubCreateEvent
- platypush.message.event.github.GithubDeleteEvent
- platypush.message.event.github.GithubEvent
- platypush.message.event.github.GithubForkEvent
- platypush.message.event.github.GithubIssueCommentEvent
- platypush.message.event.github.GithubIssueEvent
- platypush.message.event.github.GithubMemberEvent
- platypush.message.event.github.GithubPublicEvent
- platypush.message.event.github.GithubPullRequestEvent
- platypush.message.event.github.GithubPullRequestReviewCommentEvent
- platypush.message.event.github.GithubPushEvent
- platypush.message.event.github.GithubReleaseEvent
- platypush.message.event.github.GithubSponsorshipEvent
- platypush.message.event.github.GithubWatchEvent
- platypush.message.event.github.GithubWikiEvent
package: platypush.plugin.github
type: plugin