Some big changes today as multiple things are coming together! Most of the work is now in the
stages module, the code for this pots is at commit 0e67f0d
I've decided to instead of using a task queue system for timed events, simply add a
DelayStage quest stage that enacts a time delay. This reduces the complexity by knocking out a piece of infrastructure, and was surprisingly easy to write given the existing infrastructure:
class DelayStage(Stage): """ For enacting a time delay """ @property @abstractmethod def delay(cls) -> timedelta: """ how much time to delay """ return NotImplemented def prepare(self) -> None: """ On first run, insert datetime """ if self.get_stage_data() is None: now = datetime.now().timestamp() logger.info(f"Delay stage prepare", now=now) self.set_stage_data(now) def condition(self) -> bool: """ Calculate whether that has elapsed """ target = datetime.utcfromtimestamp(self.get_stage_data(0)) + self.delay now = datetime.now() logger.info(f"Delay stage prepare", now=now, target=target) return now > target
The DelayStage uses the
prepare() event to write into its stage storage (a new free-form dict value that I've added to the quest data storage model to store stage-specific data) the target timestamp, and when the condition runs next, it tests if this time has been reached or exceeded! Very simple.
Now that we have the code for characters to post github messages, I can add a
CreateIssueStage() that will create a new issue in a user's fork, it looks like this:
class CreateIssueStage(Stage): """ This stage posts a new issue to a user's fork """ @property @abstractmethod def character(cls) -> Character: """ Which character will post the issue """ return NotImplemented @property @abstractmethod def issue_title(cls) -> str: """ Issue title """ return NotImplemented @property @abstractmethod def issue_body(cls) -> str: """ Issue main body """ return NotImplemented # variable to store resulting issue ID in for later issue_id_variable: Optional[int] = None def execute(self) -> None: """ Post the issue to the fork """ game = self.quest.quest_page.game game.load() fork_url = game.data.fork_url logger.info("Creating issue", title=self.issue_title, fork_url=fork_url) issue_id = self.character.issue_create( fork_url, self.issue_title, self.issue_body ) if self.issue_id_variable: logger.info( "Storing issue Id in variable", issue_id=issue_id, variable=self.issue_id_variable, ) setattr(self.quest.quest_data, self.issue_id_variable, issue_id)
It has some abstract properties for the character, issue title, and so on; and the business part of the class simply calls
character.isseu_create() that's part of the package defined last time. The rest of the
execute() event deals with fetching the fork_url, and saving the results in a location of your choosing, so that a stage can output the issue ID created so that other stages can use it.
In usage, it looks like this:
class IntroQuest(Quest): class QuestDataModel(QuestBaseModel): issue_id: Optional[str] = None last_comment: Optional[datetime] = None version = VersionInfo.parse("0.1.0") difficulty = Difficulty.BEGINNER description = "The intro quest" class Start(CreateIssueStage): children = ["CheckNumber1"] character = character_garry issue_id_variable = "issue_id" issue_title = "Welcome, can you give me a hand?" issue_body = """\ Hello therre! """
The reason for the
"""\ is due to the use of pythons dedent method which keeps intedentation nicer in the source code for multi-line strings.
Start event fires, it will use the character defined by
character_garry to send an issue to the player's fork, with the given issue title and body. It will also store the issue_id in the quest data model's
issue_id key, so that next stages can re-use it.
In order to test, to avoid spamming data somewhere public, I've over-written the test files to fake a fork to a private github repo instead. This usually isn't possible on GitHub during a normal flow, but our system doesn't make a distinction, so happily accepts it as part of a test.
I can run the tests already in the
github_webhook_listener to test the creation of this first issue since it already starts by deleting the test user's game and starting a new one. I can also test follow-on stages using the tests already defined in