matplobbot-shared 0.1.31__tar.gz → 0.1.35__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.35}/PKG-INFO +2 -1
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/matplobbot_shared.egg-info/PKG-INFO +2 -1
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/matplobbot_shared.egg-info/SOURCES.txt +1 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/matplobbot_shared.egg-info/requires.txt +1 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/setup.py +3 -2
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/shared_lib/database.py +26 -1
- matplobbot_shared-0.1.35/shared_lib/redis_client.py +46 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/shared_lib/services/schedule_service.py +8 -8
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/README.md +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/matplobbot_shared.egg-info/dependency_links.txt +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/matplobbot_shared.egg-info/top_level.txt +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/setup.cfg +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/shared_lib/__init__.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/shared_lib/i18n.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/shared_lib/services/__init__.py +0 -0
- {matplobbot_shared-0.1.31 → matplobbot_shared-0.1.35}/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.35
|
|
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.35
|
|
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.35", # 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,10 +108,16 @@ 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
|
)
|
|
114
115
|
''')
|
|
116
|
+
# Add the last_schedule_hash column if it doesn't exist, for backward compatibility
|
|
117
|
+
await connection.execute('''
|
|
118
|
+
ALTER TABLE user_schedule_subscriptions
|
|
119
|
+
ADD COLUMN IF NOT EXISTS last_schedule_hash TEXT;
|
|
120
|
+
''')
|
|
115
121
|
logger.info("Database tables initialized.")
|
|
116
122
|
|
|
117
123
|
async def log_user_action(user_id: int, username: str | None, full_name: str, avatar_pic_url: str | None, action_type: str, action_details: str | None):
|
|
@@ -254,13 +260,32 @@ async def remove_schedule_subscription(subscription_id: int, user_id: int) -> st
|
|
|
254
260
|
|
|
255
261
|
async def get_subscriptions_for_notification(notification_time: str) -> list:
|
|
256
262
|
async with pool.acquire() as connection:
|
|
263
|
+
# Modified to select the subscription ID and the last hash
|
|
257
264
|
rows = await connection.fetch("""
|
|
258
|
-
SELECT user_id, entity_type, entity_id, entity_name
|
|
265
|
+
SELECT id, user_id, entity_type, entity_id, entity_name, last_schedule_hash
|
|
259
266
|
FROM user_schedule_subscriptions
|
|
260
267
|
WHERE is_active = TRUE AND TO_CHAR(notification_time, 'HH24:MI') = $1
|
|
261
268
|
""", notification_time)
|
|
262
269
|
return [dict(row) for row in rows]
|
|
263
270
|
|
|
271
|
+
async def get_all_active_subscriptions() -> list:
|
|
272
|
+
"""Fetches all active schedule subscriptions from the database."""
|
|
273
|
+
if not pool:
|
|
274
|
+
raise ConnectionError("Database pool is not initialized.")
|
|
275
|
+
async with pool.acquire() as connection:
|
|
276
|
+
rows = await connection.fetch("""
|
|
277
|
+
SELECT id, user_id, entity_type, entity_id, entity_name, last_schedule_hash
|
|
278
|
+
FROM user_schedule_subscriptions WHERE is_active = TRUE
|
|
279
|
+
""")
|
|
280
|
+
return [dict(row) for row in rows]
|
|
281
|
+
|
|
282
|
+
async def update_subscription_hash(subscription_id: int, new_hash: str):
|
|
283
|
+
"""Updates the schedule hash for a specific subscription."""
|
|
284
|
+
if not pool:
|
|
285
|
+
raise ConnectionError("Database pool is not initialized.")
|
|
286
|
+
async with pool.acquire() as connection:
|
|
287
|
+
await connection.execute("UPDATE user_schedule_subscriptions SET last_schedule_hash = $1 WHERE id = $2", new_hash, subscription_id)
|
|
288
|
+
|
|
264
289
|
# --- FastAPI Specific Queries ---
|
|
265
290
|
async def get_leaderboard_data_from_db(db_conn):
|
|
266
291
|
# 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.35}/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.35}/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
|