Skip to content

Commit

Permalink
Merge pull request #66 from hivesolutions/joamag/cron
Browse files Browse the repository at this point in the history
  • Loading branch information
joamag authored Apr 13, 2024
2 parents bf9e418 + 30396ad commit 4ad1e75
Show file tree
Hide file tree
Showing 11 changed files with 613 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
pip install black
black . --check
if: matrix.python-version == '3.12'
- run: |
pip install pytest
ADAPTER=tiny HTTPBIN=httpbin.bemisc.com pytest
- run: ADAPTER=tiny HTTPBIN=httpbin.bemisc.com python setup.py test
build-pypy:
name: Build PyPy
Expand All @@ -63,4 +66,7 @@ jobs:
pip install black
black . --check
if: matrix.python-version == '3.12'
- run: |
pip install pytest
ADAPTER=tiny HTTPBIN=httpbin.bemisc.com pytest
- run: ADAPTER=tiny HTTPBIN=httpbin.bemisc.com pypy setup.py test
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

*
* Support for Cron based scheduling using `SchedulerCron` class - [#65](https://github.com/hivesolutions/appier/issues/65)

### Changed

Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
python_files = *.py
testpaths = src/appier/test
2 changes: 1 addition & 1 deletion src/appier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@
from .queuing import Queue, MemoryQueue, MultiprocessQueue, AMQPQueue
from .redisdb import Redis
from .request import CODE_STRINGS, Request, MockRequest
from .scheduler import Scheduler
from .scheduler import Scheduler, CronScheduler, SchedulerTask, SchedulerDate, Cron
from .serialize import serialize_csv, serialize_ics, build_encoder
from .session import (
Session,
Expand Down
40 changes: 39 additions & 1 deletion src/appier/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from . import settings
from . import observer
from . import execution
from . import scheduler
from . import controller
from . import structures
from . import exceptions
Expand Down Expand Up @@ -427,6 +428,7 @@ def __init__(
self._user_routes = None
self._core_routes = None
self._own = self
self._cron = None
self._peers = {}
self.__routes = []
self.load(level=level, handlers=handlers)
Expand Down Expand Up @@ -732,6 +734,7 @@ def start(self, refresh=True):
self._start_controllers()
self._start_models()
self._start_supervisor()
self._start_cron()
if refresh:
self.refresh()
self.status = RUNNING
Expand All @@ -743,6 +746,7 @@ def stop(self, refresh=True):
self._print_bye()
if refresh:
self.refresh()
self._stop_cron()
self._stop_supervisor()
self._stop_models()
self._stop_controllers()
Expand Down Expand Up @@ -2323,6 +2327,30 @@ def callable_t():
thread.daemon = True
thread.start()

def cron(self, job, cron):
"""
Schedule the provided method for regular execution using
the provided Cron like string.
The method is going to be executed at the provided time
in a separate thread.
:type job: function
:param job: The function/method that is going to be executed
at the specified time using the cron like string.
:type cron: String/SchedulerDate
:param cron: The cron like string that is going to be used to
define the execution time of the provided method.
:rtype: SchedulerTask
:return: The task that has been scheduled for execution at the
provided time.
"""

if self._cron == None:
self._cron = scheduler.CronScheduler(self)
self._cron.start()
return self._cron.schedule(job, cron)

def chunks(self, data, size=32768):
for index in range(0, len(data), size):
yield data[index : index + size]
Expand Down Expand Up @@ -2620,7 +2648,7 @@ def template(
self.template_args(kwargs)

# verifies if the target locale for the template has been defined
# and if thtat's the case updates the keyword based arguments for
# and if that's the case updates the keyword based arguments for
# the current template render to include that value
if locale:
kwargs["_locale"] = locale
Expand Down Expand Up @@ -5358,6 +5386,16 @@ def _start_supervisor(self):
def _stop_supervisor(self):
pass

def _start_cron(self):
pass

def _stop_cron(self):
if not self._cron:
return
self._cron.stop()
self._cron.join()
self._cron = None

def _add_route(self, *args, **kwargs):
self.__routes.append(args)
self.clear_routes()
Expand Down
63 changes: 63 additions & 0 deletions src/appier/base.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Appier Framework
# Copyright (c) 2008-2024 Hive Solutions Lda.
#
# This file is part of Hive Appier Framework.
#
# Hive Appier Framework is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Appier Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Appier Framework. If not, see <http://www.apache.org/licenses/>.

__author__ = "João Magalhães <[email protected]>"
""" The author(s) of the module """

__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

from os import PathLike

from .scheduler import SchedulerTask, JobFunction, Cron

class App:
def start(self, refresh: bool = ...): ...
def stop(self, refresh: bool = ...): ...
def cron(self, job: JobFunction, cron: Cron) -> SchedulerTask: ...
def url_for(
self,
type: str,
filename: str | None = ...,
prefix: str | None = ...,
query: str | None = ...,
params: str | None = ...,
absolute: bool = ...,
touch: bool = ...,
session: bool = ...,
compress: str | None = ...,
base_url: str | None = ...,
*args,
**kwargs
) -> str | None: ...

class APIApp(App):
pass

class WebApp(App):
pass

def get_app() -> App: ...
def get_name() -> str | None: ...
def get_base_path() -> PathLike | None: ...
33 changes: 33 additions & 0 deletions src/appier/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

import os
import sys
import calendar
import datetime
import inspect
import functools
import itertools
Expand Down Expand Up @@ -152,6 +154,10 @@ def ctx_absolute():
interpreter is at least Python 3 compliant, this is used
to take some of the conversion decision for runtime """

PYTHON_33 = sys.version_info[0] >= 3 and sys.version_info[1] >= 3
""" Global variable that defines if the current Python
interpreter is at least Python 3.3 compliant """

PYTHON_35 = sys.version_info[0] >= 3 and sys.version_info[1] >= 5
""" Global variable that defines if the current Python
interpreter is at least Python 3.5 compliant """
Expand Down Expand Up @@ -538,6 +544,33 @@ def build_opener(*args, **kwargs):
return urllib2.build_opener(*args, **kwargs) # @UndefinedVariable


def to_timestamp(date_time):
if PYTHON_33:
return date_time.replace(tzinfo=datetime.timezone.utc).timestamp()
else:
return calendar.timegm(date_time.utctimetuple())


def to_datetime(timestamp):
if PYTHON_33:
return datetime.datetime.fromtimestamp(
timestamp, datetime.timezone.utc
).replace(tzinfo=None)
else:
return datetime.datetime.utcfromtimestamp(timestamp)


def utcfromtimestamp(timestamp):
return to_datetime(timestamp)


def utc_now():
if PYTHON_33:
return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
else:
return datetime.datetime.utcnow()


def urlparse(*args, **kwargs):
return _urlparse.urlparse(*args, **kwargs)

Expand Down
Loading

0 comments on commit 4ad1e75

Please sign in to comment.