Skip to content

Commit

Permalink
Merge pull request #5 from meirtolpin11/feature/refactor
Browse files Browse the repository at this point in the history
Feature | use jinja2 and sort flights by discounted price
  • Loading branch information
meirtolpin11 authored Nov 10, 2023
2 parents 723a715 + 8348668 commit 31f6e20
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 160 deletions.
26 changes: 2 additions & 24 deletions database.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def prepare_flights_per_city(scan_timestamp, **query_params):
)
.where(user_where_cause & (Flights.date_of_scan == scan_timestamp))
.group_by(Flights.fly_to, Flights.fly_from)
.order_by(Flights.price)
.order_by(Flights.discount_price)
)

return flights
Expand Down Expand Up @@ -102,7 +102,7 @@ def prepare_single_destination_flights(scan_timestamp, **query_params):
Flights.holiday_name,
)
.where((Flights.date_of_scan == scan_timestamp) & user_where_cause)
.order_by(Flights.price)
.order_by(Flights.discount_price)
)

flights = flights.limit(5)
Expand Down Expand Up @@ -136,26 +136,4 @@ def get_special_date_flights(scan_timestamp):

return flights


# def prepare_userquery_flights(chat_id):
#
# query_details = UserQueryDetails.select().where(UserQueryDetails.chat_id == chat_id)
# if len(query_details) == 0:
# return []
#
# for query in query_details:
# flights = Flights.select(Flights.fly_from, Flights.fly_to, Flights.price, Flights.discount_price, Flights.airlines,
# Flights.flight_numbers, Flights.nights,
# Flights.days_off, Flights.departure_to,
# Flights.arrival_to, Flights.departure_from, Flights.arrival_from,
# Flights.link_to, Flights.link_from, Flights.holiday_name).where(
# Flights.fly_from == query.fly_from,
# Flights.fly_to == query.fly_to,
# query.min_nights <= Flights.nights <= query.max_nights,
# Flights.departure_to >= query.date_from,
# Flights.arrival_from <= query.date_to,
# Flights.price <= query.max_price
# )


db.create_tables([Flights, IATA, UserQueryDetails])
72 changes: 39 additions & 33 deletions engine/kiwi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,25 @@
import holidays
import requests
import database as db
from typing import Tuple
from dataclasses import dataclass
from datetime import datetime, timedelta


@dataclass
class Holiday:
start: Tuple[datetime, str]
end: Tuple[datetime, str]
name: str


# noinspection PyBroadException
def query_flight_kiwi(search_params):
try:
response = requests.get(
return requests.get(
config.URL, params=search_params, headers=config.HEADERS
)
return response.json()
except Exception as e:
).json()
except Exception:
return -1


Expand All @@ -36,36 +44,30 @@ def generate_holidays_flights(
) -> None:
country_holidays = holidays.country_holidays(country, years=date_to.year)

holiday_start = None
prev_holiday = None
prev_holiday_name = None
holiday = None
for _holiday in country_holidays.items():
if not (date_from.date() < _holiday[0] < date_to.date()):
continue
if not holiday:
holiday = Holiday(_holiday, _holiday, _holiday[1].split("-")[0]) # type: ignore

if not prev_holiday:
holiday_start = _holiday
prev_holiday = _holiday
prev_holiday_name = _holiday[1].split("-")[0]

elif (_holiday[0] - prev_holiday[0]).days != 1:
elif (_holiday[0] - holiday.end[0]).days != 1:
generate_flights(
holiday_start[0] - timedelta(days=1),
prev_holiday[0] + timedelta(days=1),
holiday.start[0] - timedelta(days=1),
holiday.end[0] + timedelta(days=1),
fly_to,
price_to,
nights_in_dst_from,
nights_in_dst_to,
scan_timestamp,
holiday_name=prev_holiday_name,
holiday_name=holiday.name,
)

holiday_start = _holiday
prev_holiday = _holiday
prev_holiday_name = _holiday[1].split("-")[0]

holiday = Holiday(_holiday, _holiday, _holiday[1].split("-")[0])
if not (date_from.date() < _holiday[0] < date_to.date()):
logging.info(
f"Stopping holiday generation - out of range - {holiday.name}"
)
break
else:
prev_holiday = _holiday
holiday.end = _holiday


def generate_special_date(
Expand All @@ -76,9 +78,9 @@ def generate_special_date(

flights = query_flight_kiwi(kiwi_api_params)
if flights == -1:
logging.error("Unable to fetch flights from KIWI")
return

# flights data
try:
flights_data = flights["data"]
except Exception as e:
Expand Down Expand Up @@ -137,14 +139,18 @@ def get_airline_links(flight, is_round=True):
links = ["", ""]
if len(set(airlines_list)) == 1:
if hasattr(airlines, airlines_list[0].lower().replace(" ", "_")):
airline_class = getattr(airlines, airlines_list[0].lower().replace(" ", "_"))
airline_class = getattr(
airlines, airlines_list[0].lower().replace(" ", "_")
)
if airline_class:
links[0] = airline_class.generate_link(
flight["flyFrom"],
flight["flyTo"],
get_date(flight["route"][0], "local_departure"),
get_date(flight["route"][1], "local_departure") if is_round else None,
is_round=is_round
get_date(flight["route"][1], "local_departure")
if is_round
else None,
is_round=is_round,
)
if is_round:
links[1] = links[0]
Expand Down Expand Up @@ -172,15 +178,13 @@ def parse_and_save_flight(flight, scan_timestamp, special_date=False, holiday_na
# parse the relevant flight information
source = f"{flight['countryFrom']['name']}/{flight['cityFrom']}/{flight['flyFrom']}"
dest = f"{flight['countryTo']['name']}/{flight['cityTo']}/{flight['flyTo']}"
price = int(flight["price"]) - 25 # usually their price is around 25 NIS higher
price = int(flight["price"]) - 25 # usually their price is around 25 NIS higher

airlines_list = [
airlines.get_airline_name(route["airline"]) for route in flight["route"]
]

airline_class = None
discount_price = price

# get discount price is available
if len(set(airlines_list)) == 1:
# check that there is an airline class
Expand All @@ -190,7 +194,9 @@ def parse_and_save_flight(flight, scan_timestamp, special_date=False, holiday_na
)
# check there is a discount calculation function
if hasattr(airline_class, "calculate_discount_price"):
discount_price = airline_class.calculate_discount_price(price, is_round=is_round)
discount_price = airline_class.calculate_discount_price(
price, is_round=is_round
)

links = get_airline_links(flight, is_round)

Expand Down Expand Up @@ -248,7 +254,7 @@ def generate_flights(
scan_timestamp: int = int(datetime.timestamp(datetime.now())),
holiday_name="",
):
if date_to is None or date_from is None:
if not all([date_to, date_from]):
return

kiwi_api_params = prepare_kiwi_api(
Expand Down
136 changes: 33 additions & 103 deletions telegram/bot.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import os
import requests
import config
import helpers
import airports
import requests
import database as db
from datetime import datetime
from jinja2 import Environment, FileSystemLoader

# Create a Jinja2 environment and specify the template directory
env = Environment(loader=FileSystemLoader("telegram/jinja"))


def generate_cheapest_flights(scan_timestamp):
Expand All @@ -23,7 +27,7 @@ def publish_special_date_report(chat_id, scan_timestamp):
special_dates = db.get_special_date_flights(scan_timestamp)
cheapest_flights_report = generate_message(special_dates)
if config.publish:
send_message_to_chat(cheapest_flights_report, chat_id)
status_code = send_message_to_chat(cheapest_flights_report, chat_id)

return cheapest_flights_report

Expand Down Expand Up @@ -61,7 +65,7 @@ def publish_default_report(
continue

# send the message and the query
if config.publish and message and output_path:
if all([config.publish, message, output_path]):
send_message_to_chat(message, chat_id)
send_file_to_chat(output_path, "", chat_id)

Expand Down Expand Up @@ -136,121 +140,47 @@ def generate_report_per_month(


def generate_message(query):
# Load the template from the file
template = env.get_template("flight.jinja2")

# TODO: use jinja template
message = []
for flight in query:
if flight.fly_from.split('/')[1] != 'Tel Aviv':
flight_line = f"\n<b>{flight.fly_from.split('/')[1]} -> {flight.fly_to.split('/')[1]}</b> <i>({flight.fly_from.split('/')[0]} -> {flight.fly_to.split('/')[0]})</i>"
else:
flight_line = f"\n<b>{flight.fly_to.split('/')[1]}</b> <i>({flight.fly_to.split('/')[0]})</i>"
message.append(flight_line)

# generate departure and arrival dates lines

if flight.holiday_name != "":
message.append(f"\N{star of David} {flight.holiday_name} \N{star of David}")

dates_line = ""
if flight.arrival_from:
if flight.departure_to.month == flight.arrival_from.month:
dates_line += (
f"\N{calendar} \t<b>{flight.departure_to.strftime('%d/%m %H:%M')} - "
f"{flight.arrival_from.strftime('%d/%m %H:%M')}</b>"
)
else:
dates_line += (
f"\N{calendar} \t<b>{flight.departure_to.strftime('%d/%m %H:%M')} - "
f"{flight.arrival_from.strftime('%d/%m %H:%M')}</b>"
)
dates_line += (
f"<i>({flight.departure_to.strftime('%a')} - "
f"{flight.arrival_from.strftime('%a')})</i> \N{calendar}"
)
else:
dates_line += (
f"\N{calendar} \t<b>{flight.departure_to.strftime('%d/%m %H:%M')} - {flight.arrival_to.strftime('%d/%m %H:%M')}</b>"
)

message.append(dates_line)

# generate flight confirmation line
if hasattr(airports, flight.fly_from.split("/")[2].lower()):
airport_helper = getattr(airports, flight.fly_from.split("/")[2].lower())

try:
if config.CHECK_FLIGHT_CONFIRMATION:
is_flight_confirmed = airport_helper.get_flight_confirmation(
flight.flight_numbers.split(",")[0], flight.departure_to
)
else:
is_flight_confirmed = -2
except:
except Exception:
is_flight_confirmed = -2

if is_flight_confirmed == -1:
# if returned -1 that means that flight are found but the flight is missing
message.append(
f"\N{cross mark} Flight - {flight.flight_numbers.split(',')[0]} maybe is not confirmed"
)
elif is_flight_confirmed == -2 or is_flight_confirmed == -3:
# if returned -2 that means that no flight returned from iaa api - can be an error
pass
else:
# flight is found and it's confirmed
message.append(
f"\N{white heavy check mark} Flight - {flight.flight_numbers.split(',')[0]} is confirmed"
)

if flight.price == flight.discount_price:
message.append(f"\t\N{money bag} <b>{flight.price} nis</b> \N{money bag}")
else:
message.append(
f"\t\N{money bag} <b>{flight.price} nis, "
f"<i>Members: {flight.discount_price} nis</i></b> \N{money bag}"
)

# generate airlines and links line
message += generate_airline_link(flight)
return "\n".join(message)


def generate_airline_link(flight):
message = []

if len(flight.airlines.split(",")) == 2:
if flight.airlines.split(",")[0] == flight.airlines.split(",")[1]:
# in case of the same airline
if flight.link_to == flight.link_from == "":
# in case there is no link for this airline
message.append(
f"\t\N{airplane} {flight.airlines.split(',')[0]} \N{airplane}"
)
else:
message.append(
f"\t\N{airplane} <a href='{flight.link_to}'>{flight.airlines.split(',')[0]}</a> \N{airplane}"
)
else:
# different airlines
links_line = ""

# departure leg
if flight.link_to != "":
links_line += f"\t\N{airplane} <a href='{flight.link_to}'>{flight.airlines.split(',')[0]}</a>, "
else:
links_line += f"\t\N{airplane} {flight.airlines.split(',')[0]}, "

# arrival leg
if flight.link_from != "":
links_line += f"<a href='{flight.link_from}'>{flight.airlines.split(',')[1]}</a> \N{airplane}"
else:
links_line += f"{flight.airlines.split(',')[1]} \N{airplane}"

message.append(links_line)
else:
links_line = f"\t\N{airplane} <a href='{flight.link_to}'>{flight.airlines.split(',')[0]}</a>"
message.append(links_line)

return message
is_flight_confirmed = -1

message += [
template.render(
fly_from=flight.fly_from,
fly_to=flight.fly_to,
holiday=flight.holiday_name,
round=bool(flight.arrival_from),
takeoff_to=flight.departure_to,
landing_back=flight.arrival_from,
landing_to=flight.arrival_to,
flight_confirmed=is_flight_confirmed,
flight_numbers=flight.flight_numbers,
price=flight.price,
discounted_price=flight.discount_price,
airlines=flight.airlines.split(","),
link_to=flight.link_to,
link_from=flight.link_from,
).strip()
]

return "\n\n".join(message)


def send_message_to_chat(message_to_send, chat_id):
Expand Down
Loading

0 comments on commit 31f6e20

Please sign in to comment.