11"""Shared query-related request primitives."""
22
3+ import base64
4+ import binascii
35from typing import Any , Literal , Optional
46
57from pydantic import BaseModel , ConfigDict , Field , model_validator
68
7- from constants import SOLR_VECTOR_SEARCH_DEFAULT_MODE
9+ from constants import (
10+ DEFAULT_MAX_FILE_UPLOAD_SIZE ,
11+ IMAGE_CONTENT_TYPES ,
12+ SOLR_VECTOR_SEARCH_DEFAULT_MODE ,
13+ )
814from log import get_logger
915
1016logger = get_logger (__name__ )
@@ -16,24 +22,63 @@ class Attachment(BaseModel):
1622 A list of attachments can be an optional part of 'query' request.
1723
1824 Attributes:
19- attachment_type: The attachment type, like "log", "configuration" etc.
25+ attachment_type: The attachment type, like "log", "configuration", "image" etc.
2026 content_type: The content type as defined in MIME standard
21- content: The actual attachment content
27+ content: The actual attachment content (text or base64-encoded image data)
2228 """
2329
2430 attachment_type : str = Field (
25- description = "The attachment type, like 'log', 'configuration' etc." ,
26- examples = ["log" ],
31+ description = "The attachment type, like 'log', 'configuration', 'image' etc." ,
32+ examples = ["log" , "image" ],
2733 )
2834 content_type : str = Field (
2935 description = "The content type as defined in MIME standard" ,
30- examples = ["text/plain" ],
36+ examples = ["text/plain" , "image/jpeg" , "image/png" ],
3137 )
3238 content : str = Field (
33- description = "The actual attachment content" ,
39+ description = "The actual attachment content (text or base64-encoded image data) " ,
3440 examples = ["warning: quota exceeded" ],
3541 )
3642
43+ @model_validator (mode = "after" )
44+ def validate_image_attachment (self ) -> "Attachment" :
45+ """Validate consistency between attachment_type and content_type for images.
46+
47+ Raises:
48+ ValueError: If image content_type is used without attachment_type='image',
49+ if attachment_type='image' is used without an image content_type,
50+ if image content is not valid base64, or if decoded size exceeds the limit.
51+ """
52+ is_image_content_type = self .content_type in IMAGE_CONTENT_TYPES
53+ is_image_attachment_type = self .attachment_type == "image"
54+
55+ if is_image_content_type and not is_image_attachment_type :
56+ raise ValueError (
57+ f"attachment_type must be 'image' when content_type is "
58+ f"'{ self .content_type } '"
59+ )
60+
61+ if is_image_attachment_type and not is_image_content_type :
62+ raise ValueError (
63+ f"content_type must be 'image/jpeg' or 'image/png' when "
64+ f"attachment_type is 'image', got '{ self .content_type } '"
65+ )
66+
67+ if is_image_content_type :
68+ try :
69+ decoded = base64 .b64decode (self .content , validate = True )
70+ except (binascii .Error , ValueError ) as exc :
71+ raise ValueError (
72+ f"Invalid base64 content for image attachment: { exc } "
73+ ) from exc
74+ if len (decoded ) > DEFAULT_MAX_FILE_UPLOAD_SIZE :
75+ raise ValueError (
76+ f"Image attachment ({ len (decoded )} bytes) exceeds maximum "
77+ f"allowed size ({ DEFAULT_MAX_FILE_UPLOAD_SIZE } bytes)"
78+ )
79+
80+ return self
81+
3782 # provides examples for /docs endpoint
3883 model_config = {
3984 "extra" : "forbid" ,
@@ -54,6 +99,11 @@ class Attachment(BaseModel):
5499 "content_type" : "application/yaml" ,
55100 "content" : "foo: bar" ,
56101 },
102+ {
103+ "attachment_type" : "image" ,
104+ "content_type" : "image/png" ,
105+ "content" : "<base64-encoded image data>" ,
106+ },
57107 ]
58108 },
59109 }
0 commit comments