Sync schedule
This commit is contained in:
parent
b1e3d24a54
commit
ccd523d740
8 changed files with 200 additions and 17 deletions
19
db/models.py
19
db/models.py
|
|
@ -1,5 +1,8 @@
|
|||
import pytz
|
||||
import datetime
|
||||
from peewee import *
|
||||
from settings import SQLITE_DB, MASTODON_INSTANCE
|
||||
from util import sqlite_time
|
||||
|
||||
database = SqliteDatabase(SQLITE_DB)
|
||||
|
||||
|
|
@ -75,6 +78,8 @@ class OrdersPool(BaseModel):
|
|||
null=True
|
||||
)
|
||||
|
||||
updated_at = DateTimeField()
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
|
|
@ -101,6 +106,14 @@ class OrdersPool(BaseModel):
|
|||
} for order in self.orders]
|
||||
}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.updated_at = sqlite_time(datetime.datetime.now(datetime.UTC))
|
||||
|
||||
return super(OrdersPool, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}[{self.user.telegram_username}]"
|
||||
|
||||
class Meta:
|
||||
table_name = 'orders_pool'
|
||||
|
||||
|
|
@ -127,9 +140,9 @@ class OrderAddOn(BaseModel):
|
|||
# Order State
|
||||
#
|
||||
class OrderStatus(BaseModel):
|
||||
confirmed_at = DateTimeField(null=True) # TIMESTAMP
|
||||
created_at = DateTimeField() # TIMESTAMP
|
||||
due_at = DateTimeField(null=True) # TIMESTAMP
|
||||
confirmed_at = DateTimeField(null=True)
|
||||
created_at = DateTimeField()
|
||||
due_at = DateTimeField(null=True)
|
||||
mastodon_id = TextField()
|
||||
text = TextField()
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ def orders_pool(user_id, set_id):
|
|||
def orders_pool_by_id(pool_id):
|
||||
return OrdersPool.get(OrdersPool.id == pool_id)
|
||||
|
||||
def orders_pool_since(dt):
|
||||
return OrdersPool.select().where(OrdersPool.updated_at > dt)
|
||||
|
||||
def orders_pool_scheduled():
|
||||
return OrdersPool.select().where(OrdersPool.scheduled == True)
|
||||
|
||||
|
|
@ -113,7 +116,7 @@ def skip_day_put(user, date):
|
|||
return SkipDay.create(user=user, date=date)
|
||||
|
||||
def skip_day_delete(user, date):
|
||||
q = SkipDay.delete().where(SkipDay.user == user and SkipDay.date == date)
|
||||
q = SkipDay.delete().where(SkipDay.user == user & SkipDay.date == date)
|
||||
return q.execute()
|
||||
|
||||
def skip_days_upcoming(user):
|
||||
|
|
@ -123,7 +126,7 @@ def skip_days_upcoming(user):
|
|||
.limit(10))
|
||||
|
||||
def skip_day_contains(user, date):
|
||||
q = SkipDay.select().where(SkipDay.user == user and SkipDay.date == date)
|
||||
q = SkipDay.select().where(SkipDay.user == user & SkipDay.date == date)
|
||||
return len(q) > 0
|
||||
|
||||
def order_status_put(orders_pool, user, mastodon_id, created_at, due_at, text, punishment_for=None):
|
||||
|
|
|
|||
49
migrations/019_add_orders_pool_updated_at.py
Normal file
49
migrations/019_add_orders_pool_updated_at.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Peewee migrations -- 019_add_orders_pool_updated_at.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields(
|
||||
'orders_pool',
|
||||
|
||||
updated_at=pw.DateTimeField(null=True))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields('orders_pool', 'updated_at')
|
||||
44
migrations/020_update_orders_pool_updated_at.py
Normal file
44
migrations/020_update_orders_pool_updated_at.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Peewee migrations -- 020_update_orders_pool_updated_at.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
database.execute_sql("UPDATE orders_pool SET updated_at = CURRENT_TIMESTAMP")
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
44
migrations/021_not_null_orders_pool_updated_at.py
Normal file
44
migrations/021_not_null_orders_pool_updated_at.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Peewee migrations -- 021_not_null_orders_pool_updated_at.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
|
|
@ -4,10 +4,10 @@ import pytz
|
|||
|
||||
from scheduler.asyncio import Scheduler
|
||||
|
||||
from settings import TIMEZONE
|
||||
from settings import TIMEZONE, SCHEDULE_SYNC_INTERVAL
|
||||
from orders import order_issue, order_check
|
||||
from db.queries import orders_pool_by_id, orders_pool_scheduled, skip_day_contains, order_status_outstanding
|
||||
from util import order_time
|
||||
from db.queries import orders_pool_by_id, orders_pool_scheduled, orders_pool_since, skip_day_contains, order_status_outstanding
|
||||
from util import order_time, sqlite_time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -24,24 +24,35 @@ class OrderScheduler():
|
|||
|
||||
self.scheduled_pools = {}
|
||||
for orders_pool in orders_pool_scheduled():
|
||||
self.scheduled_pools[orders_pool.id] = self.scheduler.daily(
|
||||
order_time(orders_pool.time),
|
||||
self.scheduled_order,
|
||||
args=(orders_pool.id,)
|
||||
)
|
||||
self.schedule_pool(orders_pool)
|
||||
|
||||
self.outstanding_orders = {}
|
||||
for order_status in order_status_outstanding():
|
||||
self.outstanding_orders[order_status.id] = self.scheduler.once(
|
||||
self.scheduler.once(
|
||||
datetime.datetime.fromisoformat(order_status.due_at) + GRACE_PERIOD,
|
||||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
)
|
||||
|
||||
# TODO: Schedule keeping schedule up to date
|
||||
self.last_update = datetime.datetime.now(datetime.UTC)
|
||||
self.scheduler.cyclic(
|
||||
datetime.timedelta(seconds=SCHEDULE_SYNC_INTERVAL),
|
||||
self.update_schedule
|
||||
)
|
||||
|
||||
logger.info(self.scheduler)
|
||||
|
||||
def schedule_pool(self, orders_pool):
|
||||
if orders_pool.id in self.scheduled_pools:
|
||||
self.scheduler.delete_job(self.scheduled_pools[orders_pool.id])
|
||||
del self.scheduled_pools[orders_pool.id]
|
||||
|
||||
if orders_pool.scheduled:
|
||||
self.scheduled_pools[orders_pool.id] = self.scheduler.daily(
|
||||
order_time(orders_pool.time),
|
||||
self.scheduled_order,
|
||||
args=(orders_pool.id,)
|
||||
)
|
||||
|
||||
async def scheduled_order(self, orders_pool_id):
|
||||
orders_pool = orders_pool_by_id(orders_pool_id)
|
||||
|
||||
|
|
@ -68,7 +79,7 @@ class OrderScheduler():
|
|||
|
||||
if order_status is not None:
|
||||
# Schedule check
|
||||
self.outstanding_orders[order_status.id] = self.scheduler.once(
|
||||
self.scheduler.once(
|
||||
order_status.due_at + GRACE_PERIOD,
|
||||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
|
|
@ -76,3 +87,18 @@ class OrderScheduler():
|
|||
|
||||
async def scheduled_check(self, outstanding_order_id):
|
||||
await order_check(outstanding_order_id)
|
||||
|
||||
async def update_schedule(self):
|
||||
last_update_sqlite = sqlite_time(self.last_update)
|
||||
|
||||
updated = False
|
||||
|
||||
for orders_pool_updated in orders_pool_since(last_update_sqlite):
|
||||
logger.info(f'Updating schedule for {orders_pool_updated}')
|
||||
self.schedule_pool(orders_pool_updated)
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
logger.info(self.scheduler)
|
||||
|
||||
self.last_update = datetime.datetime.now(datetime.UTC)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')
|
|||
ORDERS_YML = os.environ.get('ORDERS_YML', 'orders.yml')
|
||||
|
||||
TIMEZONE = os.environ.get('TIMEZONE', 'America/Chicago')
|
||||
SCHEDULE_SYNC_INTERVAL = int(os.environ.get('SCHEDULE_SYNC_INTERVAL', 60 * 15)) # 15 minutes
|
||||
|
||||
FLASK_URL = os.environ.get("FLASK_URL")
|
||||
FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY")
|
||||
|
|
|
|||
3
util.py
3
util.py
|
|
@ -10,6 +10,9 @@ def make_session():
|
|||
def timezone():
|
||||
return pytz.timezone(TIMEZONE)
|
||||
|
||||
def sqlite_time(dt):
|
||||
return dt.astimezone(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def order_time(str):
|
||||
order_time_arr = list(map(int, str.split(':')))
|
||||
return datetime.time(
|
||||
|
|
|
|||
Loading…
Reference in a new issue