소개
Odoo를 어느 정도 다뤄본 개발자라면, 아래 메시지와 마주친 적이 있을 것입니다:
ValueError: Expected singleton
이 오류는 Odoo에서 가장 자주 마주치는 ORM 관련 예외 중 하나입니다. 메서드가 정확히 하나의 레코드를 기대했는데 여러 레코드를 전달받았을 때 발생합니다. 메시지는 기술적으로 보이지만, Odoo의 레코드셋 작동 방식을 이해하면 원인은 대부분 단순합니다.
이 글은 “Expected Singleton” 오류의 의미와 발생 원인, 그리고 비즈니스 로직이나 연동을 깨뜨리지 않고 안전하게 해결하는 방법을 설명합니다.
Odoo에서 'Expected Singleton'이란 무엇을 뜻하나?
Odoo의 ORM(Object Relational Mapping)은 보통 단일 객체가 아니라 레코드셋을 처리합니다. 레코드셋은 다음 중 하나일 수 있습니다:
- 레코드 1개
- 여러 레코드
- 레코드 없음
메서드가 한 레코드만을 대상으로 설계되어 있는데 레코드셋에 여러 레코드가 들어있는 경우, 다음과 같은 예외를 발생시킵니다:
ValueError: Expected singleton
간단히 말하면:
Odoo는 하나의 레코드를 기대했지만 여러 개를 받았습니다.
이 오류는 보통 다음 상황에서 확인됩니다:
- 서버 로그
- 커스텀 모듈의 메서드
- 계산 필드(computed fields)
- 버튼 액션
- 자동화 액션(Automated actions)
- 대량 업데이트 작업
핵심은 레코드셋 동작을 올바르게 이해하는 것입니다.
이 오류가 발생하는 이유
1. 레코드셋에 대한 오해
Odoo에서 self는 대부분 레코드셋입니다.
겉보기엔 단일 레코드로 동작한다고 생각해도, Odoo는 때때로 여러 레코드에 대해 같은 메서드를 호출합니다. 예를 들어:
- 트리뷰에서의 일괄 작업
- 자동화된 워크플로
- 서버 액션
- API를 통한 대량 데이터 임포트
코드가 단일 레코드를 전제로 작성되었다면 실패합니다.
2. 메서드에서 반복문 누락
문제가 되는 코드 예:
def action_confirm(self): self.state = 'confirmed'
self가 여러 레코드를 담고 있다면 어떤 레코드의 state를 바꿔야 할지 모호해져 오류가 납니다.
올바른 접근법:
def action_confirm(self): for record in self: record.state = 'confirmed'
3. ensure_one()의 잘못된 사용
Odoo는 다음을 제공합니다:
self.ensure_one()
이 메서드는 반드시 하나의 레코드만 있을 때만 통과시키고, 여러 레코드면 의도적으로 singleton 오류를 발생시킵니다.
폼을 열어야 하는 경우처럼 비즈니스 로직상 반드시 단일 레코드가 필요할 때만 사용하세요.
4. 검색(search)으로 여러 레코드가 반환되는 경우
예시:
partner = self.env['res.partner'].search([('name', '=', 'John')])
만약 ‘John’이라는 이름의 레코드가 여러 개라면, 이후 로직이 단 하나만 있다고 가정하면 오류가 납니다.
안전한 대안:
partner = self.env['res.partner'].search([('name', '=', 'John')], limit=1)
5. 관계형 필드의 모호성
Many2one이나 One2many 같은 관계 필드에서 오류가 자주 발생합니다.
예시:
예: self.order_line.product_id.name
order_line에 여러 라인이 들어있다면 위 표현은 어느 라인의 product_id를 참조할지 애매합니다.
Expected Singleton 오류 해결 방법
1단계 – 레코드셋에 대해 반복 처리하기
Odoo의 기본 규칙:
self에 여러 레코드가 들어있을 수 있다고 항상 가정하세요.
for record in self: record.process_logic()
2단계 – 필요할 때 limit=1 사용하기
논리적으로 단 하나의 레코드만 유효하다면:
record = self.env['model.name'].search(domain, limit=1)
3단계 – 관계형 필드 점검하기
다음 항목을 확인하세요:
- Many2one 관계
- One2many 컬렉션
- 도메인 필터 조건
실수로 여러 행을 다루고 있지 않은지 확인합니다.
4단계 – API나 임포트 프로세스 검토하기
통합이 많은 환경에서는 대량 작업이 이 오류를 촉발하는 경우가 많습니다. 여러 레코드를 동시에 처리하기 때문입니다.
외부 시스템과 동기화하는 Odoo 인스턴스라면 배치 안전(batch-safe)한 로직을 설계해야 합니다.
앞으로 같은 오류를 예방하는 방법
- 단일 레코드 컨텍스트를 당연시하지 마세요.
- 여러 레코드를 선택해 메서드를 테스트해보세요.
- 기본적으로 반복문을 사용하세요.
- limit=1은 의식적으로 사용하세요.
- 관계형 필드는 명확하게 설계하세요.
자동화된 임포트나 예약 작업 같은 복잡한 통합 환경에서는 이러한 오류가 자주 드러납니다. 배치에 안전한 메서드를 설계하면 시스템 불안정성을 줄일 수 있습니다.
Dasolo는 레코드셋 및 ORM 오류를 어떻게 처리하나
“Expected Singleton” 오류는 단순한 코딩 실수 이상의 의미를 가질 때가 많습니다. 구조화된 Odoo 환경에서는 레코드셋 행동, ORM 사용 방식, 데이터 흐름에 대한 숨은 가정을 드러냅니다.
Dasolo에서는 ORM 관련 오류를 모듈 수명 주기 전체 관점에서 검토합니다. 싱글턴 문제는 흔히 단일 레코드용으로 작성된 비즈니스 로직이 자동화 워크플로, 통합, 계산 필드 등에서 다중 레코드로 실행될 때 발생합니다.
반복적으로 싱글턴 예외가 발생하지 않도록 하기 위해 우리는 다음에 집중합니다:
- 명시적인 레코드셋 반복 패턴
- ensure_one()의 안전한 사용
- 예측 가능한 도메인 필터링
- 명확한 관계형 아키텍처
- 제어된 자동화 트리거 설계
확장성을 고려해 ORM 로직을 설계하면 운영 환경에서의 예기치 않은 런타임 오류를 크게 줄일 수 있습니다.
결론
요약하자면 Odoo의 “Expected Singleton” 오류는 메서드가 하나의 레코드를 기대하는데 여러 레코드로 동작하려 할 때 발생하는 일반적인 ORM 예외입니다. 단순한 개발자 실수로 보일 수 있지만, 커스텀 모듈이나 자동화 프로세스 전반에 걸친 레코드셋 처리 일관성 문제를 시사할 때가 많습니다.
Odoo의 레코드셋 동작을 이해하고 안전한 반복 패턴을 적용하면 이 오류의 재발을 방지할 수 있습니다. 구조화된 레코드 처리, 명시적 검증, 통제된 자동화 로직이 안정적인 Odoo 운영의 핵심입니다.
싱글턴 오류를 제대로 해결하면 코드 품질과 시스템 안정성이 오히려 향상되는 신호로 활용할 수 있습니다.
자주 묻는 질문
아니요. Odoo 14, 15, 16, 17 등 여러 버전에서 공통적으로 나타납니다.
아니요. 레코드 처리 방식의 논리적 문제이지 데이터베이스 손상은 아닙니다.
아니요. 비즈니스 로직이 엄격히 단일 레코드 실행을 요구할 때만 사용하세요.