Skip to content

Commit e6154fe

Browse files
committed
pulled from upstream
2 parents 4c09398 + bb388ce commit e6154fe

36 files changed

+7816
-52
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ dmypy.json
138138
# Pyre type checker
139139
.pyre/
140140

141-
142141
# VScode
143142
.vscode/
144143
app/.vscode/

app/config.py.example

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import os
22

33
from fastapi_mail import ConnectionConfig
4-
# flake8: noqa
4+
from pydantic import BaseSettings
55

6-
# general
6+
7+
class Settings(BaseSettings):
8+
app_name: str = "PyLander"
9+
bot_api: str = "BOT_API"
10+
webhook_url: str = "WEBHOOK_URL"
11+
12+
class Config:
13+
env_file = ".env"
14+
15+
16+
# GENERAL
717
DOMAIN = 'Our-Domain'
818

919
# DATABASE
@@ -19,11 +29,11 @@ AVATAR_SIZE = (120, 120)
1929
# API-KEYS
2030
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
2131

22-
# export
32+
# EXPORT
2333
ICAL_VERSION = '2.0'
2434
PRODUCT_ID = '-//Our product id//'
2535

26-
# email
36+
# EMAIL
2737
email_conf = ConnectionConfig(
2838
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
2939
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
@@ -37,3 +47,14 @@ email_conf = ConnectionConfig(
3747

3848
# PATHS
3949
STATIC_ABS_PATH = os.path.abspath("static")
50+
51+
# LOGGER
52+
LOG_PATH = "./var/log"
53+
LOG_FILENAME = "calendar.log"
54+
LOG_LEVEL = "error"
55+
LOG_ROTATION_INTERVAL = "20 days"
56+
LOG_RETENTION_INTERVAL = "1 month"
57+
LOG_FORMAT = ("<level>{level: <8}</level>"
58+
" <green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
59+
" - <cyan>{name}</cyan>:<cyan>{function}</cyan>"
60+
" - <level>{message}</level>")

app/database/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class User(Base):
3333
language = Column(String)
3434
description = Column(String, default="Happy new user!")
3535
avatar = Column(String, default="profile.png")
36+
telegram_id = Column(String, unique=True)
3637
is_active = Column(Boolean, default=False)
3738

3839
events = relationship("UserEvent", back_populates="participants")
@@ -108,3 +109,11 @@ def __repr__(self):
108109
f'({self.event.owner}'
109110
f'to {self.recipient})>'
110111
)
112+
113+
114+
class Quote(Base):
115+
__tablename__ = "quotes"
116+
117+
id = Column(Integer, primary_key=True, index=True)
118+
text = Column(String, nullable=False)
119+
author = Column(String)

app/dependencies.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from functools import lru_cache
12
import os
23

34
from fastapi.templating import Jinja2Templates
@@ -11,3 +12,8 @@
1112
TEMPLATES_PATH = os.path.join(APP_PATH, "templates")
1213

1314
templates = Jinja2Templates(directory=TEMPLATES_PATH)
15+
16+
17+
@lru_cache()
18+
def get_settings():
19+
return config.Settings()

app/internal/logger_customizer.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import sys
2+
3+
from pathlib import Path
4+
from loguru import logger, _Logger as Logger
5+
6+
7+
class LoggerConfigError(Exception):
8+
pass
9+
10+
11+
class LoggerCustomizer:
12+
13+
@classmethod
14+
def make_logger(cls, log_path: Path,
15+
log_filename: str,
16+
log_level: str,
17+
log_rotation_interval: str,
18+
log_retention_interval: str,
19+
log_format: str) -> Logger:
20+
"""Creates a logger from given configurations
21+
22+
Args:
23+
log_path (Path): Path where the log file is located
24+
log_filename (str):
25+
26+
log_level (str): The level we want to start logging from
27+
log_rotation_interval (str): Every how long the logs
28+
would be rotated
29+
log_retention_interval (str): Amount of time in words defining
30+
how long the log will be kept
31+
log_format (str): The logging format
32+
33+
Raises:
34+
LoggerConfigError: Error raised when the configuration is invalid
35+
36+
Returns:
37+
Logger: Loguru logger instance
38+
"""
39+
try:
40+
logger = cls.customize_logging(
41+
file_path=Path(log_path) / Path(log_filename),
42+
level=log_level,
43+
retention=log_retention_interval,
44+
rotation=log_rotation_interval,
45+
format=log_format
46+
)
47+
except (TypeError, ValueError) as err:
48+
raise LoggerConfigError(
49+
f"You have an issue with the logger configuration: {err!r}, "
50+
"fix it please")
51+
52+
return logger
53+
54+
@classmethod
55+
def customize_logging(cls,
56+
file_path: Path,
57+
level: str,
58+
rotation: str,
59+
retention: str,
60+
format: str
61+
) -> Logger:
62+
"""Used to customize the logger instance
63+
64+
Args:
65+
file_path (Path): Path where the log file is located
66+
level (str): The level wanted to start logging from
67+
rotation (str): Every how long the logs would be
68+
rotated(creation of new file)
69+
retention (str): Amount of time in words defining how
70+
long a log is kept
71+
format (str): The logging format
72+
73+
Returns:
74+
Logger: Instance of a logger mechanism
75+
"""
76+
logger.remove()
77+
logger.add(
78+
sys.stdout,
79+
enqueue=True,
80+
backtrace=True,
81+
level=level.upper(),
82+
format=format
83+
)
84+
logger.add(
85+
str(file_path),
86+
rotation=rotation,
87+
retention=retention,
88+
enqueue=True,
89+
backtrace=True,
90+
level=level.upper(),
91+
format=format
92+
)
93+
94+
return logger

app/internal/quotes/daily_quotes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from datetime import date
2+
from typing import Optional
3+
4+
from app.database.models import Quote
5+
6+
from sqlalchemy.orm import Session
7+
from sqlalchemy.sql.expression import func
8+
9+
TOTAL_DAYS = 366
10+
11+
12+
def quote_per_day(
13+
session: Session, date: date = date.today()
14+
) -> Optional[Quote]:
15+
"""This function provides a daily quote, relevant to the current
16+
day of the year. The quote is randomally selected from a set
17+
of quotes matching to the given day"""
18+
day_num = date.timetuple().tm_yday
19+
quote = session.query(Quote).filter(
20+
Quote.id % TOTAL_DAYS == day_num).order_by(func.random()).first()
21+
return quote

app/internal/quotes/load_quotes.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
from typing import Dict, List, Optional
3+
4+
from app.database.models import Quote
5+
6+
from sqlalchemy.orm import Session
7+
8+
9+
def get_quotes_from_json() -> List[Dict[str, Optional[str]]]:
10+
"""This function reads all of the daily quotes from a specific JSON file.
11+
The JSON file content is copied from the free API:
12+
'https://type.fit/api/quotes'. I saved the content so the API won't be
13+
called every time the app is initialized."""
14+
try:
15+
with open('app/resources/quotes.json', 'r') as f:
16+
quotes_list = json.load(f)
17+
except (IOError, ValueError):
18+
return []
19+
return quotes_list
20+
21+
22+
def add_quotes_to_db(session: Session) -> None:
23+
"""This function reads the quotes and inserts them into the db"""
24+
all_quotes = get_quotes_from_json()
25+
quotes_objects = [
26+
Quote(text=quote['text'], author=quote['author'])
27+
for quote in all_quotes
28+
]
29+
session.add_all(quotes_objects)
30+
session.commit()
31+
32+
33+
def is_quotes_table_empty(session: Session) -> bool:
34+
return session.query(Quote).count() == 0
35+
36+
37+
def load_daily_quotes(session: Session) -> None:
38+
"""This function loads the daily quotes to the db,
39+
if they weren't already loaden"""
40+
if is_quotes_table_empty(session):
41+
add_quotes_to_db(session)

app/main.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
from fastapi import FastAPI, Request
1+
from fastapi import Depends, FastAPI, Request
22
from fastapi.staticfiles import StaticFiles
3+
from sqlalchemy.orm import Session
34

45
from app.config import PSQL_ENVIRONMENT
56
from app.database import models
6-
from app.database.database import engine
7+
from app.database.database import engine, get_db
78
from app.dependencies import (
89
MEDIA_PATH, STATIC_PATH, templates)
9-
from app.routers import (agenda, dayview, email, event, invitation, profile,
10-
search)
10+
from app.internal.quotes import load_quotes, daily_quotes
11+
from app.routers import (
12+
agenda, dayview, email, event, invitation, profile, search, telegram,
13+
whatsapp
14+
)
15+
from app.telegram.bot import telegram_bot
16+
from app.internal.logger_customizer import LoggerCustomizer
17+
from app import config
1118

1219

1320
def create_tables(engine, psql_environment):
@@ -26,18 +33,38 @@ def create_tables(engine, psql_environment):
2633
app.mount("/static", StaticFiles(directory=STATIC_PATH), name="static")
2734
app.mount("/media", StaticFiles(directory=MEDIA_PATH), name="media")
2835

36+
load_quotes.load_daily_quotes(next(get_db()))
37+
38+
# Configure logger
39+
logger = LoggerCustomizer.make_logger(config.LOG_PATH,
40+
config.LOG_FILENAME,
41+
config.LOG_LEVEL,
42+
config.LOG_ROTATION_INTERVAL,
43+
config.LOG_RETENTION_INTERVAL,
44+
config.LOG_FORMAT)
45+
app.logger = logger
46+
2947
app.include_router(profile.router)
3048
app.include_router(event.router)
3149
app.include_router(agenda.router)
50+
app.include_router(telegram.router)
3251
app.include_router(dayview.router)
3352
app.include_router(email.router)
3453
app.include_router(invitation.router)
54+
app.include_router(whatsapp.router)
3555
app.include_router(search.router)
3656

57+
telegram_bot.set_webhook()
58+
3759

60+
# TODO: I add the quote day to the home page
61+
# until the relavent calendar view will be developed.
3862
@app.get("/")
39-
async def home(request: Request):
63+
@app.logger.catch()
64+
async def home(request: Request, db: Session = Depends(get_db)):
65+
quote = daily_quotes.quote_per_day(db)
4066
return templates.TemplateResponse("home.html", {
4167
"request": request,
4268
"message": "Hello, World!",
69+
"quote": quote
4370
})

app/media/fake_user.png

-3.47 KB
Binary file not shown.

0 commit comments

Comments
 (0)