dragoneye-python 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dragoneye-python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Requires-Python: >=3.8
5
5
  License-File: LICENSE
6
6
  Requires-Dist: requests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dragoneye-python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Requires-Python: >=3.8
5
5
  License-File: LICENSE
6
6
  Requires-Dist: requests
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ requirements.txt
5
+ dragoneye_python.egg-info/PKG-INFO
6
+ dragoneye_python.egg-info/SOURCES.txt
7
+ dragoneye_python.egg-info/dependency_links.txt
8
+ dragoneye_python.egg-info/requires.txt
9
+ dragoneye_python.egg-info/top_level.txt
10
+ src/dragoneye/__init__.py
11
+ src/dragoneye/classification.py
12
+ src/dragoneye/client.py
13
+ src/dragoneye/types/__init__.py
14
+ src/dragoneye/types/common.py
15
+ src/dragoneye/types/image.py
@@ -0,0 +1,11 @@
1
+ [project]
2
+ name = "dragoneye-python"
3
+ version = "0.2.0"
4
+ requires-python = ">=3.8"
5
+ dynamic = ["dependencies"]
6
+
7
+ [tool.setuptools.dynamic]
8
+ dependencies = {file = ["requirements.txt"]}
9
+
10
+ [tool.setuptools.package-dir]
11
+ dragoneye = "src/dragoneye"
@@ -0,0 +1,22 @@
1
+ from .classification import (
2
+ Classification,
3
+ ClassificationObjectPrediction,
4
+ ClassificationPredictImageResponse,
5
+ ClassificationTraitRootPrediction,
6
+ )
7
+ from .client import Dragoneye
8
+ from .types.common import NormalizedBbox, TaxonID, TaxonPrediction, TaxonType
9
+ from .types.image import Image
10
+
11
+ __all__ = [
12
+ "Classification",
13
+ "ClassificationObjectPrediction",
14
+ "ClassificationPredictImageResponse",
15
+ "ClassificationTraitRootPrediction",
16
+ "Dragoneye",
17
+ "Image",
18
+ "NormalizedBbox",
19
+ "TaxonID",
20
+ "TaxonPrediction",
21
+ "TaxonType",
22
+ ]
@@ -0,0 +1,117 @@
1
+ import io
2
+ from typing import TYPE_CHECKING, BinaryIO, Sequence
3
+
4
+ import requests
5
+ from pydantic import BaseModel
6
+
7
+ from .types.common import BASE_API_URL, NormalizedBbox, TaxonID, TaxonPrediction
8
+ from .types.image import Image, assert_consistent_data_type
9
+
10
+ if TYPE_CHECKING:
11
+ from .client import Dragoneye
12
+
13
+
14
+ class ClassificationTraitRootPrediction(BaseModel):
15
+ id: TaxonID
16
+ name: str
17
+ displayName: str
18
+ taxons: Sequence[TaxonPrediction]
19
+
20
+
21
+ class ClassificationObjectPrediction(BaseModel):
22
+ normalizedBbox: NormalizedBbox
23
+ category: TaxonPrediction
24
+ traits: Sequence[ClassificationTraitRootPrediction]
25
+
26
+
27
+ class ClassificationPredictImageResponse(BaseModel):
28
+ predictions: Sequence[ClassificationObjectPrediction]
29
+
30
+
31
+ class ClassificationProductPrediction(BaseModel):
32
+ category: TaxonPrediction
33
+ traits: Sequence[ClassificationTraitRootPrediction]
34
+
35
+
36
+ class ClassificationPredictProductResponse(BaseModel):
37
+ predictions: Sequence[ClassificationProductPrediction]
38
+
39
+
40
+ class Classification:
41
+ def __init__(self, client: "Dragoneye"):
42
+ self._client = client
43
+
44
+ def predict(
45
+ self, image: Image, model_name: str
46
+ ) -> ClassificationPredictImageResponse:
47
+ url = f"{BASE_API_URL}/predict"
48
+
49
+ files = {}
50
+ data = {}
51
+
52
+ if image.file_or_bytes is not None:
53
+ if isinstance(image.file_or_bytes, bytes):
54
+ files["image_file"] = io.BytesIO(image.file_or_bytes)
55
+ elif isinstance(image.file_or_bytes, BinaryIO): # pyright: ignore [reportUnnecessaryIsInstance]
56
+ files["image_file"] = image.file_or_bytes
57
+ else:
58
+ raise ValueError("Invalid image type: Must be bytes or BinaryIO")
59
+ elif image.url is not None:
60
+ data["image_url"] = image.url
61
+ else:
62
+ raise ValueError(
63
+ "Missing image: Either image file or image url must be specified"
64
+ )
65
+
66
+ data["model_name"] = model_name
67
+
68
+ headers = {"Authorization": f"Bearer {self._client.api_key}"}
69
+
70
+ try:
71
+ response = requests.post(url, files=files, data=data, headers=headers)
72
+ response.raise_for_status()
73
+ except requests.RequestException as error:
74
+ raise Exception(
75
+ "Error during Dragoneye Classification prediction request:", error
76
+ )
77
+
78
+ return ClassificationPredictImageResponse.model_validate(response.json())
79
+
80
+ def predict_product(
81
+ self, images: Sequence[Image], model_name: str
82
+ ) -> ClassificationPredictProductResponse:
83
+ url = f"{BASE_API_URL}/predict-product"
84
+
85
+ files = []
86
+ data = {}
87
+
88
+ assert_consistent_data_type(images)
89
+
90
+ for image in images:
91
+ if image.file_or_bytes is not None:
92
+ if isinstance(image.file_or_bytes, bytes):
93
+ files.append(("image_file", io.BytesIO(image.file_or_bytes)))
94
+ elif (
95
+ isinstance(image.file_or_bytes, BinaryIO) # pyright: ignore [reportUnnecessaryIsInstance]
96
+ or issubclass(type(image.file_or_bytes), BinaryIO)
97
+ or isinstance(image.file_or_bytes, io.BufferedReader)
98
+ ):
99
+ files.append(("image_file", image.file_or_bytes))
100
+ else:
101
+ raise ValueError("Invalid image type: Must be bytes or BinaryIO")
102
+ elif image.url is not None:
103
+ data.setdefault("image_urls", []).append(image.url)
104
+
105
+ data["model_name"] = model_name
106
+
107
+ headers = {"Authorization": f"Bearer {self._client.api_key}"}
108
+
109
+ try:
110
+ response = requests.post(url, files=files, data=data, headers=headers)
111
+ response.raise_for_status()
112
+ except requests.RequestException as error:
113
+ raise Exception(
114
+ "Error during Dragoneye Classification prediction request:", error
115
+ )
116
+
117
+ return ClassificationPredictProductResponse.model_validate(response.json())
@@ -0,0 +1,18 @@
1
+ import os
2
+ from typing import Optional
3
+
4
+ from dragoneye.classification import Classification
5
+
6
+
7
+ class Dragoneye:
8
+ def __init__(self, api_key: Optional[str] = None):
9
+ if api_key is None:
10
+ api_key = os.getenv("DRAGONEYE_API_KEY")
11
+
12
+ assert (
13
+ api_key is not None
14
+ ), "API key is required - set the DRAGONEYE_API_KEY environment variable or pass it to the [Dragoneye] constructor"
15
+
16
+ self.api_key = api_key
17
+
18
+ self.classification = Classification(self)
File without changes
@@ -0,0 +1,26 @@
1
+ from enum import Enum
2
+ from typing import NewType, Optional, Sequence, Tuple
3
+
4
+ from pydantic import BaseModel
5
+
6
+ NormalizedBbox = NewType("NormalizedBbox", Tuple[float, float, float, float])
7
+
8
+
9
+ class TaxonType(str, Enum):
10
+ CATEGORY = ("category",)
11
+ TRAIT = ("trait",)
12
+
13
+
14
+ TaxonID = NewType("TaxonID", int)
15
+
16
+
17
+ class TaxonPrediction(BaseModel):
18
+ id: TaxonID
19
+ type: TaxonType
20
+ name: str
21
+ displayName: str
22
+ score: Optional[float]
23
+ children: Sequence["TaxonPrediction"]
24
+
25
+
26
+ BASE_API_URL = "https://api.dragoneye.ai"
@@ -0,0 +1,12 @@
1
+ from typing import BinaryIO, NamedTuple, Optional, Sequence, Union
2
+
3
+
4
+ class Image(NamedTuple):
5
+ file_or_bytes: Optional[Union[bytes, BinaryIO]] = None
6
+ url: Optional[str] = None
7
+
8
+
9
+ def assert_consistent_data_type(images: Sequence[Image]) -> None:
10
+ assert all(image.file_or_bytes is not None for image in images) ^ all(
11
+ image.url is not None for image in images
12
+ )
@@ -1,9 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- requirements.txt
5
- dragoneye_python.egg-info/PKG-INFO
6
- dragoneye_python.egg-info/SOURCES.txt
7
- dragoneye_python.egg-info/dependency_links.txt
8
- dragoneye_python.egg-info/requires.txt
9
- dragoneye_python.egg-info/top_level.txt
@@ -1,8 +0,0 @@
1
- [project]
2
- name = "dragoneye-python"
3
- version = "0.1.0"
4
- requires-python = ">=3.8"
5
- dynamic = ["dependencies"]
6
-
7
- [tool.setuptools.dynamic]
8
- dependencies = {file = ["requirements.txt"]}