This commit is contained in:
ada-dmitry
2024-09-13 00:10:39 +03:00
commit 5ea57e2f4a
1626 changed files with 289568 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
dbname: Test # В классе менять не надо
user: postgres # Замените на свой логин от БД в классе
password: Qwerty # Замените на свой пароль от БД в классе
host: localhost # Замените на 192.168.0.48 в классе
dbtableprefix: "public." # Замените на свой логин от БД
+61
View File
@@ -0,0 +1,61 @@
import psycopg2
class DbConnection:
'''config это класс, который находится в файле config.yaml'''
def __init__(self, config):
'''это метод, который вызывается при создании объекта на основе класса DbConnection,
и в нём задаются все ключевые параметры'''
self.dbname = config.dbname
self.user = config.user
self.password = config.password
self.host = config.host
self.prefix = config.dbtableprefix
''' conn - это переменная/атрибут'''
''' self.conn - это мы создаем атрибутт conn для ЭТОГО объекта и он хранит информацию о соединении с бд'''
self.conn = psycopg2.connect(dbname = self.dbname,
user = self.user,
password = self.password,
host = self.host)
def __del__(self):
if self.conn:
self.conn.close()
# def dif_stations(self):
# '''cursor - это посредник между питоном и бд'''
# cur = self.conn.cursor()
# cur.execute("DROP TABLE IF EXISTS stations CASCADE")
# cur.execute("CREATE TABLE stations(st_id serial, st_name varchar(60), tarrif_zone_id integer, st_index integer)")
# cur.execute("INSERT INTO stations(st_id, st_name, tarrif_zone_id, st_index) VALUES(1, 'Нахабино', 1,1)")
# self.conn.commit()
# cur.execute("SELECT * FROM stations")
# result = cur.fetchall()
# cur.execute("DROP TABLE stations")
# self.conn.commit()
# return (result[0][0] == 1)
# def dif_routes(self):
# cur = self.conn.cursor()
# cur.execute("DROP TABLE IF EXISTS routes CASCADE")
# cur.execute("CREATE TABLE routes(route_id serial, first_st_id integer, last_st_id integer)")
# cur.execute("INSERT INTO routes(route_id, first_st_id, last_st_id) VALUES(1,1,2)")
# self.conn.commit()
# cur.execute("SELECT * FROM routes")
# result = cur.fetchall()
# cur.execute("DROP TABLE routes")
# self.conn.commit()
# return (result[0][0] == 1)
def test(self):
'''cursor - это посредник между питоном и бд'''
cur = self.conn.cursor()
cur.execute("DROP TABLE IF EXISTS test CASCADE")
cur.execute("CREATE TABLE test(test integer)")
cur.execute("INSERT INTO test(test) VALUES(1)")
self.conn.commit()
cur.execute("SELECT * FROM test")
result = cur.fetchall()
# cur.execute("DROP TABLE test")
self.conn.commit()
return (result[0][0] == 1)
+84
View File
@@ -0,0 +1,84 @@
from dbconnection import *
class DbTable:
dbconn = None
def __init__(self):
return
def table_name(self):
return self.dbconn.prefix + "table"
def columns(self):
return {"test": ["integer", "PRIMARY KEY"]}
def column_names(self):
return sorted(self.columns().keys(), key = lambda x: x)
def primary_key(self):
return ['id']
def column_names_without_id(self):
res = sorted(self.columns().keys(), key = lambda x: x)
if 'id' in res:
res.remove('id')
return res
def table_constraints(self):
return []
def create(self):
sql = "CREATE TABLE " + self.table_name() + "("
arr = [k + " " + " ".join(v) for k, v in sorted(self.columns().items(), key = lambda x: x[0])]
sql += ", ".join(arr + self.table_constraints())
sql += ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def drop(self):
sql = "DROP TABLE IF EXISTS " + self.table_name()
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def insert_one(self, vals):
for i in range(0, len(vals)):
if type(vals[i]) == str:
vals[i] = "'" + vals[i] + "'"
else:
vals[i] = str(vals[i])
sql = "INSERT INTO " + self.table_name() + "("
sql += ", ".join(self.column_names_without_id()) + ") VALUES("
sql += ", ".join(vals) + ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def first(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def last(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join([x + " DESC" for x in self.primary_key()])
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def all(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
+16
View File
@@ -0,0 +1,16 @@
def validate_input(prompt, min_value, max_value):
"""Проверка ввода для выбора номера строки
"""
while True:
user_input = input(prompt)
try:
value = int(user_input)
if min_value <= value <= max_value and value != 0:
return value
elif value == 0:
return -1
else:
print(f"Введенное значение должно быть в диапазоне от {min_value} до {max_value}")
except ValueError:
print("Введенное значение должно быть числом.")
+249
View File
@@ -0,0 +1,249 @@
import sys
import functions
sys.path.append('tables')
from project_config import *
from dbconnection import *
from tables.stations_table import *
from tables.routes_table import *
class Main:
config = ProjectConfig()
connection = DbConnection(config)
def __init__(self):
DbTable.dbconn = self.connection
return
def db_init(self):
st = StationTable()
rt = RoutesTable()
st.create()
rt.create()
return
def db_insert_somethings(self):
st = StationTable()
rt = RoutesTable()
st.example_insert()
rt.example_insert()
def db_drop(self):
rt = RoutesTable()
st = StationTable()
rt.drop()
st.drop()
return
def show_main_menu(self):
menu = """Добро пожаловать!
Основное меню (выберите цифру в соответствии с необходимым действием):
1 - просмотр станций;
2 - сброс и инициализация таблиц;
10 - выход."""
print(menu)
return
def read_next_step(self):
return input("=> ").strip()
def after_main_menu(self, next_step):
if next_step == "2":
self.db_drop()
self.db_init()
self.db_insert_somethings()
print("Таблицы созданы заново!")
return "0"
elif next_step != "1" and next_step != "10":
print("Выбрано неверное число! Повторите ввод!")
return "0"
else:
return next_step
def show_stations(self):
self.st_id = -1
self.st_arr = []
self.max_name_len = 0
lst = StationTable().all()
self.max_st_index = len(lst)
for i in lst:
self.st_arr.append([str(i[2]), str(i[3]), str(i[1])])
self.max_name_len = max(self.max_name_len, len(str(i[2])))
menu = f"""Просмотр списка станций.
\tНазвание {" "*(self.max_name_len-4)}Тарифная зона{" " *3}Индекс\n\
----------------------------------------------------------------"""
print(menu)
for i in range(len(lst)):
txt = str(i+1) + "\t"
txt += self.st_arr[i][0] + " "*(4+self.max_name_len-len(self.st_arr[i][0]))
txt += self.st_arr[i][1] + " "*(13 - len(self.st_arr[i][1]) + 3) + self.st_arr[i][2]
print(txt)
menu = """Дальнейшие операции:
0 - возврат в главное меню;
4 - удаление станции;
5 - добавление станции;
6 - изменение названия станций;
7 - просмотр маршрутов;
9 - добавление маршрута(с использованием станций);
10 - выход."""
print(menu)
return
def after_show_stations(self, next_step):
while True:
if next_step == "4":#delete station
x = functions.validate_input('Введите номер удаляемой станции (0 - для отмены): ', 0, self.max_st_index)
if(x!=-1):
StationTable().delete(self.st_arr[int(x)-1])
print('Станция успешно удалена')
return "1"
elif next_step == "5":#insert station
StationTable().insert()
print('Станция успешно добавлена')
return "1"
elif next_step == "6":#update station
x = functions.validate_input('Введите номер изменяемой станции (0 - для отмены): ', 0, self.max_st_index)
if(x!=-1):
StationTable().update(self.st_arr[int(x)-1])
print('Станция успешно изменена')
return "1"
elif(next_step == "8"): # delete route
if(self.st_id != -1):
x = functions.validate_input('Введите номер удаляемого маршрута (0 - для отмены): ', 0, self.max_rt_index)
if(x!=-1):
RoutesTable().delete(self.rt_arr[int(x)-1])
print('Маршрут успешно удален')
else:
next_step = "7"
return "1"
else:
print("Необходимо выбрать станцию")
return "1"
elif next_step == "7":#show routes
self.show_routes_with_station()
elif next_step == "9":#insert route
RoutesTable().insert(self.max_st_index)
print('Маршрут успешно добавлен')
return "1"
elif next_step != "0" and next_step != "10":
print("Выбрано неверное число! Повторите ввод!")
return "1"
else:
return next_step
def show_routes_with_station(self):
"""Вывод всех маршрутов по введенной станции
"""
self.routes_arr = []
if self.st_id == -1:
while True:
t = functions.validate_input('Вывести маршрут по:\
\n1 - начальной станции;\
\n2 - конечной станции;\n', 0, 2)
if t == 1:
x = functions.validate_input('Выберите номер интересуемой строки с начальной станцией (0 - отмена): ', 0, self.max_st_index)
if(x==-1):
return
else:
self.st_id = StationTable().find_by_name(self.st_arr[x-1][0])
self.st_obj = self.st_arr[x-1][0]
lst = RoutesTable().all_by_st_id(self.st_id, 1)
self.max_rt_index = len(lst)
if lst == []:
print('Нет маршрута с такой начальной станцией')
else:
print("Выбрана station: " + self.st_obj)
print("Маршруты:")
print("\tНомер маршрута\tВыбранная станция\tКонечная станция\
\n--------------------------------------------------------")
for i in lst:
self.routes_arr.append([str(i[0]), str(i[2]), i[1]])
for i in range(len(self.routes_arr)):
output = f"""{str(i+1)}\t{str(self.routes_arr[i][2])} \t\t\
{self.st_obj}\t\t{str(StationTable().name_by_id(self.routes_arr[i][1])[0])}"""
print(output)
menu = """Дальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр станций;
8 - удаление маршрута;
10 - выход."""
print(menu)
return self.read_next_step()
elif t == 2:
x = functions.validate_input('Выберите номер интересуемой строки с конечной станцией (0 - отмена): ', 0, self.max_st_index)
if(x==0):
return
else:
self.st_id = StationTable().find_by_name(self.st_arr[x-1][0])
self.st_obj = self.st_arr[x-1][0]
lst = RoutesTable().all_by_st_id(self.st_id, 2)
self.max_rt_index = len(lst)
if lst == []:
print('Нет маршрута с такой конечной станцией')
else:
print("Выбрана station: " + self.st_obj)
print("Маршруты:")
print("\tНомер маршрута\tВыбранная станция\tНачальная станция\
\n--------------------------------------------------------")
for i in lst:
self.routes_arr.append([str(i[0]), str(i[2]), i[1]])
for i in range(len(self.routes_arr)):
output = f"""{str(i+1)}\t{str(self.routes_arr[i][1])} \t\t\
{self.st_obj}\t\t{str(StationTable().name_by_id(self.routes_arr[i][0])[0])}"""
print(output)
menu = """Дальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр станций;
8 - удаление маршрута;
10 - выход."""
print(menu)
return self.read_next_step()
else:
print("Неверный ввод!")
def main_cycle(self):
current_menu = "0"
next_step = None
while(current_menu != "10"):
if current_menu == "0":
self.show_main_menu()
next_step = self.read_next_step()
current_menu = self.after_main_menu(next_step)
elif current_menu == "1":
self.show_stations()
next_step = self.read_next_step()
current_menu = self.after_show_stations(next_step)
# elif current_menu == "2":
# self.show_main_menu()
# current_menu = "1"
print("До свидания!")
return
def test(self):
DbTable.dbconn.test()
m = Main()
# Откоментируйте эту строку и закоментируйте следующую для теста
# соединения с БД
# m.test()
m.main_cycle()
+21
View File
@@ -0,0 +1,21 @@
# Загрузка настроек проекта (в данном случае только настроек соединения с БД)
# из файла config.yaml.
import yaml
class ProjectConfig:
"""Класс считывает базовые настройки из файла config.yaml"""
def __init__(self):
with open('config.yaml') as f:
config = yaml.safe_load(f)
self.dbname = config['dbname']
self.user = config['user']
self.password = config['password']
self.host = config['host']
self.dbtableprefix = config['dbtableprefix']
# Этот метод запускается только, если запускать
# данный файл, а не подключать его.
if __name__ == "__main__":
x = ProjectConfig()
print(x.dbfilepath)
+91
View File
@@ -0,0 +1,91 @@
from tables.stations_table import *
from dbtable import *
import functions
class RoutesTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "routes"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"first_st_id": ["integer", "NOT NULL", "REFERENCES stations(id) ON DELETE CASCADE"],
"last_st_id": ["integer", "NOT NULL", "REFERENCES stations(id) ON DELETE CASCADE"]}
def primary_key(self):
return ['id']
'''def table_constraints(self):
return ["PRIMARY KEY(route_id)"]'''
def all_by_st_id(self, pid, t):
if(t == 1):
sql = f"""SELECT * FROM {self.table_name()} WHERE first_st_id = {str(pid)} ORDER BY {", ".join(self.primary_key())};"""
else:
sql = f"""SELECT * FROM {self.table_name()} WHERE last_st_id = {str(pid)} ORDER BY {", ".join(self.primary_key())};"""
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
def delete(self, val):
par_sql = "DELETE FROM routes WHERE st_name = %s"
cur = self.dbconn.conn.cursor()
value = "".join(val)
cur.execute(par_sql, (value,))
self.dbconn.conn.commit()
def insert(self, max_index):
while True:
first_st_id = functions.validate_input('Введите номер добавляемой начальной станции (0 - для отмены): ', 0, max_index)
if(first_st_id == -1): return "1"
last_st_id = functions.validate_input('Введите номер добавляемой конечной станции (0 - для отмены): ', 0, max_index)
if(last_st_id == -1): return "1"
t1 = StationTable().index_by_id(first_st_id)
t2 = StationTable().index_by_id(last_st_id)
if t1>t2:
print('Нельзя добавить такой маршрут')
else:
insert = [first_st_id, last_st_id]
self.insert_one(insert)
return
def update(self, max_index):
while True:
t = functions.validate_input('Изменить маршрут по:\
\n1 - начальной станции;\
\n2- конечной станции;\n', 0, 2)
if(t==1):
n1 = functions.validate_input('Введите номер начальной станции, который вы хотите заменить (0 - для отмены): ', 0, max_index)
if(n1 == -1): return "1"
n2 = functions.validate_input('Введите номер, на который вы хотите поменять (0 - для отмены): ', 0, max_index)
if(n2 == -1): return "1"
if n1 != n2:
sql = f"""UPDATE {self.table_name()} SET first_st_id = '{n2}' WHERE first_st_id = '{n1}';"""
# sql2 = f"""UPDATE stations SET id = '{n2}' WHERE id = '{n1}';"""
break
else:
print('Невозможно поменять на такой номер')
elif(t==2):
n1 = functions.validate_input('Введите номер конечной станции, который вы хотите заменить (0 - для отмены): ', 0, max_index)
if(n1 == -1): return "1"
n2 = functions.validate_input('Введите номер, на который вы хотите поменять (0 - для отмены): ', 0, max_index)
if(n2 == -1): return "1"
if n1 != n2:
sql = f"""UPDATE {self.table_name()} SET last_st_id = '{n2}' WHERE last_st_id = '{n1}';"""
# sql2 = f"""UPDATE stations SET id = '{n2}' WHERE id = '{n1}';"""
break
else:
print('Невозможно поменять на такой номер')
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def example_insert(self):
self.insert_one([1,2])
self.insert_one([1,6])
self.insert_one([1,7])
self.insert_one([1,8])
self.insert_one([1,9])
self.insert_one([1,6])
return
+137
View File
@@ -0,0 +1,137 @@
from dbtable import *
import functions
class StationTable(DbTable):
'''
Это класс для таблиц
'''
def table_name(self):
'''
Это метод для названия таблицы
'''
return self.dbconn.prefix + "stations"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"st_name": ["varchar(60)", "NOT NULL"],
"tarrif_zone_id": ["integer", "NOT NULL"],
"st_index": ["integer", "NOT NULL"]}
def table_constraint(self):
return ['CONTRAINT "Name" UNIQUE (st_name)']
def find_by_position(self, num):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
sql += " LIMIT 1 OFFSET %(offset)s"
cur = self.dbconn.conn.cursor()
cur.execute(sql, {"offset": num - 1})
return cur.fetchone()
def delete(self, val):
par_sql = f"DELETE FROM stations WHERE st_name = '{val[0]}';"
cur = self.dbconn.conn.cursor()
cur.execute(par_sql)
self.dbconn.conn.commit()
def insert(self):
while True:
st_name = input('Введите название добавляемой станции (enter - отмена): ').strip()
if len(st_name) > 60:
print('Недопустмая длина названия станции')
elif st_name == '':
return "1"
else:
break
tarrif_zone_id = functions.validate_input('Введите номер тарифной зоны: ', 0, 7)
if(tarrif_zone_id==-1):
return "1"
st_index = functions.validate_input('Введите индекс добавляемой станции: ', 0, 100)
if(st_index==-1):
return "1"
insert = [st_index, st_name, tarrif_zone_id]
self.insert_one(insert)
def update(self, old):
"""Функция для обновления категории
"""
print(f'''Выбрана станция для изменения: {old[0]}''')
cur = self.dbconn.conn.cursor()
while True:
data = input("Введите название (enter - отмена): ").strip()
if(len(data.strip()) > 60):
data = input("Название слишком длинное! Введите название заново (enter - отмена):").strip()
elif(self.check_by_name(data.strip())):
data = input("Такое название уже существует. Введите новое (enter - отмена):").strip()
elif(data==''): return
else:
param_sql = f"UPDATE {self.table_name()} SET st_name = '{data}' WHERE st_name = '{old[0]}';"
cur.execute(param_sql)
self.dbconn.conn.commit()
return
def check_by_name(self, value):
sql = f"SELECT * FROM {self.table_name()} WHERE st_name='{value}'"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
result = cur.fetchone()
cur.close()
if result:
return True
else:
return False
def find_by_name(self, name):
cur = self.dbconn.conn.cursor()
param_query = "SELECT id FROM Stations WHERE st_name = %s;"
# sql_sel = "SELECT id FROM " + self.table_name()
# sql_sel += " WHERE cath_name = " + "'" + name + "'" + ";"
cur.execute(param_query, (name,))
ret = cur.fetchone()
return list(ret)[0]
def name_by_id(self, id):
cur = self.dbconn.conn.cursor()
query = "SELECT st_name FROM Stations WHERE id = %s;"
cur.execute(query, str(id))
return cur.fetchone()
def index_by_id(self,id):
cur = self.dbconn.conn.cursor()
query = "SELECT st_index FROM Stations WHERE id = %s;"
cur.execute(query, str(id))
return cur.fetchone()
def example_insert(self):
self.insert_one([1, "Нахабино", 1])
self.insert_one([2, "Стрешнево", 2])
self.insert_one([2, "Царицыно", 2])
self.insert_one([3, "Яуза", 3])
self.insert_one([2, "Щукинская", 3])
self.insert_one([4, "Москва-Курская", 4])
self.insert_one([4, "Дмитровская", 4])
self.insert_one([4, "Остафьево", 4])
self.insert_one([4, "Подольск", 4])
return
# класс человек:
# атрибуты(имя, фамилия, возраст)
# методы(пукнуть, моргнуть)
# класс женщина(человек):
# атрибуты(размер груди)
# методы(краситься, купить бюстгалтер)
# def buy_lifchik(self, size):
# пойти в магазин и купить лифчик размер size
# Катя = женщина(Катя, Ускова, 19, 3-4)
# Саша = женщина(Саша, Антипенко, 14, 1)
# катя.купить бюстгалтер()
# саша.купить бюстгалтер()
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
import re
def validate_input(prompt, min_value, max_value):
"""Проверка ввода для выбора номера строки
Args:
prompt (string): "Промт" или запрос пользователю
min_value (int): Нижняя граница, по умолчанию 0
max_value (int): Верхняя граница
Returns:
int: Либо число, введенное пользователем, либо -1 для выхода
"""
while True:
user_input = input(prompt)
try:
value = int(user_input)
if min_value <= value <= max_value and value != 0:
return value
elif value == 0:
return -1
else:
print(f"Введенное значение должно быть в диапазоне от {min_value} до {max_value}")
except ValueError:
print("Введенное значение должно быть числом.")
def add_zero_before(old_index: str, max_len: str) -> str:
"""Функция для "красивого" вывода индексов
Args:
old_index (str): текущий индекс для вывода
max_len (int): максимальная длина индексов
Returns:
new_index (str): окончательная версия индекса
"""
ln = len(old_index)
new_index = old_index[:]
for i in range(max_len-ln):
new_index = '0' + new_index
return new_index
# test1 = 'erhwogngonwro'
# test2 = '1 minute'
# test3 = '2 hours 3 minutes'
# print(validate_time_format(test1), validate_time_format(test2), validate_time_format(test3))
+6
View File
@@ -0,0 +1,6 @@
# Базовые параметры настройки конфигурации проекта
dbname: Test # В классе менять не надо
user: postgres # Замените на свой логин от БД в классе
password: Qwerty # Замените на свой пароль от БД в классе
host: localhost # Замените на 192.168.0.48 в классе
dbtableprefix: "public." # Замените на свой логин от БД
+34
View File
@@ -0,0 +1,34 @@
# Установка соединения с базой данных
# (параметры передаются через класс конфиг).
import psycopg2
class DbConnection:
def __init__(self, config):
self.dbname = config.dbname
self.user = config.user
self.password = config.password
self.host = config.host
self.prefix = config.dbtableprefix
self.conn = psycopg2.connect(dbname = self.dbname,
user = self.user,
password = self.password,
host = self.host)
def __del__(self):
if self.conn:
self.conn.close()
def test(self):
cur = self.conn.cursor()
cur.execute("DROP TABLE IF EXISTS test CASCADE")
cur.execute("CREATE TABLE test(test integer)")
cur.execute("INSERT INTO test(test) VALUES(1)")
self.conn.commit()
cur.execute("SELECT * FROM test")
result = cur.fetchall()
cur.execute("DROP TABLE test")
self.conn.commit()
return (result[0][0] == 1)
+124
View File
@@ -0,0 +1,124 @@
# Базовые действия с таблицами
from dbconnection import *
'''
FIXME:
'''
class DbTable:
dbconn = None
def __init__(self):
return
def table_name(self):
return self.dbconn.prefix + "table"
def columns(self):
return {"test": ["integer", "PRIMARY KEY"]}
def column_names(self):
return sorted(self.columns().keys(), key = lambda x: x)
def primary_key(self):
return ['id']
def column_names_without_id(self):
res = sorted(self.columns().keys(), key = lambda x: x)
if 'id' in res:
res.remove('id')
return res
def table_constraints(self):
return []
def create(self):
sql = "CREATE TABLE " + self.table_name() + "("
arr = [k + " " + " ".join(v) for k, v in sorted(self.columns().items(), key = lambda x: x[0])]
sql += ", ".join(arr + self.table_constraints())
sql += ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def drop(self):
sql = "DROP TABLE IF EXISTS " + self.table_name()
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def insert_one(self, vals):
for i in range(len(vals)):
if type(vals[i]) == str:
vals[i] = "'" + vals[i] + "'"
else:
vals[i] = str(vals[i])
values = ", ".join(vals)
query = f"""INSERT INTO {self.table_name()}({", ".join(self.column_names_without_id())}) VALUES({values})"""
cur = self.dbconn.conn.cursor()
# cur.execute(sql)
try:
cur.execute(query)
self.dbconn.conn.commit()
except psycopg2.errors.UniqueViolation:
self.dbconn.conn.rollback()
print('Такая строка уже существует. Повторите попытку')
return
def update(self, column, values, wh):
sql = f"UPDATE {self.table_name()} SET {column} = {values} WHERE id = {wh};"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def first(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def last(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join([x + " DESC" for x in self.primary_key()])
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def all(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
def select_one(self, **kwargs):
conditions = []
values = []
sorted_kwargs = sorted(kwargs.items(), key=lambda x: x[0])
for key, value in sorted_kwargs():
conditions.append(f"{key}=%s")
values.append(value)
sql = f"SELECT * FROM {self.table_name()} WHERE " + " AND ".join(conditions)
cur = self.dbconn.conn.cursor()
cur.execute(sql, tuple(values))
result = cur.fetchone()
cur.close()
if result:
return True
else:
return False
+265
View File
@@ -0,0 +1,265 @@
import sys
sys.path.append('tables')
from project_config import *
from dbconnection import *
from tables.routes_table import *
from tables.station_table import *
class Main:
config = ProjectConfig()
connection = DbConnection(config)
def __init__(self):
DbTable.dbconn = self.connection
return
def db_init(self):
stat = StationTable()
rout = RoutesTable()
stat.create()
rout.create()
return
def db_insert_somethings(self):
stat = StationTable()
rout = RoutesTable()
stat.example_insert()
rout.example_insert()
def db_drop(self):
stat = StationTable()
rout = RoutesTable()
rout.drop()
stat.drop()
return
def show_main_menu(self):
menu = """Привутствуем в меню, выберите действие:
1 - просмотр станций;
2 - сброс и инициализация БД;
9 - выход;"""
print(menu)
return
def read_next_step(self):
return input("=> ").strip()
def after_main_menu(self, next_step):
if next_step == "2":
self.db_drop()
self.db_init()
self.db_insert_somethings()
print("Таблицы созданы заново!")
return "0"
elif next_step != "1" and next_step != "9":
print("Выбрано неверное число! Повторите ввод!")
return "0"
else:
return next_step
def show_station(self):
self.station_id = -1
self.station_arr = []
self.max_len_name = -9000
lst = StationTable().all()
self.max_station_index = (len(lst))
for i in lst:
self.station_arr.append([i[0], str(i[2]), str(i[3]), str(i[1])])
# 0 - id, 1 - name, 2 - tarrif_id, 3 - index
self.max_len_name = max(self.max_len_name, len(str(i[2])))
menu = f"""Просмотр списка станций.
\tНазвание {" "*(self.max_len_name-4)}Тарифная зона Индекс\n{"-"*100}"""
print(menu)
for i in range(len(self.station_arr)):
output = f"""{str(i+1)}\t{self.station_arr[i][1]} {" "*(4+self.max_len_name - len(self.station_arr[i][1]))}\
{str(self.station_arr[i][2])} {" "*(13-len(str(self.station_arr[i][2])))}{str(self.station_arr[i][3])}"""
print(output)
menu = f"""{"-"*100}\nДальнейшие операции:
0 - возврат в главное меню;
3 - добавление новой станции;
4 - удаление станции;
5 - просмотр маршрутов по станции;
6 - добавление маршрута;
8 - обновить станцию;
9 - выход."""
print(menu)
return
def after_show_station(self, next_step):
"""Выбор действий после вывода категорий
"""
while True:
if next_step == "4": # Удаление станции
x = add_func.validate_input('Введите номер удаляемой станции (0 - для отмены): ', 0, self.max_station_index)
if(x!=-1):
StationTable().delete(self.station_arr[int(x)-1])
return "1"
elif next_step == "6": #Добавление маршрута
RoutesTable().insert_route_one(self.max_station_index)
next_step = "5"
elif next_step == "7":#Удаление маршрута
x = add_func.validate_input('Введите номер удаляемого маршрута (0 - для отмены): ', 0, self.max_routes_index)
if(x!=-1):
RoutesTable().delete(self.routes_arr[int(x)-1][2])
else:
return "1"
next_step = "5"
elif next_step == "5":
next_step = self.show_routes_with_station()
elif next_step == "8": # Обновление названия станции
x = add_func.validate_input('Введите номер обновляемой станции (0 - для отмены): ', 0, self.max_station_index)
if(x!=-1):
StationTable().update(self.station_arr[int(x)-1])
return "1"
elif next_step != "0" and next_step != "9" and next_step != "3":
print("Выбрано неверное число! Повторите ввод!")
return "1"
else:
return next_step
def add_station(self):
while True:
st_name = input('Введите название добавляемой станции (0 - отмена): ').strip()
if len(st_name) > 20:
print('Недопустмая длина названия станции')
elif st_name == '0':
return "1"
else:
break
tarrif_zone_id = add_func.validate_input('Введите номер тарифной зоны(0-отмена): ', 0, 7)
if(tarrif_zone_id==-1) or tarrif_zone_id == '1':
return "1"
st_index = add_func.validate_input('Введите индекс добавляемой станции(0-отмена): ', 0, 100)
if(st_index==-1) or st_index == '0':
return "1"
insert = [st_index, st_name, tarrif_zone_id]
StationTable().insert_one(insert)
def show_routes_with_station(self):
"""Вывод всех маршрутов по введенной станции
"""
self.routes_arr = []
if self.station_id == -1:
while True:
t = add_func.validate_input('Вывести маршрут по:\
\n1 - начальной станции;\
\n2 - конечной станции;\n(0 - отмена);\n', 0, 2)
if t == 1:
x = add_func.validate_input('Выберите номер интересуемой строки с начальной станцией (0 - отмена): ', 0, self.max_station_index)
if(x==-1):
return
self.station_id = self.station_arr[x-1][0]
self.station_obj = self.station_arr[x-1]
space_lenth = len(self.station_obj[1])
lst = RoutesTable().all_by_station_id(self.station_id, 1)
self.max_routes_index = len(lst)
if lst == []:
print('Нет маршрута с такой начальной станцией')
else:
print("Выбрана station: " + self.station_obj[1])
print("Маршруты:")
print(f"""\tНомер маршрута\tВыбранная станция {" "*8} Конечная станция\n{"-"*100}-""")
for i in lst:
self.routes_arr.append([str(i[0]), str(i[2]), i[1]])
for i in range(len(self.routes_arr)):
output = f"""{str(i+1)}\t{str(self.routes_arr[i][2])}\
{" " * 15}{self.station_obj[1]} {"-" * (24 - space_lenth)}> {str(StationTable().name_by_id(self.routes_arr[i][1])[0])}"""
print(output)
menu = """Дальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр станций;
7 - удаление маршрута;
9 - выход."""
print(menu)
return self.read_next_step()
elif t == 2:
x = add_func.validate_input('Выберите номер интересуемой строки с конечной станцией (0 - отмена): ', 0, self.max_station_index)
if(x==0):
return
self.station_id = self.station_arr[x-1][0]
self.station_obj = self.station_arr[x-1]
space_lenth = len(self.station_obj[1])
lst = RoutesTable().all_by_station_id(self.station_id, 2)
self.max_routes_index = len(lst)
if lst == []:
print('Нет маршрута с такой конечной станцией')
else:
print("Выбрана station: " + self.station_obj[1])
print("Маршруты:")
print(f"""\tНомер маршрута\tВыбранная станция {" "*8} Начальная станция\n{"-"*100}""")
for i in lst:
self.routes_arr.append([str(i[0]), str(i[2]), i[1]])
for i in range(len(self.routes_arr)):
output = f"""{str(i+1)}\t{str(self.routes_arr[i][2])}\
{" " * 15}{self.station_obj[1]} <{"-" * (24 - space_lenth)} {str(StationTable().name_by_id(self.routes_arr[i][2])[0])}"""
print(output)
menu = f"""{"-"*100}\nДальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр станций;
7 - удаление маршрута;
9 - выход."""
print(menu)
return self.read_next_step()
else:
return "5"
def main_cycle(self):
"""Основной цикл программы, регулирующий порядок действий
"""
current_menu = "0"
next_step = None
while(current_menu != "9"):
if current_menu == "0":
self.show_main_menu()
next_step = self.read_next_step()
current_menu = self.after_main_menu(next_step)
elif current_menu == "1":
self.show_station()
next_step = self.read_next_step()
current_menu = self.after_show_station(next_step)
elif current_menu == "2":
self.show_main_menu()
elif current_menu == "3":
self.add_station()
current_menu = "1"
print("До свидания!")
return
def test(self):
DbTable.dbconn.test()
m = Main()
# m.test()
m.main_cycle()
+21
View File
@@ -0,0 +1,21 @@
# Загрузка настроек проекта (в данном случае только настроек соединения с БД)
# из файла config.yaml.
import yaml
class ProjectConfig:
"""Класс считывает базовые настройки из файла config.yaml"""
def __init__(self):
with open('config.yaml') as f:
config = yaml.safe_load(f)
self.dbname = config['dbname']
self.user = config['user']
self.password = config['password']
self.host = config['host']
self.dbtableprefix = config['dbtableprefix']
# Этот метод запускается только, если запускать
# данный файл, а не подключать его.
if __name__ == "__main__":
x = ProjectConfig()
print(x.dbfilepath)
Binary file not shown.
Binary file not shown.
+91
View File
@@ -0,0 +1,91 @@
from tables.station_table import *
from dbtable import *
import add_func
class RoutesTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "routes"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"first_st_id": ["integer", "NOT NULL", "REFERENCES stations(id) ON DELETE CASCADE"],
"last_st_id": ["integer", "NOT NULL", "REFERENCES stations(id) ON DELETE CASCADE"]}
def primary_key(self):
return ['id']
'''def table_constraints(self):
return ["PRIMARY KEY(route_id)"]'''
def all_by_station_id(self, pid, t):
if(t == 1):
sql = f"""SELECT * FROM {self.table_name()} WHERE first_st_id = {str(pid)} ORDER BY {", ".join(self.primary_key())};"""
else:
sql = f"""SELECT * FROM {self.table_name()} WHERE last_st_id = {str(pid)} ORDER BY {", ".join(self.primary_key())};"""
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
def delete(self, id):
par_sql = f"DELETE FROM routes WHERE id = {str(id)}"
cur = self.dbconn.conn.cursor()
cur.execute(par_sql)
self.dbconn.conn.commit()
def insert_route_one(self, max_index):
while True:
first_st_id = add_func.validate_input('Введите номер добавляемой начальной станции (0 - для отмены): ', 0, max_index)
if(first_st_id == -1): return "1"
last_st_id = add_func.validate_input('Введите номер добавляемой конечной станции (0 - для отмены): ', 0, max_index)
if(last_st_id == -1): return "1"
t1 = StationTable().index_by_id(first_st_id)
t2 = StationTable().index_by_id(last_st_id)
if t1>t2:
print('Нельзя добавить такой маршрут')
else:
insert = [first_st_id, last_st_id]
self.insert_one(insert)
return
def update(self, max_index):
while True:
t = add_func.validate_input('Изменить маршрут по:\
\n1 - начальной станции;\
\n2- конечной станции;\n(0 - отмена);\n', 0, 2)
if(t==1):
n1 = add_func.validate_input('Введите номер начальной станции, который вы хотите заменить (0 - для отмены): ', 0, max_index)
if(n1 == -1): return "1"
n2 = add_func.validate_input('Введите номер, на который вы хотите поменять (0 - для отмены): ', 0, max_index)
if(n2 == -1): return "1"
if n1 != n2:
sql = f"""UPDATE {self.table_name()} SET first_st_id = '{n2}' WHERE first_st_id = '{n1}';"""
break
else:
print('Невозможно поменять на такой номер')
elif(t==2):
n1 = add_func.validate_input('Введите номер конечной станции, который вы хотите заменить (0 - для отмены): ', 0, max_index)
if(n1 == -1): return "1"
n2 = add_func.validate_input('Введите номер, на который вы хотите поменять (0 - для отмены): ', 0, max_index)
if(n2 == -1): return "1"
if n1 != n2:
sql = f"""UPDATE {self.table_name()} SET last_st_id = '{n2}' WHERE last_st_id = '{n1}';"""
# sql2 = f"""UPDATE stations SET id = '{n2}' WHERE id = '{n1}';"""
break
else:
print('Невозможно поменять на такой номер')
else:
return "1"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def example_insert(self):
self.insert_one([1,2])
self.insert_one([1,6])
self.insert_one([1,7])
self.insert_one([1,8])
self.insert_one([1,9])
self.insert_one([1,6])
return
+120
View File
@@ -0,0 +1,120 @@
from dbtable import *
import add_func
class StationTable(DbTable):
'''
Это класс для таблиц
'''
def table_name(self):
'''
Это метод для названия таблицы
'''
return self.dbconn.prefix + "stations"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"st_name": ["varchar(20)", "NOT NULL"],
"tarrif_zone_id": ["integer", "NOT NULL"],
"st_index": ["integer", "NOT NULL"]}
def table_constraint(self):
return ['CONTRAINT "Name" UNIQUE (st_name)']
def find_by_position(self, num):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
sql += " LIMIT 1 OFFSET %(offset)s"
cur = self.dbconn.conn.cursor()
cur.execute(sql, {"offset": num - 1})
return cur.fetchone()
def delete(self, val):
par_sql = f"DELETE FROM stations WHERE st_name = '{val[1]}';"
cur = self.dbconn.conn.cursor()
cur.execute(par_sql)
self.dbconn.conn.commit()
def insert(self):
while True:
st_name = input('Введите название добавляемой станции (enter - отмена): ').strip()
if len(st_name) > 20:
print('Недопустмая длина названия станции')
elif st_name == '':
return "1"
else:
break
tarrif_zone_id = add_func.validate_input('Введите номер тарифной зоны: ', 0, 7)
if(tarrif_zone_id==-1):
return "1"
st_index = add_func.validate_input('Введите индекс добавляемой станции: ', 0, 100)
if(st_index==-1):
return "1"
insert = [st_index, st_name, tarrif_zone_id]
self.insert_one(insert)
def update(self, old):
"""Функция для обновления станции
"""
print(f'''Выбрана станция для изменения: {old[1]}''')
cur = self.dbconn.conn.cursor()
while True:
data = input("Введите название (enter - отмена): ").strip()
if(len(data.strip()) > 20):
data = input("Название слишком длинное! Введите название заново (enter - отмена):").strip()
elif(self.check_by_name(data.strip())):
data = input("Такое название уже существует. Введите новое (enter - отмена):").strip()
elif(data==''): return
else:
param_sql = f"UPDATE {self.table_name()} SET st_name = '{data}' WHERE st_name = '{old[1]}';"
cur.execute(param_sql)
self.dbconn.conn.commit()
return
def check_by_name(self, value):
sql = f"SELECT * FROM {self.table_name()} WHERE st_name='{value}'"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
result = cur.fetchone()
cur.close()
if result:
return True
else:
return False
def find_by_name(self, name):
cur = self.dbconn.conn.cursor()
param_query = f"SELECT id FROM Stations WHERE st_name = '{name}';"
# sql_sel = "SELECT id FROM " + self.table_name()
# sql_sel += " WHERE cath_name = " + "'" + name + "'" + ";"
cur.execute(param_query)
ret = cur.fetchone()
return ret
def name_by_id(self, id):
cur = self.dbconn.conn.cursor()
query = "SELECT st_name FROM Stations WHERE id = %s;"
cur.execute(query, str(id))
return cur.fetchone()
def index_by_id(self,id):
cur = self.dbconn.conn.cursor()
query = f"SELECT st_index FROM Stations WHERE id = {id};"
cur.execute(query)
return cur.fetchone()
def example_insert(self):
self.insert_one([1, "Нахабино", 1])
self.insert_one([2, "Стрешнево", 2])
self.insert_one([2, "Царицыно", 2])
self.insert_one([3, "Яуза", 3])
self.insert_one([2, "Щукинская", 3])
self.insert_one([4, "Москва-Курская", 4])
self.insert_one([4, "Дмитровская", 4])
self.insert_one([4, "Остафьево", 4])
self.insert_one([4, "Подольск", 4])
return
+1
View File
@@ -0,0 +1 @@
print(len("-------------------------------------------------------------------------------------------"))
+6
View File
@@ -0,0 +1,6 @@
# Базовые параметры настройки конфигурации проекта
dbname: postgres # В классе менять не надо
user: postgres # Замените на свой логин от БД в классе
password: qwerty # Замените на свой пароль от БД в классе
host: localhost # Замените на 192.168.0.48 в классе
dbtableprefix: "public." # Замените на свой логин от БД
+34
View File
@@ -0,0 +1,34 @@
# Установка соединения с базой данных
# (параметры передаются через класс конфиг).
import psycopg2
class DbConnection:
def __init__(self, config):
self.dbname = config.dbname
self.user = config.user
self.password = config.password
self.host = config.host
self.prefix = config.dbtableprefix
self.conn = psycopg2.connect(dbname = self.dbname,
user = self.user,
password = self.password,
host = self.host)
def __del__(self):
if self.conn:
self.conn.close()
def test(self):
cur = self.conn.cursor()
cur.execute("DROP TABLE IF EXISTS test CASCADE")
cur.execute("CREATE TABLE test(test integer)")
cur.execute("INSERT INTO test(test) VALUES(1)")
self.conn.commit()
cur.execute("SELECT * FROM test")
result = cur.fetchall()
cur.execute("DROP TABLE test")
self.conn.commit()
return (result[0][0] == 1)
+86
View File
@@ -0,0 +1,86 @@
# Базовые действия с таблицами
from dbconnection import *
class DbTable:
dbconn = None
def __init__(self):
return
def table_name(self):
return self.dbconn.prefix + "table"
def columns(self):
return {"test": ["integer", "PRIMARY KEY"]}
def column_names(self):
return sorted(self.columns().keys(), key = lambda x: x)
def primary_key(self):
return ['id']
def column_names_without_id(self):
res = sorted(self.columns().keys(), key = lambda x: x)
if 'id' in res:
res.remove('id')
return res
def table_constraints(self):
return []
def create(self):
sql = "CREATE TABLE " + self.table_name() + "("
arr = [k + " " + " ".join(v) for k, v in sorted(self.columns().items(), key = lambda x: x[0])]
sql += ", ".join(arr + self.table_constraints())
sql += ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def drop(self):
sql = "DROP TABLE IF EXISTS " + self.table_name()
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def insert_one(self, vals):
for i in range(0, len(vals)):
if type(vals[i]) == str:
vals[i] = "'" + vals[i] + "'"
else:
vals[i] = str(vals[i])
sql = "INSERT INTO " + self.table_name() + "("
sql += ", ".join(self.column_names_without_id()) + ") VALUES("
sql += ", ".join(vals) + ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def first(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def last(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join([x + " DESC" for x in self.primary_key()])
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def all(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
+182
View File
@@ -0,0 +1,182 @@
import sys
sys.path.append('tables')
from project_config import *
from dbconnection import *
from people_table import *
from phones_table import *
class Main:
config = ProjectConfig()
connection = DbConnection(config)
def __init__(self):
DbTable.dbconn = self.connection
return
def db_init(self):
pt = PeopleTable()
pht = PhonesTable()
pt.create()
pht.create()
return
def db_insert_somethings(self):
pt = PeopleTable()
pht = PhonesTable()
pt.insert_one(["Test", "Test", "Test"])
pt.insert_one(["Test2", "Test2", "Test2"])
pt.insert_one(["Test3", "Test3", "Test3"])
pht.insert_one([1, "123"])
pht.insert_one([2, "123"])
pht.insert_one([3, "123"])
def db_drop(self):
pht = PhonesTable()
pt = PeopleTable()
pht.drop()
pt.drop()
return
def show_main_menu(self):
menu = """Добро пожаловать!
Основное меню (выберите цифру в соответствии с необходимым действием):
1 - просмотр людей;
2 - сброс и инициализация таблиц;
9 - выход."""
print(menu)
return
def read_next_step(self):
return input("=> ").strip()
def after_main_menu(self, next_step):
if next_step == "2":
self.db_drop()
self.db_init()
self.db_insert_somethings()
print("Таблицы созданы заново!")
return "0"
elif next_step != "1" and next_step != "9":
print("Выбрано неверное число! Повторите ввод!")
return "0"
else:
return next_step
def show_people(self):
self.person_id = -1
menu = """Просмотр списка людей!
\tФамилия\tИмя\tОтчество"""
print(menu)
lst = PeopleTable().all()
for i in lst:
print(str(i[1]) + "\t" + str(i[2]) + "\t" + str(i[0]) + "\t" + str(i[3]))
menu = """Дальнейшие операции:
0 - возврат в главное меню;
3 - добавление нового человека;
4 - удаление человека;
5 - просмотр телефонов человека;
9 - выход."""
print(menu)
return
def after_show_people(self, next_step):
while True:
if next_step == "4":
print("Пока не реализовано!")
return "1"
elif next_step == "6" or next_step == "7":
print("Пока не реализовано!")
next_step = "5"
elif next_step == "5":
next_step = self.show_phones_by_people()
elif next_step != "0" and next_step != "9" and next_step != "3":
print("Выбрано неверное число! Повторите ввод!")
return "1"
else:
return next_step
def show_add_person(self):
# Не реализована проверка на максимальную длину строк. Нужно доделать самостоятельно!
data = []
data.append(input("Введите имя (1 - отмена): ").strip())
if data[0] == "1":
return
while len(data[0].strip()) == 0:
data[0] = input("Имя не может быть пустым! Введите имя заново (1 - отмена):").strip()
if data[0] == "1":
return
data.append(input("Введите фамилию (1 - отмена): ").strip())
if data[1] == "1":
return
while len(data[1].strip()) == 0:
data[1] = input("Фамилия не может быть пустой! Введите фамилию заново (1 - отмена):").strip()
if data[1] == "1":
return
data.append(input("Введите отчество (1 - отмена):").strip())
if data[2] == "1":
return
PeopleTable().insert_one(data)
return
def show_phones_by_people(self):
if self.person_id == -1:
while True:
num = input("Укажите номер строки, в которой записана интересующая Вас персона (0 - отмена):")
while len(num.strip()) == 0:
num = input("Пустая строка. Повторите ввод! Укажите номер строки, в которой записана интересующая Вас персона (0 - отмена):")
if num == "0":
return "1"
person = PeopleTable().find_by_position(int(num))
if not person:
print("Введено число, неудовлетворяющее количеству людей!")
else:
self.person_id = int(person[1])
self.person_obj = person
break
print("Выбран человек: " + self.person_obj[2] + " " + self.person_obj[0] + " " + self.person_obj[3])
print("Телефоны:")
lst = PhonesTable().all_by_person_id(self.person_id)
for i in lst:
print(i[1])
menu = """Дальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр людей;
6 - добавление нового телефона;
7 - удаление телефона;
9 - выход."""
print(menu)
return self.read_next_step()
return self.read_next_step()
def main_cycle(self):
current_menu = "0"
next_step = None
while(current_menu != "9"):
if current_menu == "0":
self.show_main_menu()
next_step = self.read_next_step()
current_menu = self.after_main_menu(next_step)
elif current_menu == "1":
self.show_people()
next_step = self.read_next_step()
current_menu = self.after_show_people(next_step)
elif current_menu == "2":
self.show_main_menu()
elif current_menu == "3":
self.show_add_person()
current_menu = "1"
print("До свидания!")
return
def test(self):
DbTable.dbconn.test()
m = Main()
# Откоментируйте эту строку и закоментируйте следующую для теста
# соединения с БД
# m.test()
m.main_cycle()
+21
View File
@@ -0,0 +1,21 @@
# Загрузка настроек проекта (в данном случае только настроек соединения с БД)
# из файла config.yaml.
import yaml
class ProjectConfig:
"""Класс считывает базовые настройки из файла config.yaml"""
def __init__(self):
with open('config.yaml') as f:
config = yaml.safe_load(f)
self.dbname = config['dbname']
self.user = config['user']
self.password = config['password']
self.host = config['host']
self.dbtableprefix = config['dbtableprefix']
# Этот метод запускается только, если запускать
# данный файл, а не подключать его.
if __name__ == "__main__":
x = ProjectConfig()
print(x.dbfilepath)
+23
View File
@@ -0,0 +1,23 @@
# Таблица персоны и особые действия с ней
from dbtable import *
class PeopleTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "people"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"last_name": ["varchar(32)", "NOT NULL"],
"first_name": ["varchar(32)", "NOT NULL"],
"second_name": ["varchar(32)"]}
def find_by_position(self, num):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
sql += " LIMIT 1 OFFSET %(offset)s"
cur = self.dbconn.conn.cursor()
cur.execute(sql, {"offset": num - 1})
return cur.fetchone()
+27
View File
@@ -0,0 +1,27 @@
# Таблица Телефоны и особые действия с ней.
from dbtable import *
class PhonesTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "phones"
def columns(self):
return {"person_id": ["integer", "REFERENCES people(id)"],
"phone": ["varchar(12)", "NOT NULL"]}
def primary_key(self):
return ['person_id', 'phone']
def table_constraints(self):
return ["PRIMARY KEY(person_id, phone)"]
def all_by_person_id(self, pid):
sql = "SELECT * FROM " + self.table_name()
sql += " WHERE person_id = %s"
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql, str(pid))
return cur.fetchall()
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
# Базовые параметры настройки конфигурации проекта
dbname: postgres # В классе менять не надо
user: postgres # Замените на свой логин от БД в классе
password: qwerty # Замените на свой пароль от БД в классе
host: localhost # Замените на 192.168.0.48 в классе
dbtableprefix: "public." # Замените на свой логин от БД
+34
View File
@@ -0,0 +1,34 @@
# Установка соединения с базой данных
# (параметры передаются через класс конфиг).
import psycopg2
class DbConnection:
def __init__(self, config):
self.dbname = config.dbname
self.user = config.user
self.password = config.password
self.host = config.host
self.prefix = config.dbtableprefix
self.conn = psycopg2.connect(dbname = self.dbname,
user = self.user,
password = self.password,
host = self.host)
def __del__(self):
if self.conn:
self.conn.close()
def test(self):
cur = self.conn.cursor()
cur.execute("DROP TABLE IF EXISTS test CASCADE")
cur.execute("CREATE TABLE test(test integer)")
cur.execute("INSERT INTO test(test) VALUES(1)")
self.conn.commit()
cur.execute("SELECT * FROM test")
result = cur.fetchall()
cur.execute("DROP TABLE test")
self.conn.commit()
return (result[0][0] == 1)
+86
View File
@@ -0,0 +1,86 @@
# Базовые действия с таблицами
from dbconnection import *
class DbTable:
dbconn = None
def __init__(self):
return
def table_name(self):
return self.dbconn.prefix + "table"
def columns(self):
return {"test": ["integer", "PRIMARY KEY"]}
def column_names(self):
return sorted(self.columns().keys(), key = lambda x: x)
def primary_key(self):
return ['id']
def column_names_without_id(self):
res = sorted(self.columns().keys(), key = lambda x: x)
if 'id' in res:
res.remove('id')
return res
def table_constraints(self):
return []
def create(self):
sql = "CREATE TABLE " + self.table_name() + "("
arr = [k + " " + " ".join(v) for k, v in sorted(self.columns().items(), key = lambda x: x[0])]
sql += ", ".join(arr + self.table_constraints())
sql += ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def drop(self):
sql = "DROP TABLE IF EXISTS " + self.table_name()
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def insert_one(self, vals):
for i in range(0, len(vals)):
if type(vals[i]) == str:
vals[i] = "'" + vals[i] + "'"
else:
vals[i] = str(vals[i])
sql = "INSERT INTO " + self.table_name() + "("
sql += ", ".join(self.column_names_without_id()) + ") VALUES("
sql += ", ".join(vals) + ")"
cur = self.dbconn.conn.cursor()
cur.execute(sql)
self.dbconn.conn.commit()
return
def first(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def last(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join([x + " DESC" for x in self.primary_key()])
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchone()
def all(self):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql)
return cur.fetchall()
+182
View File
@@ -0,0 +1,182 @@
import sys
sys.path.append('tables')
from project_config import *
from dbconnection import *
from people_table import *
from phones_table import *
class Main:
config = ProjectConfig()
connection = DbConnection(config)
def __init__(self):
DbTable.dbconn = self.connection
return
def db_init(self):
pt = PeopleTable()
pht = PhonesTable()
pt.create()
pht.create()
return
def db_insert_somethings(self):
pt = PeopleTable()
pht = PhonesTable()
pt.insert_one(["Test", "Test", "Test"])
pt.insert_one(["Test2", "Test2", "Test2"])
pt.insert_one(["Test3", "Test3", "Test3"])
pht.insert_one([1, "123"])
pht.insert_one([2, "123"])
pht.insert_one([3, "123"])
def db_drop(self):
pht = PhonesTable()
pt = PeopleTable()
pht.drop()
pt.drop()
return
def show_main_menu(self):
menu = """Добро пожаловать!
Основное меню (выберите цифру в соответствии с необходимым действием):
1 - просмотр людей;
2 - сброс и инициализация таблиц;
9 - выход."""
print(menu)
return
def read_next_step(self):
return input("=> ").strip()
def after_main_menu(self, next_step):
if next_step == "2":
self.db_drop()
self.db_init()
self.db_insert_somethings()
print("Таблицы созданы заново!")
return "0"
elif next_step != "1" and next_step != "9":
print("Выбрано неверное число! Повторите ввод!")
return "0"
else:
return next_step
def show_people(self):
self.person_id = -1
menu = """Просмотр списка людей!
\tФамилия\tИмя\tОтчество"""
print(menu)
lst = PeopleTable().all()
for i in lst:
print(str(i[1]) + "\t" + str(i[2]) + "\t" + str(i[0]) + "\t" + str(i[3]))
menu = """Дальнейшие операции:
0 - возврат в главное меню;
3 - добавление нового человека;
4 - удаление человека;
5 - просмотр телефонов человека;
9 - выход."""
print(menu)
return
def after_show_people(self, next_step):
while True:
if next_step == "4":
print("Пока не реализовано!")
return "1"
elif next_step == "6" or next_step == "7":
print("Пока не реализовано!")
next_step = "5"
elif next_step == "5":
next_step = self.show_phones_by_people()
elif next_step != "0" and next_step != "9" and next_step != "3":
print("Выбрано неверное число! Повторите ввод!")
return "1"
else:
return next_step
def show_add_person(self):
# Не реализована проверка на максимальную длину строк. Нужно доделать самостоятельно!
data = []
data.append(input("Введите имя (1 - отмена): ").strip())
if data[0] == "1":
return
while len(data[0].strip()) == 0:
data[0] = input("Имя не может быть пустым! Введите имя заново (1 - отмена):").strip()
if data[0] == "1":
return
data.append(input("Введите фамилию (1 - отмена): ").strip())
if data[1] == "1":
return
while len(data[1].strip()) == 0:
data[1] = input("Фамилия не может быть пустой! Введите фамилию заново (1 - отмена):").strip()
if data[1] == "1":
return
data.append(input("Введите отчество (1 - отмена):").strip())
if data[2] == "1":
return
PeopleTable().insert_one(data)
return
def show_phones_by_people(self):
if self.person_id == -1:
while True:
num = input("Укажите номер строки, в которой записана интересующая Вас персона (0 - отмена):")
while len(num.strip()) == 0:
num = input("Пустая строка. Повторите ввод! Укажите номер строки, в которой записана интересующая Вас персона (0 - отмена):")
if num == "0":
return "1"
person = PeopleTable().find_by_position(int(num))
if not person:
print("Введено число, неудовлетворяющее количеству людей!")
else:
self.person_id = int(person[1])
self.person_obj = person
break
print("Выбран человек: " + self.person_obj[2] + " " + self.person_obj[0] + " " + self.person_obj[3])
print("Телефоны:")
lst = PhonesTable().all_by_person_id(self.person_id)
for i in lst:
print(i[1])
menu = """Дальнейшие операции:
0 - возврат в главное меню;
1 - возврат в просмотр людей;
6 - добавление нового телефона;
7 - удаление телефона;
9 - выход."""
print(menu)
return self.read_next_step()
return self.read_next_step()
def main_cycle(self):
current_menu = "0"
next_step = None
while(current_menu != "9"):
if current_menu == "0":
self.show_main_menu()
next_step = self.read_next_step()
current_menu = self.after_main_menu(next_step)
elif current_menu == "1":
self.show_people()
next_step = self.read_next_step()
current_menu = self.after_show_people(next_step)
elif current_menu == "2":
self.show_main_menu()
elif current_menu == "3":
self.show_add_person()
current_menu = "1"
print("До свидания!")
return
def test(self):
DbTable.dbconn.test()
m = Main()
# Откоментируйте эту строку и закоментируйте следующую для теста
# соединения с БД
# m.test()
m.main_cycle()
+21
View File
@@ -0,0 +1,21 @@
# Загрузка настроек проекта (в данном случае только настроек соединения с БД)
# из файла config.yaml.
import yaml
class ProjectConfig:
"""Класс считывает базовые настройки из файла config.yaml"""
def __init__(self):
with open('config.yaml') as f:
config = yaml.safe_load(f)
self.dbname = config['dbname']
self.user = config['user']
self.password = config['password']
self.host = config['host']
self.dbtableprefix = config['dbtableprefix']
# Этот метод запускается только, если запускать
# данный файл, а не подключать его.
if __name__ == "__main__":
x = ProjectConfig()
print(x.dbfilepath)
@@ -0,0 +1,23 @@
# Таблица персоны и особые действия с ней
from dbtable import *
class PeopleTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "people"
def columns(self):
return {"id": ["serial", "PRIMARY KEY"],
"last_name": ["varchar(32)", "NOT NULL"],
"first_name": ["varchar(32)", "NOT NULL"],
"second_name": ["varchar(32)"]}
def find_by_position(self, num):
sql = "SELECT * FROM " + self.table_name()
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
sql += " LIMIT 1 OFFSET %(offset)s"
cur = self.dbconn.conn.cursor()
cur.execute(sql, {"offset": num - 1})
return cur.fetchone()
@@ -0,0 +1,27 @@
# Таблица Телефоны и особые действия с ней.
from dbtable import *
class PhonesTable(DbTable):
def table_name(self):
return self.dbconn.prefix + "phones"
def columns(self):
return {"person_id": ["integer", "REFERENCES people(id)"],
"phone": ["varchar(12)", "NOT NULL"]}
def primary_key(self):
return ['person_id', 'phone']
def table_constraints(self):
return ["PRIMARY KEY(person_id, phone)"]
def all_by_person_id(self, pid):
sql = "SELECT * FROM " + self.table_name()
sql += " WHERE person_id = %s"
sql += " ORDER BY "
sql += ", ".join(self.primary_key())
cur = self.dbconn.conn.cursor()
cur.execute(sql, str(pid))
return cur.fetchall()
+11
View File
@@ -0,0 +1,11 @@
# import sys
# sys.path.append('tables')
# from project_config import *
# from dbconnection import *
# # from tables.people_table import *
# # from tables.phones_table import *
# from tables.dish_table import *
# from tables.cath_table import *
# cath = CathTable().find_by_name('За')
+1
View File
@@ -0,0 +1 @@
SELECT * FROM dish;
@@ -0,0 +1 @@
pip
@@ -0,0 +1,20 @@
Copyright (c) 2017-2021 Ingy döt Net
Copyright (c) 2006-2016 Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,46 @@
Metadata-Version: 2.1
Name: PyYAML
Version: 6.0.1
Summary: YAML parser and emitter for Python
Home-page: https://pyyaml.org/
Download-URL: https://pypi.org/project/PyYAML/
Author: Kirill Simonov
Author-email: xi@resolvent.net
License: MIT
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
Project-URL: CI, https://github.com/yaml/pyyaml/actions
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
Project-URL: Source Code, https://github.com/yaml/pyyaml
Platform: Any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Cython
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.6
License-File: LICENSE
YAML is a data serialization format designed for human readability
and interaction with scripting languages. PyYAML is a YAML parser
and emitter for Python.
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
support, capable extension API, and sensible error messages. PyYAML
supports standard YAML tags and provides Python-specific tags that
allow to represent an arbitrary Python object.
PyYAML is applicable for a broad range of tasks from complex
configuration files to object serialization and persistence.
@@ -0,0 +1,44 @@
PyYAML-6.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
PyYAML-6.0.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
PyYAML-6.0.1.dist-info/METADATA,sha256=i3GoINVJ0RnmgIBLEFUIA75PtRGAhQAZGeWJNZFKogc,2104
PyYAML-6.0.1.dist-info/RECORD,,
PyYAML-6.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
PyYAML-6.0.1.dist-info/WHEEL,sha256=9wvhO-5NhjjD8YmmxAvXTPQXMDOZ50W5vklzeoqFtkM,102
PyYAML-6.0.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
_yaml/__pycache__/__init__.cpython-311.pyc,,
yaml/__init__.py,sha256=bhl05qSeO-1ZxlSRjGrvl2m9nrXb1n9-GQatTN0Mrqc,12311
yaml/__pycache__/__init__.cpython-311.pyc,,
yaml/__pycache__/composer.cpython-311.pyc,,
yaml/__pycache__/constructor.cpython-311.pyc,,
yaml/__pycache__/cyaml.cpython-311.pyc,,
yaml/__pycache__/dumper.cpython-311.pyc,,
yaml/__pycache__/emitter.cpython-311.pyc,,
yaml/__pycache__/error.cpython-311.pyc,,
yaml/__pycache__/events.cpython-311.pyc,,
yaml/__pycache__/loader.cpython-311.pyc,,
yaml/__pycache__/nodes.cpython-311.pyc,,
yaml/__pycache__/parser.cpython-311.pyc,,
yaml/__pycache__/reader.cpython-311.pyc,,
yaml/__pycache__/representer.cpython-311.pyc,,
yaml/__pycache__/resolver.cpython-311.pyc,,
yaml/__pycache__/scanner.cpython-311.pyc,,
yaml/__pycache__/serializer.cpython-311.pyc,,
yaml/__pycache__/tokens.cpython-311.pyc,,
yaml/_yaml.cp311-win_amd64.pyd,sha256=oMYsA1zRMc4eV0dC2R1BXedhpcXVw1pPNqQbjgsKsZU,233984
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.40.0)
Root-Is-Purelib: false
Tag: cp311-cp311-win_amd64
@@ -0,0 +1,2 @@
_yaml
yaml
@@ -0,0 +1,222 @@
# don't import any costly modules
import sys
import os
is_pypy = '__pypy__' in sys.builtin_module_names
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
if is_pypy and sys.version_info < (3, 7):
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
return
import warnings
warnings.warn(
"Distutils was imported before Setuptools, but importing Setuptools "
"also replaces the `distutils` module in `sys.modules`. This may lead "
"to undesirable behaviors or errors. To avoid these issues, avoid "
"using distutils directly, ensure that setuptools is installed in the "
"traditional way (e.g. not an editable install), and/or make sure "
"that setuptools is always imported before distutils."
)
def clear_distutils():
if 'distutils' not in sys.modules:
return
import warnings
warnings.warn("Setuptools is replacing distutils.")
mods = [
name
for name in sys.modules
if name == "distutils" or name.startswith("distutils.")
]
for name in mods:
del sys.modules[name]
def enabled():
"""
Allow selection of distutils by environment variable.
"""
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
return which == 'local'
def ensure_local_distutils():
import importlib
clear_distutils()
# With the DistutilsMetaFinder in place,
# perform an import to cause distutils to be
# loaded from setuptools._distutils. Ref #2906.
with shim():
importlib.import_module('distutils')
# check that submodules load as expected
core = importlib.import_module('distutils.core')
assert '_distutils' in core.__file__, core.__file__
assert 'setuptools._distutils.log' not in sys.modules
def do_override():
"""
Ensure that the local copy of distutils is preferred over stdlib.
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
for more motivation.
"""
if enabled():
warn_distutils_present()
ensure_local_distutils()
class _TrivialRe:
def __init__(self, *patterns):
self._patterns = patterns
def match(self, string):
return all(pat in string for pat in self._patterns)
class DistutilsMetaFinder:
def find_spec(self, fullname, path, target=None):
# optimization: only consider top level modules and those
# found in the CPython test suite.
if path is not None and not fullname.startswith('test.'):
return
method_name = 'spec_for_{fullname}'.format(**locals())
method = getattr(self, method_name, lambda: None)
return method()
def spec_for_distutils(self):
if self.is_cpython():
return
import importlib
import importlib.abc
import importlib.util
try:
mod = importlib.import_module('setuptools._distutils')
except Exception:
# There are a couple of cases where setuptools._distutils
# may not be present:
# - An older Setuptools without a local distutils is
# taking precedence. Ref #2957.
# - Path manipulation during sitecustomize removes
# setuptools from the path but only after the hook
# has been loaded. Ref #2980.
# In either case, fall back to stdlib behavior.
return
class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
mod.__name__ = 'distutils'
return mod
def exec_module(self, module):
pass
return importlib.util.spec_from_loader(
'distutils', DistutilsLoader(), origin=mod.__file__
)
@staticmethod
def is_cpython():
"""
Suppress supplying distutils for CPython (build and tests).
Ref #2965 and #3007.
"""
return os.path.isfile('pybuilddir.txt')
def spec_for_pip(self):
"""
Ensure stdlib distutils when running under pip.
See pypa/pip#8761 for rationale.
"""
if self.pip_imported_during_build():
return
clear_distutils()
self.spec_for_distutils = lambda: None
@classmethod
def pip_imported_during_build(cls):
"""
Detect if pip is being imported in a build script. Ref #2355.
"""
import traceback
return any(
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
)
@staticmethod
def frame_file_is_setup(frame):
"""
Return True if the indicated frame suggests a setup.py file.
"""
# some frames may not have __file__ (#2940)
return frame.f_globals.get('__file__', '').endswith('setup.py')
def spec_for_sensitive_tests(self):
"""
Ensure stdlib distutils when running select tests under CPython.
python/cpython#91169
"""
clear_distutils()
self.spec_for_distutils = lambda: None
sensitive_tests = (
[
'test.test_distutils',
'test.test_peg_generator',
'test.test_importlib',
]
if sys.version_info < (3, 10)
else [
'test.test_distutils',
]
)
for name in DistutilsMetaFinder.sensitive_tests:
setattr(
DistutilsMetaFinder,
f'spec_for_{name}',
DistutilsMetaFinder.spec_for_sensitive_tests,
)
DISTUTILS_FINDER = DistutilsMetaFinder()
def add_shim():
DISTUTILS_FINDER in sys.meta_path or insert_shim()
class shim:
def __enter__(self):
insert_shim()
def __exit__(self, exc, value, tb):
remove_shim()
def insert_shim():
sys.meta_path.insert(0, DISTUTILS_FINDER)
def remove_shim():
try:
sys.meta_path.remove(DISTUTILS_FINDER)
except ValueError:
pass
@@ -0,0 +1 @@
__import__('_distutils_hack').do_override()
@@ -0,0 +1,33 @@
# This is a stub package designed to roughly emulate the _yaml
# extension module, which previously existed as a standalone module
# and has been moved into the `yaml` package namespace.
# It does not perfectly mimic its old counterpart, but should get
# close enough for anyone who's relying on it even when they shouldn't.
import yaml
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
# to tread carefully when poking at it here (it may not have the attributes we expect)
if not getattr(yaml, '__with_libyaml__', False):
from sys import version_info
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
raise exc("No module named '_yaml'")
else:
from yaml._yaml import *
import warnings
warnings.warn(
'The _yaml extension module is now located at yaml._yaml'
' and its location is subject to change. To use the'
' LibYAML-based parser and emitter, import from `yaml`:'
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
DeprecationWarning
)
del warnings
# Don't `del yaml` here because yaml is actually an existing
# namespace member of _yaml.
__name__ = '_yaml'
# If the module is top-level (i.e. not a part of any specific package)
# then the attribute should be set to ''.
# https://docs.python.org/3.8/library/types.html
__package__ = ''
@@ -0,0 +1 @@
import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'local') == 'local'; enabled and __import__('_distutils_hack').add_shim();
@@ -0,0 +1,760 @@
@Switch01
A_Rog
Aakanksha Agrawal
Abhinav Sagar
ABHYUDAY PRATAP SINGH
abs51295
AceGentile
Adam Chainz
Adam Tse
Adam Wentz
admin
Adrien Morison
ahayrapetyan
Ahilya
AinsworthK
Akash Srivastava
Alan Yee
Albert Tugushev
Albert-Guan
albertg
Alberto Sottile
Aleks Bunin
Ales Erjavec
Alethea Flowers
Alex Gaynor
Alex Grönholm
Alex Hedges
Alex Loosley
Alex Morega
Alex Stachowiak
Alexander Shtyrov
Alexandre Conrad
Alexey Popravka
Aleš Erjavec
Alli
Ami Fischman
Ananya Maiti
Anatoly Techtonik
Anders Kaseorg
Andre Aguiar
Andreas Lutro
Andrei Geacar
Andrew Gaul
Andrew Shymanel
Andrey Bienkowski
Andrey Bulgakov
Andrés Delfino
Andy Freeland
Andy Kluger
Ani Hayrapetyan
Aniruddha Basak
Anish Tambe
Anrs Hu
Anthony Sottile
Antoine Musso
Anton Ovchinnikov
Anton Patrushev
Antonio Alvarado Hernandez
Antony Lee
Antti Kaihola
Anubhav Patel
Anudit Nagar
Anuj Godase
AQNOUCH Mohammed
AraHaan
Arindam Choudhury
Armin Ronacher
Artem
Arun Babu Neelicattu
Ashley Manton
Ashwin Ramaswami
atse
Atsushi Odagiri
Avinash Karhana
Avner Cohen
Awit (Ah-Wit) Ghirmai
Baptiste Mispelon
Barney Gale
barneygale
Bartek Ogryczak
Bastian Venthur
Ben Bodenmiller
Ben Darnell
Ben Hoyt
Ben Mares
Ben Rosser
Bence Nagy
Benjamin Peterson
Benjamin VanEvery
Benoit Pierre
Berker Peksag
Bernard
Bernard Tyers
Bernardo B. Marques
Bernhard M. Wiedemann
Bertil Hatt
Bhavam Vidyarthi
Blazej Michalik
Bogdan Opanchuk
BorisZZZ
Brad Erickson
Bradley Ayers
Brandon L. Reiss
Brandt Bucher
Brett Randall
Brett Rosen
Brian Cristante
Brian Rosner
briantracy
BrownTruck
Bruno Oliveira
Bruno Renié
Bruno S
Bstrdsmkr
Buck Golemon
burrows
Bussonnier Matthias
bwoodsend
c22
Caleb Martinez
Calvin Smith
Carl Meyer
Carlos Liam
Carol Willing
Carter Thayer
Cass
Chandrasekhar Atina
Chih-Hsuan Yen
Chris Brinker
Chris Hunt
Chris Jerdonek
Chris Kuehl
Chris McDonough
Chris Pawley
Chris Pryer
Chris Wolfe
Christian Clauss
Christian Heimes
Christian Oudard
Christoph Reiter
Christopher Hunt
Christopher Snyder
cjc7373
Clark Boylan
Claudio Jolowicz
Clay McClure
Cody
Cody Soyland
Colin Watson
Collin Anderson
Connor Osborn
Cooper Lees
Cooper Ry Lees
Cory Benfield
Cory Wright
Craig Kerstiens
Cristian Sorinel
Cristina
Cristina Muñoz
Curtis Doty
cytolentino
Daan De Meyer
Dale
Damian
Damian Quiroga
Damian Shaw
Dan Black
Dan Savilonis
Dan Sully
Dane Hillard
daniel
Daniel Collins
Daniel Hahler
Daniel Holth
Daniel Jost
Daniel Katz
Daniel Shaulov
Daniele Esposti
Daniele Nicolodi
Daniele Procida
Daniil Konovalenko
Danny Hermes
Danny McClanahan
Darren Kavanagh
Dav Clark
Dave Abrahams
Dave Jones
David Aguilar
David Black
David Bordeynik
David Caro
David D Lowe
David Evans
David Hewitt
David Linke
David Poggi
David Pursehouse
David Runge
David Tucker
David Wales
Davidovich
ddelange
Deepak Sharma
Deepyaman Datta
Denise Yu
dependabot[bot]
derwolfe
Desetude
Devesh Kumar Singh
Diego Caraballo
Diego Ramirez
DiegoCaraballo
Dimitri Merejkowsky
Dimitri Papadopoulos
Dirk Stolle
Dmitry Gladkov
Dmitry Volodin
Domen Kožar
Dominic Davis-Foster
Donald Stufft
Dongweiming
doron zarhi
Dos Moonen
Douglas Thor
DrFeathers
Dustin Ingram
Dwayne Bailey
Ed Morley
Edgar Ramírez
Edgar Ramírez Mondragón
Ee Durbin
Efflam Lemaillet
efflamlemaillet
Eitan Adler
ekristina
elainechan
Eli Schwartz
Elisha Hollander
Ellen Marie Dash
Emil Burzo
Emil Styrke
Emmanuel Arias
Endoh Takanao
enoch
Erdinc Mutlu
Eric Cousineau
Eric Gillingham
Eric Hanchrow
Eric Hopper
Erik M. Bray
Erik Rose
Erwin Janssen
Eugene Vereshchagin
everdimension
Federico
Felipe Peter
Felix Yan
fiber-space
Filip Kokosiński
Filipe Laíns
Finn Womack
finnagin
Flavio Amurrio
Florian Briand
Florian Rathgeber
Francesco
Francesco Montesano
Frost Ming
Gabriel Curio
Gabriel de Perthuis
Garry Polley
gavin
gdanielson
Geoffrey Sneddon
George Song
Georgi Valkov
Georgy Pchelkin
ghost
Giftlin Rajaiah
gizmoguy1
gkdoc
Godefroid Chapelle
Gopinath M
GOTO Hayato
gousaiyang
gpiks
Greg Roodt
Greg Ward
Guilherme Espada
Guillaume Seguin
gutsytechster
Guy Rozendorn
Guy Tuval
gzpan123
Hanjun Kim
Hari Charan
Harsh Vardhan
harupy
Harutaka Kawamura
hauntsaninja
Henrich Hartzer
Henry Schreiner
Herbert Pfennig
Holly Stotelmyer
Honnix
Hsiaoming Yang
Hugo Lopes Tavares
Hugo van Kemenade
Hugues Bruant
Hynek Schlawack
Ian Bicking
Ian Cordasco
Ian Lee
Ian Stapleton Cordasco
Ian Wienand
Igor Kuzmitshov
Igor Sobreira
Ilan Schnell
Illia Volochii
Ilya Baryshev
Inada Naoki
Ionel Cristian Mărieș
Ionel Maries Cristian
Itamar Turner-Trauring
Ivan Pozdeev
J. Nick Koston
Jacob Kim
Jacob Walls
Jaime Sanz
jakirkham
Jakub Kuczys
Jakub Stasiak
Jakub Vysoky
Jakub Wilk
James Cleveland
James Curtin
James Firth
James Gerity
James Polley
Jan Pokorný
Jannis Leidel
Jarek Potiuk
jarondl
Jason Curtis
Jason R. Coombs
JasonMo
JasonMo1
Jay Graves
Jean Abou Samra
Jean-Christophe Fillion-Robin
Jeff Barber
Jeff Dairiki
Jeff Widman
Jelmer Vernooij
jenix21
Jeremy Stanley
Jeremy Zafran
Jesse Rittner
Jiashuo Li
Jim Fisher
Jim Garrison
Jiun Bae
Jivan Amara
Joe Bylund
Joe Michelini
John Paton
John T. Wodder II
John-Scott Atlakson
johnthagen
Jon Banafato
Jon Dufresne
Jon Parise
Jonas Nockert
Jonathan Herbert
Joonatan Partanen
Joost Molenaar
Jorge Niedbalski
Joseph Bylund
Joseph Long
Josh Bronson
Josh Hansen
Josh Schneier
Joshua
Juan Luis Cano Rodríguez
Juanjo Bazán
Judah Rand
Julian Berman
Julian Gethmann
Julien Demoor
Jussi Kukkonen
jwg4
Jyrki Pulliainen
Kai Chen
Kai Mueller
Kamal Bin Mustafa
kasium
kaustav haldar
keanemind
Keith Maxwell
Kelsey Hightower
Kenneth Belitzky
Kenneth Reitz
Kevin Burke
Kevin Carter
Kevin Frommelt
Kevin R Patterson
Kexuan Sun
Kit Randel
Klaas van Schelven
KOLANICH
kpinc
Krishna Oza
Kumar McMillan
Kurt McKee
Kyle Persohn
lakshmanaram
Laszlo Kiss-Kollar
Laurent Bristiel
Laurent LAPORTE
Laurie O
Laurie Opperman
layday
Leon Sasson
Lev Givon
Lincoln de Sousa
Lipis
lorddavidiii
Loren Carvalho
Lucas Cimon
Ludovic Gasc
Lukas Geiger
Lukas Juhrich
Luke Macken
Luo Jiebin
luojiebin
luz.paz
László Kiss Kollár
M00nL1ght
Marc Abramowitz
Marc Tamlyn
Marcus Smith
Mariatta
Mark Kohler
Mark Williams
Markus Hametner
Martey Dodoo
Martin Fischer
Martin Häcker
Martin Pavlasek
Masaki
Masklinn
Matej Stuchlik
Mathew Jennings
Mathieu Bridon
Mathieu Kniewallner
Matt Bacchi
Matt Good
Matt Maker
Matt Robenolt
matthew
Matthew Einhorn
Matthew Feickert
Matthew Gilliard
Matthew Iversen
Matthew Treinish
Matthew Trumbell
Matthew Willson
Matthias Bussonnier
mattip
Maurits van Rees
Max W Chase
Maxim Kurnikov
Maxime Rouyrre
mayeut
mbaluna
mdebi
memoselyk
meowmeowcat
Michael
Michael Aquilina
Michael E. Karpeles
Michael Klich
Michael Mintz
Michael Williamson
michaelpacer
Michał Górny
Mickaël Schoentgen
Miguel Araujo Perez
Mihir Singh
Mike
Mike Hendricks
Min RK
MinRK
Miro Hrončok
Monica Baluna
montefra
Monty Taylor
Muha Ajjan
Nadav Wexler
Nahuel Ambrosini
Nate Coraor
Nate Prewitt
Nathan Houghton
Nathaniel J. Smith
Nehal J Wani
Neil Botelho
Nguyễn Gia Phong
Nicholas Serra
Nick Coghlan
Nick Stenning
Nick Timkovich
Nicolas Bock
Nicole Harris
Nikhil Benesch
Nikhil Ladha
Nikita Chepanov
Nikolay Korolev
Nipunn Koorapati
Nitesh Sharma
Niyas Sait
Noah
Noah Gorny
Nowell Strite
NtaleGrey
nvdv
OBITORASU
Ofek Lev
ofrinevo
Oliver Freund
Oliver Jeeves
Oliver Mannion
Oliver Tonnhofer
Olivier Girardot
Olivier Grisel
Ollie Rutherfurd
OMOTO Kenji
Omry Yadan
onlinejudge95
Oren Held
Oscar Benjamin
Oz N Tiram
Pachwenko
Patrick Dubroy
Patrick Jenkins
Patrick Lawson
patricktokeeffe
Patrik Kopkan
Paul Ganssle
Paul Kehrer
Paul Moore
Paul Nasrat
Paul Oswald
Paul van der Linden
Paulus Schoutsen
Pavel Safronov
Pavithra Eswaramoorthy
Pawel Jasinski
Paweł Szramowski
Pekka Klärck
Peter Gessler
Peter Lisák
Peter Waller
petr-tik
Phaneendra Chiruvella
Phil Elson
Phil Freo
Phil Pennock
Phil Whelan
Philip Jägenstedt
Philip Molloy
Philippe Ombredanne
Pi Delport
Pierre-Yves Rofes
Pieter Degroote
pip
Prabakaran Kumaresshan
Prabhjyotsing Surjit Singh Sodhi
Prabhu Marappan
Pradyun Gedam
Prashant Sharma
Pratik Mallya
pre-commit-ci[bot]
Preet Thakkar
Preston Holmes
Przemek Wrzos
Pulkit Goyal
q0w
Qiangning Hong
Qiming Xu
Quentin Lee
Quentin Pradet
R. David Murray
Rafael Caricio
Ralf Schmitt
Razzi Abuissa
rdb
Reece Dunham
Remi Rampin
Rene Dudfield
Riccardo Magliocchetti
Riccardo Schirone
Richard Jones
Richard Si
Ricky Ng-Adam
Rishi
RobberPhex
Robert Collins
Robert McGibbon
Robert Pollak
Robert T. McGibbon
robin elisha robinson
Roey Berman
Rohan Jain
Roman Bogorodskiy
Roman Donchenko
Romuald Brunet
ronaudinho
Ronny Pfannschmidt
Rory McCann
Ross Brattain
Roy Wellington Ⅳ
Ruairidh MacLeod
Russell Keith-Magee
Ryan Shepherd
Ryan Wooden
ryneeverett
Sachi King
Salvatore Rinchiera
sandeepkiran-js
Sander Van Balen
Savio Jomton
schlamar
Scott Kitterman
Sean
seanj
Sebastian Jordan
Sebastian Schaetz
Segev Finer
SeongSoo Cho
Sergey Vasilyev
Seth Michael Larson
Seth Woodworth
Shahar Epstein
Shantanu
shireenrao
Shivansh-007
Shlomi Fish
Shovan Maity
Simeon Visser
Simon Cross
Simon Pichugin
sinoroc
sinscary
snook92
socketubs
Sorin Sbarnea
Srinivas Nyayapati
Stavros Korokithakis
Stefan Scherfke
Stefano Rivera
Stephan Erb
Stephen Rosen
stepshal
Steve (Gadget) Barnes
Steve Barnes
Steve Dower
Steve Kowalik
Steven Myint
Steven Silvester
stonebig
studioj
Stéphane Bidoul
Stéphane Bidoul (ACSONE)
Stéphane Klein
Sumana Harihareswara
Surbhi Sharma
Sviatoslav Sydorenko
Swat009
Sylvain
Takayuki SHIMIZUKAWA
Taneli Hukkinen
tbeswick
Thiago
Thijs Triemstra
Thomas Fenzl
Thomas Grainger
Thomas Guettler
Thomas Johansson
Thomas Kluyver
Thomas Smith
Thomas VINCENT
Tim D. Smith
Tim Gates
Tim Harder
Tim Heap
tim smith
tinruufu
Tobias Hermann
Tom Forbes
Tom Freudenheim
Tom V
Tomas Hrnciar
Tomas Orsava
Tomer Chachamu
Tommi Enenkel | AnB
Tomáš Hrnčiar
Tony Beswick
Tony Narlock
Tony Zhaocheng Tan
TonyBeswick
toonarmycaptain
Toshio Kuratomi
toxinu
Travis Swicegood
Tushar Sadhwani
Tzu-ping Chung
Valentin Haenel
Victor Stinner
victorvpaulo
Vikram - Google
Viktor Szépe
Ville Skyttä
Vinay Sajip
Vincent Philippon
Vinicyus Macedo
Vipul Kumar
Vitaly Babiy
Vladimir Fokow
Vladimir Rutsky
W. Trevor King
Wil Tan
Wilfred Hughes
William Edwards
William ML Leslie
William T Olson
William Woodruff
Wilson Mo
wim glenn
Winson Luk
Wolfgang Maier
Wu Zhenyu
XAMES3
Xavier Fernandez
xoviat
xtreak
YAMAMOTO Takashi
Yen Chi Hsuan
Yeray Diaz Diaz
Yoval P
Yu Jian
Yuan Jing Vincent Yan
Yusuke Hayashi
Zearin
Zhiping Deng
ziebam
Zvezdan Petkovic
Łukasz Langa
Роман Донченко
Семён Марьясин
rekcäH nitraM
@@ -0,0 +1 @@
pip
@@ -0,0 +1,20 @@
Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,88 @@
Metadata-Version: 2.1
Name: pip
Version: 24.0
Summary: The PyPA recommended tool for installing Python packages.
Author-email: The pip developers <distutils-sig@python.org>
License: MIT
Project-URL: Homepage, https://pip.pypa.io/
Project-URL: Documentation, https://pip.pypa.io
Project-URL: Source, https://github.com/pypa/pip
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Build Tools
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
License-File: AUTHORS.txt
pip - The Python Package Installer
==================================
.. image:: https://img.shields.io/pypi/v/pip.svg
:target: https://pypi.org/project/pip/
:alt: PyPI
.. image:: https://img.shields.io/pypi/pyversions/pip
:target: https://pypi.org/project/pip
:alt: PyPI - Python Version
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
:target: https://pip.pypa.io/en/latest
:alt: Documentation
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
Please take a look at our documentation for how to install and use pip:
* `Installation`_
* `Usage`_
We release updates regularly, with a new version every 3 months. Find more details in our documentation:
* `Release notes`_
* `Release process`_
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
* `Issue tracking`_
* `Discourse channel`_
* `User IRC`_
If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
* `GitHub page`_
* `Development documentation`_
* `Development IRC`_
Code of Conduct
---------------
Everyone interacting in the pip project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _package installer: https://packaging.python.org/guides/tool-recommendations/
.. _Python Package Index: https://pypi.org
.. _Installation: https://pip.pypa.io/en/stable/installation/
.. _Usage: https://pip.pypa.io/en/stable/
.. _Release notes: https://pip.pypa.io/en/stable/news.html
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
.. _GitHub page: https://github.com/pypa/pip
.. _Development documentation: https://pip.pypa.io/en/latest/development
.. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.42.0)
Root-Is-Purelib: true
Tag: py3-none-any
@@ -0,0 +1,4 @@
[console_scripts]
pip = pip._internal.cli.main:main
pip3 = pip._internal.cli.main:main
pip3.10 = pip._internal.cli.main:main
@@ -0,0 +1 @@
pip
+13
View File
@@ -0,0 +1,13 @@
from typing import List, Optional
__version__ = "24.0"
def main(args: Optional[List[str]] = None) -> int:
"""This is an internal API only meant for use by pip's own console scripts.
For additional details, see https://github.com/pypa/pip/issues/7498.
"""
from pip._internal.utils.entrypoints import _wrapper
return _wrapper(args)
+24
View File
@@ -0,0 +1,24 @@
import os
import sys
# Remove '' and current working directory from the first entry
# of sys.path, if present to avoid using current directory
# in pip commands check, freeze, install, list and show,
# when invoked as python -m pip <command>
if sys.path[0] in ("", os.getcwd()):
sys.path.pop(0)
# If we are running from a wheel, add the wheel to sys.path
# This allows the usage python pip-*.whl/pip install pip-*.whl
if __package__ == "":
# __file__ is pip-*.whl/pip/__main__.py
# first dirname call strips of '/__main__.py', second strips off '/pip'
# Resulting path is the name of the wheel itself
# Add that to sys.path so we can import pip
path = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, path)
if __name__ == "__main__":
from pip._internal.cli.main import main as _main
sys.exit(_main())
@@ -0,0 +1,50 @@
"""Execute exactly this copy of pip, within a different environment.
This file is named as it is, to ensure that this module can't be imported via
an import statement.
"""
# /!\ This version compatibility check section must be Python 2 compatible. /!\
import sys
# Copied from setup.py
PYTHON_REQUIRES = (3, 7)
def version_str(version): # type: ignore
return ".".join(str(v) for v in version)
if sys.version_info[:2] < PYTHON_REQUIRES:
raise SystemExit(
"This version of pip does not support python {} (requires >={}).".format(
version_str(sys.version_info[:2]), version_str(PYTHON_REQUIRES)
)
)
# From here on, we can use Python 3 features, but the syntax must remain
# Python 2 compatible.
import runpy # noqa: E402
from importlib.machinery import PathFinder # noqa: E402
from os.path import dirname # noqa: E402
PIP_SOURCES_ROOT = dirname(dirname(__file__))
class PipImportRedirectingFinder:
@classmethod
def find_spec(self, fullname, path=None, target=None): # type: ignore
if fullname != "pip":
return None
spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target)
assert spec, (PIP_SOURCES_ROOT, fullname)
return spec
sys.meta_path.insert(0, PipImportRedirectingFinder())
assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module"
runpy.run_module("pip", run_name="__main__", alter_sys=True)
@@ -0,0 +1,18 @@
from typing import List, Optional
from pip._internal.utils import _log
# init_logging() must be called before any call to logging.getLogger()
# which happens at import of most modules.
_log.init_logging()
def main(args: (Optional[List[str]]) = None) -> int:
"""This is preserved for old console scripts that may still be referencing
it.
For additional details, see https://github.com/pypa/pip/issues/7498.
"""
from pip._internal.utils.entrypoints import _wrapper
return _wrapper(args)
@@ -0,0 +1,311 @@
"""Build Environment used for isolation during sdist building
"""
import logging
import os
import pathlib
import site
import sys
import textwrap
from collections import OrderedDict
from types import TracebackType
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
from pip._vendor.certifi import where
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.version import Version
from pip import __file__ as pip_location
from pip._internal.cli.spinners import open_spinner
from pip._internal.locations import get_platlib, get_purelib, get_scheme
from pip._internal.metadata import get_default_environment, get_environment
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder
logger = logging.getLogger(__name__)
def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
return (a, b) if a != b else (a,)
class _Prefix:
def __init__(self, path: str) -> None:
self.path = path
self.setup = False
scheme = get_scheme("", prefix=path)
self.bin_dir = scheme.scripts
self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
def get_runnable_pip() -> str:
"""Get a file to pass to a Python executable, to run the currently-running pip.
This is used to run a pip subprocess, for installing requirements into the build
environment.
"""
source = pathlib.Path(pip_location).resolve().parent
if not source.is_dir():
# This would happen if someone is using pip from inside a zip file. In that
# case, we can use that directly.
return str(source)
return os.fsdecode(source / "__pip-runner__.py")
def _get_system_sitepackages() -> Set[str]:
"""Get system site packages
Usually from site.getsitepackages,
but fallback on `get_purelib()/get_platlib()` if unavailable
(e.g. in a virtualenv created by virtualenv<20)
Returns normalized set of strings.
"""
if hasattr(site, "getsitepackages"):
system_sites = site.getsitepackages()
else:
# virtualenv < 20 overwrites site.py without getsitepackages
# fallback on get_purelib/get_platlib.
# this is known to miss things, but shouldn't in the cases
# where getsitepackages() has been removed (inside a virtualenv)
system_sites = [get_purelib(), get_platlib()]
return {os.path.normcase(path) for path in system_sites}
class BuildEnvironment:
"""Creates and manages an isolated environment to install build deps"""
def __init__(self) -> None:
temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
self._prefixes = OrderedDict(
(name, _Prefix(os.path.join(temp_dir.path, name)))
for name in ("normal", "overlay")
)
self._bin_dirs: List[str] = []
self._lib_dirs: List[str] = []
for prefix in reversed(list(self._prefixes.values())):
self._bin_dirs.append(prefix.bin_dir)
self._lib_dirs.extend(prefix.lib_dirs)
# Customize site to:
# - ensure .pth files are honored
# - prevent access to system site packages
system_sites = _get_system_sitepackages()
self._site_dir = os.path.join(temp_dir.path, "site")
if not os.path.exists(self._site_dir):
os.mkdir(self._site_dir)
with open(
os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
) as fp:
fp.write(
textwrap.dedent(
"""
import os, site, sys
# First, drop system-sites related paths.
original_sys_path = sys.path[:]
known_paths = set()
for path in {system_sites!r}:
site.addsitedir(path, known_paths=known_paths)
system_paths = set(
os.path.normcase(path)
for path in sys.path[len(original_sys_path):]
)
original_sys_path = [
path for path in original_sys_path
if os.path.normcase(path) not in system_paths
]
sys.path = original_sys_path
# Second, add lib directories.
# ensuring .pth file are processed.
for path in {lib_dirs!r}:
assert not path in sys.path
site.addsitedir(path)
"""
).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
)
def __enter__(self) -> None:
self._save_env = {
name: os.environ.get(name, None)
for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
}
path = self._bin_dirs[:]
old_path = self._save_env["PATH"]
if old_path:
path.extend(old_path.split(os.pathsep))
pythonpath = [self._site_dir]
os.environ.update(
{
"PATH": os.pathsep.join(path),
"PYTHONNOUSERSITE": "1",
"PYTHONPATH": os.pathsep.join(pythonpath),
}
)
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
for varname, old_value in self._save_env.items():
if old_value is None:
os.environ.pop(varname, None)
else:
os.environ[varname] = old_value
def check_requirements(
self, reqs: Iterable[str]
) -> Tuple[Set[Tuple[str, str]], Set[str]]:
"""Return 2 sets:
- conflicting requirements: set of (installed, wanted) reqs tuples
- missing requirements: set of reqs
"""
missing = set()
conflicting = set()
if reqs:
env = (
get_environment(self._lib_dirs)
if hasattr(self, "_lib_dirs")
else get_default_environment()
)
for req_str in reqs:
req = Requirement(req_str)
# We're explicitly evaluating with an empty extra value, since build
# environments are not provided any mechanism to select specific extras.
if req.marker is not None and not req.marker.evaluate({"extra": ""}):
continue
dist = env.get_distribution(req.name)
if not dist:
missing.add(req_str)
continue
if isinstance(dist.version, Version):
installed_req_str = f"{req.name}=={dist.version}"
else:
installed_req_str = f"{req.name}==={dist.version}"
if not req.specifier.contains(dist.version, prereleases=True):
conflicting.add((installed_req_str, req_str))
# FIXME: Consider direct URL?
return conflicting, missing
def install_requirements(
self,
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
) -> None:
prefix = self._prefixes[prefix_as_string]
assert not prefix.setup
prefix.setup = True
if not requirements:
return
self._install_requirements(
get_runnable_pip(),
finder,
requirements,
prefix,
kind=kind,
)
@staticmethod
def _install_requirements(
pip_runnable: str,
finder: "PackageFinder",
requirements: Iterable[str],
prefix: _Prefix,
*,
kind: str,
) -> None:
args: List[str] = [
sys.executable,
pip_runnable,
"install",
"--ignore-installed",
"--no-user",
"--prefix",
prefix.path,
"--no-warn-script-location",
]
if logger.getEffectiveLevel() <= logging.DEBUG:
args.append("-v")
for format_control in ("no_binary", "only_binary"):
formats = getattr(finder.format_control, format_control)
args.extend(
(
"--" + format_control.replace("_", "-"),
",".join(sorted(formats or {":none:"})),
)
)
index_urls = finder.index_urls
if index_urls:
args.extend(["-i", index_urls[0]])
for extra_index in index_urls[1:]:
args.extend(["--extra-index-url", extra_index])
else:
args.append("--no-index")
for link in finder.find_links:
args.extend(["--find-links", link])
for host in finder.trusted_hosts:
args.extend(["--trusted-host", host])
if finder.allow_all_prereleases:
args.append("--pre")
if finder.prefer_binary:
args.append("--prefer-binary")
args.append("--")
args.extend(requirements)
extra_environ = {"_PIP_STANDALONE_CERT": where()}
with open_spinner(f"Installing {kind}") as spinner:
call_subprocess(
args,
command_desc=f"pip subprocess to install {kind}",
spinner=spinner,
extra_environ=extra_environ,
)
class NoOpBuildEnvironment(BuildEnvironment):
"""A no-op drop-in replacement for BuildEnvironment"""
def __init__(self) -> None:
pass
def __enter__(self) -> None:
pass
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
pass
def cleanup(self) -> None:
pass
def install_requirements(
self,
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
) -> None:
raise NotImplementedError()
@@ -0,0 +1,290 @@
"""Cache Management
"""
import hashlib
import json
import logging
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import InvalidWheelFilename
from pip._internal.models.direct_url import DirectUrl
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.urls import path_to_url
logger = logging.getLogger(__name__)
ORIGIN_JSON_NAME = "origin.json"
def _hash_dict(d: Dict[str, str]) -> str:
"""Return a stable sha224 of a dictionary."""
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha224(s.encode("ascii")).hexdigest()
class Cache:
"""An abstract class - provides cache directories for data from links
:param cache_dir: The root of the cache.
"""
def __init__(self, cache_dir: str) -> None:
super().__init__()
assert not cache_dir or os.path.isabs(cache_dir)
self.cache_dir = cache_dir or None
def _get_cache_path_parts(self, link: Link) -> List[str]:
"""Get parts of part that must be os.path.joined with cache_dir"""
# We want to generate an url to use as our cache key, we don't want to
# just re-use the URL because it might have other items in the fragment
# and we don't care about those.
key_parts = {"url": link.url_without_fragment}
if link.hash_name is not None and link.hash is not None:
key_parts[link.hash_name] = link.hash
if link.subdirectory_fragment:
key_parts["subdirectory"] = link.subdirectory_fragment
# Include interpreter name, major and minor version in cache key
# to cope with ill-behaved sdists that build a different wheel
# depending on the python version their setup.py is being run on,
# and don't encode the difference in compatibility tags.
# https://github.com/pypa/pip/issues/7296
key_parts["interpreter_name"] = interpreter_name()
key_parts["interpreter_version"] = interpreter_version()
# Encode our key url with sha224, we'll use this because it has similar
# security properties to sha256, but with a shorter total output (and
# thus less secure). However the differences don't make a lot of
# difference for our use case here.
hashed = _hash_dict(key_parts)
# We want to nest the directories some to prevent having a ton of top
# level directories where we might run out of sub directories on some
# FS.
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
return parts
def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
can_not_cache = not self.cache_dir or not canonical_package_name or not link
if can_not_cache:
return []
path = self.get_path_for_link(link)
if os.path.isdir(path):
return [(candidate, path) for candidate in os.listdir(path)]
return []
def get_path_for_link(self, link: Link) -> str:
"""Return a directory to store cached items in for link."""
raise NotImplementedError()
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
"""
raise NotImplementedError()
class SimpleWheelCache(Cache):
"""A cache of wheels for future installs."""
def __init__(self, cache_dir: str) -> None:
super().__init__(cache_dir)
def get_path_for_link(self, link: Link) -> str:
"""Return a directory to store cached wheels for link
Because there are M wheels for any one sdist, we provide a directory
to cache them in, and then consult that directory when looking up
cache hits.
We only insert things into the cache if they have plausible version
numbers, so that we don't contaminate the cache with things that were
not unique. E.g. ./package might have dozens of installs done for it
and build a version of 0.0...and if we built and cached a wheel, we'd
end up using the same wheel even if the source has been edited.
:param link: The link of the sdist for which this will cache wheels.
"""
parts = self._get_cache_path_parts(link)
assert self.cache_dir
# Store wheels within the root cache_dir
return os.path.join(self.cache_dir, "wheels", *parts)
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
candidates = []
if not package_name:
return link
canonical_package_name = canonicalize_name(package_name)
for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
try:
wheel = Wheel(wheel_name)
except InvalidWheelFilename:
continue
if canonicalize_name(wheel.name) != canonical_package_name:
logger.debug(
"Ignoring cached wheel %s for %s as it "
"does not match the expected distribution name %s.",
wheel_name,
link,
package_name,
)
continue
if not wheel.supported(supported_tags):
# Built for a different python/arch/etc
continue
candidates.append(
(
wheel.support_index_min(supported_tags),
wheel_name,
wheel_dir,
)
)
if not candidates:
return link
_, wheel_name, wheel_dir = min(candidates)
return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
class EphemWheelCache(SimpleWheelCache):
"""A SimpleWheelCache that creates it's own temporary cache directory"""
def __init__(self) -> None:
self._temp_dir = TempDirectory(
kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
globally_managed=True,
)
super().__init__(self._temp_dir.path)
class CacheEntry:
def __init__(
self,
link: Link,
persistent: bool,
):
self.link = link
self.persistent = persistent
self.origin: Optional[DirectUrl] = None
origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
if origin_direct_url_path.exists():
try:
self.origin = DirectUrl.from_json(
origin_direct_url_path.read_text(encoding="utf-8")
)
except Exception as e:
logger.warning(
"Ignoring invalid cache entry origin file %s for %s (%s)",
origin_direct_url_path,
link.filename,
e,
)
class WheelCache(Cache):
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
This Cache allows for gracefully degradation, using the ephem wheel cache
when a certain link is not found in the simple wheel cache first.
"""
def __init__(self, cache_dir: str) -> None:
super().__init__(cache_dir)
self._wheel_cache = SimpleWheelCache(cache_dir)
self._ephem_cache = EphemWheelCache()
def get_path_for_link(self, link: Link) -> str:
return self._wheel_cache.get_path_for_link(link)
def get_ephem_path_for_link(self, link: Link) -> str:
return self._ephem_cache.get_path_for_link(link)
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
if cache_entry is None:
return link
return cache_entry.link
def get_cache_entry(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Optional[CacheEntry]:
"""Returns a CacheEntry with a link to a cached item if it exists or
None. The cache entry indicates if the item was found in the persistent
or ephemeral cache.
"""
retval = self._wheel_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=True)
retval = self._ephem_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=False)
return None
@staticmethod
def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
if origin_path.exists():
try:
origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8"))
except Exception as e:
logger.warning(
"Could not read origin file %s in cache entry (%s). "
"Will attempt to overwrite it.",
origin_path,
e,
)
else:
# TODO: use DirectUrl.equivalent when
# https://github.com/pypa/pip/pull/10564 is merged.
if origin.url != download_info.url:
logger.warning(
"Origin URL %s in cache entry %s does not match download URL "
"%s. This is likely a pip bug or a cache corruption issue. "
"Will overwrite it with the new value.",
origin.url,
cache_dir,
download_info.url,
)
origin_path.write_text(download_info.to_json(), encoding="utf-8")
@@ -0,0 +1,4 @@
"""Subpackage containing all of pip's command line interface related code
"""
# This file intentionally does not import submodules

Some files were not shown because too many files have changed in this diff Show More