Tu Nguyen commited on
Commit
9be3b9f
·
1 Parent(s): c31d80d

refactoring ok

Browse files
Files changed (35) hide show
  1. app/api/routes.py +6 -6
  2. app/application/services/orchestration/process_image.py +3 -10
  3. app/application/services/orchestration/process_remote_image.py +2 -2
  4. app/application/services/orchestration/process_upload_image.py +2 -2
  5. app/application/services/orchestration/source_cleanup.py +2 -1
  6. app/application/services/outputs/derivative_generator.py +5 -2
  7. app/application/services/outputs/manifest_writer.py +1 -1
  8. app/application/services/setup/job_workspace.py +1 -1
  9. app/application/services/setup/request_options.py +3 -9
  10. app/domain/constants.py +6 -0
  11. app/domain/ports/__init__.py +1 -0
  12. app/domain/ports/media.py +44 -0
  13. app/domain/{interfaces.py → ports/pipeline.py} +2 -63
  14. app/domain/ports/sources.py +21 -0
  15. app/domain/{models.py → schemas.py} +2 -68
  16. app/domain/types.py +71 -0
  17. app/infrastructure/composition/dependencies.py +2 -1
  18. app/infrastructure/config/config_loader.py +2 -1
  19. app/infrastructure/media/encoder.py +1 -1
  20. app/infrastructure/media/enhancer.py +1 -1
  21. app/infrastructure/media/metadata_service.py +1 -1
  22. app/infrastructure/media/watermark.py +1 -1
  23. app/infrastructure/sources/remote_source_fetcher.py +1 -1
  24. app/infrastructure/sources/upload_source_stager.py +1 -1
  25. app/infrastructure/storage/r2_uploader.py +1 -1
  26. tests/demo.jpg +3 -0
  27. tests/integration/api/support.py +2 -1
  28. tests/integration/api/test_process_upload.py +31 -0
  29. tests/unit/application/services/orchestration/test_process_image.py +2 -8
  30. tests/unit/application/services/orchestration/test_process_sources.py +2 -1
  31. tests/unit/application/services/outputs/test_derivative_generator.py +2 -1
  32. tests/unit/application/services/outputs/test_manifest_writer.py +2 -4
  33. tests/unit/application/services/setup/test_request_options.py +1 -1
  34. tests/unit/infrastructure/media/test_metadata_service.py +1 -1
  35. tests/unit/infrastructure/media/test_watermark.py +1 -1
app/api/routes.py CHANGED
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
7
  from pydantic import ValidationError
8
 
9
  from app.api.dependencies import get_container
10
- from app.domain.models import ProcessRequestModel
11
  from app.infrastructure.composition.dependencies import ServiceContainer
12
 
13
  router = APIRouter(tags=["Image Pipeline"])
@@ -21,16 +21,16 @@ PROCESS_RESPONSE_EXAMPLE = {
21
  "processed": True,
22
  },
23
  "metadata": {
24
- "title": "Can ho Goldmark City",
25
- "subject": "Hinh anh can ho Goldmark City",
26
- "tags": ["Goldmark City", "chung cu Goldmark"],
27
- "comments": "Anh duoc xu ly boi image pipeline",
28
  "rating": 5,
29
  "gpsLatitude": 21.042953,
30
  "gpsLongitude": 105.767916,
31
  "artist": "https://www.goldmark-city.com/",
32
  "copyright": "https://www.goldmark-city.com/",
33
- "imageDescription": "Hinh anh can ho Goldmark City",
34
  "extraTags": {},
35
  },
36
  "watermark": {
 
7
  from pydantic import ValidationError
8
 
9
  from app.api.dependencies import get_container
10
+ from app.domain.schemas import ProcessRequestModel
11
  from app.infrastructure.composition.dependencies import ServiceContainer
12
 
13
  router = APIRouter(tags=["Image Pipeline"])
 
21
  "processed": True,
22
  },
23
  "metadata": {
24
+ "title": "Căn hộ Goldmark City",
25
+ "subject": "Hình ảnh căn hộ Goldmark City",
26
+ "tags": ["Goldmark City", "chung Goldmark"],
27
+ "comments": "Ảnh được xử bởi image pipeline",
28
  "rating": 5,
29
  "gpsLatitude": 21.042953,
30
  "gpsLongitude": 105.767916,
31
  "artist": "https://www.goldmark-city.com/",
32
  "copyright": "https://www.goldmark-city.com/",
33
+ "imageDescription": "Hình ảnh căn hộ Goldmark City",
34
  "extraTags": {},
35
  },
36
  "watermark": {
app/application/services/orchestration/process_image.py CHANGED
@@ -3,16 +3,9 @@ from __future__ import annotations
3
  from pathlib import Path
4
  from typing import Any
5
 
6
- from app.domain.interfaces import (
7
- DerivativeGenerator,
8
- Enhancer,
9
- JobWorkspaceFactory,
10
- ManifestWriter,
11
- MetadataPort,
12
- ProcessingOptionsResolver,
13
- WatermarkAssetResolver,
14
- )
15
- from app.domain.models import PipelineDefaultsModel, ProcessRequestModel
16
 
17
 
18
  class ProcessImageUseCase:
 
3
  from pathlib import Path
4
  from typing import Any
5
 
6
+ from app.domain.ports.media import Enhancer, MetadataPort, WatermarkAssetResolver
7
+ from app.domain.ports.pipeline import DerivativeGenerator, JobWorkspaceFactory, ManifestWriter, ProcessingOptionsResolver
8
+ from app.domain.schemas import PipelineDefaultsModel, ProcessRequestModel
 
 
 
 
 
 
 
9
 
10
 
11
  class ProcessImageUseCase:
app/application/services/orchestration/process_remote_image.py CHANGED
@@ -4,8 +4,8 @@ from typing import Any
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
  from app.application.services.orchestration.source_cleanup import process_fetched_source
7
- from app.domain.interfaces import RemoteSourceFetcher
8
- from app.domain.models import ProcessRequestModel
9
 
10
 
11
  class ProcessRemoteImageUseCase:
 
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
  from app.application.services.orchestration.source_cleanup import process_fetched_source
7
+ from app.domain.ports.sources import RemoteSourceFetcher
8
+ from app.domain.schemas import ProcessRequestModel
9
 
10
 
11
  class ProcessRemoteImageUseCase:
app/application/services/orchestration/process_upload_image.py CHANGED
@@ -4,8 +4,8 @@ from typing import Any
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
  from app.application.services.orchestration.source_cleanup import process_fetched_source
7
- from app.domain.interfaces import UploadSourceStager
8
- from app.domain.models import ProcessRequestModel
9
 
10
 
11
  class ProcessUploadedImageUseCase:
 
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
  from app.application.services.orchestration.source_cleanup import process_fetched_source
7
+ from app.domain.ports.sources import UploadSourceStager
8
+ from app.domain.schemas import ProcessRequestModel
9
 
10
 
11
  class ProcessUploadedImageUseCase:
app/application/services/orchestration/source_cleanup.py CHANGED
@@ -3,7 +3,8 @@ from __future__ import annotations
3
  from typing import Any
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
- from app.domain.models import FetchedSource, ProcessRequestModel
 
7
 
8
 
9
  def process_fetched_source(
 
3
  from typing import Any
4
 
5
  from app.application.services.orchestration.process_image import ProcessImageUseCase
6
+ from app.domain.schemas import ProcessRequestModel
7
+ from app.domain.types import FetchedSource
8
 
9
 
10
  def process_fetched_source(
app/application/services/outputs/derivative_generator.py CHANGED
@@ -2,8 +2,11 @@ from __future__ import annotations
2
 
3
  from pathlib import Path
4
 
5
- from app.domain.interfaces import AssetUploader, DerivativeEncoder, MetadataPort
6
- from app.domain.models import GeneratedDerivatives, JobWorkspace, OutputArtifact, OutputFormat, R2ConfigModel, ResolvedProcessRequest
 
 
 
7
 
8
 
9
  class DefaultDerivativeGenerator:
 
2
 
3
  from pathlib import Path
4
 
5
+ from app.domain.constants import OutputFormat
6
+ from app.domain.ports.media import DerivativeEncoder, MetadataPort
7
+ from app.domain.ports.sources import AssetUploader
8
+ from app.domain.schemas import R2ConfigModel
9
+ from app.domain.types import GeneratedDerivatives, JobWorkspace, OutputArtifact, ResolvedProcessRequest
10
 
11
 
12
  class DefaultDerivativeGenerator:
app/application/services/outputs/manifest_writer.py CHANGED
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
5
  from pathlib import Path
6
  from typing import Any
7
 
8
- from app.domain.models import EnhancementResult, GeneratedDerivatives, JobWorkspace, ResolvedProcessRequest
9
 
10
 
11
  class JsonManifestWriter:
 
5
  from pathlib import Path
6
  from typing import Any
7
 
8
+ from app.domain.types import EnhancementResult, GeneratedDerivatives, JobWorkspace, ResolvedProcessRequest
9
 
10
 
11
  class JsonManifestWriter:
app/application/services/setup/job_workspace.py CHANGED
@@ -6,7 +6,7 @@ import shutil
6
  from datetime import datetime, timezone
7
  from pathlib import Path
8
 
9
- from app.domain.models import JobWorkspace
10
 
11
 
12
  class LocalJobWorkspaceFactory:
 
6
  from datetime import datetime, timezone
7
  from pathlib import Path
8
 
9
+ from app.domain.types import JobWorkspace
10
 
11
 
12
  class LocalJobWorkspaceFactory:
app/application/services/setup/request_options.py CHANGED
@@ -2,15 +2,9 @@ from __future__ import annotations
2
 
3
  from typing import Any
4
 
5
- from app.domain.models import (
6
- MetadataModel,
7
- OutputFormat,
8
- PipelineDefaultsModel,
9
- ProcessRequestModel,
10
- ResolvedProcessRequest,
11
- SUPPORTED_OUTPUT_FORMATS,
12
- WatermarkModel,
13
- )
14
 
15
 
16
  class DefaultProcessingOptionsResolver:
 
2
 
3
  from typing import Any
4
 
5
+ from app.domain.constants import OutputFormat, SUPPORTED_OUTPUT_FORMATS
6
+ from app.domain.schemas import MetadataModel, PipelineDefaultsModel, ProcessRequestModel, WatermarkModel
7
+ from app.domain.types import ResolvedProcessRequest
 
 
 
 
 
 
8
 
9
 
10
  class DefaultProcessingOptionsResolver:
app/domain/constants.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ SUPPORTED_OUTPUT_FORMATS = ("avif", "jpg")
6
+ OutputFormat = Literal["avif", "jpg"]
app/domain/ports/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Domain port definitions."""
app/domain/ports/media.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Protocol
5
+
6
+ from app.domain.schemas import ImageSizeModel, MetadataModel, WatermarkModel
7
+ from app.domain.types import EnhancementResult
8
+
9
+
10
+ class Enhancer(Protocol):
11
+ def build_working_master(self, source_path: Path, target_path: Path) -> EnhancementResult:
12
+ ...
13
+
14
+
15
+ class MetadataPort(Protocol):
16
+ def ensure_available(self) -> None:
17
+ ...
18
+
19
+ def write_metadata(self, file_path: Path, metadata: MetadataModel | None) -> None:
20
+ ...
21
+
22
+ def copy_all_metadata(self, source_path: Path, target_path: Path) -> None:
23
+ ...
24
+
25
+
26
+ class DerivativeEncoder(Protocol):
27
+ def ensure_available(self) -> None:
28
+ ...
29
+
30
+ def encode(
31
+ self,
32
+ source_path: Path,
33
+ target_path: Path,
34
+ size: ImageSizeModel,
35
+ image_format: str,
36
+ watermark_png_path: Path | None,
37
+ watermark: WatermarkModel | None,
38
+ ) -> None:
39
+ ...
40
+
41
+
42
+ class WatermarkAssetResolver(Protocol):
43
+ def resolve_png(self, watermark: WatermarkModel, job_dir: Path) -> Path:
44
+ ...
app/domain/{interfaces.py → ports/pipeline.py} RENAMED
@@ -3,59 +3,8 @@ from __future__ import annotations
3
  from pathlib import Path
4
  from typing import Any, Protocol
5
 
6
- from app.domain.models import (
7
- EnhancementResult,
8
- FetchedSource,
9
- GeneratedDerivatives,
10
- ImageSizeModel,
11
- JobWorkspace,
12
- MetadataModel,
13
- ProcessRequestModel,
14
- ResolvedProcessRequest,
15
- WatermarkModel,
16
- )
17
-
18
-
19
- class Enhancer(Protocol):
20
- def build_working_master(self, source_path: Path, target_path: Path) -> EnhancementResult:
21
- ...
22
-
23
-
24
- class MetadataPort(Protocol):
25
- def ensure_available(self) -> None:
26
- ...
27
-
28
- def write_metadata(self, file_path: Path, metadata: MetadataModel | None) -> None:
29
- ...
30
-
31
- def copy_all_metadata(self, source_path: Path, target_path: Path) -> None:
32
- ...
33
-
34
-
35
- class DerivativeEncoder(Protocol):
36
- def ensure_available(self) -> None:
37
- ...
38
-
39
- def encode(
40
- self,
41
- source_path: Path,
42
- target_path: Path,
43
- size: ImageSizeModel,
44
- image_format: str,
45
- watermark_png_path: Path | None,
46
- watermark: WatermarkModel | None,
47
- ) -> None:
48
- ...
49
-
50
-
51
- class WatermarkAssetResolver(Protocol):
52
- def resolve_png(self, watermark: WatermarkModel, job_dir: Path) -> Path:
53
- ...
54
-
55
-
56
- class AssetUploader(Protocol):
57
- def upload_file(self, file_path: Path, object_key: str, content_type: str) -> dict[str, str]:
58
- ...
59
 
60
 
61
  class ProcessingOptionsResolver(Protocol):
@@ -93,13 +42,3 @@ class ManifestWriter(Protocol):
93
  generated: GeneratedDerivatives,
94
  ) -> dict[str, Any]:
95
  ...
96
-
97
-
98
- class RemoteSourceFetcher(Protocol):
99
- def download(self, source_url: str) -> FetchedSource:
100
- ...
101
-
102
-
103
- class UploadSourceStager(Protocol):
104
- def stage(self, file_name: str | None, content: bytes) -> FetchedSource:
105
- ...
 
3
  from pathlib import Path
4
  from typing import Any, Protocol
5
 
6
+ from app.domain.schemas import ProcessRequestModel
7
+ from app.domain.types import EnhancementResult, GeneratedDerivatives, JobWorkspace, ResolvedProcessRequest
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  class ProcessingOptionsResolver(Protocol):
 
42
  generated: GeneratedDerivatives,
43
  ) -> dict[str, Any]:
44
  ...
 
 
 
 
 
 
 
 
 
 
app/domain/ports/sources.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Protocol
5
+
6
+ from app.domain.types import FetchedSource
7
+
8
+
9
+ class AssetUploader(Protocol):
10
+ def upload_file(self, file_path: Path, object_key: str, content_type: str) -> dict[str, str]:
11
+ ...
12
+
13
+
14
+ class RemoteSourceFetcher(Protocol):
15
+ def download(self, source_url: str) -> FetchedSource:
16
+ ...
17
+
18
+
19
+ class UploadSourceStager(Protocol):
20
+ def stage(self, file_name: str | None, content: bytes) -> FetchedSource:
21
+ ...
app/domain/{models.py → schemas.py} RENAMED
@@ -1,15 +1,13 @@
1
  from __future__ import annotations
2
 
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
  from typing import Literal
6
 
7
  from pydantic import BaseModel, Field, model_validator
8
 
 
 
9
  MetadataScalar = str | int | float | bool
10
  MetadataValue = MetadataScalar | list[MetadataScalar]
11
- SUPPORTED_OUTPUT_FORMATS = ("avif", "jpg")
12
- OutputFormat = Literal["avif", "jpg"]
13
 
14
 
15
  class ImageSizeModel(BaseModel):
@@ -185,67 +183,3 @@ class ProcessRequestModel(BaseModel):
185
  ]
186
  },
187
  }
188
-
189
-
190
- @dataclass(frozen=True)
191
- class RuntimeSettings:
192
- host: str
193
- port: int
194
- output_dir: Path
195
- device: str
196
- max_input_side: int
197
- restore_original_size: bool
198
- jpeg_quality: int
199
- brightness_skip_threshold: float
200
- hvi_model_id: str
201
- hvi_gamma: float
202
- hvi_alpha_s: float
203
- hvi_alpha_i: float
204
- default_formats: tuple[str, ...]
205
- config_path: Path
206
- r2_config: R2ConfigModel | None
207
-
208
-
209
- @dataclass(frozen=True)
210
- class EnhancementResult:
211
- path: Path
212
- brightness: float
213
- skipped: bool
214
- strategy: str
215
-
216
-
217
- @dataclass(frozen=True)
218
- class ResolvedProcessRequest:
219
- sizes: tuple[ImageSizeModel, ...]
220
- formats: tuple[OutputFormat, ...]
221
- metadata: MetadataModel | None
222
- watermark: WatermarkModel | None
223
- upload_to_r2: bool
224
- r2_prefix: str | None
225
-
226
-
227
- @dataclass(frozen=True)
228
- class JobWorkspace:
229
- job_id: str
230
- job_dir: Path
231
- staged_source_path: Path
232
- working_master_path: Path
233
-
234
-
235
- @dataclass(frozen=True)
236
- class OutputArtifact:
237
- image_format: str
238
- size: ImageSizeModel
239
- file_path: Path
240
-
241
-
242
- @dataclass(frozen=True)
243
- class GeneratedDerivatives:
244
- outputs: tuple[OutputArtifact, ...]
245
- r2_summary: dict[str, object] | None
246
-
247
-
248
- @dataclass(frozen=True)
249
- class FetchedSource:
250
- temp_path: Path
251
- display_name: str
 
1
  from __future__ import annotations
2
 
 
 
3
  from typing import Literal
4
 
5
  from pydantic import BaseModel, Field, model_validator
6
 
7
+ from app.domain.constants import OutputFormat
8
+
9
  MetadataScalar = str | int | float | bool
10
  MetadataValue = MetadataScalar | list[MetadataScalar]
 
 
11
 
12
 
13
  class ImageSizeModel(BaseModel):
 
183
  ]
184
  },
185
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/domain/types.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from app.domain.constants import OutputFormat
7
+ from app.domain.schemas import ImageSizeModel, MetadataModel, R2ConfigModel, WatermarkModel
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class RuntimeSettings:
12
+ host: str
13
+ port: int
14
+ output_dir: Path
15
+ device: str
16
+ max_input_side: int
17
+ restore_original_size: bool
18
+ jpeg_quality: int
19
+ brightness_skip_threshold: float
20
+ hvi_model_id: str
21
+ hvi_gamma: float
22
+ hvi_alpha_s: float
23
+ hvi_alpha_i: float
24
+ default_formats: tuple[str, ...]
25
+ config_path: Path
26
+ r2_config: R2ConfigModel | None
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class EnhancementResult:
31
+ path: Path
32
+ brightness: float
33
+ skipped: bool
34
+ strategy: str
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ResolvedProcessRequest:
39
+ sizes: tuple[ImageSizeModel, ...]
40
+ formats: tuple[OutputFormat, ...]
41
+ metadata: MetadataModel | None
42
+ watermark: WatermarkModel | None
43
+ upload_to_r2: bool
44
+ r2_prefix: str | None
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class JobWorkspace:
49
+ job_id: str
50
+ job_dir: Path
51
+ staged_source_path: Path
52
+ working_master_path: Path
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class OutputArtifact:
57
+ image_format: str
58
+ size: ImageSizeModel
59
+ file_path: Path
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class GeneratedDerivatives:
64
+ outputs: tuple[OutputArtifact, ...]
65
+ r2_summary: dict[str, object] | None
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class FetchedSource:
70
+ temp_path: Path
71
+ display_name: str
app/infrastructure/composition/dependencies.py CHANGED
@@ -10,7 +10,8 @@ from app.application.services.outputs.derivative_generator import DefaultDerivat
10
  from app.application.services.outputs.manifest_writer import JsonManifestWriter
11
  from app.application.services.setup.job_workspace import LocalJobWorkspaceFactory
12
  from app.application.services.setup.request_options import DefaultProcessingOptionsResolver
13
- from app.domain.models import PipelineDefaultsModel, RuntimeSettings
 
14
  from app.infrastructure.config.config_loader import load_pipeline_defaults, load_settings
15
  from app.infrastructure.media.command_runner import CommandRunner
16
  from app.infrastructure.media.encoder import ImageEncoder
 
10
  from app.application.services.outputs.manifest_writer import JsonManifestWriter
11
  from app.application.services.setup.job_workspace import LocalJobWorkspaceFactory
12
  from app.application.services.setup.request_options import DefaultProcessingOptionsResolver
13
+ from app.domain.schemas import PipelineDefaultsModel
14
+ from app.domain.types import RuntimeSettings
15
  from app.infrastructure.config.config_loader import load_pipeline_defaults, load_settings
16
  from app.infrastructure.media.command_runner import CommandRunner
17
  from app.infrastructure.media.encoder import ImageEncoder
app/infrastructure/config/config_loader.py CHANGED
@@ -6,7 +6,8 @@ from pathlib import Path
6
 
7
  from dotenv import dotenv_values
8
 
9
- from app.domain.models import PipelineDefaultsModel, R2ConfigModel, RuntimeSettings
 
10
 
11
 
12
  def load_settings(project_root: Path) -> RuntimeSettings:
 
6
 
7
  from dotenv import dotenv_values
8
 
9
+ from app.domain.schemas import PipelineDefaultsModel, R2ConfigModel
10
+ from app.domain.types import RuntimeSettings
11
 
12
 
13
  def load_settings(project_root: Path) -> RuntimeSettings:
app/infrastructure/media/encoder.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
 
3
  from pathlib import Path
4
 
5
- from app.domain.models import ImageSizeModel, WatermarkModel
6
  from app.infrastructure.media.command_runner import CommandRunner
7
 
8
 
 
2
 
3
  from pathlib import Path
4
 
5
+ from app.domain.schemas import ImageSizeModel, WatermarkModel
6
  from app.infrastructure.media.command_runner import CommandRunner
7
 
8
 
app/infrastructure/media/enhancer.py CHANGED
@@ -7,7 +7,7 @@ from huggingface_hub import hf_hub_download
7
  from PIL import Image
8
  from safetensors.torch import load_file
9
 
10
- from app.domain.models import EnhancementResult, RuntimeSettings
11
  from app.infrastructure.media.image_ops import (
12
  estimate_brightness,
13
  load_image_as_tensor,
 
7
  from PIL import Image
8
  from safetensors.torch import load_file
9
 
10
+ from app.domain.types import EnhancementResult, RuntimeSettings
11
  from app.infrastructure.media.image_ops import (
12
  estimate_brightness,
13
  load_image_as_tensor,
app/infrastructure/media/metadata_service.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
  import tempfile
4
  from pathlib import Path
5
 
6
- from app.domain.models import MetadataModel
7
  from app.infrastructure.media.command_runner import CommandRunner
8
 
9
 
 
3
  import tempfile
4
  from pathlib import Path
5
 
6
+ from app.domain.schemas import MetadataModel
7
  from app.infrastructure.media.command_runner import CommandRunner
8
 
9
 
app/infrastructure/media/watermark.py CHANGED
@@ -4,7 +4,7 @@ from pathlib import Path
4
 
5
  from PIL import Image, ImageDraw, ImageFont
6
 
7
- from app.domain.models import WatermarkModel
8
 
9
 
10
  class WatermarkAssetFactory:
 
4
 
5
  from PIL import Image, ImageDraw, ImageFont
6
 
7
+ from app.domain.schemas import WatermarkModel
8
 
9
 
10
  class WatermarkAssetFactory:
app/infrastructure/sources/remote_source_fetcher.py CHANGED
@@ -5,7 +5,7 @@ from pathlib import Path
5
  from urllib.parse import urlparse
6
  from urllib.request import Request, urlopen
7
 
8
- from app.domain.models import FetchedSource
9
 
10
 
11
  class RemoteImageFetcher:
 
5
  from urllib.parse import urlparse
6
  from urllib.request import Request, urlopen
7
 
8
+ from app.domain.types import FetchedSource
9
 
10
 
11
  class RemoteImageFetcher:
app/infrastructure/sources/upload_source_stager.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
  import tempfile
4
  from pathlib import Path
5
 
6
- from app.domain.models import FetchedSource
7
 
8
 
9
  class TemporaryUploadSourceStager:
 
3
  import tempfile
4
  from pathlib import Path
5
 
6
+ from app.domain.types import FetchedSource
7
 
8
 
9
  class TemporaryUploadSourceStager:
app/infrastructure/storage/r2_uploader.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
 
3
  from pathlib import Path
4
 
5
- from app.domain.models import R2ConfigModel
6
 
7
 
8
  class R2Uploader:
 
2
 
3
  from pathlib import Path
4
 
5
+ from app.domain.schemas import R2ConfigModel
6
 
7
 
8
  class R2Uploader:
tests/demo.jpg ADDED

Git LFS Details

  • SHA256: a56f7de964662f06dfca4ab72c0c73922ae2b23ff845489dd4bbc8b999267d11
  • Pointer size: 131 Bytes
  • Size of remote file: 265 kB
tests/integration/api/support.py CHANGED
@@ -3,7 +3,8 @@ from __future__ import annotations
3
  from pathlib import Path
4
  from types import SimpleNamespace
5
 
6
- from app.domain.models import PipelineDefaultsModel, RuntimeSettings
 
7
 
8
 
9
  class FakeProcessRemoteImage:
 
3
  from pathlib import Path
4
  from types import SimpleNamespace
5
 
6
+ from app.domain.schemas import PipelineDefaultsModel
7
+ from app.domain.types import RuntimeSettings
8
 
9
 
10
  class FakeProcessRemoteImage:
tests/integration/api/test_process_upload.py CHANGED
@@ -13,6 +13,37 @@ from tests.integration.api.support import FakeProcessRemoteImage, FakeProcessUpl
13
 
14
 
15
  class ApiProcessUploadIntegrationTests(unittest.TestCase):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def test_process_upload_returns_job_id_on_success(self) -> None:
17
  with tempfile.TemporaryDirectory() as temp_dir:
18
  root = Path(temp_dir)
 
13
 
14
 
15
  class ApiProcessUploadIntegrationTests(unittest.TestCase):
16
+ def test_process_upload_accepts_real_demo_image_file(self) -> None:
17
+ with tempfile.TemporaryDirectory() as temp_dir:
18
+ root = Path(temp_dir)
19
+ process_uploaded_image = FakeProcessUploadedImage()
20
+ app = create_app()
21
+ app.dependency_overrides[get_container] = lambda: make_container(
22
+ root,
23
+ process_remote_image=FakeProcessRemoteImage(),
24
+ process_uploaded_image=process_uploaded_image,
25
+ )
26
+ client = TestClient(app)
27
+ demo_path = Path(__file__).resolve().parents[2] / "demo.jpg"
28
+ expected_bytes = demo_path.read_bytes()
29
+ try:
30
+ with demo_path.open("rb") as image_file:
31
+ response = client.post(
32
+ "/process/upload",
33
+ files={"file": ("demo.jpg", image_file, "image/jpeg")},
34
+ data={"options": '{"formats":["jpg"]}'},
35
+ )
36
+
37
+ self.assertEqual(status.HTTP_200_OK, response.status_code)
38
+ self.assertEqual("test-upload-job", response.json()["jobId"])
39
+ self.assertEqual(1, len(process_uploaded_image.calls))
40
+ self.assertEqual("demo.jpg", process_uploaded_image.calls[0][0])
41
+ self.assertEqual(expected_bytes, process_uploaded_image.calls[0][1])
42
+ self.assertEqual(["jpg"], process_uploaded_image.calls[0][2].formats)
43
+ finally:
44
+ client.close()
45
+ app.dependency_overrides.clear()
46
+
47
  def test_process_upload_returns_job_id_on_success(self) -> None:
48
  with tempfile.TemporaryDirectory() as temp_dir:
49
  root = Path(temp_dir)
tests/unit/application/services/orchestration/test_process_image.py CHANGED
@@ -5,14 +5,8 @@ import unittest
5
  from pathlib import Path
6
 
7
  from app.application.services.orchestration.process_image import ProcessImageUseCase
8
- from app.domain.models import (
9
- EnhancementResult,
10
- JobWorkspace,
11
- WatermarkModel,
12
- PipelineDefaultsModel,
13
- ProcessRequestModel,
14
- ResolvedProcessRequest,
15
- )
16
 
17
 
18
  class _FakeOptionsResolver:
 
5
  from pathlib import Path
6
 
7
  from app.application.services.orchestration.process_image import ProcessImageUseCase
8
+ from app.domain.schemas import PipelineDefaultsModel, ProcessRequestModel, WatermarkModel
9
+ from app.domain.types import EnhancementResult, JobWorkspace, ResolvedProcessRequest
 
 
 
 
 
 
10
 
11
 
12
  class _FakeOptionsResolver:
tests/unit/application/services/orchestration/test_process_sources.py CHANGED
@@ -6,7 +6,8 @@ from pathlib import Path
6
 
7
  from app.application.services.orchestration.process_remote_image import ProcessRemoteImageUseCase
8
  from app.application.services.orchestration.process_upload_image import ProcessUploadedImageUseCase
9
- from app.domain.models import FetchedSource, ProcessRequestModel
 
10
 
11
 
12
  class _FakeRemoteSourceFetcher:
 
6
 
7
  from app.application.services.orchestration.process_remote_image import ProcessRemoteImageUseCase
8
  from app.application.services.orchestration.process_upload_image import ProcessUploadedImageUseCase
9
+ from app.domain.schemas import ProcessRequestModel
10
+ from app.domain.types import FetchedSource
11
 
12
 
13
  class _FakeRemoteSourceFetcher:
tests/unit/application/services/outputs/test_derivative_generator.py CHANGED
@@ -5,7 +5,8 @@ import unittest
5
  from pathlib import Path
6
 
7
  from app.application.services.outputs.derivative_generator import DefaultDerivativeGenerator
8
- from app.domain.models import GeneratedDerivatives, ImageSizeModel, JobWorkspace, R2ConfigModel, ResolvedProcessRequest
 
9
 
10
 
11
  class _FakeEncoder:
 
5
  from pathlib import Path
6
 
7
  from app.application.services.outputs.derivative_generator import DefaultDerivativeGenerator
8
+ from app.domain.schemas import ImageSizeModel, R2ConfigModel
9
+ from app.domain.types import GeneratedDerivatives, JobWorkspace, ResolvedProcessRequest
10
 
11
 
12
  class _FakeEncoder:
tests/unit/application/services/outputs/test_manifest_writer.py CHANGED
@@ -6,15 +6,13 @@ import unittest
6
  from pathlib import Path
7
 
8
  from app.application.services.outputs.manifest_writer import JsonManifestWriter
9
- from app.domain.models import (
 
10
  EnhancementResult,
11
  GeneratedDerivatives,
12
- ImageSizeModel,
13
  JobWorkspace,
14
- MetadataModel,
15
  OutputArtifact,
16
  ResolvedProcessRequest,
17
- WatermarkModel,
18
  )
19
 
20
 
 
6
  from pathlib import Path
7
 
8
  from app.application.services.outputs.manifest_writer import JsonManifestWriter
9
+ from app.domain.schemas import ImageSizeModel, MetadataModel, WatermarkModel
10
+ from app.domain.types import (
11
  EnhancementResult,
12
  GeneratedDerivatives,
 
13
  JobWorkspace,
 
14
  OutputArtifact,
15
  ResolvedProcessRequest,
 
16
  )
17
 
18
 
tests/unit/application/services/setup/test_request_options.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
  import unittest
4
 
5
  from app.application.services.setup.request_options import DefaultProcessingOptionsResolver
6
- from app.domain.models import (
7
  ImageSizeModel,
8
  MetadataModel,
9
  PipelineDefaultsModel,
 
3
  import unittest
4
 
5
  from app.application.services.setup.request_options import DefaultProcessingOptionsResolver
6
+ from app.domain.schemas import (
7
  ImageSizeModel,
8
  MetadataModel,
9
  PipelineDefaultsModel,
tests/unit/infrastructure/media/test_metadata_service.py CHANGED
@@ -4,7 +4,7 @@ import tempfile
4
  import unittest
5
  from pathlib import Path
6
 
7
- from app.domain.models import MetadataModel
8
  from app.infrastructure.media.metadata_service import MetadataService
9
 
10
 
 
4
  import unittest
5
  from pathlib import Path
6
 
7
+ from app.domain.schemas import MetadataModel
8
  from app.infrastructure.media.metadata_service import MetadataService
9
 
10
 
tests/unit/infrastructure/media/test_watermark.py CHANGED
@@ -6,7 +6,7 @@ from pathlib import Path
6
 
7
  from PIL import Image
8
 
9
- from app.domain.models import WatermarkModel
10
  from app.infrastructure.media.watermark import WatermarkAssetFactory
11
 
12
 
 
6
 
7
  from PIL import Image
8
 
9
+ from app.domain.schemas import WatermarkModel
10
  from app.infrastructure.media.watermark import WatermarkAssetFactory
11
 
12