Conversation
Walkthrough환경 변수와 설정을 확장하고, Bot 초기화 구조를 재설계했습니다. Gemini 클라이언트는 지시문별 캐시로 관리됩니다. 음성 채널 기반 학습 추적과 역할 부여 로직을 Cog 분리와 헬퍼들로 모듈화하고, 예외 처리·로깅을 강화했습니다. DB 접속/컬렉션 처리에 동적 DB명과 종료 함수, 에러 로깅을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant D as Discord Gateway
participant B as DiePpiBot
participant E as Extensions (Cogs)
participant G as GeminiClient(cache)
Note over B: on startup
D->>B: Ready / setup_hook
B->>E: load_ext(cogs/*)
Note over B,G: get_gemini_client(instruction)
B->>G: lookup by instruction
alt cache hit
G-->>B: return existing instance
else cache miss
B->>G: create & initialize(model from env)
G-->>B: instance stored in cache
end
sequenceDiagram
autonumber
participant M as Member
participant V as VoiceStateUpdate
participant T as StudyTracker(BaseCog)
participant DB as study/attend collections
participant GC as GeminiClient
participant C as Alert Channel
V->>T: on_voice_state_update(before, after)
alt Join study channel
T->>T: _handle_study_start
T->>T: record entry time
T->>DB: insert_attend
DB-->>T: ok / raise (logged)
else Leave study channel
T->>T: _handle_study_end
T->>T: compute duration
T->>DB: insert_study
DB-->>T: inserted_id / raise
T->>DB: find_total_study_min_in_today
DB-->>T: total_min
T->>GC: encouragement(instruction from env)
GC-->>T: text / error message
T->>C: post summary & encouragement
end
sequenceDiagram
autonumber
participant V as VoiceStateUpdate
participant R as RoleChange(BaseCog)
participant DB as StudyCollection
participant C as Alert Channel
V->>R: on_voice_state_update(member)
R->>DB: find_total_study_min_in_today(member)
DB-->>R: total_min
loop for each (min_required, role)
alt eligible & not already has role
R->>R: _assign_role_if_eligible
R->>C: notify (optional)
else skip
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (7)
.env.example (2)
6-6: 불필요한 따옴표 제거를 고려하세요.환경 변수 값에 따옴표가 포함되어 있습니다. Python의 dotenv 로더는 일반적으로 따옴표를 문자열 리터럴의 일부로 해석하므로, 의도하지 않은 결과가 발생할 수 있습니다.
다음과 같이 수정하는 것을 권장합니다:
-GEMINI_MODEL="gemini-2.0-flash-lite" +GEMINI_MODEL=gemini-2.0-flash-lite
4-8: 환경 변수 키 순서 정리 (선택사항).정적 분석 도구가 키 순서에 대한 경고를 표시하고 있습니다. 알파벳 순서로 정렬하면 가독성과 유지보수성이 향상될 수 있습니다. 하지만 이는 기능적 문제가 아니므로 선택적으로 적용할 수 있습니다.
core/config.py (1)
19-33: 하드코딩된 채널 이름의 유연성 부족.
from_env메서드가 환경(PROD/DEV)에 따라 채널 이름을 하드코딩하고 있습니다. 이는 채널 이름 변경 시 코드 수정이 필요하며, 다른 환경(예: staging)을 추가하기 어렵게 만듭니다.더 유연한 접근 방식으로 채널 이름을 환경 변수로 관리하는 것을 고려하세요. 예를 들어,
core.env.Env에STUDY_CHANNEL과ALERT_CHANNEL필드를 추가하고, 각 환경의.env파일에서 설정할 수 있습니다.제안된 개선 방법:
core/env.py에 새 필드 추가:class Env(BaseSettings): # ... 기존 필드들 STUDY_CHANNEL: str ALERT_CHANNEL: str그런 다음
core/config.py를 다음과 같이 단순화:@classmethod def from_env(cls) -> "BotConfig": """환경변수로부터 Config 생성""" return cls( study_channel=env.STUDY_CHANNEL, alert_channel=env.ALERT_CHANNEL, mode=env.MODE, )core/random_messages.py (1)
7-37: 가변 클래스 속성에 ClassVar 사용을 권장합니다.
good_job_messages와greetings는 모든 인스턴스가 공유하는 클래스 수준의 가변 리스트입니다. 정적 분석 도구(Ruff)가 지적한 것처럼, 이러한 속성은typing.ClassVar로 명시적으로 표시하는 것이 좋습니다. 이는 타입 체커와 개발자에게 이 속성이 인스턴스별이 아닌 클래스 수준임을 명확히 알려줍니다.다음과 같이 수정하세요:
+from typing import ClassVar import random class RandomMessageManager: """랜덤한 인사말과 격려 메시지를 제공하는 관리자.""" - good_job_messages: list[str] = [ + good_job_messages: ClassVar[list[str]] = [ "대단하다 삐!", # ... 나머지 메시지들 ] - greetings: list[str] = [ + greetings: ClassVar[list[str]] = [ "안녕하세요 삐! 만나서 반가워요!", # ... 나머지 메시지들 ]db/client.py (1)
11-13: DB 연결 종료 시 에러 처리 추가를 권장합니다.
close_db_connection함수가 추가되어 리소스 정리가 가능해졌습니다. 그러나 연결 종료 중 발생할 수 있는 예외에 대한 처리가 없습니다.프로덕션 환경에서는 종료 중 발생하는 오류를 로깅하거나 우아하게 처리하는 것이 좋습니다.
다음과 같이 에러 처리를 추가하는 것을 권장합니다:
+import logging + +logger = logging.getLogger(__name__) + def close_db_connection(): """DB 연결을 종료합니다.""" - client.close() + try: + client.close() + logger.info("데이터베이스 연결이 정상적으로 종료되었습니다.") + except Exception as e: + logger.error(f"데이터베이스 연결 종료 중 오류 발생: {e}")cogs/gemini.py (1)
30-59: Gemini 명령어 구현이 잘 개선되었습니다.
defer/followup패턴을 사용하여 장기 실행 작업을 처리하고, 봇의 중앙화된 클라이언트 관리를 활용합니다. 에러 처리는 다층으로 구성되어 예외 전파를 방지합니다.선택적 개선: followup 실패 로깅
Lines 52-53과 58-59의 중첩된
try/except/pass는followup.send실패를 조용히 무시합니다. 이는 의도적인 방어적 프로그래밍이지만, 디버깅을 위해 최소한의 로깅을 추가하는 것을 고려해 보세요:except Exception: - pass + logger.debug("followup 메시지 전송 실패 (이미 처리된 interaction일 수 있음)")이렇게 하면 정상적인 실패 경로(예: interaction 타임아웃)를 추적할 수 있습니다.
main.py (1)
15-40: DiePpiBot 클래스 설계 적절—메모이제이션 캐시 동시 접근 보호 필요
get_gemini_client(main.py) 호출이cogs/time_tracking.py와cogs/gemini.py의 비동기 핸들러에서 동시에 수행될 수 있음- 현재
self.gemini_clients에 락이 없어 동일 instruction에 대해 중복 인스턴스 생성 가능asyncio.Lock또는 이중 검사 잠금 패턴으로 캐시 접근을 보호하세요
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
.env.example(1 hunks)README.md(1 hunks)cogs/base_cog.py(1 hunks)cogs/gemini.py(1 hunks)cogs/hello.py(1 hunks)cogs/role_change.py(1 hunks)cogs/time_tracking.py(1 hunks)core/config.py(1 hunks)core/constants.py(1 hunks)core/env.py(1 hunks)core/gemini_client.py(1 hunks)core/messages.py(2 hunks)core/random_messages.py(2 hunks)db/attend_collection.py(1 hunks)db/client.py(1 hunks)db/study_collection.py(2 hunks)main.py(2 hunks)utils/discord_utils.py(1 hunks)utils/time_utils.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
cogs/role_change.py (5)
cogs/base_cog.py (2)
BaseCog(11-32)get_alert_channel(22-32)core/enums.py (1)
Role(9-12)core/messages.py (1)
upgrade_role_message(32-33)db/study_collection.py (2)
StudyCollection(12-55)find_total_study_min_in_today(28-55)cogs/time_tracking.py (1)
on_voice_state_update(117-133)
cogs/gemini.py (3)
core/messages.py (1)
gemini_response_message(36-37)main.py (1)
get_gemini_client(28-39)core/gemini_client.py (1)
create_gemini_message(52-76)
core/config.py (1)
core/enums.py (2)
Mode(4-6)Role(9-12)
db/attend_collection.py (2)
models/attend_model.py (1)
AttendModel(8-10)utils/time_utils.py (1)
get_study_day_range(23-44)
cogs/hello.py (1)
core/messages.py (1)
hello_message(7-8)
db/study_collection.py (2)
models/study_model.py (1)
StudyModel(8-12)utils/time_utils.py (1)
get_study_day_range(23-44)
main.py (3)
core/config.py (2)
BotConfig(9-33)from_env(20-33)core/gemini_client.py (2)
GeminiClient(11-76)get_instance(26-39)db/client.py (1)
close_db_connection(11-13)
core/constants.py (1)
core/enums.py (1)
Role(9-12)
core/messages.py (1)
core/random_messages.py (3)
RandomMessageManager(4-45)random_greeting_message(44-45)random_good_job_message(40-41)
core/env.py (1)
core/enums.py (1)
Mode(4-6)
cogs/base_cog.py (1)
utils/discord_utils.py (1)
get_text_channel_by_name(8-23)
cogs/time_tracking.py (6)
cogs/base_cog.py (2)
BaseCog(11-32)get_alert_channel(22-32)core/messages.py (3)
attend_study_message(15-16)end_study_message(19-29)start_study_message(11-12)db/attend_collection.py (3)
AttendCollection(12-41)get_today_user_is_attend(27-41)insert_attend(16-24)db/study_collection.py (3)
StudyCollection(12-55)insert_study(16-25)find_total_study_min_in_today(28-55)main.py (1)
get_gemini_client(28-39)core/gemini_client.py (1)
create_gemini_message(52-76)
🪛 dotenv-linter (3.3.0)
.env.example
[warning] 4-4: [UnorderedKey] The MODE key should go before the MONGO_DB key
(UnorderedKey)
[warning] 5-5: [UnorderedKey] The GEMINI_API_KEY key should go before the MODE key
(UnorderedKey)
[warning] 6-6: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 6-6: [UnorderedKey] The GEMINI_MODEL key should go before the MODE key
(UnorderedKey)
[warning] 7-7: [UnorderedKey] The GEMINI_WORD_SEARCH_INSTRUCTION key should go before the MODE key
(UnorderedKey)
[warning] 8-8: [UnorderedKey] The GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION key should go before the GEMINI_WORD_SEARCH_INSTRUCTION key
(UnorderedKey)
🪛 Ruff (0.13.3)
cogs/role_change.py
64-64: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
66-66: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
72-72: Unused method argument: before
(ARG002)
73-73: Unused method argument: after
(ARG002)
83-83: Do not catch blind exception: Exception
(BLE001)
84-84: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
core/gemini_client.py
14-14: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
66-66: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
69-69: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
72-72: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
cogs/gemini.py
49-49: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
52-53: try-except-pass detected, consider logging the exception
(S110)
52-52: Do not catch blind exception: Exception
(BLE001)
58-59: try-except-pass detected, consider logging the exception
(S110)
58-58: Do not catch blind exception: Exception
(BLE001)
db/attend_collection.py
38-38: Consider moving this statement to an else block
(TRY300)
cogs/hello.py
30-30: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
main.py
70-70: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
72-72: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
74-74: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
core/random_messages.py
26-37: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
cogs/time_tracking.py
32-32: Unused method argument: before
(ARG002)
68-68: Do not catch blind exception: Exception
(BLE001)
69-69: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
109-109: Do not catch blind exception: Exception
(BLE001)
110-110: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
🔇 Additional comments (27)
README.md (1)
11-22: 문서화가 잘 되어있습니다!시작 방법에 대한 두 가지 옵션을 명확하게 제공하고 있습니다.
core/constants.py (1)
1-13: LGTM! 잘 정의된 상수들입니다.역할 부여 기준 시간과 요구사항이 명확하게 정의되어 있습니다. 타입 힌트도 적절하고 문서화도 잘 되어있습니다.
core/env.py (1)
12-14: 필수 환경 변수의 누락 시나리오를 확인하세요.
GEMINI_MODEL,GEMINI_WORD_SEARCH_INSTRUCTION,GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION필드는 기본값이 없는 필수 필드입니다. 이러한 환경 변수가 제공되지 않으면 애플리케이션 시작 시 Pydantic ValidationError가 발생합니다.
.env.example파일에 이러한 변수가 포함되어 있어 문서화는 잘 되어있습니다. 그러나 프로덕션 환경에서 이러한 변수가 누락될 경우를 대비하여 적절한 에러 처리나 명확한 에러 메시지가 있는지 확인하세요.core/messages.py (1)
4-23: LGTM! 더 명확한 변수명입니다.
rm_manager를random_message_manager로 변경하여 변수의 의도가 더 명확해졌습니다. 모든 참조가 일관되게 업데이트되었습니다.db/client.py (1)
8-8: LGTM! 동적 데이터베이스 이름 지원.하드코딩된 "base" 대신
env.DB_NAME을 사용하여 더 유연한 구성이 가능해졌습니다. 이는 환경별로 다른 데이터베이스를 사용할 수 있게 해줍니다.db/study_collection.py (3)
2-2: 로깅 설정이 올바르게 추가되었습니다.모듈 레벨 로거를 추가하여 에러 추적이 가능하도록 개선되었습니다.
Also applies to: 9-10
17-25: 에러 처리가 적절하게 추가되었습니다.데이터베이스 삽입 실패 시 사용자 컨텍스트와 함께 로그를 남기고 예외를 재발생시켜 상위 레이어에서 처리할 수 있도록 구현되었습니다.
35-55: 에러 처리 및 집계 로직이 올바르게 구현되었습니다.MongoDB 집계 파이프라인을 사용하여 총 공부 시간을 계산하고, 결과가 없을 경우 0을 반환하는 엣지 케이스 처리가 적절합니다.
db/attend_collection.py (3)
2-2: 로깅 설정이 올바르게 추가되었습니다.모듈 레벨 로거를 통해 일관된 에러 추적이 가능합니다.
Also applies to: 9-10
18-24: 에러 처리가 적절하게 추가되었습니다.출석 정보 삽입 실패 시 컨텍스트와 함께 로그를 남기고 예외를 재발생시킵니다.
28-41: 에러 처리가 적절하게 추가되었습니다.출석 정보 조회 실패 시 컨텍스트와 함께 로그를 남기고 예외를 재발생시킵니다. 현재 구조는 정상 경로와 예외 경로가 명확하게 분리되어 있습니다.
utils/discord_utils.py (1)
8-23: 채널 검색 헬퍼 함수가 잘 구현되었습니다.
discord.utils.get을 사용하여 텍스트 채널을 검색하고, 찾지 못한 경우 경고 로그를 남기는 구조가 적절합니다.utils/time_utils.py (4)
3-7: 상수 추출이 잘 수행되었습니다.하드코딩된 시간 값을 상수로 추출하여 유지보수성과 가독성이 향상되었습니다.
10-17: 문서화가 적절하게 추가되었습니다.함수의 입력과 출력을 명확하게 설명하는 docstring이 추가되었습니다.
24-34: 상세한 문서화가 추가되었습니다.공부 시간 집계의 특수한 하루 정의(오전 4시~오전 4시)와 전날 기준 처리 로직을 명확하게 설명합니다.
38-38: 상수 사용이 올바르게 적용되었습니다.하드코딩된 값 대신 정의된 상수를 사용하여 코드 일관성이 향상되었습니다.
Also applies to: 41-41
cogs/hello.py (3)
1-2: 로깅 설정이 올바르게 추가되었습니다.Cog에 일관된 로깅 메커니즘이 추가되었습니다.
Also applies to: 9-10
13-20: 문서화와 타입 힌트가 잘 추가되었습니다.클래스와 초기화 메서드에 명확한 docstring과 타입 힌트가 추가되어 코드 이해도가 향상되었습니다.
24-32: 에러 처리가 적절하게 추가되었습니다.Discord HTTP 예외와 일반 예외를 구분하여 처리하며, 예상치 못한 오류는 전체 스택 트레이스를 로그에 남깁니다. 타입 힌트도 올바르게 추가되었습니다.
main.py (4)
45-47: 봇 초기화가 환경 기반 설정으로 잘 구성되었습니다.
BotConfig.from_env()를 사용하여 환경에 따른 설정을 적용하는 구조가 명확합니다.
69-76: 확장 로딩 에러 처리가 잘 구조화되었습니다.확장 로딩 실패 시 오류 유형을 구분하여 로그에 남기므로, 문제 진단이 용이합니다. 각 예외 타입에 대한 명시적 처리는 디버깅에 유용합니다.
79-79: 설정 모드 로그가 추가되었습니다.봇 시작 시 현재 모드를 로그에 남겨 환경 확인이 용이합니다.
83-86: DB 연결 종료 처리가 적절하게 추가되었습니다.
try/finally구조를 사용하여 봇 종료 시 데이터베이스 연결이 확실히 정리되도록 보장합니다.cogs/base_cog.py (2)
11-20: BaseCog 구조가 잘 설계되었습니다.공통 기능을 제공하는 기본 Cog 클래스로, 코드 재사용성과 일관성이 향상됩니다.
22-32: 알림 채널 조회 메서드가 잘 구현되었습니다.설정에서 알림 채널 이름을 가져와 헬퍼 함수로 해결하는 구조가 명확하고 재사용 가능합니다.
cogs/gemini.py (2)
1-2: 로깅 설정이 올바르게 추가되었습니다.일관된 로깅 메커니즘이 적용되었습니다.
Also applies to: 10-11
14-22: 문서화와 타입 힌트가 적절하게 추가되었습니다.클래스와 초기화 메서드의 목적이 명확하게 문서화되었습니다.
| if total_study_min <= min_required: | ||
| return | ||
|
|
||
| guild = member.guild | ||
| role = discord.utils.get(guild.roles, name=role_enum.value) | ||
|
|
||
| alert_channel = discord.utils.get(guild.text_channels, name=ALERT_CHANNEL) | ||
| if role is None: | ||
| logger.error(f"역할 '{role_enum.value}'을 찾을 수 없습니다.") | ||
| return | ||
|
|
||
| total_study_min = await StudyCollection.find_total_study_min_in_today( | ||
| str(member.id) | ||
| ) | ||
| if role in member.roles: | ||
| return |
There was a problem hiding this comment.
역할 조건 비교 연산 수정 필요
total_study_min <= min_required 조건 때문에 최소 요구 시간을 정확히 달성한 멤버에게도 역할이 부여되지 않습니다. 누적 공부 시간이 기준과 같을 때 역할을 지급해야 하므로 < 비교로 바꿔 주세요.
- if total_study_min <= min_required:
+ if total_study_min < min_required:
return🤖 Prompt for AI Agents
In cogs/role_change.py around lines 42 to 53, the early return uses
`total_study_min <= min_required` which prevents members who exactly meet the
threshold from receiving the role; change the check to `total_study_min <
min_required` so members with study time equal to the minimum pass the guard and
continue to role lookup/assignment.
| if not alert_channel: | ||
| return | ||
|
|
||
| study_record = StudyModel( | ||
| user_id=str(member.id), | ||
| start_time=start_time, | ||
| end_time=end_time, | ||
| total_min=minutes, | ||
| ) | ||
|
|
||
| try: | ||
| await StudyCollection.insert_study(study_record) | ||
| total_minutes = await StudyCollection.find_total_study_min_in_today( | ||
| str(member.id) | ||
| ) | ||
|
|
||
| gemini_client = self.bot.get_gemini_client( | ||
| env.GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION | ||
| ) | ||
| status, text = await gemini_client.create_gemini_message( | ||
| f"공부시간:{total_minutes}분" | ||
| ) | ||
|
|
||
| await alert_channel.send( | ||
| end_study_message(member.mention, minutes, total_minutes, text, status) | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"공부 기록 저장 중 오류 발생: {e}") | ||
| if alert_channel: | ||
| await alert_channel.send( | ||
| f"{member.mention}님의 공부 기록 저장 중 오류가 발생했다 삐!" | ||
| ) |
There was a problem hiding this comment.
알림 채널 부재 시에도 공부 기록은 저장되어야 합니다
alert_channel이 없으면 바로 반환해 버려 DB에 공부 기록이 저장되지 않습니다. 채널 설정 오류나 임시 삭제만으로 학습 데이터가 통째로 사라지는 치명적인 문제입니다. 알림 전송만 생략하고, 기록 저장과 누적 시간 갱신은 항상 수행하도록 수정해 주세요.
- if not alert_channel:
- return
-
study_record = StudyModel(
user_id=str(member.id),
start_time=start_time,
end_time=end_time,
total_min=minutes,
)
try:
await StudyCollection.insert_study(study_record)
- total_minutes = await StudyCollection.find_total_study_min_in_today(
- str(member.id)
- )
-
- gemini_client = self.bot.get_gemini_client(
- env.GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION
- )
- status, text = await gemini_client.create_gemini_message(
- f"공부시간:{total_minutes}분"
- )
-
- await alert_channel.send(
- end_study_message(member.mention, minutes, total_minutes, text, status)
- )
+ total_minutes = await StudyCollection.find_total_study_min_in_today(
+ str(member.id)
+ )
+
+ if alert_channel:
+ gemini_client = self.bot.get_gemini_client(
+ env.GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION
+ )
+ status, text = await gemini_client.create_gemini_message(
+ f"공부시간:{total_minutes}분"
+ )
+ await alert_channel.send(
+ end_study_message(
+ member.mention, minutes, total_minutes, text, status
+ )
+ )
except Exception as e:
logger.error(f"공부 기록 저장 중 오류 발생: {e}")
if alert_channel:
await alert_channel.send(
f"{member.mention}님의 공부 기록 저장 중 오류가 발생했다 삐!"
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if not alert_channel: | |
| return | |
| study_record = StudyModel( | |
| user_id=str(member.id), | |
| start_time=start_time, | |
| end_time=end_time, | |
| total_min=minutes, | |
| ) | |
| try: | |
| await StudyCollection.insert_study(study_record) | |
| total_minutes = await StudyCollection.find_total_study_min_in_today( | |
| str(member.id) | |
| ) | |
| gemini_client = self.bot.get_gemini_client( | |
| env.GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION | |
| ) | |
| status, text = await gemini_client.create_gemini_message( | |
| f"공부시간:{total_minutes}분" | |
| ) | |
| await alert_channel.send( | |
| end_study_message(member.mention, minutes, total_minutes, text, status) | |
| ) | |
| except Exception as e: | |
| logger.error(f"공부 기록 저장 중 오류 발생: {e}") | |
| if alert_channel: | |
| await alert_channel.send( | |
| f"{member.mention}님의 공부 기록 저장 중 오류가 발생했다 삐!" | |
| ) | |
| study_record = StudyModel( | |
| user_id=str(member.id), | |
| start_time=start_time, | |
| end_time=end_time, | |
| total_min=minutes, | |
| ) | |
| try: | |
| await StudyCollection.insert_study(study_record) | |
| total_minutes = await StudyCollection.find_total_study_min_in_today( | |
| str(member.id) | |
| ) | |
| if alert_channel: | |
| gemini_client = self.bot.get_gemini_client( | |
| env.GEMINI_STUDY_ENCOURAGEMENT_INSTRUCTION | |
| ) | |
| status, text = await gemini_client.create_gemini_message( | |
| f"공부시간:{total_minutes}분" | |
| ) | |
| await alert_channel.send( | |
| end_study_message( | |
| member.mention, | |
| minutes, | |
| total_minutes, | |
| text, | |
| status | |
| ) | |
| ) | |
| except Exception as e: | |
| logger.error(f"공부 기록 저장 중 오류 발생: {e}") | |
| if alert_channel: | |
| await alert_channel.send( | |
| f"{member.mention}님의 공부 기록 저장 중 오류가 발생했다 삐!" | |
| ) |
🧰 Tools
🪛 Ruff (0.13.3)
109-109: Do not catch blind exception: Exception
(BLE001)
110-110: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
🤖 Prompt for AI Agents
In cogs/time_tracking.py around lines 83 to 114, the current code returns early
when alert_channel is falsy which prevents saving the StudyModel and updating
total minutes; instead always create and insert the StudyModel and compute
total_minutes/gemini message regardless of alert_channel, and only skip sending
messages when alert_channel is None. Move the early return so it only bypasses
the await alert_channel.send(...) calls (or wrap send calls in "if
alert_channel:"), keep the try/except around the DB insert and totals lookup,
and ensure any error handling that notifies the member checks alert_channel
before sending.
|
|
||
| study_channel: str | ||
| alert_channel: str | ||
| mode: str |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
mode 필드의 타입 불일치.
BotConfig.mode 필드는 str 타입으로 선언되어 있지만, from_env 메서드에서는 문자열 리터럴 "PROD" 또는 "DEV"를 할당합니다. 이는 core.env.Env.MODE가 Mode enum으로 변경된 것과 일관성이 없습니다.
타입 안전성을 향상시키기 위해 mode 필드도 Mode enum을 사용하는 것을 권장합니다.
다음과 같이 수정하세요:
@dataclass(frozen=True)
class BotConfig:
"""봇 설정을 관리하는 클래스"""
study_channel: str
alert_channel: str
- mode: str
+ mode: Mode
role_requirements: list[tuple[int, Role]] = field(
default_factory=lambda: DEFAULT_ROLE_REQUIREMENTS
)
@classmethod
def from_env(cls) -> "BotConfig":
"""환경변수로부터 Config 생성"""
if env.MODE == Mode.PROD:
return cls(
study_channel="공부방",
alert_channel="스터디-알림",
- mode="PROD",
+ mode=Mode.PROD,
)
else:
return cls(
study_channel="디스코드-봇-만드는-채널",
alert_channel="디스코드-봇-만드는-채널",
- mode="DEV",
+ mode=Mode.DEV,
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| mode: str | |
| @dataclass(frozen=True) | |
| class BotConfig: | |
| """봇 설정을 관리하는 클래스""" | |
| study_channel: str | |
| alert_channel: str | |
| mode: Mode | |
| role_requirements: list[tuple[int, Role]] = field( | |
| default_factory=lambda: DEFAULT_ROLE_REQUIREMENTS | |
| ) | |
| @classmethod | |
| def from_env(cls) -> "BotConfig": | |
| """환경변수로부터 Config 생성""" | |
| if env.MODE == Mode.PROD: | |
| return cls( | |
| study_channel="공부방", | |
| alert_channel="스터디-알림", | |
| mode=Mode.PROD, | |
| ) | |
| else: | |
| return cls( | |
| study_channel="디스코드-봇-만드는-채널", | |
| alert_channel="디스코드-봇-만드는-채널", | |
| mode=Mode.DEV, | |
| ) |
🤖 Prompt for AI Agents
In core/config.py around line 14, the BotConfig.mode field is annotated as str
but should use the Mode enum from core.env for type safety; update the
annotation to Mode, add the appropriate import (from core.env import Mode), and
adjust any assignments in from_env to assign a Mode value (or convert Env.MODE
to Mode) so the field and all usages consistently use the Mode enum.
| def close_db_connection(): | ||
| """DB 연결을 종료합니다.""" | ||
| client.close() |
There was a problem hiding this comment.
🧩 Analysis chain
Motor 클라이언트의 비동기 종료 패턴을 확인하세요.
Motor의 AsyncIOMotorClient.close()는 동기 메서드로 문서화되어 있지만, Motor의 일부 버전이나 사용 패턴에 따라 비동기 컨텍스트에서 제대로 정리되지 않을 수 있습니다.
Motor의 공식 문서를 확인하여 현재 버전에서 권장하는 종료 패턴을 따르고 있는지 검증하세요. 필요한 경우 비동기 종료 메서드를 사용하거나 이벤트 루프가 실행 중인 상태에서 종료를 수행해야 할 수 있습니다.
다음 스크립트로 Motor 버전과 권장 종료 패턴을 확인하세요:
또한 최신 Motor 문서를 확인하세요:
🏁 Script executed:
#!/bin/bash
# Motor 버전 확인 및 공식 문서에서 종료 패턴 검증
# pyproject.toml이나 requirements에서 Motor 버전 확인
echo "=== Motor 버전 확인 ==="
rg -n "motor" pyproject.toml poetry.lock requirements.txt 2>/dev/null || echo "Motor 버전 정보를 찾을 수 없습니다."
# 코드베이스에서 다른 close 패턴 사용 사례 검색
echo -e "\n=== 기존 close 패턴 검색 ==="
rg -n -A 3 "client\.close\(\)" --type=pyLength of output: 444
🌐 Web query:
Motor AsyncIOMotorClient close method best practices and async cleanup
💡 Result:
Short best-practices summary for cleaning up an AsyncIOMotorClient (Motor)
-
Always await client.close() when shutting down.
- This tells Motor to end server sessions, close sockets and stop monitors. [1]
-
Ensure all your app tasks using the client (queries, open cursors, sessions, background tasks) have completed or been cancelled before calling close.
- Close long‑running cursors (await cursor.close()) or exhaust them; don’t rely on GC. [1][2]
-
If you use ClientSession explicitly, prefer the async context manager so sessions end automatically:
- async with client.start_session(): ... ensures proper session cleanup. [1]
-
If you spawn background tasks that use the client, cancel/await them before close.
- Close while tasks still using sockets can leave in‑flight work or half‑closed connections.
-
Await close before shutting down the asyncio event loop (or call close from inside a task before loop stops).
- Closing the loop before awaiting network cleanup can leave sockets/monitors open or cause warnings. (General asyncio server/connection behavior.) [3][4]
-
Handle repeated closes: in Motor v4+ a closed client cannot be reused (InvalidOperation). Don’t call client methods after close. [1]
-
Consider migrating to PyMongo’s Async API (recommended by MongoDB) — it’s the supported async driver going forward. Test cleanup semantics after migrating. [5]
Minimal example
- cancel background tasks -> await their completion -> await cursor.close() if needed -> await client.close() -> then stop loop.
Sources
[1] Motor AsyncIO client docs — AsyncIOMotorClient (close, sessions, sockets).
[2] Motor cursor docs (explicit cursor.close recommended when not exhausted).
[3] asyncio Server/connection close discussion (wait_closed/close caveats).
[4] asyncio socket/connection shutdown notes (ensure writers/transports closed before wait_closed).
[5] MongoDB docs — Migrate from Motor to PyMongo Async (Motor deprecation/migration guidance).
If you want, I can paste small runnable shutdown examples for (a) a simple script, (b) an aiohttp app, or (c) a FastAPI/uvicorn app.
AsyncIOMotorClient 종료 시 await 호출 필요
db/client.py의 close_db_connection을 async def로 변경하고, client.close() 대신 await client.close()로 소켓과 세션 정리가 완료되도록 수정하세요.
🤖 Prompt for AI Agents
In db/client.py around lines 11–13, change close_db_connection to an async
function and await the AsyncIOMotorClient close call: update the signature to
async def close_db_connection(): and replace client.close() with await
client.close(); also update any call sites to await close_db_connection() (or
schedule it in the event loop) so the socket/session teardown completes
properly.
Summary by CodeRabbit