-
Couldn't load subscription status.
- Fork 1
Avro Schema Namespace Change #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev-0.2
Are you sure you want to change the base?
Conversation
Walkthrough다양한 계층에서 단어셋(wordset) 상태 관리 기능을 구현하고, 챗봇 생성 시 나무위키 콘텐츠 검증을 강화하며, 문서화 및 인프라를 개선하는 종합적인 업데이트입니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Router as API Router
participant Service as Wordset Service
participant Repo as Repository
participant DB as MongoDB
Note over Client,DB: 단어셋 승인 워크플로우
Client->>Router: PATCH /approve_wordset/{id}
Router->>Service: approve_wordset(wordset_id)
rect rgb(200, 220, 255)
Note over Service,Repo: 상태 검증 및 업데이트
Service->>Repo: find_by_id(wordset_id)
Repo->>DB: 쿼리
DB-->>Repo: ChatBotWordSet 반환
alt 이미 승인됨
Repo-->>Service: 기존 상태 확인
Service->>Service: AlreadyApprovedWordSetException 발생
else 미승인 상태
Service->>Repo: update_status(wordset_id, "APPROVED")
Repo->>DB: 상태 업데이트
DB-->>Repo: 업데이트 완료
Service->>Repo: chatbot_repo.add_wordset()
Repo->>DB: 챗봇에 단어셋 추가
end
end
Service-->>Router: BaseResponse (성공)
Router-->>Client: 200 OK
Note over Client,DB: 단어셋 거절 워크플로우
Client->>Router: PATCH /reject_wordset/{id}
Router->>Service: reject_wordset(wordset_id)
Service->>Repo: update_status(wordset_id, "REJECTED")
Repo->>DB: 상태 업데이트
Service-->>Router: BaseResponse
Router-->>Client: 200 OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 분 여러 계층에 걸친 연관된 변경사항(저장소 7개 메서드, 서비스 5개 메서드, API 4개 엔드포인트), 새로운 예외 클래스 및 상태 관리 로직, 문서화 추가가 혼합되어 있습니다. 이들 변경사항 간의 상호작용과 기존 로직과의 통합을 검토해야 합니다. Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Nitpick comments (6)
core/events/deps.py (1)
8-13: 환경 변수 기반 설정으로 개선됨Kafka 설정을 환경 변수 기반으로 변경하여 배포 유연성이 향상되었습니다.
선택적 개선사항:
pydantic_settings.BaseSettings는 환경 변수를 자동으로 처리합니다.os.getenv()를 직접 사용하는 대신 pydantic의 기본 동작에 의존할 수 있습니다:-import os - class KafkaSettings(BaseSettings): """Kafka 설정""" - kafka_bootstrap_servers: str = os.getenv("KAFKA_BOOTSTRAP_SERVERS", "localhost:9092") - kafka_client_id: str = os.getenv("KAFKA_PRODUCER_ID", "fastapi-event-publisher") + kafka_bootstrap_servers: str = "localhost:9092" + kafka_client_id: str = "fastapi-event-publisher" kafka_compression_type: str = "gzip" kafka_acks: str = "all" class Config: env_file = ".env" case_sensitive = False extra = "ignore" + # BaseSettings가 KAFKA_BOOTSTRAP_SERVERS와 KAFKA_CLIENT_ID 환경 변수를 자동으로 매핑단,
KAFKA_PRODUCER_ID를kafka_client_id에 매핑하려면 Field를 사용해야 합니다:from pydantic import Field class KafkaSettings(BaseSettings): kafka_bootstrap_servers: str = "localhost:9092" kafka_client_id: str = Field(default="fastapi-event-publisher", alias="KAFKA_PRODUCER_ID")현재 구현도 정상 작동하므로 필수는 아닙니다.
.serena/memories/suggested_commands.md (1)
47-49: 코드 블록에 언어 식별자를 추가하세요.마크다운 린터에서 47번 줄의 코드 블록에 언어 식별자가 없다고 지적하고 있습니다.
다음과 같이 수정하세요:
-``` +```bash # 기본 실행 docker run -p 4449:4449 fastapi-project.serena/memories/task_completion_checklist.md (1)
47-50: 코드 블록에 언어 식별자를 추가하세요.마크다운 린터에서 47번 줄의 코드 블록에 언어 식별자가 없다고 지적하고 있습니다.
다음과 같이 수정하세요:
-``` +```bash docker run -p 4449:4449 fastapi-projectapp/chatbot_wordset/repository/chatbot_wordset_repo.py (3)
90-99:find_by_status의status는 Enum 사용 권장도메인 모델은
WordSetStatusEnum입니다. 문자열 비교 대신 Enum으로 타입을 고정하세요.적용 제안:
-async def find_by_status(status: str, size: int) -> List[ChatBotWordSet]: +async def find_by_status(status: WordSetStatus, size: int) -> List[ChatBotWordSet]:Based on learnings
115-127:find_by_character_id_and_status— Enum 사용으로 타입 안전성 확보
status: str→WordSetStatus로 교체 권장.적용 제안:
-async def find_by_character_id_and_status(character_id: int, status: str, size: int) -> List[ChatBotWordSet]: +async def find_by_character_id_and_status(character_id: int, status: WordSetStatus, size: int) -> List[ChatBotWordSet]:Based on learnings
90-143: 중복 로직(필터/정렬/페이징) 공통화로 유지보수성 향상반복되는
find(...).sort(...).limit(size+1).to_list()패턴을 유틸로 추출하면 테스트/변경 비용을 줄일 수 있습니다.예시:
async def _paged(query, size: int): return await query.sort(("_id", SortDirection.DESCENDING)).limit(size + 1).to_list()각 함수에서
await _paged(ChatBotWordSet.find(...), size)로 대체.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.serena/memories/code_style_and_conventions.md(1 hunks).serena/memories/project_overview.md(1 hunks).serena/memories/suggested_commands.md(1 hunks).serena/memories/task_completion_checklist.md(1 hunks)Dockerfile(1 hunks)api/routers/chatbot.py(1 hunks)api/routers/chatbot_wordset.py(2 hunks)app/chatbot/exception/no_content_found_exception.py(1 hunks)app/chatbot/repository/chatbot_repo.py(1 hunks)app/chatbot/service/chatbot_service.py(3 hunks)app/chatbot_wordset/document/chatbot_wordset.py(1 hunks)app/chatbot_wordset/document/wordset_status.py(1 hunks)app/chatbot_wordset/exception/already_approved_wordset_exception.py(1 hunks)app/chatbot_wordset/repository/chatbot_wordset_repo.py(1 hunks)app/chatbot_wordset/service/chatbot_wordset_service.py(2 hunks)avro/ChatAvroSchema.avsc(1 hunks)core/events/deps.py(1 hunks)requirements.txt(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
app/chatbot_wordset/repository/chatbot_wordset_repo.py (1)
app/chatbot_wordset/document/chatbot_wordset.py (1)
ChatBotWordSet(6-14)
app/chatbot/repository/chatbot_repo.py (1)
app/chatbot/document/chatbot.py (2)
CharacterWordSet(7-10)ChatBot(12-28)
app/chatbot_wordset/exception/already_approved_wordset_exception.py (1)
core/exceptions/business_exception.py (1)
BusinessException(1-5)
app/chatbot/exception/no_content_found_exception.py (1)
core/exceptions/business_exception.py (1)
BusinessException(1-5)
app/chatbot/service/chatbot_service.py (1)
app/chatbot/exception/no_content_found_exception.py (1)
NoContentFoundException(4-8)
app/chatbot_wordset/service/chatbot_wordset_service.py (6)
api/routers/chatbot_wordset.py (4)
chatbot_wordset(14-20)approve_wordset(25-27)reject_wordset(32-34)delete_wordset(40-42)app/chatbot_wordset/document/wordset_status.py (1)
WordSetStatus(4-7)app/chatbot/document/chatbot.py (1)
CharacterWordSet(7-10)app/chatbot_wordset/exception/already_approved_wordset_exception.py (1)
AlreadyApprovedWordSetException(4-8)app/chatbot/repository/chatbot_repo.py (5)
find_by_id(21-25)add_wordset(36-44)remove_wordset(48-60)find(68-75)find_by_cursor_id(77-87)app/chatbot_wordset/repository/chatbot_wordset_repo.py (11)
find_by_id(70-72)update_status(75-80)delete_by_id(84-87)find_by_status(90-98)find_by_cursor_id_and_status(101-112)find(20-27)find_by_cursor_id(29-36)find_by_character_id_and_status(115-126)find_by_cursor_id_character_id_and_status(129-143)find_by_character_id(39-46)find_by_cursor_id_and_character_id(49-56)
app/chatbot_wordset/document/chatbot_wordset.py (2)
api/routers/chatbot_wordset.py (1)
chatbot_wordset(14-20)app/chatbot_wordset/document/wordset_status.py (1)
WordSetStatus(4-7)
api/routers/chatbot_wordset.py (5)
api/schemas/request/chatbot_wordset_request.py (1)
ChatBotWordIdsRequest(5-7)api/depends/get_user_id.py (1)
get_user_id(3-4)api/schemas/common/response/base_response.py (1)
BaseResponse(5-7)app/chatbot_wordset/service/chatbot_wordset_service.py (6)
add(10-11)approve_wordset(27-47)reject_wordset(50-54)delete_wordset(58-72)get_chatbot_wordset_by_character(20-23)get_chatbot_wordset_by_character_with_status(94-114)api/schemas/common/request/cursor_query.py (1)
CursorQuery(6-8)
🪛 markdownlint-cli2 (0.18.1)
.serena/memories/task_completion_checklist.md
47-47: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
.serena/memories/project_overview.md
47-47: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
.serena/memories/suggested_commands.md
47-47: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Ruff (0.14.1)
app/chatbot/service/chatbot_service.py
193-193: f-string without any placeholders
Remove extraneous f prefix
(F541)
194-194: f-string without any placeholders
Remove extraneous f prefix
(F541)
195-195: f-string without any placeholders
Remove extraneous f prefix
(F541)
196-196: f-string without any placeholders
Remove extraneous f prefix
(F541)
199-199: Consider moving this statement to an else block
(TRY300)
204-204: f-string without any placeholders
Remove extraneous f prefix
(F541)
205-205: f-string without any placeholders
Remove extraneous f prefix
(F541)
206-206: f-string without any placeholders
Remove extraneous f prefix
(F541)
207-207: f-string without any placeholders
Remove extraneous f prefix
(F541)
208-208: f-string without any placeholders
Remove extraneous f prefix
(F541)
app/chatbot_wordset/service/chatbot_wordset_service.py
75-75: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
95-95: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
api/routers/chatbot_wordset.py
48-48: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
🔇 Additional comments (21)
avro/ChatAvroSchema.avsc (1)
3-4: 스키마 파일이 코드베이스에서 사용되지 않음을 확인했습니다.검증 결과:
- Python 코드의 어느 곳에서도
.avsc스키마 파일을 임포트하거나 사용하지 않음- 이전 스키마 이름 "ChatEvent" 또는 네임스페이스 "com.chatbot.events"에 대한 참조 없음
- 코드베이스에 Avro 스키마 관련 하드코딩된 참조 없음
- 유일한
.avsc파일은avro/ChatAvroSchema.avsc하나뿐현재 이 스키마 파일은 코드에서 적극적으로 사용되지 않고 있어, 이름과 네임스페이스 변경이 기존 코드에 영향을 주지 않습니다. 향후 이 스키마를 프로덕션 코드에 통합할 때는 관련 설정을 확인하는 것이 좋습니다.
api/routers/chatbot.py (1)
52-58: LGTM!라우트 경로를
"/"에서""로 변경하여 URL이/chatbots/에서/chatbots로 해석됩니다. RESTful 규칙을 더 잘 따르는 적절한 변경입니다.app/chatbot_wordset/document/wordset_status.py (1)
1-7: LGTM!말투셋의 승인 워크플로우를 지원하는 명확한 상태 enum 정의입니다.
str과Enum을 함께 상속받아 JSON 직렬화가 원활하게 이루어집니다.app/chatbot_wordset/exception/already_approved_wordset_exception.py (1)
1-8: LGTM!이미 승인된 말투셋에 대한 작업을 방지하는 명확한 예외 클래스입니다. 400 상태 코드는 이 비즈니스 로직 오류에 적절합니다.
app/chatbot/exception/no_content_found_exception.py (1)
1-8: LGTM!나무위키 콘텐츠를 찾을 수 없을 때 발생하는 명확한 예외 클래스입니다. 404 상태 코드는 리소스를 찾을 수 없는 이 시나리오에 적절합니다.
Dockerfile (3)
1-2: ARM64 플랫폼 명시로 인한 크로스 플랫폼 빌드 고려 필요ARM64 플랫폼을 명시적으로 지정했습니다. x86_64 머신에서 빌드하는 개발자는 Docker의 BuildKit 또는 QEMU 에뮬레이션이 필요할 수 있습니다.
개발 문서에 다음 내용이 포함되어 있는지 확인하세요:
- ARM64 머신이 아닌 환경에서 빌드 시
docker buildx사용 방법- 또는 멀티 아키텍처 빌드를 위한 가이드
크로스 플랫폼 빌드가 필요한 경우 Dockerfile을 다음과 같이 수정할 수 있습니다:
# 멀티 플랫폼 지원 FROM python:3.11-slim # 또는 buildx를 사용하여 빌드 시 --platform 지정
8-21: LGTM!Headless Chrome 실행에 필요한 시스템 의존성이 적절히 추가되었으며, apt 캐시 정리로 이미지 크기를 최적화했습니다.
23-26: LGTM!Chrome 및 Chromedriver 경로를 명시적으로 지정하고, 최신 headless 모드(
--headless=new)를 사용하는 것은 좋은 관행입니다.requirements.txt (1)
66-66: nadf 패키지 버전 검증 완료
nadf버전 0.1.30은 유효한 최신 버전이며, 보안 취약점이 없습니다. 버전 업데이트는 안전하게 진행할 수 있습니다..serena/memories/suggested_commands.md (1)
1-158: 문서가 잘 작성되었습니다.개발 환경 설정부터 배포까지 필요한 모든 명령어를 체계적으로 정리했습니다. 개발자들에게 유용한 레퍼런스가 될 것입니다.
.serena/memories/code_style_and_conventions.md (1)
1-109: 코드 스타일 가이드가 체계적으로 작성되었습니다.네이밍 규칙부터 프로젝트 구조까지 명확한 예시와 함께 잘 정리되어 있습니다. 팀 내 코드 일관성 유지에 도움이 될 것입니다.
.serena/memories/task_completion_checklist.md (1)
1-100: 작업 완료 체크리스트가 잘 구성되었습니다.코드 작성부터 배포까지 확인해야 할 항목들이 체계적으로 정리되어 있어 품질 관리에 도움이 될 것입니다.
app/chatbot_wordset/document/chatbot_wordset.py (1)
7-11: 상태 필드 추가가 적절합니다.
status필드를PENDING기본값과 함께 추가하여 워드셋 승인 워크플로우를 지원하는 것은 올바른 설계입니다.app/chatbot/service/chatbot_service.py (1)
148-149: 콘텐츠 검증 로직이 적절합니다.나무위키 크롤링 후 콘텐츠가 비어있는지 조기에 확인하여
NoContentFoundException을 발생시키는 것은 올바른 접근입니다.app/chatbot/repository/chatbot_repo.py (2)
36-44: 워드셋 추가 로직이 올바릅니다.
Push로 워드셋을 추가하고AddToSet으로 기여자를 추가하는 원자적 업데이트 구현이 적절합니다.
48-60: 워드셋 제거 로직이 올바릅니다.
question과answer가 모두 일치하는 워드셋만 제거하도록 구현되어 정확성을 보장합니다.api/routers/chatbot_wordset.py (1)
46-54: 상태 필터링 기능이 적절히 추가되었습니다.캐릭터별 워드셋 조회에 선택적 상태 필터(
PENDING,APPROVED,REJECTED)를 추가한 것은 좋은 개선입니다.참고: 정적 분석 도구가 line 48의
Depends사용에 대해 B008 경고를 표시하지만, 이는 FastAPI의 표준 패턴이므로 무시해도 됩니다.app/chatbot_wordset/service/chatbot_wordset_service.py (3)
27-47: 워드셋 승인 로직이 올바르게 구현되었습니다.이미 승인된 워드셋에 대한 중복 승인을 방지하고, 상태 업데이트와 챗봇 추가를 순차적으로 처리하는 로직이 적절합니다.
50-54: 워드셋 거절 로직이 적절합니다.상태를
REJECTED로 업데이트하는 간단한 구현이 요구사항에 부합합니다.
58-72: 워드셋 삭제 로직이 올바릅니다.승인된 워드셋인 경우에만 챗봇에서 제거하는 조건부 로직이 적절하게 구현되었습니다.
app/chatbot_wordset/repository/chatbot_wordset_repo.py (1)
70-143: 검증 결과: 타입 정합성 문제 없음코드 검증 결과, 다음 사항이 확인되었습니다:
WordSetStatus는class WordSetStatus(str, Enum)으로 정의되어 str을 상속하므로, 열거형 값(APPROVED, REJECTED)이 문자열로 동작합니다.- 서비스 계층에서
WordSetStatus.APPROVED,WordSetStatus.REJECTED를update_status(status: str)함수에 전달할 때 타입 호환성이 유지됩니다.- 커서 ID는 모든 계층에서
int타입으로 일관성 있게 처리되고 있습니다.- 라우터 → 서비스 → 리포지토리 전체 계층의 타입 시그니처가 정합적입니다.
제시된 함수 시그니처에서 타입 불일치나 에러 처리 정합성 문제는 발견되지 않았습니다.
Likely an incorrect or invalid review comment.
| ``` | ||
| FastAPIProject/ | ||
| ├── main.py # FastAPI 앱 진입점 | ||
| ├── requirements.txt # Python 의존성 | ||
| ├── Dockerfile # ARM64 Docker 이미지 설정 | ||
| ├── api/ # API 계층 | ||
| │ ├── routers/ # 엔드포인트 라우터 | ||
| │ │ ├── chatbot.py # 챗봇 관련 API | ||
| │ │ ├── chatbot_wordset.py # 말투셋 관리 API | ||
| │ │ ├── chat_history.py # 대화 기록 API | ||
| │ │ └── dit.py # DIT 관련 API | ||
| │ ├── schemas/ # Pydantic 스키마 (요청/응답) | ||
| │ └── depends/ # 의존성 주입 | ||
| ├── app/ # 비즈니스 로직 계층 | ||
| │ ├── chatbot/ # 챗봇 서비스 | ||
| │ ├── chatbot_wordset/ # 말투셋 서비스 | ||
| │ ├── chat_history/ # 대화 기록 서비스 | ||
| │ └── dit/ # DIT 서비스 | ||
| ├── core/ # 핵심 인프라 | ||
| │ ├── db/ # 데이터베이스 설정 | ||
| │ ├── exceptions/ # 커스텀 예외 | ||
| │ ├── grpcs/ # gRPC 클라이언트 | ||
| │ ├── events/ # 이벤트 발행/처리 | ||
| │ ├── embedder/ # 임베딩 생성 | ||
| │ ├── vectorstores/ # 벡터 저장소 연동 | ||
| │ ├── sessions/ # 세션 관리 | ||
| │ ├── loader/ # 데이터 로더 | ||
| │ └── util/ # 유틸리티 | ||
| ├── ai/ # AI 관련 | ||
| │ ├── character_chat_bot.py # 캐릭터 챗봇 구현 | ||
| │ ├── llm.py # LLM 래퍼 | ||
| │ ├── memory/ # 대화 메모리 | ||
| │ └── callbacks/ # LangChain 콜백 | ||
| ├── protos/ # gRPC protobuf 정의 | ||
| └── avro/ # Avro 스키마 | ||
| ``` | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 블록에 언어 식별자 추가 필요
Line 47의 fenced 코드 블록에 언어 식별자가 누락되어 있습니다.
다음과 같이 수정하세요:
-```
+```text
FastAPIProject/
├── main.py # FastAPI 앱 진입점🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
47-47: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
.serena/memories/project_overview.md around lines 47 to 83: the fenced code
block showing the project tree is missing a language identifier; update the
opening fence from ``` to ```text so the block is tagged as plain text (i.e.,
replace the opening backticks with ```text and leave the rest of the block
unchanged).
| @router.patch("/{wordset_id}/approve") | ||
| async def approve_wordset(wordset_id: str) -> BaseResponse: | ||
| await chatbot_wordset_service.approve_wordset(wordset_id) | ||
| return BaseResponse(message="chatbot wordset successfully approved") | ||
|
|
||
|
|
||
| # 어드민 - 말투셋 거절 | ||
| @router.patch("/{wordset_id}/reject") | ||
| async def reject_wordset(wordset_id: str) -> BaseResponse: | ||
| await chatbot_wordset_service.reject_wordset(wordset_id) | ||
| return BaseResponse(message="chatbot wordset successfully rejected") | ||
|
|
||
|
|
||
|
|
||
| @router.get("/") | ||
| async def get_chatbot_wordset(params : CursorQuery = Depends()) -> BaseResponse: | ||
| chatbot_response = await chatbot_wordset_service.get_chatbot_wordset(params.cursor_id, params.size) | ||
| return BaseResponse(message="chatbot wordset successfully get", data=chatbot_response) | ||
| # 말투셋 삭제 | ||
| @router.delete("/{wordset_id}") | ||
| async def delete_wordset(wordset_id: str) -> BaseResponse: | ||
| await chatbot_wordset_service.delete_wordset(wordset_id) | ||
| return BaseResponse(message="chatbot wordset successfully deleted") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
워드셋 관리 엔드포인트가 잘 구현되었습니다.
승인, 거절, 삭제 엔드포인트가 RESTful 설계 원칙에 따라 적절히 구현되었습니다.
어드민 전용 엔드포인트(승인, 거절)에 권한 검증이 필요한지 확인하세요. 현재 get_user_id 의존성 없이 누구나 호출 가능한 상태입니다.
🏁 Script executed:
#!/bin/bash
# 어드민 권한 검증 의존성 확인
rg -n "admin|auth|permission" api/depends/ -C3Length of output: 0
Now let me check the actual codebase to verify the implementation:
🏁 Script executed:
#!/bin/bash
# 실제 파일 확인 - 인증/권한 의존성 구조
cat api/routers/chatbot_wordset.py | head -50Length of output: 1846
🏁 Script executed:
#!/bin/bash
# 다른 라우터 파일에서 권한 검증 패턴 확인
fd -e py api/routers/ --exec basename {} \; | head -10Length of output: 353
🏁 Script executed:
#!/bin/bash
# 승인/거절 엔드포인트가 있는 다른 라우터에서 권한 패턴 검색
rg -n "approve|reject" api/routers/ -C5 --type pyLength of output: 1422
🏁 Script executed:
#!/bin/bash
# get_user_id 의존성 검색 - 인증 패턴 확인
rg -n "get_user_id" api/ -C3 --type py | head -50Length of output: 3320
🏁 Script executed:
#!/bin/bash
# admin 관련 권한 검증 패턴 - 다른 위치에서 검색
rg -n "admin" api/ -C3 --type py | head -60Length of output: 0
어드민 전용 엔드포인트에 권한 검증 누락 - 보안 취약점
확인 결과, 승인(approve_wordset), 거절(reject_wordset), 삭제(delete_wordset) 엔드포인트가 인증 및 권한 검증 없이 누구나 호출 가능합니다.
- 라인 17: POST 엔드포인트는
user_id: str = Depends(get_user_id)사용 - 라인 25, 32, 40: 어드민 엔드포인트들은
get_user_id의존성 없음 - 코드 주석 자체가 이들을 "어드민" 작업으로 표시하나 접근 제어 부재
# 현재 상태 (취약함)
@router.patch("/{wordset_id}/approve")
async def approve_wordset(wordset_id: str) -> BaseResponse: # 누구나 호출 가능
# 필요한 수정
@router.patch("/{wordset_id}/approve")
async def approve_wordset(wordset_id: str, user_id: str = Depends(get_user_id)) -> BaseResponse:어드민 역할 검증 로직도 추가하여 인증된 어드민만 이 작업들을 수행할 수 있도록 구현하세요.
🤖 Prompt for AI Agents
In api/routers/chatbot_wordset.py around lines 24 to 42, the
approve/reject/delete endpoints lack authentication and admin authorization;
update each endpoint signature to accept user_id: str = Depends(get_user_id)
(and import Depends if not present) and then enforce admin-role verification
before calling the service (e.g., call an existing is_admin(check) utility or
fetch the user's roles and if not admin raise HTTPException(status_code=403,
detail="admin privileges required")); ensure any helper used is imported and
consistent with project patterns and keep error messages and status codes
uniform with other admin endpoints.
| from datetime import datetime | ||
| from beanie import Document, before_event, Insert, Update | ||
| from app.chatbot_wordset.document.wordset_status import WordSetStatus |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
import된 심볼들의 사용 여부를 확인하세요.
datetime, before_event, Insert, Update가 import되었지만 제공된 코드에서 사용되지 않는 것으로 보입니다. 이 심볼들이 파일의 다른 부분에서 사용되는지 확인하세요.
다음 스크립트로 사용 여부를 확인하세요:
🏁 Script executed:
#!/bin/bash
# 파일 내에서 import된 심볼들의 사용 여부 확인
cat app/chatbot_wordset/document/chatbot_wordset.py | grep -E "datetime|before_event|Insert|Update" | grep -v "^from\|^import"Length of output: 0
더 정확한 검증을 위해 파일 전체를 확인하겠습니다.
🏁 Script executed:
#!/bin/bash
# 파일 전체 내용 확인
cat app/chatbot_wordset/document/chatbot_wordset.pyLength of output: 371
🏁 Script executed:
#!/bin/bash
# import 제외하고 각 심볼의 사용 여부 확인 (더 포괄적)
echo "=== datetime usage ==="
rg -n "datetime" app/chatbot_wordset/document/chatbot_wordset.py | grep -v "^[0-9]*:from\|^[0-9]*:import"
echo "=== before_event usage ==="
rg -n "before_event" app/chatbot_wordset/document/chatbot_wordset.py | grep -v "^[0-9]*:from\|^[0-9]*:import"
echo "=== Insert usage ==="
rg -n "Insert" app/chatbot_wordset/document/chatbot_wordset.py | grep -v "^[0-9]*:from\|^[0-9]*:import"
echo "=== Update usage ==="
rg -n "Update" app/chatbot_wordset/document/chatbot_wordset.py | grep -v "^[0-9]*:from\|^[0-9]*:import"Length of output: 91
불필요한 import 제거 필요
파일 전체를 검토한 결과, datetime, before_event, Insert, Update는 이 파일에서 사용되지 않습니다. 이 import들을 제거하세요.
from beanie import Document
from app.chatbot_wordset.document.wordset_status import WordSetStatus🤖 Prompt for AI Agents
In app/chatbot_wordset/document/chatbot_wordset.py around lines 1 to 3, remove
the unused imports datetime, before_event, Insert, and Update and only keep the
required imports so the top of the file reads the minimal needed imports (i.e.,
import Document and WordSetStatus) to eliminate unused-import warnings.
| return chatbot_wordsets | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
함수명 오탈자: find_chatbot_wordests → find_chatbot_wordsets 권장
검색/호출 시 혼선을 유발합니다. API 표면에 노출되면 향후 호환성 이슈로 번질 수 있습니다. 가능하면 지금 바로 리네임하고 호출부 일괄 변경하세요.
다음으로 호출부를 확인하세요:
🏁 Script executed:
#!/bin/bash
rg -nP 'find_chatbot_worde?sts' -C2Length of output: 918
함수명 오탈자 수정 필요: find_chatbot_wordests → find_chatbot_wordsets
다음 위치에서 오탈자가 확인되었습니다:
app/chatbot_wordset/repository/chatbot_wordset_repo.py:59- 함수 정의app/chatbot/service/chatbot_service.py:224- 함수 호출
두 위치 모두 함수명을 find_chatbot_wordsets로 수정하세요.
🤖 Prompt for AI Agents
app/chatbot_wordset/repository/chatbot_wordset_repo.py around lines 59-68: the
function name is misspelled as find_chatbot_wordests; rename the function to
find_chatbot_wordsets and update any internal references; then update the call
site in app/chatbot/service/chatbot_service.py at line 224 to use
find_chatbot_wordsets (and update any imports/exports or typing references
accordingly) so both definition and all usages match.
| async def find_by_id(wordset_id: str) -> ChatBotWordSet: | ||
| """특정 ID로 wordset 조회""" | ||
| return await ChatBotWordSet.get(PydanticObjectId(wordset_id)) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_by_id 유효하지 않은 ID 처리 및 반환 타입 보강 필요
잘못된 문자열이 들어오면 예외가 발생하거나, 미존재 시 None이 반환될 수 있으므로 Optional 명시 및 예외 가드가 필요합니다.
적용 제안:
-async def find_by_id(wordset_id: str) -> ChatBotWordSet:
- """특정 ID로 wordset 조회"""
- return await ChatBotWordSet.get(PydanticObjectId(wordset_id))
+async def find_by_id(wordset_id: str) -> Optional[ChatBotWordSet]:
+ """특정 ID로 wordset 조회"""
+ try:
+ oid = PydanticObjectId(wordset_id)
+ except Exception:
+ return None
+ return await ChatBotWordSet.get(oid)또한 파일 상단에 다음 임포트를 추가하세요:
from typing import OptionalBased on learnings
🤖 Prompt for AI Agents
In app/chatbot_wordset/repository/chatbot_wordset_repo.py around lines 70 to 73,
update find_by_id to return Optional[ChatBotWordSet] and guard against invalid
IDs and missing documents: add "from typing import Optional" at the top, change
the function signature to return Optional[ChatBotWordSet], wrap the
PydanticObjectId conversion and DB get call in a try/except to catch invalid ID
conversion and document-not-found errors, and return None in those cases instead
of letting exceptions propagate.
| async def delete_by_id(wordset_id: str): | ||
| """wordset 삭제""" | ||
| wordset = await find_by_id(wordset_id) | ||
| await wordset.delete() | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete_by_id 미존재 ID 처리 누락 — 잠재적 NPE
find_by_id가 None을 반환하면 await wordset.delete()에서 실패합니다. 삭제 결과를 bool로 반환하는 것도 유용합니다.
적용 제안:
-async def delete_by_id(wordset_id: str):
+async def delete_by_id(wordset_id: str) -> bool:
"""wordset 삭제"""
wordset = await find_by_id(wordset_id)
- await wordset.delete()
+ if wordset is None:
+ return False
+ await wordset.delete()
+ return True📝 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.
| async def delete_by_id(wordset_id: str): | |
| """wordset 삭제""" | |
| wordset = await find_by_id(wordset_id) | |
| await wordset.delete() | |
| async def delete_by_id(wordset_id: str) -> bool: | |
| """wordset 삭제""" | |
| wordset = await find_by_id(wordset_id) | |
| if wordset is None: | |
| return False | |
| await wordset.delete() | |
| return True |
🤖 Prompt for AI Agents
In app/chatbot_wordset/repository/chatbot_wordset_repo.py around lines 84 to 88,
the delete_by_id function assumes find_by_id always returns an object and will
raise if it returns None; update the function to handle a non-existent ID by
checking the result of await find_by_id(wordset_id) and return False (or raise a
controlled exception) when None, otherwise await wordset.delete() and return
True; also update the function signature/type hints to reflect a boolean return
value.
| async def find_by_cursor_id_and_status(cursor_id: int, status: str, size: int) -> List[ChatBotWordSet]: | ||
| """커서 ID와 상태로 wordset 조회""" | ||
| chatbot_wordsets = ( | ||
| await ChatBotWordSet.find( | ||
| oper.LT(ChatBotWordSet.id, cursor_id), | ||
| oper.Eq(ChatBotWordSet.status, status) | ||
| ) | ||
| .sort(("_id", SortDirection.DESCENDING)) | ||
| .limit(size + 1) | ||
| .to_list() | ||
| ) | ||
| return chatbot_wordsets | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
커서 비교 타입 오류: cursor_id는 ObjectId여야 함
ChatBotWordSet.id는 PydanticObjectId입니다. 현재 int로 비교하여 런타임 오류/무효 쿼리 가능성이 큽니다. 또한 잘못된 ID 문자열 입력 시 예외 가드 필요.
적용 제안:
-async def find_by_cursor_id_and_status(cursor_id: int, status: str, size: int) -> List[ChatBotWordSet]:
+async def find_by_cursor_id_and_status(cursor_id: str, status: WordSetStatus, size: int) -> List[ChatBotWordSet]:
"""커서 ID와 상태로 wordset 조회"""
- chatbot_wordsets = (
- await ChatBotWordSet.find(
- oper.LT(ChatBotWordSet.id, cursor_id),
- oper.Eq(ChatBotWordSet.status, status)
- )
+ try:
+ oid = PydanticObjectId(cursor_id)
+ except Exception:
+ return []
+ chatbot_wordsets = (
+ await ChatBotWordSet.find(
+ oper.LT(ChatBotWordSet.id, oid),
+ oper.Eq(ChatBotWordSet.status, status)
+ )
.sort(("_id", SortDirection.DESCENDING))
.limit(size + 1)
.to_list()
)
return chatbot_wordsets임포트:
from app.chatbot_wordset.document.wordset_status import WordSetStatusBased on learnings
| async def find_by_cursor_id_character_id_and_status( | ||
| character_id: int, cursor_id: int, status: str, size: int | ||
| ) -> List[ChatBotWordSet]: | ||
| """커서 ID, 캐릭터 ID, 상태로 wordset 조회""" | ||
| chatbot_wordsets = ( | ||
| await ChatBotWordSet.find( | ||
| oper.LT(ChatBotWordSet.id, cursor_id), | ||
| oper.Eq(ChatBotWordSet.character_id, character_id), | ||
| oper.Eq(ChatBotWordSet.status, status) | ||
| ) | ||
| .sort(("_id", SortDirection.DESCENDING)) | ||
| .limit(size + 1) | ||
| .to_list() | ||
| ) | ||
| return chatbot_wordsets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
커서/상태 동시 필터 함수도 동일한 타입 문제
cursor_id: int→str로 받고PydanticObjectId로 변환.status: str→WordSetStatus.- 잘못된 커서 입력 시 빈 리스트 반환으로 페이징 실패를 방지.
적용 제안:
-async def find_by_cursor_id_character_id_and_status(
- character_id: int, cursor_id: int, status: str, size: int
+async def find_by_cursor_id_character_id_and_status(
+ character_id: int, cursor_id: str, status: WordSetStatus, size: int
) -> List[ChatBotWordSet]:
"""커서 ID, 캐릭터 ID, 상태로 wordset 조회"""
- chatbot_wordsets = (
- await ChatBotWordSet.find(
- oper.LT(ChatBotWordSet.id, cursor_id),
+ try:
+ oid = PydanticObjectId(cursor_id)
+ except Exception:
+ return []
+ chatbot_wordsets = (
+ await ChatBotWordSet.find(
+ oper.LT(ChatBotWordSet.id, oid),
oper.Eq(ChatBotWordSet.character_id, character_id),
oper.Eq(ChatBotWordSet.status, status)
)
.sort(("_id", SortDirection.DESCENDING))
.limit(size + 1)
.to_list()
)
return chatbot_wordsetsBased on learnings
🤖 Prompt for AI Agents
In app/chatbot_wordset/repository/chatbot_wordset_repo.py around lines 129-143,
the function currently types cursor_id as int and status as str causing
mismatches; change the signature to accept cursor_id: str and status: str,
convert cursor_id to PydanticObjectId (or return an empty list on conversion
failure to avoid breaking pagination), convert status string to the
WordSetStatus enum before querying, and then use the converted PydanticObjectId
in the oper.LT filter (or on _id) while keeping size handling the same so the
query returns at most size+1 items.
| async def get_chatbot_wordset_with_status(cursor_id: int, size: int, status: str = None) -> CursorResponse: | ||
| """상태별 wordset 조회""" | ||
| if status: | ||
| chatbot_wordset = ( | ||
| await chatbot_wordset_repo.find_by_status(status, size) | ||
| if cursor_id is None | ||
| else await chatbot_wordset_repo.find_by_cursor_id_and_status(cursor_id, status, size) | ||
| ) | ||
| else: | ||
| chatbot_wordset = ( | ||
| await chatbot_wordset_repo.find(size) | ||
| if cursor_id is None | ||
| else await chatbot_wordset_repo.find_by_cursor_id(cursor_id, size) | ||
| ) | ||
|
|
||
| has_next = len(chatbot_wordset) > size | ||
| return CursorResponse(hasNext=has_next, values=chatbot_wordset[:size]) | ||
|
|
||
|
|
||
| async def get_chatbot_wordset_by_character_with_status( | ||
| character_id: int, cursor_id: int, size: int, status: str = None | ||
| ) -> CursorResponse: | ||
| """캐릭터별, 상태별 wordset 조회""" | ||
| if status: | ||
| chatbot_wordset = ( | ||
| await chatbot_wordset_repo.find_by_character_id_and_status(character_id, status, size) | ||
| if cursor_id is None | ||
| else await chatbot_wordset_repo.find_by_cursor_id_character_id_and_status( | ||
| character_id, cursor_id, status, size | ||
| ) | ||
| ) | ||
| else: | ||
| chatbot_wordset = ( | ||
| await chatbot_wordset_repo.find_by_character_id(character_id, size) | ||
| if cursor_id is None | ||
| else await chatbot_wordset_repo.find_by_cursor_id_and_character_id(character_id, cursor_id, size) | ||
| ) | ||
|
|
||
| has_next = len(chatbot_wordset) > size | ||
| return CursorResponse(hasNext=has_next, values=chatbot_wordset[:size]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
상태 기반 조회 로직이 잘 구현되었으나 타입 힌트 개선이 필요합니다.
상태 필터링과 커서 기반 페이지네이션이 올바르게 구현되었습니다. 하지만 타입 힌트를 개선해야 합니다.
다음과 같이 타입 힌트를 명시적으로 수정하세요:
-async def get_chatbot_wordset_with_status(cursor_id: int, size: int, status: str = None) -> CursorResponse:
+async def get_chatbot_wordset_with_status(cursor_id: int, size: int, status: str | None = None) -> CursorResponse:
"""상태별 wordset 조회""" async def get_chatbot_wordset_by_character_with_status(
- character_id: int, cursor_id: int, size: int, status: str = None
+ character_id: int, cursor_id: int, size: int, status: str | None = None
) -> CursorResponse:
"""캐릭터별, 상태별 wordset 조회"""이유: PEP 484는 암묵적 Optional을 금지합니다. T | None을 명시적으로 사용해야 합니다.
As per static analysis hints
🧰 Tools
🪛 Ruff (0.14.1)
75-75: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
95-95: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
🤖 Prompt for AI Agents
In app/chatbot_wordset/service/chatbot_wordset_service.py around lines 75 to
114, the function signatures use bare int and str with default None which is
disallowed by PEP 484; update the type hints to explicitly allow None: change
cursor_id: int to cursor_id: int | None and status: str = None to status: str |
None (and similarly for the second function's cursor_id), keeping return type
CursorResponse unchanged so static typing correctly reflects optional
parameters.
| async def _crawl_namuwiki(character_name : str) -> List[Tuple[str, str, str]]: | ||
| print(f"Attempting to crawl Namuwiki for character: '{character_name}'") | ||
| crawler = Crawler() | ||
| namuwiki_list = await crawler.get_namuwiki_list(name=character_name) | ||
| return namuwiki_list | ||
|
|
||
| try: | ||
| namuwiki_list = await crawler.get_namuwiki_list(name=character_name) | ||
| print(f"Crawler returned {len(namuwiki_list) if namuwiki_list else 0} items") | ||
|
|
||
| if not namuwiki_list: | ||
| print(f"Warning: No namuwiki content found for character '{character_name}'") | ||
| print(f"This could be due to:") | ||
| print(f" 1. Character name not found in Namuwiki") | ||
| print(f" 2. Network connectivity issues") | ||
| print(f" 3. Character name format/encoding issues") | ||
| raise NoContentFoundException(character_name=character_name) | ||
|
|
||
| return namuwiki_list | ||
|
|
||
| except IndexError as e: | ||
| # 크롤러 내부에서 deque가 비어있을 때 발생 | ||
| print(f"IndexError caught while crawling Namuwiki for '{character_name}': {e}") | ||
| print(f"Possible causes:") | ||
| print(f" 1. No search results found in Namuwiki") | ||
| print(f" 2. HTML parsing failed (structure changed)") | ||
| print(f" 3. Network error or access blocked") | ||
| print(f" 4. Character name encoding issue") | ||
| raise NoContentFoundException(character_name=character_name) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
에러 처리가 개선되었으나 코드 품질 문제가 있습니다.
나무위키 크롤링 실패 시 IndexError를 캐치하고 진단 로깅을 추가한 것은 좋은 개선입니다. 하지만 정적 분석 도구가 여러 문제를 지적하고 있습니다.
다음 개선사항을 적용하세요:
print(f"Attempting to crawl Namuwiki for character: '{character_name}'")
crawler = Crawler()
try:
namuwiki_list = await crawler.get_namuwiki_list(name=character_name)
print(f"Crawler returned {len(namuwiki_list) if namuwiki_list else 0} items")
if not namuwiki_list:
print(f"Warning: No namuwiki content found for character '{character_name}'")
- print(f"This could be due to:")
- print(f" 1. Character name not found in Namuwiki")
- print(f" 2. Network connectivity issues")
- print(f" 3. Character name format/encoding issues")
+ print("This could be due to:")
+ print(" 1. Character name not found in Namuwiki")
+ print(" 2. Network connectivity issues")
+ print(" 3. Character name format/encoding issues")
raise NoContentFoundException(character_name=character_name)
-
- return namuwiki_list
+ else:
+ return namuwiki_list
except IndexError as e:
# 크롤러 내부에서 deque가 비어있을 때 발생
print(f"IndexError caught while crawling Namuwiki for '{character_name}': {e}")
- print(f"Possible causes:")
- print(f" 1. No search results found in Namuwiki")
- print(f" 2. HTML parsing failed (structure changed)")
- print(f" 3. Network error or access blocked")
- print(f" 4. Character name encoding issue")
+ print("Possible causes:")
+ print(" 1. No search results found in Namuwiki")
+ print(" 2. HTML parsing failed (structure changed)")
+ print(" 3. Network error or access blocked")
+ print(" 4. Character name encoding issue")
raise NoContentFoundException(character_name=character_name) from e문제:
- 플레이스홀더가 없는 f-string들 (lines 193-196, 204-208)
- Return 문이 try 블록 끝에 있음 (line 199) - else 블록으로 이동 권장
As per static analysis hints
📝 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.
| async def _crawl_namuwiki(character_name : str) -> List[Tuple[str, str, str]]: | |
| print(f"Attempting to crawl Namuwiki for character: '{character_name}'") | |
| crawler = Crawler() | |
| namuwiki_list = await crawler.get_namuwiki_list(name=character_name) | |
| return namuwiki_list | |
| try: | |
| namuwiki_list = await crawler.get_namuwiki_list(name=character_name) | |
| print(f"Crawler returned {len(namuwiki_list) if namuwiki_list else 0} items") | |
| if not namuwiki_list: | |
| print(f"Warning: No namuwiki content found for character '{character_name}'") | |
| print(f"This could be due to:") | |
| print(f" 1. Character name not found in Namuwiki") | |
| print(f" 2. Network connectivity issues") | |
| print(f" 3. Character name format/encoding issues") | |
| raise NoContentFoundException(character_name=character_name) | |
| return namuwiki_list | |
| except IndexError as e: | |
| # 크롤러 내부에서 deque가 비어있을 때 발생 | |
| print(f"IndexError caught while crawling Namuwiki for '{character_name}': {e}") | |
| print(f"Possible causes:") | |
| print(f" 1. No search results found in Namuwiki") | |
| print(f" 2. HTML parsing failed (structure changed)") | |
| print(f" 3. Network error or access blocked") | |
| print(f" 4. Character name encoding issue") | |
| raise NoContentFoundException(character_name=character_name) from e | |
| async def _crawl_namuwiki(character_name : str) -> List[Tuple[str, str, str]]: | |
| print(f"Attempting to crawl Namuwiki for character: '{character_name}'") | |
| crawler = Crawler() | |
| try: | |
| namuwiki_list = await crawler.get_namuwiki_list(name=character_name) | |
| print(f"Crawler returned {len(namuwiki_list) if namuwiki_list else 0} items") | |
| if not namuwiki_list: | |
| print(f"Warning: No namuwiki content found for character '{character_name}'") | |
| print("This could be due to:") | |
| print(" 1. Character name not found in Namuwiki") | |
| print(" 2. Network connectivity issues") | |
| print(" 3. Character name format/encoding issues") | |
| raise NoContentFoundException(character_name=character_name) | |
| else: | |
| return namuwiki_list | |
| except IndexError as e: | |
| # 크롤러 내부에서 deque가 비어있을 때 발생 | |
| print(f"IndexError caught while crawling Namuwiki for '{character_name}': {e}") | |
| print("Possible causes:") | |
| print(" 1. No search results found in Namuwiki") | |
| print(" 2. HTML parsing failed (structure changed)") | |
| print(" 3. Network error or access blocked") | |
| print(" 4. Character name encoding issue") | |
| raise NoContentFoundException(character_name=character_name) from e |
🧰 Tools
🪛 Ruff (0.14.1)
193-193: f-string without any placeholders
Remove extraneous f prefix
(F541)
194-194: f-string without any placeholders
Remove extraneous f prefix
(F541)
195-195: f-string without any placeholders
Remove extraneous f prefix
(F541)
196-196: f-string without any placeholders
Remove extraneous f prefix
(F541)
199-199: Consider moving this statement to an else block
(TRY300)
204-204: f-string without any placeholders
Remove extraneous f prefix
(F541)
205-205: f-string without any placeholders
Remove extraneous f prefix
(F541)
206-206: f-string without any placeholders
Remove extraneous f prefix
(F541)
207-207: f-string without any placeholders
Remove extraneous f prefix
(F541)
208-208: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In app/chatbot/service/chatbot_service.py around lines 183-209, replace
f-strings that contain no placeholders (lines ~193-196 and ~204-208) with normal
string literals to satisfy static analysis (e.g., change print(f" 1. ...") to
print(" 1. ...")), and move the successful-return from inside the try block to
an else block that runs only when no exception occurred (i.e., remove return
from the try and add an else: return namuwiki_list after the try/except),
keeping the IndexError except handler intact and preserving the
NoContentFoundException raises.
Avro Schema Namespace Change
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
인프라