matplobbot-shared 0.1.31__tar.gz → 0.1.33__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matplobbot-shared might be problematic. Click here for more details.
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/PKG-INFO +2 -1
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/PKG-INFO +2 -1
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/SOURCES.txt +1 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/requires.txt +1 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/setup.py +3 -2
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/database.py +21 -1
- matplobbot_shared-0.1.33/shared_lib/redis_client.py +46 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/services/schedule_service.py +8 -8
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/README.md +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/dependency_links.txt +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/top_level.txt +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/setup.cfg +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/__init__.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/i18n.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/services/__init__.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/services/university_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matplobbot-shared
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.33
|
|
4
4
|
Summary: Shared library for the Matplobbot ecosystem (database, services, i18n).
|
|
5
5
|
Author: Ackrome
|
|
6
6
|
Author-email: ivansergeyevich@gmail.com
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Requires-Dist: asyncpg
|
|
9
9
|
Requires-Dist: aiohttp
|
|
10
10
|
Requires-Dist: certifi
|
|
11
|
+
Requires-Dist: redis
|
|
11
12
|
Dynamic: author
|
|
12
13
|
Dynamic: author-email
|
|
13
14
|
Dynamic: requires-dist
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matplobbot-shared
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.33
|
|
4
4
|
Summary: Shared library for the Matplobbot ecosystem (database, services, i18n).
|
|
5
5
|
Author: Ackrome
|
|
6
6
|
Author-email: ivansergeyevich@gmail.com
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Requires-Dist: asyncpg
|
|
9
9
|
Requires-Dist: aiohttp
|
|
10
10
|
Requires-Dist: certifi
|
|
11
|
+
Requires-Dist: redis
|
|
11
12
|
Dynamic: author
|
|
12
13
|
Dynamic: author-email
|
|
13
14
|
Dynamic: requires-dist
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="matplobbot-shared",
|
|
5
|
-
version="0.1.
|
|
5
|
+
version="0.1.33", # Let's use the version from your requirements.txt
|
|
6
6
|
packages=find_packages(include=['shared_lib', 'shared_lib.*']),
|
|
7
7
|
description="Shared library for the Matplobbot ecosystem (database, services, i18n).",
|
|
8
8
|
author="Ackrome",
|
|
@@ -11,7 +11,8 @@ setup(
|
|
|
11
11
|
install_requires=[
|
|
12
12
|
"asyncpg",
|
|
13
13
|
"aiohttp", # Specify versions as needed
|
|
14
|
-
"certifi"
|
|
14
|
+
"certifi",
|
|
15
|
+
"redis"
|
|
15
16
|
],
|
|
16
17
|
# This tells setuptools that the package data (like .json files) should be included
|
|
17
18
|
package_data={
|
|
@@ -108,6 +108,7 @@ async def init_db():
|
|
|
108
108
|
entity_id TEXT NOT NULL,
|
|
109
109
|
entity_name TEXT NOT NULL,
|
|
110
110
|
notification_time TIME NOT NULL,
|
|
111
|
+
last_schedule_hash TEXT,
|
|
111
112
|
is_active BOOLEAN DEFAULT TRUE,
|
|
112
113
|
UNIQUE(user_id, entity_type, entity_id)
|
|
113
114
|
)
|
|
@@ -254,13 +255,32 @@ async def remove_schedule_subscription(subscription_id: int, user_id: int) -> st
|
|
|
254
255
|
|
|
255
256
|
async def get_subscriptions_for_notification(notification_time: str) -> list:
|
|
256
257
|
async with pool.acquire() as connection:
|
|
258
|
+
# Modified to select the subscription ID and the last hash
|
|
257
259
|
rows = await connection.fetch("""
|
|
258
|
-
SELECT user_id, entity_type, entity_id, entity_name
|
|
260
|
+
SELECT id, user_id, entity_type, entity_id, entity_name, last_schedule_hash
|
|
259
261
|
FROM user_schedule_subscriptions
|
|
260
262
|
WHERE is_active = TRUE AND TO_CHAR(notification_time, 'HH24:MI') = $1
|
|
261
263
|
""", notification_time)
|
|
262
264
|
return [dict(row) for row in rows]
|
|
263
265
|
|
|
266
|
+
async def get_all_active_subscriptions() -> list:
|
|
267
|
+
"""Fetches all active schedule subscriptions from the database."""
|
|
268
|
+
if not pool:
|
|
269
|
+
raise ConnectionError("Database pool is not initialized.")
|
|
270
|
+
async with pool.acquire() as connection:
|
|
271
|
+
rows = await connection.fetch("""
|
|
272
|
+
SELECT id, user_id, entity_type, entity_id, entity_name, last_schedule_hash
|
|
273
|
+
FROM user_schedule_subscriptions WHERE is_active = TRUE
|
|
274
|
+
""")
|
|
275
|
+
return [dict(row) for row in rows]
|
|
276
|
+
|
|
277
|
+
async def update_subscription_hash(subscription_id: int, new_hash: str):
|
|
278
|
+
"""Updates the schedule hash for a specific subscription."""
|
|
279
|
+
if not pool:
|
|
280
|
+
raise ConnectionError("Database pool is not initialized.")
|
|
281
|
+
async with pool.acquire() as connection:
|
|
282
|
+
await connection.execute("UPDATE user_schedule_subscriptions SET last_schedule_hash = $1 WHERE id = $2", new_hash, subscription_id)
|
|
283
|
+
|
|
264
284
|
# --- FastAPI Specific Queries ---
|
|
265
285
|
async def get_leaderboard_data_from_db(db_conn):
|
|
266
286
|
# The timestamp is stored in UTC (TIMESTAMPTZ). We convert it to Moscow time for display.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import redis.asyncio as redis
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
# TTL для кэша в секундах (например, 1 час)
|
|
8
|
+
CACHE_TTL = 3600
|
|
9
|
+
|
|
10
|
+
class RedisClient:
|
|
11
|
+
def __init__(self, host='localhost', port=6379):
|
|
12
|
+
# Используем connection_pool для более эффективного управления соединениями
|
|
13
|
+
self.pool = redis.ConnectionPool(host=host, port=port, db=0, decode_responses=True)
|
|
14
|
+
self.client = redis.Redis(connection_pool=self.pool)
|
|
15
|
+
|
|
16
|
+
async def set_user_cache(self, user_id: int, key: str, data: dict, ttl: int = CACHE_TTL):
|
|
17
|
+
"""Сохраняет данные в кэш для конкретного пользователя."""
|
|
18
|
+
try:
|
|
19
|
+
redis_key = f"user_cache:{user_id}:{key}"
|
|
20
|
+
await self.client.set(redis_key, json.dumps(data), ex=ttl)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logger.error(f"Ошибка при записи в Redis для user_id={user_id}, key={key}: {e}")
|
|
23
|
+
|
|
24
|
+
async def get_user_cache(self, user_id: int, key: str) -> dict | None:
|
|
25
|
+
"""Получает данные из кэша для конкретного пользователя."""
|
|
26
|
+
try:
|
|
27
|
+
redis_key = f"user_cache:{user_id}:{key}"
|
|
28
|
+
data = await self.client.get(redis_key)
|
|
29
|
+
if data:
|
|
30
|
+
return json.loads(data)
|
|
31
|
+
return None
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logger.error(f"Ошибка при чтении из Redis для user_id={user_id}, key={key}: {e}")
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
async def clear_all_user_cache(self):
|
|
37
|
+
"""Очищает весь пользовательский кэш (ключи, начинающиеся с 'user_cache:')."""
|
|
38
|
+
try:
|
|
39
|
+
async for key in self.client.scan_iter("user_cache:*"):
|
|
40
|
+
await self.client.delete(key)
|
|
41
|
+
logger.info("Весь пользовательский кэш в Redis очищен.")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"Ошибка при очистке кэша Redis: {e}")
|
|
44
|
+
|
|
45
|
+
# Создаем единственный экземпляр клиента
|
|
46
|
+
redis_client = RedisClient(host='redis')
|
{matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/shared_lib/services/schedule_service.py
RENAMED
|
@@ -23,8 +23,8 @@ def format_schedule(schedule_data: List[Dict[str, Any]], lang: str, entity_name:
|
|
|
23
23
|
"""Formats a list of lessons into a readable daily schedule."""
|
|
24
24
|
if not schedule_data:
|
|
25
25
|
# Different message for single day vs week
|
|
26
|
-
no_lessons_key = "schedule_no_lessons_week" if is_week_view else "schedule_no_lessons_day"
|
|
27
|
-
return
|
|
26
|
+
no_lessons_key = "schedule_no_lessons_week" if is_week_view else "schedule_no_lessons_day" # This was Russian text
|
|
27
|
+
return translator.gettext(lang, "schedule_header_for", entity_name=entity_name) + f"\n\n{translator.gettext(lang, no_lessons_key)}"
|
|
28
28
|
|
|
29
29
|
# Group lessons by date
|
|
30
30
|
days = defaultdict(list)
|
|
@@ -41,24 +41,24 @@ def format_schedule(schedule_data: List[Dict[str, Any]], lang: str, entity_name:
|
|
|
41
41
|
formatted_lessons = []
|
|
42
42
|
for lesson in sorted(lessons, key=lambda x: x['beginLesson']):
|
|
43
43
|
lesson_details = [
|
|
44
|
-
f"`{lesson['beginLesson']} - {lesson['endLesson']}`",
|
|
44
|
+
f"`{lesson['beginLesson']} - {lesson['endLesson']} | {lesson['auditorium']}`",
|
|
45
45
|
f"{lesson['discipline']} | {names_shorter[lesson['kindOfWork']]}"
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
if entity_type == 'group':
|
|
49
|
-
lesson_details.append(f"
|
|
49
|
+
lesson_details.append(f"{lesson['lecturer_title'].replace('_',' ')}\n{lesson.get('lecturerEmail', 'Почта не указана')}")
|
|
50
50
|
elif entity_type == 'person': # Lecturer
|
|
51
|
-
lesson_details.append(f"
|
|
51
|
+
lesson_details.append(f" {lesson.get('group', 'Группа не указана')}")
|
|
52
52
|
elif entity_type == 'auditorium':
|
|
53
|
-
lesson_details.append(f"{lesson.get('group', 'Группа не указана')} | {lesson['lecturer_title'].replace('_',' ')}")
|
|
53
|
+
lesson_details.append(f"{lesson.get('group', 'Группа не указана')} | {lesson['lecturer_title'].replace('_',' ')}\n{lesson.get('lecturerEmail', 'Почта не указана')}")
|
|
54
54
|
else: # Fallback to a generic format
|
|
55
|
-
lesson_details.append(f"
|
|
55
|
+
lesson_details.append(f"{lesson['lecturer_title'].replace('_',' ')}")
|
|
56
56
|
|
|
57
57
|
formatted_lessons.append("\n".join(lesson_details))
|
|
58
58
|
|
|
59
59
|
formatted_days.append(f"{day_header}\n" + "\n\n".join(formatted_lessons))
|
|
60
60
|
|
|
61
|
-
main_header =
|
|
61
|
+
main_header = translator.gettext(lang, "schedule_header_for", entity_name=entity_name)
|
|
62
62
|
return f"{main_header}\n\n" + "\n\n---\n\n".join(formatted_days)
|
|
63
63
|
|
|
64
64
|
def generate_ical_from_schedule(schedule_data: List[Dict[str, Any]], entity_name: str) -> str:
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.31 → matplobbot_shared-0.1.33}/matplobbot_shared.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|