Skip to content

Commit a923103

Browse files
committed
Merge branch 'develop' of https://github.com/PythonFreeCourse/calendar into oron_exercise
2 parents fa5b4ee + 5c2136b commit a923103

File tree

103 files changed

+4761
-483
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+4761
-483
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ dmypy.json
147147
.pyre/
148148

149149
# mac env
150+
.DS_Store
150151
bin
151152

152153
# register stuff

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* PureDreamer - Developer
3434
* ShiZinDle - Developer
3535
* YairEn - Developer
36+
* LiranCaduri - Developer
3637
* IdanPelled - Developer
3738

3839
# Special thanks to

app/config.py.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ email_conf = ConnectionConfig(
6767
JWT_KEY = "JWT_KEY_PLACEHOLDER"
6868
JWT_ALGORITHM = "HS256"
6969
JWT_MIN_EXP = 60 * 24 * 7
70+
7071
templates = Jinja2Templates(directory=os.path.join("app", "templates"))
7172

7273
# application name

app/database/models.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
JSON,
1010
Boolean,
1111
Column,
12+
Date,
1213
DateTime,
1314
Enum,
1415
Float,
@@ -34,6 +35,16 @@
3435
Base: DeclarativeMeta = declarative_base()
3536

3637

38+
class UserFeature(Base):
39+
__tablename__ = "user_feature"
40+
41+
id = Column(Integer, primary_key=True, index=True)
42+
feature_id = Column('feature_id', Integer, ForeignKey('features.id'))
43+
user_id = Column('user_id', Integer, ForeignKey('users.id'))
44+
45+
is_enable = Column(Boolean, default=False)
46+
47+
3748
class User(Base):
3849
__tablename__ = "users"
3950

@@ -69,7 +80,10 @@ class User(Base):
6980
back_populates="user",
7081
)
7182
comments = relationship("Comment", back_populates="user")
83+
tasks = relationship(
84+
"Task", cascade="all, delete", back_populates="owner")
7285

86+
features = relationship("Feature", secondary=UserFeature.__tablename__)
7387
oauth_credentials = relationship(
7488
"OAuthCredentials",
7589
cascade="all, delete",
@@ -86,6 +100,7 @@ async def get_by_username(db: Session, username: str) -> User:
86100
return db.query(User).filter(User.username == username).first()
87101

88102

103+
89104
class UserExercise(Base):
90105
"""
91106
Table name user exercise
@@ -97,6 +112,18 @@ class UserExercise(Base):
97112
user_id = Column(Integer, ForeignKey("users.id"))
98113
start_date = Column(DateTime, nullable=False)
99114

115+
class Feature(Base):
116+
__tablename__ = "features"
117+
118+
id = Column(Integer, primary_key=True, index=True)
119+
name = Column(String, nullable=False)
120+
route = Column(String, nullable=False)
121+
creator = Column(String, nullable=True)
122+
description = Column(String, nullable=False)
123+
124+
users = relationship("User", secondary=UserFeature.__tablename__)
125+
126+
100127

101128
class Event(Base):
102129
__tablename__ = "events"
@@ -455,6 +482,20 @@ class WikipediaEvents(Base):
455482
date_inserted = Column(DateTime, default=datetime.utcnow)
456483

457484

485+
class CoronaStats(Base):
486+
__tablename__ = "corona_stats"
487+
488+
id = Column(Integer, primary_key=True, index=True)
489+
date_ = Column(DateTime, nullable=False)
490+
date_inserted = Column(DateTime, default=datetime.utcnow)
491+
vaccinated = Column(Integer, nullable=False)
492+
vaccinated_total = Column(Integer, nullable=False)
493+
vaccinated_population_perc = Column(Integer, nullable=False)
494+
vaccinated_second_dose = Column(Integer, nullable=False)
495+
vaccinated_second_dose_total = Column(Integer, nullable=False)
496+
vaccinated_second_dose_perc = Column(Float, nullable=False)
497+
498+
458499
class Quote(Base):
459500
__tablename__ = "quotes"
460501

@@ -463,6 +504,14 @@ class Quote(Base):
463504
author = Column(String)
464505

465506

507+
class Country(Base):
508+
__tablename__ = "countries"
509+
510+
id = Column(Integer, primary_key=True, index=True)
511+
name = Column(String, nullable=False, unique=True)
512+
timezone = Column(String, nullable=False)
513+
514+
466515
class Comment(Base):
467516
__tablename__ = "comments"
468517

@@ -549,6 +598,21 @@ class InternationalDays(Base):
549598
international_day = Column(String, nullable=False)
550599

551600

601+
class Task(Base):
602+
__tablename__ = "tasks"
603+
604+
id = Column(Integer, primary_key=True, index=True)
605+
title = Column(String, nullable=False)
606+
description = Column(String, nullable=False)
607+
is_done = Column(Boolean, default=False)
608+
is_important = Column(Boolean, nullable=False)
609+
date = Column(Date, nullable=False)
610+
time = Column(Time, nullable=False)
611+
owner_id = Column(Integer, ForeignKey("users.id"))
612+
613+
owner = relationship("User", back_populates="tasks")
614+
615+
552616
# insert language data
553617

554618

app/database/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def username_length(cls, username: str) -> Union[ValueError, str]:
7575
"""Validating username length is legal"""
7676
if not (MIN_FIELD_LENGTH < len(username) < MAX_FIELD_LENGTH):
7777
raise ValueError("must contain between 3 to 20 charactars")
78+
if username.startswith("@"):
79+
raise ValueError("username can not start with '@'")
7880
return username
7981

8082
@validator("password")

app/dependencies.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
templates = Jinja2Templates(directory=TEMPLATES_PATH)
1818
templates.env.add_extension("jinja2.ext.i18n")
1919

20+
2021
# Configure logger
2122
logger = LoggerCustomizer.make_logger(
2223
config.LOG_PATH,

app/internal/corona_stats.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import json
2+
import random
3+
from datetime import date, datetime
4+
from typing import Any, Dict
5+
6+
import httpx
7+
from fastapi import Depends
8+
from loguru import logger
9+
from sqlalchemy import desc, func
10+
from sqlalchemy.exc import SQLAlchemyError
11+
from sqlalchemy.orm import Session
12+
from sqlalchemy.orm.exc import NoResultFound
13+
14+
from app.database.models import CoronaStats
15+
from app.dependencies import get_db
16+
17+
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
18+
CORONA_API_URL = (
19+
"https://datadashboardapi.health.gov.il/api/queries/vaccinated"
20+
)
21+
USER_AGENT_OPTIONS = [
22+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5)"
23+
" AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
24+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0)"
25+
" Gecko/20100101 Firefox/77.0",
26+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) "
27+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
28+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) "
29+
"Gecko/20100101 Firefox/77.0",
30+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
31+
"AppleWebKit/537.36 (KHTML, like Gecko)"
32+
" Chrome/83.0.4103.97 Safari/537.36",
33+
]
34+
35+
36+
def create_stats_object(corona_stats_data: Dict[str, Any]) -> CoronaStats:
37+
"""Dict -> DB Object"""
38+
return CoronaStats(
39+
date_=datetime.strptime(
40+
corona_stats_data.get("Day_Date"),
41+
DATETIME_FORMAT,
42+
),
43+
vaccinated=corona_stats_data.get("vaccinated"),
44+
vaccinated_total=corona_stats_data.get("vaccinated_cum"),
45+
vaccinated_population_perc=corona_stats_data.get(
46+
"vaccinated_population_perc",
47+
),
48+
vaccinated_second_dose=corona_stats_data.get(
49+
"vaccinated_seconde_dose",
50+
),
51+
vaccinated_second_dose_total=corona_stats_data.get(
52+
"vaccinated_seconde_dose_cum",
53+
),
54+
vaccinated_second_dose_perc=corona_stats_data.get(
55+
"vaccinated_seconde_dose_population_perc",
56+
),
57+
)
58+
59+
60+
def serialize_stats(stats_object: CoronaStats) -> Dict[str, Any]:
61+
""" DB Object -> Dict """
62+
return {
63+
"vaccinated_second_dose_perc": (
64+
stats_object.vaccinated_second_dose_perc
65+
),
66+
"vaccinated_second_dose_total": (
67+
stats_object.vaccinated_second_dose_total
68+
),
69+
}
70+
71+
72+
def serialize_dict_stats(stats_dict: Dict[str, Any]) -> Dict[str, Any]:
73+
""" api Dict -> pylender Dict """
74+
return {
75+
"vaccinated_second_dose_perc": (
76+
stats_dict.get("vaccinated_seconde_dose_population_perc")
77+
),
78+
"vaccinated_second_dose_total": (
79+
stats_dict.get("vaccinated_seconde_dose_cum")
80+
),
81+
}
82+
83+
84+
def save_corona_stats(
85+
corona_stats_data: Dict[str, Any],
86+
db: Session = Depends(get_db),
87+
) -> None:
88+
db.add(create_stats_object(corona_stats_data))
89+
db.commit()
90+
91+
92+
def insert_to_db_if_needed(
93+
corona_stats_data: Dict[str, Any],
94+
db: Session = Depends(get_db),
95+
) -> Dict[str, Any]:
96+
""" gets the latest data inserted to gov database """
97+
latest_date = datetime.strptime(
98+
corona_stats_data.get("Day_Date"),
99+
DATETIME_FORMAT,
100+
)
101+
latest_saved = None
102+
try:
103+
latest_saved = (
104+
db.query(CoronaStats).order_by(desc(CoronaStats.date_)).one()
105+
)
106+
except NoResultFound:
107+
# on first system load, the table is empty
108+
save_corona_stats(corona_stats_data, db)
109+
return corona_stats_data
110+
111+
if latest_saved is not None:
112+
# on more recent data arrival, we update the database
113+
if latest_saved.date_ < latest_date:
114+
save_corona_stats(corona_stats_data, db)
115+
return corona_stats_data
116+
else:
117+
return serialize_stats(latest_saved)
118+
119+
120+
async def get_vacinated_data() -> Dict[str, Any]:
121+
async with httpx.AsyncClient() as client:
122+
headers = {"User-Agent": random.choice(USER_AGENT_OPTIONS)}
123+
res = await client.get(CORONA_API_URL, headers=headers)
124+
return json.loads(res.text)[-1]
125+
126+
127+
def get_vacinated_data_from_db(db: Session = Depends(get_db)) -> CoronaStats:
128+
# pulls once a day, it won't be the most updated data
129+
# but we dont want to be blocked for too many requests
130+
return (
131+
db.query(CoronaStats)
132+
.filter(func.date(CoronaStats.date_inserted) == date.today())
133+
.one()
134+
)
135+
136+
137+
async def get_corona_stats(db: Session = Depends(get_db)) -> Dict[str, Any]:
138+
try:
139+
db_data = get_vacinated_data_from_db(db)
140+
corona_stats_data = serialize_stats(db_data)
141+
142+
except NoResultFound:
143+
try:
144+
response_data = await get_vacinated_data()
145+
insert_to_db_if_needed(response_data, db)
146+
corona_stats_data = serialize_dict_stats(response_data)
147+
except json.decoder.JSONDecodeError:
148+
corona_stats_data = {"error": "No data"}
149+
150+
except (SQLAlchemyError, AttributeError) as e:
151+
logger.exception(f"corona stats failed with error: {e}")
152+
corona_stats_data = {"error": "No data"}
153+
return corona_stats_data

0 commit comments

Comments
 (0)