inferencesh 0.1.10__tar.gz → 0.1.12__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.

Potentially problematic release.


This version of inferencesh might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: inferencesh
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: inference.sh Python SDK
5
5
  Author: Inference Shell Inc.
6
6
  Author-email: "Inference Shell Inc." <hello@inference.sh>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inferencesh"
7
- version = "0.1.10"
7
+ version = "0.1.12"
8
8
  description = "inference.sh Python SDK"
9
9
  authors = [
10
10
  {name = "Inference Shell Inc.", email = "hello@inference.sh"},
@@ -0,0 +1,206 @@
1
+ from typing import Optional, Union, ClassVar
2
+ from pydantic import BaseModel, ConfigDict
3
+ import mimetypes
4
+ import os
5
+ import urllib.request
6
+ import urllib.parse
7
+ import tempfile
8
+
9
+ from typing import Any, Dict, List
10
+ import inspect
11
+ import ast
12
+ import textwrap
13
+ from collections import OrderedDict
14
+
15
+
16
+ # inspired by https://github.com/pydantic/pydantic/issues/7580
17
+ class OrderedSchemaModel(BaseModel):
18
+ """A base model that ensures the JSON schema properties and required fields are in the order of field definition."""
19
+
20
+ @classmethod
21
+ def model_json_schema(cls, by_alias: bool = True, **kwargs: Any) -> Dict[str, Any]:
22
+ schema = super().model_json_schema(by_alias=by_alias, **kwargs)
23
+
24
+ field_order = cls._get_field_order()
25
+
26
+ if field_order:
27
+ # Order properties
28
+ ordered_properties = OrderedDict()
29
+ for field_name in field_order:
30
+ if field_name in schema['properties']:
31
+ ordered_properties[field_name] = schema['properties'][field_name]
32
+
33
+ # Add any remaining properties that weren't in field_order
34
+ for field_name, field_schema in schema['properties'].items():
35
+ if field_name not in ordered_properties:
36
+ ordered_properties[field_name] = field_schema
37
+
38
+ schema['properties'] = ordered_properties
39
+
40
+ # Order required fields
41
+ if 'required' in schema:
42
+ ordered_required = [field for field in field_order if field in schema['required']]
43
+ # Add any remaining required fields that weren't in field_order
44
+ ordered_required.extend([field for field in schema['required'] if field not in ordered_required])
45
+ schema['required'] = ordered_required
46
+
47
+ return schema
48
+
49
+ @classmethod
50
+ def _get_field_order(cls) -> List[str]:
51
+ """Get the order of fields as they were defined in the class."""
52
+ source = inspect.getsource(cls)
53
+
54
+ # Unindent the entire source code
55
+ source = textwrap.dedent(source)
56
+
57
+ try:
58
+ module = ast.parse(source)
59
+ except IndentationError:
60
+ # If we still get an IndentationError, wrap the class in a dummy module
61
+ source = f"class DummyModule:\n{textwrap.indent(source, ' ')}"
62
+ module = ast.parse(source)
63
+ # Adjust to look at the first class def inside DummyModule
64
+ # noinspection PyUnresolvedReferences
65
+ class_def = module.body[0].body[0]
66
+ else:
67
+ # Find the class definition
68
+ class_def = next(
69
+ node for node in module.body if isinstance(node, ast.ClassDef) and node.name == cls.__name__
70
+ )
71
+
72
+ # Extract field names in the order they were defined
73
+ field_order = []
74
+ for node in class_def.body:
75
+ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
76
+ field_order.append(node.target.id)
77
+
78
+ return field_order
79
+
80
+ class BaseAppInput(OrderedSchemaModel):
81
+ pass
82
+
83
+ class BaseAppOutput(OrderedSchemaModel):
84
+ pass
85
+
86
+ class BaseApp(BaseModel):
87
+ model_config = ConfigDict(
88
+ arbitrary_types_allowed=True,
89
+ extra='allow'
90
+ )
91
+
92
+ async def setup(self):
93
+ pass
94
+
95
+ async def run(self, app_input: BaseAppInput) -> BaseAppOutput:
96
+ raise NotImplementedError("run method must be implemented")
97
+
98
+ async def unload(self):
99
+ pass
100
+
101
+
102
+ class File(BaseModel):
103
+ """A class representing a file in the inference.sh ecosystem."""
104
+ path: str # Absolute path to the file or URL
105
+ mime_type: Optional[str] = None # MIME type of the file
106
+ size: Optional[int] = None # File size in bytes
107
+ filename: Optional[str] = None # Original filename if available
108
+ _tmp_path: Optional[str] = None # Internal storage for temporary file path
109
+
110
+ def __init__(self, **data):
111
+ super().__init__(**data)
112
+ if self._is_url(self.path):
113
+ self._download_url()
114
+ elif not os.path.isabs(self.path):
115
+ self.path = os.path.abspath(self.path)
116
+ self._populate_metadata()
117
+
118
+ def _is_url(self, path: str) -> bool:
119
+ """Check if the path is a URL."""
120
+ parsed = urllib.parse.urlparse(path)
121
+ return parsed.scheme in ('http', 'https')
122
+ def _download_url(self) -> None:
123
+ """Download the URL to a temporary file and update the path."""
124
+ original_url = self.path
125
+ tmp_file = None
126
+ try:
127
+ # Create a temporary file with a suffix based on the URL path
128
+ suffix = os.path.splitext(urllib.parse.urlparse(original_url).path)[1]
129
+ tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
130
+ self._tmp_path = tmp_file.name
131
+
132
+ # Set up request with user agent
133
+ headers = {
134
+ 'User-Agent': (
135
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
136
+ 'AppleWebKit/537.36 (KHTML, like Gecko) '
137
+ 'Chrome/91.0.4472.124 Safari/537.36'
138
+ )
139
+ }
140
+ req = urllib.request.Request(original_url, headers=headers)
141
+
142
+ # Download the file
143
+ print(f"Downloading URL: {original_url} to {self._tmp_path}")
144
+ try:
145
+ with urllib.request.urlopen(req) as response, open(self._tmp_path, 'wb') as out_file:
146
+ out_file.write(response.read())
147
+ self.path = self._tmp_path
148
+ except (urllib.error.URLError, urllib.error.HTTPError) as e:
149
+ raise RuntimeError(f"Failed to download URL {original_url}: {str(e)}")
150
+ except IOError as e:
151
+ raise RuntimeError(f"Failed to write downloaded file to {self._tmp_path}: {str(e)}")
152
+ except Exception as e:
153
+ # Clean up temp file if something went wrong
154
+ if tmp_file is not None and hasattr(self, '_tmp_path'):
155
+ try:
156
+ os.unlink(self._tmp_path)
157
+ except:
158
+ pass
159
+ raise RuntimeError(f"Error downloading URL {original_url}: {str(e)}")
160
+
161
+ def __del__(self):
162
+ """Cleanup temporary file if it exists."""
163
+ if hasattr(self, '_tmp_path') and self._tmp_path:
164
+ try:
165
+ os.unlink(self._tmp_path)
166
+ except:
167
+ pass
168
+
169
+ def _populate_metadata(self) -> None:
170
+ """Populate file metadata from the path if it exists."""
171
+ if os.path.exists(self.path):
172
+ if not self.mime_type:
173
+ self.mime_type = self._guess_mime_type()
174
+ if not self.size:
175
+ self.size = self._get_file_size()
176
+ if not self.filename:
177
+ self.filename = self._get_filename()
178
+
179
+ @classmethod
180
+ def from_path(cls, path: Union[str, os.PathLike]) -> 'File':
181
+ """Create a File instance from a file path."""
182
+ return cls(path=str(path))
183
+
184
+ def _guess_mime_type(self) -> Optional[str]:
185
+ """Guess the MIME type of the file."""
186
+ return mimetypes.guess_type(self.path)[0]
187
+
188
+ def _get_file_size(self) -> int:
189
+ """Get the size of the file in bytes."""
190
+ return os.path.getsize(self.path)
191
+
192
+ def _get_filename(self) -> str:
193
+ """Get the base filename from the path."""
194
+ return os.path.basename(self.path)
195
+
196
+ def exists(self) -> bool:
197
+ """Check if the file exists."""
198
+ return os.path.exists(self.path)
199
+
200
+ def refresh_metadata(self) -> None:
201
+ """Refresh all metadata from the file."""
202
+ self._populate_metadata()
203
+
204
+ class Config:
205
+ """Pydantic config"""
206
+ arbitrary_types_allowed = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: inferencesh
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: inference.sh Python SDK
5
5
  Author: Inference Shell Inc.
6
6
  Author-email: "Inference Shell Inc." <hello@inference.sh>
@@ -1,121 +0,0 @@
1
- from typing import Optional, Union, ClassVar
2
- from pydantic import BaseModel, ConfigDict
3
- import mimetypes
4
- import os
5
- import urllib.request
6
- import urllib.parse
7
- import tempfile
8
-
9
- class BaseAppInput(BaseModel):
10
- pass
11
-
12
- class BaseAppOutput(BaseModel):
13
- pass
14
-
15
- class BaseApp(BaseModel):
16
- model_config = ConfigDict(
17
- arbitrary_types_allowed=True,
18
- extra='allow'
19
- )
20
-
21
- async def setup(self):
22
- pass
23
-
24
- async def run(self, app_input: BaseAppInput) -> BaseAppOutput:
25
- raise NotImplementedError("run method must be implemented")
26
-
27
- async def unload(self):
28
- pass
29
-
30
-
31
- class File(BaseModel):
32
- """A class representing a file in the inference.sh ecosystem."""
33
- path: str # Absolute path to the file or URL
34
- mime_type: Optional[str] = None # MIME type of the file
35
- size: Optional[int] = None # File size in bytes
36
- filename: Optional[str] = None # Original filename if available
37
- _tmp_path: Optional[str] = None # Internal storage for temporary file path
38
-
39
- def __init__(self, **data):
40
- super().__init__(**data)
41
- if self._is_url(self.path):
42
- self._download_url()
43
- elif not os.path.isabs(self.path):
44
- self.path = os.path.abspath(self.path)
45
- self._populate_metadata()
46
-
47
- def _is_url(self, path: str) -> bool:
48
- """Check if the path is a URL."""
49
- parsed = urllib.parse.urlparse(path)
50
- return parsed.scheme in ('http', 'https')
51
-
52
- def _download_url(self) -> None:
53
- """Download the URL to a temporary file and update the path."""
54
- original_url = self.path
55
- # Create a temporary file with a suffix based on the URL path
56
- suffix = os.path.splitext(urllib.parse.urlparse(original_url).path)[1]
57
- tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
58
- self._tmp_path = tmp_file.name
59
-
60
- # Set up request with user agent
61
- headers = {
62
- 'User-Agent': (
63
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
64
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
65
- 'Chrome/91.0.4472.124 Safari/537.36'
66
- )
67
- }
68
- req = urllib.request.Request(original_url, headers=headers)
69
-
70
- # Download the file
71
- print(f"Downloading URL: {original_url} to {self._tmp_path}")
72
- with urllib.request.urlopen(req) as response, open(self._tmp_path, 'wb') as out_file:
73
- out_file.write(response.read())
74
- self.path = self._tmp_path
75
-
76
- def __del__(self):
77
- """Cleanup temporary file if it exists."""
78
- if hasattr(self, '_tmp_path') and self._tmp_path:
79
- try:
80
- os.unlink(self._tmp_path)
81
- except:
82
- pass
83
-
84
- def _populate_metadata(self) -> None:
85
- """Populate file metadata from the path if it exists."""
86
- if os.path.exists(self.path):
87
- if not self.mime_type:
88
- self.mime_type = self._guess_mime_type()
89
- if not self.size:
90
- self.size = self._get_file_size()
91
- if not self.filename:
92
- self.filename = self._get_filename()
93
-
94
- @classmethod
95
- def from_path(cls, path: Union[str, os.PathLike]) -> 'File':
96
- """Create a File instance from a file path."""
97
- return cls(path=str(path))
98
-
99
- def _guess_mime_type(self) -> Optional[str]:
100
- """Guess the MIME type of the file."""
101
- return mimetypes.guess_type(self.path)[0]
102
-
103
- def _get_file_size(self) -> int:
104
- """Get the size of the file in bytes."""
105
- return os.path.getsize(self.path)
106
-
107
- def _get_filename(self) -> str:
108
- """Get the base filename from the path."""
109
- return os.path.basename(self.path)
110
-
111
- def exists(self) -> bool:
112
- """Check if the file exists."""
113
- return os.path.exists(self.path)
114
-
115
- def refresh_metadata(self) -> None:
116
- """Refresh all metadata from the file."""
117
- self._populate_metadata()
118
-
119
- class Config:
120
- """Pydantic config"""
121
- arbitrary_types_allowed = True
File without changes
File without changes
File without changes
File without changes