Skip to content

Commit 58db62c

Browse files
committed
Merge branch 'develop' of https://github.com/PythonFreeCourse/calendar into feature/past-invites-to-same-events
2 parents aae5d7a + 4df85bc commit 58db62c

File tree

105 files changed

+2785
-697
lines changed

Some content is hidden

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

105 files changed

+2785
-697
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This workflow will extract new translatable strings from files under /app and /tests into a base.pot file,
2+
# and update the 'en' and 'he' base.po and base.mo files.
3+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
4+
5+
name: update-translations
6+
7+
on:
8+
# Trigger the workflow on push request,
9+
# but only for the main branch
10+
push:
11+
branches:
12+
- main
13+
14+
jobs:
15+
update-translations:
16+
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v2
21+
22+
- name: Set up Python 3.x
23+
uses: actions/setup-python@v2
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Install prerequesits
28+
run: |
29+
python -m pip install --upgrade pip
30+
pip install Babel Jinja2
31+
32+
- name: Create base.pot file
33+
run: pybabel extract --mapping-file=app/babel_mapping.ini app tests -o app/locales/base.pot -c i18n
34+
35+
- name: Update all language base.po files
36+
run: pybabel update -i app/locales/base.pot -d app/locales -D base
37+
38+
- name: Update all .mo files
39+
run: pybabel compile -d app/locales -D base
40+
41+
# https://github.com/stefanzweifel/git-auto-commit-action
42+
- name: Commit changes
43+
uses: stefanzweifel/git-auto-commit-action@v4
44+
with:
45+
commit_message: Apply automatic translatable string changes

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,5 @@ app/.vscode/
148148

149149
# PyCharm
150150
.idea
151+
152+
junit/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ uvicorn app.main:app --reload
3535
python -m venv venv
3636
source venv/bin/activate
3737
pip install -r requirements.txt
38-
cp app/config.py.example app/configuration.py
38+
cp app/config.py.example app/config.py
3939
# Edit the variables' values.
4040
uvicorn app.main:app --reload
4141
```

app/babel_mapping.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Extraction from Python source files
2+
3+
[python: **.py]
4+
5+
# Extraction from Jinja2 HTML and text templates
6+
7+
[jinja2: **/templates/**.html]
8+
extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_

app/config.py.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ MEDIA_DIRECTORY = 'media'
2727
PICTURE_EXTENSION = '.png'
2828
AVATAR_SIZE = (120, 120)
2929

30+
31+
# DEFAULT WEBSITE LANGUAGE
32+
WEBSITE_LANGUAGE = "en"
33+
3034
# API-KEYS
35+
# Get a free API KEY for Astronomy feature @ www.weatherapi.com/signup.aspx
36+
ASTRONOMY_API_KEY = os.getenv('ASTRONOMY_API_KEY')
3137
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
3238

3339
# EXPORT

app/database/models.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from datetime import datetime
44
from typing import Dict, Any
55

6+
7+
from app.config import PSQL_ENVIRONMENT
8+
from app.database.database import Base
69
from sqlalchemy import (DDL, Boolean, Column, DateTime, ForeignKey, Index,
7-
Integer, String, event, UniqueConstraint)
10+
Integer, String, event, UniqueConstraint, JSON)
811
from sqlalchemy.dialects.postgresql import TSVECTOR
912
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
1013
from sqlalchemy.orm import relationship, Session
1114

12-
from app.config import PSQL_ENVIRONMENT
13-
from app.database.database import Base
1415
from app.dependencies import logger
1516

1617

@@ -41,7 +42,7 @@ class User(Base):
4142
avatar = Column(String, default="profile.png")
4243
telegram_id = Column(String, unique=True)
4344
is_active = Column(Boolean, default=False)
44-
45+
language_id = Column(Integer, ForeignKey("languages.id"))
4546
events = relationship("UserEvent", back_populates="participants")
4647

4748
def __repr__(self):
@@ -80,6 +81,13 @@ def __repr__(self):
8081
return f'<Event {self.id}>'
8182

8283

84+
class Language(Base):
85+
__tablename__ = "languages"
86+
87+
id = Column(Integer, primary_key=True, index=True)
88+
name = Column(String, unique=True, nullable=False)
89+
90+
8391
class Category(Base):
8492
__tablename__ = "categories"
8593

@@ -153,9 +161,38 @@ def __repr__(self):
153161
)
154162

155163

164+
class WikipediaEvents(Base):
165+
__tablename__ = "wikipedia_events"
166+
167+
id = Column(Integer, primary_key=True, index=True)
168+
date_ = Column(String, nullable=False)
169+
wikipedia = Column(String, nullable=False)
170+
events = Column(JSON, nullable=True)
171+
date_inserted = Column(DateTime, default=datetime.utcnow)
172+
173+
156174
class Quote(Base):
157175
__tablename__ = "quotes"
158176

159177
id = Column(Integer, primary_key=True, index=True)
160178
text = Column(String, nullable=False)
161179
author = Column(String)
180+
181+
182+
class Zodiac(Base):
183+
__tablename__ = "zodiac-signs"
184+
185+
id = Column(Integer, primary_key=True, index=True)
186+
name = Column(String, nullable=False)
187+
start_month = Column(Integer, nullable=False)
188+
start_day_in_month = Column(Integer, nullable=False)
189+
end_month = Column(Integer, nullable=False)
190+
end_day_in_month = Column(Integer, nullable=False)
191+
192+
def __repr__(self):
193+
return (
194+
f'<Zodiac '
195+
f'{self.name} '
196+
f'{self.start_day_in_month}/{self.start_month}-'
197+
f'{self.end_day_in_month}/{self.end_month}>'
198+
)

app/dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from app import config
77
from app.internal.logger_customizer import LoggerCustomizer
88

9-
109
APP_PATH = os.path.dirname(os.path.realpath(__file__))
1110
MEDIA_PATH = os.path.join(APP_PATH, config.MEDIA_DIRECTORY)
1211
STATIC_PATH = os.path.join(APP_PATH, "static")
1312
TEMPLATES_PATH = os.path.join(APP_PATH, "templates")
1413

1514
templates = Jinja2Templates(directory=TEMPLATES_PATH)
15+
templates.env.add_extension('jinja2.ext.i18n')
1616

1717
# Configure logger
1818
logger = LoggerCustomizer.make_logger(config.LOG_PATH,

app/internal/astronomy.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import datetime
2+
import functools
3+
import httpx
4+
from typing import Dict
5+
6+
from app import config
7+
8+
9+
# This feature requires an API KEY - get yours free @ www.weatherapi.com
10+
11+
ASTRONOMY_URL = "http://api.weatherapi.com/v1/astronomy.json"
12+
NO_API_RESPONSE = "No response from server"
13+
14+
15+
@functools.lru_cache(maxsize=128, typed=False)
16+
async def get_data_from_api(formatted_date: str, location: str)\
17+
-> Dict[str, int]:
18+
""" get the relevant astronomical data by calling the "weather api" API.
19+
Args:
20+
formatted_date (date) - relevant date.
21+
location (str) - location name.
22+
Returns:
23+
response_json (json dict) including:
24+
relevant part (data / error) of the JSON returned by the API.
25+
Success (bool)
26+
ErrorDescription (str) - error message.
27+
"""
28+
input_query_string = {'key': config.ASTRONOMY_API_KEY, 'q': location,
29+
'dt': formatted_date}
30+
output = {}
31+
try:
32+
async with httpx.AsyncClient() as client:
33+
response = await client.get(ASTRONOMY_URL,
34+
params=input_query_string)
35+
except httpx.HTTPError:
36+
output["Success"] = False
37+
output["ErrorDescription"] = NO_API_RESPONSE
38+
return output
39+
if response.status_code != httpx.codes.OK:
40+
output["Success"] = False
41+
output["ErrorDescription"] = NO_API_RESPONSE
42+
return output
43+
output["Success"] = True
44+
try:
45+
output.update(response.json()['location'])
46+
return output
47+
except KeyError:
48+
output["Success"] = False
49+
output["ErrorDescription"] = response.json()['error']['message']
50+
return output
51+
52+
53+
async def get_astronomical_data(requested_date: datetime.datetime,
54+
location: str) -> Dict[str, int]:
55+
""" get astronomical data (Sun & Moon) for date & location -
56+
main function.
57+
Args:
58+
requested_date (date) - date requested for astronomical data.
59+
location (str) - location name.
60+
Returns: dictionary with the following entries:
61+
Status - success / failure.
62+
ErrorDescription - error description (relevant only in case of error).
63+
location - relevant location values(relevant only in case of success).
64+
name, region, country, lat, lon etc.
65+
astronomy - relevant astronomy values, all time in local time -
66+
(relevant only in case of success):
67+
sunrise, sunset, moonrise, moonset, moon_phase, moon_illumination.
68+
"""
69+
formatted_date = requested_date.strftime('%Y-%m-%d')
70+
return await get_data_from_api(formatted_date, location)

app/internal/quotes/daily_quotes.py renamed to app/internal/daily_quotes.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import date
2-
from typing import Optional
2+
from typing import Dict, Optional
33

44
from app.database.models import Quote
55

@@ -9,6 +9,15 @@
99
TOTAL_DAYS = 366
1010

1111

12+
def create_quote_object(quotes_fields: Dict[str, Optional[str]]) -> Quote:
13+
"""This function create a quote object from given fields dictionary.
14+
It is used for adding the data from the json into the db"""
15+
return Quote(
16+
text=quotes_fields['text'],
17+
author=quotes_fields['author']
18+
)
19+
20+
1221
def quote_per_day(
1322
session: Session, date: date = date.today()
1423
) -> Optional[Quote]:

app/internal/json_data_loader.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import json
2+
import os
3+
from typing import Dict, List, Callable, Union
4+
5+
from loguru import logger
6+
from sqlalchemy.orm import Session
7+
8+
from app.database.database import Base
9+
from app.database.models import Quote, Zodiac
10+
from app.internal import daily_quotes, zodiac
11+
12+
13+
def get_data_from_json(path: str) -> List[Dict[str, Union[str, int, None]]]:
14+
"""This function reads all of the data from a specific JSON file.
15+
The json file consists of list of dictionaries"""
16+
try:
17+
with open(path, 'r') as f:
18+
json_content_list = json.load(f)
19+
except (IOError, ValueError):
20+
file_name = os.path.basename(path)
21+
logger.exception(
22+
f"An error occurred during reading of json file: {file_name}")
23+
return []
24+
return json_content_list
25+
26+
27+
def is_table_empty(session: Session, table: Base) -> bool:
28+
return session.query(table).count() == 0
29+
30+
31+
def load_data(
32+
session: Session, path: str,
33+
table: Base, object_creator_function: Callable) -> None:
34+
"""This function loads the specific data to the db,
35+
if it wasn't already loaded"""
36+
if not is_table_empty(session, table):
37+
return None
38+
json_objects_list = get_data_from_json(path)
39+
objects = [
40+
object_creator_function(json_object)
41+
for json_object in json_objects_list]
42+
session.add_all(objects)
43+
session.commit()
44+
45+
46+
def load_to_db(session) -> None:
47+
"""This function loads all the data for features
48+
based on pre-defind json data"""
49+
load_data(
50+
session, 'app/resources/zodiac.json',
51+
Zodiac, zodiac.create_zodiac_object)
52+
load_data(
53+
session, 'app/resources/quotes.json',
54+
Quote, daily_quotes.create_quote_object)
55+
"""The quotes JSON file content is copied from the free API:
56+
'https://type.fit/api/quotes'. I saved the content so the API won't be
57+
called every time the app is initialized."""

0 commit comments

Comments
 (0)