matplobbot-shared 0.1.332__tar.gz → 0.1.333__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.
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/PKG-INFO +1 -1
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/PKG-INFO +1 -1
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/setup.py +1 -1
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/redis_client.py +39 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/university_api.py +1 -3
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_keyboards.py +40 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_schedule_search_api.py +2 -6
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_studio_router_api.py +1 -3
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/LICENSE +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/README.md +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/SOURCES.txt +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/dependency_links.txt +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/requires.txt +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/top_level.txt +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/pyproject.toml +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/setup.cfg +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/__init__.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/celery_app.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/constants.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/database.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/egress.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/html_tools.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/i18n.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/locales/en.json +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/locales/ru.json +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/mail_bridge.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/models.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/request_context.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/schemas.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/__init__.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/broadcast_service.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/calendar_sync_state.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/schedule_service.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/semantic_search.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/tasks.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/telegram_bot_session.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/telegram_http.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/telegram_polling.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/telemetry.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_auth_flow.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_authorization_guards.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_broadcast_service.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_build_audit_requirements.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_calendar_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_correlation_middleware.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_dashboard_text_encoding.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_egress.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_fastapi_config.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_latex_task_security.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_localization_completeness.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_mail_bridge.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_myschedule_filters_settings.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_openapi_docs.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_proxy_cleaner.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_proxy_config.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_rate_limit.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_schedule_service_calendar.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_scheduler_html_splitter.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_scheduler_service.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_search_center_helpers.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_action_users_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_avatar_security.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_export_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_leaderboard_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_profile_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_proxy_diagnostics_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_send_message_api.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_telegram_bot_session.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_telegram_polling.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_telemetry.py +0 -0
- {matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_version_bumper.py +0 -0
|
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="matplobbot-shared",
|
|
5
|
-
version="0.1.
|
|
5
|
+
version="0.1.333", # Bump version
|
|
6
6
|
packages=find_packages(include=["shared_lib", "shared_lib.*"]),
|
|
7
7
|
description="Shared library for the Matplobbot ecosystem.",
|
|
8
8
|
author="Ackrome",
|
|
@@ -8,6 +8,11 @@ logger = logging.getLogger(__name__)
|
|
|
8
8
|
|
|
9
9
|
# TTL для кэша в секундах (например, 1 час)
|
|
10
10
|
CACHE_TTL = 3600
|
|
11
|
+
CALLBACK_PATH_KEY_PREFIX = "callback_path:"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _is_callback_path_hash(value: str) -> bool:
|
|
15
|
+
return len(value) == 16 and all(char in "0123456789abcdef" for char in value)
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
class RedisClient:
|
|
@@ -66,6 +71,40 @@ class RedisClient:
|
|
|
66
71
|
except Exception as e:
|
|
67
72
|
logger.error(f"Ошибка при очистке кэша Redis: {e}")
|
|
68
73
|
|
|
74
|
+
async def set_callback_path(self, path_hash: str, path: str, ttl: int):
|
|
75
|
+
"""Persist a Telegram callback hash mapping without serializing secrets."""
|
|
76
|
+
if not _is_callback_path_hash(path_hash):
|
|
77
|
+
raise ValueError("invalid callback path hash")
|
|
78
|
+
if ttl <= 0:
|
|
79
|
+
raise ValueError("callback path TTL must be positive")
|
|
80
|
+
try:
|
|
81
|
+
await self.client.set(
|
|
82
|
+
f"{CALLBACK_PATH_KEY_PREFIX}{path_hash}",
|
|
83
|
+
path,
|
|
84
|
+
ex=ttl,
|
|
85
|
+
)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error("Ошибка при записи callback path в Redis для hash=%s: %s", path_hash, e)
|
|
88
|
+
|
|
89
|
+
async def get_callback_path(self, path_hash: str) -> str | None:
|
|
90
|
+
"""Load a callback mapping created by this application."""
|
|
91
|
+
if not _is_callback_path_hash(path_hash):
|
|
92
|
+
return None
|
|
93
|
+
try:
|
|
94
|
+
return await self.client.get(f"{CALLBACK_PATH_KEY_PREFIX}{path_hash}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error("Ошибка при чтении callback path из Redis для hash=%s: %s", path_hash, e)
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
async def clear_callback_paths(self):
|
|
100
|
+
"""Remove persistent callback mappings during an explicit admin cache clear."""
|
|
101
|
+
try:
|
|
102
|
+
async for key in self.client.scan_iter(f"{CALLBACK_PATH_KEY_PREFIX}*"):
|
|
103
|
+
await self.client.delete(key)
|
|
104
|
+
logger.info("Все callback path mappings в Redis очищены.")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error("Ошибка при очистке callback path mappings в Redis: %s", e)
|
|
107
|
+
|
|
69
108
|
|
|
70
109
|
# Создаем единственный экземпляр клиента
|
|
71
110
|
redis_client = RedisClient(
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/university_api.py
RENAMED
|
@@ -78,9 +78,7 @@ class RuzAPIClient:
|
|
|
78
78
|
|
|
79
79
|
async def search(self, term: str, search_type: str) -> list[dict[str, Any]]:
|
|
80
80
|
"""Generic search function."""
|
|
81
|
-
return await self._request(
|
|
82
|
-
"/api/search", params={"term": term, "type": search_type}
|
|
83
|
-
)
|
|
81
|
+
return await self._request("/api/search", params={"term": term, "type": search_type})
|
|
84
82
|
|
|
85
83
|
async def get_schedule(
|
|
86
84
|
self, entity_type: str, entity_id: str, start: str, finish: str
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import importlib
|
|
2
3
|
import sys
|
|
3
4
|
import types
|
|
4
5
|
import unittest
|
|
6
|
+
from unittest.mock import AsyncMock, patch
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def _install_matplobblib_stub() -> None:
|
|
@@ -90,3 +92,41 @@ class TestTelegramWebAppKeyboards(unittest.IsolatedAsyncioTestCase):
|
|
|
90
92
|
buttons = [button for row in help_markup.inline_keyboard for button in row]
|
|
91
93
|
self.assertFalse(any(button.web_app for button in buttons))
|
|
92
94
|
self.assertIn("help_btn_matp_all", [button.text for button in buttons])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@unittest.skipUnless(KEYBOARDS_AVAILABLE, "bot keyboard dependencies are not installed")
|
|
98
|
+
class TestCallbackPathPersistence(unittest.IsolatedAsyncioTestCase):
|
|
99
|
+
async def asyncSetUp(self):
|
|
100
|
+
kb.code_path_cache.clear()
|
|
101
|
+
|
|
102
|
+
async def asyncTearDown(self):
|
|
103
|
+
kb.code_path_cache.clear()
|
|
104
|
+
|
|
105
|
+
async def test_resolve_code_path_restores_mapping_from_redis(self):
|
|
106
|
+
path_hash = "a" * 16
|
|
107
|
+
with patch.object(
|
|
108
|
+
kb.redis_client,
|
|
109
|
+
"get_callback_path",
|
|
110
|
+
new=AsyncMock(return_value="owner/repo/README.md"),
|
|
111
|
+
) as get_callback_path:
|
|
112
|
+
value = await kb.resolve_code_path(path_hash)
|
|
113
|
+
|
|
114
|
+
self.assertEqual(value, "owner/repo/README.md")
|
|
115
|
+
self.assertEqual(kb.code_path_cache.get(path_hash), "owner/repo/README.md")
|
|
116
|
+
get_callback_path.assert_awaited_once_with(path_hash)
|
|
117
|
+
|
|
118
|
+
async def test_cache_write_persists_mapping_to_redis(self):
|
|
119
|
+
path_hash = "b" * 16
|
|
120
|
+
with patch.object(
|
|
121
|
+
kb.redis_client,
|
|
122
|
+
"set_callback_path",
|
|
123
|
+
new=AsyncMock(),
|
|
124
|
+
) as set_callback_path:
|
|
125
|
+
kb.code_path_cache[path_hash] = "owner/repo/src/main.py"
|
|
126
|
+
await asyncio.sleep(0)
|
|
127
|
+
|
|
128
|
+
set_callback_path.assert_awaited_once_with(
|
|
129
|
+
path_hash,
|
|
130
|
+
"owner/repo/src/main.py",
|
|
131
|
+
ttl=kb.CALLBACK_PATH_TTL_SECONDS,
|
|
132
|
+
)
|
|
@@ -566,9 +566,7 @@ class TestRuzAPIClient(unittest.IsolatedAsyncioTestCase):
|
|
|
566
566
|
await client.get_schedule("group", "M80/101", "2026-04-01", "2026-04-30")
|
|
567
567
|
|
|
568
568
|
self.assertEqual(session.calls[0][0], "https://ruz.fa.ru/api/search")
|
|
569
|
-
self.assertEqual(
|
|
570
|
-
session.calls[0][1]["params"], {"term": "М80 & 101", "type": "group"}
|
|
571
|
-
)
|
|
569
|
+
self.assertEqual(session.calls[0][1]["params"], {"term": "М80 & 101", "type": "group"})
|
|
572
570
|
self.assertEqual(session.calls[1][0], "https://ruz.fa.ru/api/schedule/group/M80/101")
|
|
573
571
|
self.assertEqual(
|
|
574
572
|
session.calls[1][1]["params"],
|
|
@@ -578,9 +576,7 @@ class TestRuzAPIClient(unittest.IsolatedAsyncioTestCase):
|
|
|
578
576
|
|
|
579
577
|
class TestCachedScheduleLabels(unittest.TestCase):
|
|
580
578
|
def test_cached_entity_name_uses_type_specific_display_field(self):
|
|
581
|
-
self.assertEqual(
|
|
582
|
-
_cached_entity_name("group", [{"group": "M80-101"}], "group-1"), "M80-101"
|
|
583
|
-
)
|
|
579
|
+
self.assertEqual(_cached_entity_name("group", [{"group": "M80-101"}], "group-1"), "M80-101")
|
|
584
580
|
self.assertEqual(
|
|
585
581
|
_cached_entity_name("person", [{"lecturer_title": "Ivan Petrov"}], "person-1"),
|
|
586
582
|
"Ivan Petrov",
|
|
@@ -82,9 +82,7 @@ class TestStudioRouterAPI(unittest.TestCase):
|
|
|
82
82
|
self.app.dependency_overrides.clear()
|
|
83
83
|
|
|
84
84
|
def test_project_filename_rejects_paths_and_control_characters(self):
|
|
85
|
-
self.assertEqual(
|
|
86
|
-
studio_router._sanitize_project_filename("diagram.png"), "diagram.png"
|
|
87
|
-
)
|
|
85
|
+
self.assertEqual(studio_router._sanitize_project_filename("diagram.png"), "diagram.png")
|
|
88
86
|
for filename in ("../secret.txt", "nested/image.png", r"nested\image.png", "bad\x00.txt"):
|
|
89
87
|
with self.subTest(filename=filename), self.assertRaises(HTTPException) as raised:
|
|
90
88
|
studio_router._sanitize_project_filename(filename)
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/matplobbot_shared.egg-info/requires.txt
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/broadcast_service.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/calendar_sync_state.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/schedule_service.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/shared_lib/services/semantic_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_build_audit_requirements.py
RENAMED
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_correlation_middleware.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_dashboard_text_encoding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_localization_completeness.py
RENAMED
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_myschedule_filters_settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_schedule_service_calendar.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_scheduler_html_splitter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_action_users_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_proxy_diagnostics_api.py
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.332 → matplobbot_shared-0.1.333}/tests/test_stats_send_message_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|