struai 0.1.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

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.
struai/__init__.py CHANGED
@@ -17,50 +17,52 @@ Example:
17
17
  >>> results = project.search("W12x26 beam connections")
18
18
  """
19
19
 
20
- from ._version import __version__
21
- from ._client import StruAI, AsyncStruAI
20
+ from ._client import AsyncStruAI, StruAI
22
21
  from ._exceptions import (
23
- StruAIError,
24
22
  APIError,
25
23
  AuthenticationError,
26
- PermissionDeniedError,
24
+ ConnectionError,
25
+ InternalServerError,
26
+ JobFailedError,
27
27
  NotFoundError,
28
- ValidationError,
28
+ PermissionDeniedError,
29
29
  RateLimitError,
30
- InternalServerError,
30
+ StruAIError,
31
31
  TimeoutError,
32
- ConnectionError,
33
- JobFailedError,
32
+ ValidationError,
34
33
  )
34
+ from ._version import __version__
35
35
 
36
36
  # Re-export commonly used models
37
37
  from .models import (
38
- # Common
39
- Point,
38
+ Annotations,
40
39
  BBox,
41
- TextSpan,
40
+ DetailTag,
42
41
  Dimensions,
43
42
  # Tier 1 - Drawings
44
43
  DrawingResult,
45
- Annotations,
44
+ # Entities
45
+ Entity,
46
+ EntityListItem,
47
+ EntityRelation,
48
+ Fact,
49
+ JobStatus,
46
50
  Leader,
47
- SectionTag,
48
- DetailTag,
49
- RevisionTriangle,
50
- RevisionCloud,
51
- TitleBlock,
51
+ # Common
52
+ Point,
52
53
  # Tier 2 - Projects
53
54
  Project,
54
- Sheet,
55
- JobStatus,
56
- SheetResult,
55
+ QueryResponse,
56
+ RevisionCloud,
57
+ RevisionTriangle,
58
+ SearchHit,
57
59
  # Search
58
60
  SearchResponse,
59
- SearchHit,
60
- QueryResponse,
61
- # Entities
62
- Entity,
63
- Fact,
61
+ SectionTag,
62
+ Sheet,
63
+ SheetResult,
64
+ TextSpan,
65
+ TitleBlock,
64
66
  )
65
67
 
66
68
  __all__ = [
@@ -104,5 +106,7 @@ __all__ = [
104
106
  "SearchHit",
105
107
  "QueryResponse",
106
108
  "Entity",
109
+ "EntityListItem",
110
+ "EntityRelation",
107
111
  "Fact",
108
112
  ]
struai/_base.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Base HTTP client with retry logic."""
2
2
  import time
3
3
  from typing import Any, Dict, Optional, Type, TypeVar, Union
4
+ from urllib.parse import urlparse
4
5
 
5
6
  import httpx
6
7
  from pydantic import BaseModel
@@ -20,11 +21,24 @@ from ._version import __version__
20
21
 
21
22
  T = TypeVar("T", bound=BaseModel)
22
23
 
23
- DEFAULT_BASE_URL = "https://api.stru.ai/v1"
24
+ DEFAULT_BASE_URL = "https://api.stru.ai"
24
25
  DEFAULT_TIMEOUT = 60.0
25
26
  DEFAULT_MAX_RETRIES = 2
26
27
 
27
28
 
29
+ def _normalize_base_url(base_url: str) -> str:
30
+ trimmed = base_url.rstrip("/")
31
+ parsed = urlparse(trimmed)
32
+ if parsed.scheme and parsed.netloc:
33
+ path = parsed.path.rstrip("/")
34
+ if path in ("", "/"):
35
+ return f"{trimmed}/v1"
36
+ return trimmed
37
+ if trimmed.endswith("/v1"):
38
+ return trimmed
39
+ return f"{trimmed}/v1"
40
+
41
+
28
42
  class BaseClient:
29
43
  """Base HTTP client with retry logic."""
30
44
 
@@ -36,7 +50,7 @@ class BaseClient:
36
50
  max_retries: int = DEFAULT_MAX_RETRIES,
37
51
  ):
38
52
  self.api_key = api_key
39
- self.base_url = base_url.rstrip("/")
53
+ self.base_url = _normalize_base_url(base_url)
40
54
  self.timeout = timeout
41
55
  self.max_retries = max_retries
42
56
  self._client: Optional[httpx.Client] = None
@@ -195,7 +209,7 @@ class AsyncBaseClient:
195
209
  max_retries: int = DEFAULT_MAX_RETRIES,
196
210
  ):
197
211
  self.api_key = api_key
198
- self.base_url = base_url.rstrip("/")
212
+ self.base_url = _normalize_base_url(base_url)
199
213
  self.timeout = timeout
200
214
  self.max_retries = max_retries
201
215
  self._client: Optional[httpx.AsyncClient] = None
struai/_client.py CHANGED
@@ -1,9 +1,10 @@
1
1
  """Main StruAI client classes."""
2
+
2
3
  import os
3
4
  from functools import cached_property
4
5
  from typing import Optional
5
6
 
6
- from ._base import AsyncBaseClient, BaseClient, DEFAULT_BASE_URL, DEFAULT_TIMEOUT
7
+ from ._base import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, AsyncBaseClient, BaseClient
7
8
  from ._exceptions import StruAIError
8
9
  from .resources.drawings import AsyncDrawings, Drawings
9
10
  from .resources.projects import AsyncProjects, Projects
@@ -14,7 +15,8 @@ class StruAI(BaseClient):
14
15
 
15
16
  Args:
16
17
  api_key: Your API key. Falls back to STRUAI_API_KEY env var.
17
- base_url: API base URL. Defaults to windowseat internal endpoint.
18
+ base_url: API base URL. Defaults to https://api.stru.ai.
19
+ If no path is provided, /v1 is appended automatically.
18
20
  timeout: Request timeout in seconds. Default 60.
19
21
  max_retries: Max retry attempts for failed requests. Default 2.
20
22
 
struai/_version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """Version information."""
2
- __version__ = "0.1.0"
2
+ __version__ = "1.0.1"
struai/models/__init__.py CHANGED
@@ -10,7 +10,7 @@ from .drawings import (
10
10
  SectionTag,
11
11
  TitleBlock,
12
12
  )
13
- from .entities import Entity, EntityLocation, Fact
13
+ from .entities import Entity, EntityListItem, EntityLocation, EntityRelation, Fact
14
14
  from .projects import JobStatus, JobStep, Project, Sheet, SheetResult
15
15
  from .search import (
16
16
  EntitySummary,
@@ -55,6 +55,8 @@ __all__ = [
55
55
  "QuerySource",
56
56
  # Entities
57
57
  "Entity",
58
+ "EntityListItem",
58
59
  "EntityLocation",
60
+ "EntityRelation",
59
61
  "Fact",
60
62
  ]
struai/models/common.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Common types shared across models."""
2
- from typing import List, Tuple
2
+ from typing import Tuple
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
struai/models/entities.py CHANGED
@@ -3,27 +3,53 @@ from typing import List, Optional
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
+ from .common import BBox
7
+
6
8
 
7
9
  class EntityLocation(BaseModel):
8
10
  """Where an entity appears."""
9
11
 
10
12
  sheet_id: str
11
13
  sheet_title: Optional[str] = None
12
- page: int
14
+ page: Optional[int] = None
15
+
16
+
17
+ class EntityRelation(BaseModel):
18
+ """Relationship entry on an entity detail response."""
19
+
20
+ uuid: str
21
+ type: str
22
+ fact: str
23
+ source_id: Optional[str] = None
24
+ source_label: Optional[str] = None
25
+ target_id: Optional[str] = None
26
+ target_label: Optional[str] = None
13
27
 
14
28
 
15
29
  class Fact(BaseModel):
16
- """Relationship between entities."""
30
+ """Relationship between entities (list endpoint)."""
17
31
 
18
32
  id: str
33
+ type: str
19
34
  fact: str
20
- edge_type: str
21
35
  source_id: str
22
- source_label: Optional[str] = None
23
36
  target_id: str
37
+ source_label: Optional[str] = None
24
38
  target_label: Optional[str] = None
25
39
 
26
40
 
41
+ class EntityListItem(BaseModel):
42
+ """Entity summary from list endpoint."""
43
+
44
+ id: str
45
+ type: str
46
+ label: str
47
+ description: Optional[str] = None
48
+ sheet_id: Optional[str] = None
49
+ bbox: Optional[BBox] = None
50
+ attributes: Optional[str] = None
51
+
52
+
27
53
  class Entity(BaseModel):
28
54
  """Full entity with relationships."""
29
55
 
@@ -31,7 +57,6 @@ class Entity(BaseModel):
31
57
  type: str
32
58
  label: str
33
59
  description: Optional[str] = None
34
- group_id: str
35
- outgoing_facts: List[Fact] = []
36
- incoming_facts: List[Fact] = []
60
+ outgoing: List[EntityRelation] = []
61
+ incoming: List[EntityRelation] = []
37
62
  locations: List[EntityLocation] = []
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import TYPE_CHECKING, BinaryIO, Dict, List, Optional, Union
7
7
 
8
8
  from .._exceptions import JobFailedError, TimeoutError
9
- from ..models.entities import Entity, Fact
9
+ from ..models.entities import Entity, EntityListItem, Fact
10
10
  from ..models.projects import JobStatus, Project, Sheet, SheetResult
11
11
  from ..models.search import QueryResponse, SearchResponse
12
12
 
@@ -266,7 +266,7 @@ class Entities:
266
266
  sheet_id: Optional[str] = None,
267
267
  type: Optional[str] = None,
268
268
  limit: int = 100,
269
- ) -> List[Entity]:
269
+ ) -> List[EntityListItem]:
270
270
  """List entities in project."""
271
271
  params: Dict[str, Union[str, int]] = {"limit": limit}
272
272
  if sheet_id:
@@ -278,7 +278,7 @@ class Entities:
278
278
  f"/projects/{self._project_id}/entities",
279
279
  params=params,
280
280
  )
281
- return [Entity.model_validate(e) for e in response["entities"]]
281
+ return [EntityListItem.model_validate(e) for e in response["entities"]]
282
282
 
283
283
  def get(self, entity_id: str) -> Entity:
284
284
  """Get entity with all relationships."""
@@ -300,7 +300,7 @@ class AsyncEntities:
300
300
  sheet_id: Optional[str] = None,
301
301
  type: Optional[str] = None,
302
302
  limit: int = 100,
303
- ) -> List[Entity]:
303
+ ) -> List[EntityListItem]:
304
304
  """List entities in project."""
305
305
  params: Dict[str, Union[str, int]] = {"limit": limit}
306
306
  if sheet_id:
@@ -312,7 +312,7 @@ class AsyncEntities:
312
312
  f"/projects/{self._project_id}/entities",
313
313
  params=params,
314
314
  )
315
- return [Entity.model_validate(e) for e in response["entities"]]
315
+ return [EntityListItem.model_validate(e) for e in response["entities"]]
316
316
 
317
317
  async def get(self, entity_id: str) -> Entity:
318
318
  """Get entity with all relationships."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: struai
3
- Version: 0.1.0
3
+ Version: 1.0.1
4
4
  Summary: StruAI Drawing Analysis SDK - AI-powered construction drawing analysis
5
5
  Project-URL: Homepage, https://struai.com
6
6
  Project-URL: Documentation, https://docs.struai.com/python
@@ -46,6 +46,9 @@ pip install struai
46
46
  from struai import StruAI
47
47
 
48
48
  client = StruAI(api_key="sk-xxx") # or set STRUAI_API_KEY env var
49
+
50
+ # Optional: override base URL (http://localhost:8000 or http://localhost:8000/v1)
51
+ client = StruAI(api_key="sk-xxx", base_url="http://localhost:8000")
49
52
  ```
50
53
 
51
54
  ## Tier 1: Raw Detection ($0.02/page)
@@ -0,0 +1,18 @@
1
+ struai/__init__.py,sha256=3YTAjICfleWQh33O7y-yVqAJ8k9Pn2eU2epzrroKqt4,2274
2
+ struai/_base.py,sha256=JiAxWI1PMmIFVr9v_hBABra8m2fRkEvBp7d2Vc-5IAk,11208
3
+ struai/_client.py,sha256=WOcGC7LjaG469shx1YX2GCt0yJs-Ew-vOrxsqUmVmHM,3542
4
+ struai/_exceptions.py,sha256=GK0aVnOdkmcFaO16e0lzj9S2NmnTjQiy5H6nH6yDV24,2021
5
+ struai/_version.py,sha256=da8k-y4StnRTawPjG6KLx2cylvLgrpCSR6eZl8UgnKA,49
6
+ struai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ struai/models/__init__.py,sha256=qA_e6ZGG7Uhq9aLX2LeXlnUuV8VE6ur3OCFOPcj1oco,1220
8
+ struai/models/common.py,sha256=0Ox9PiuyRoyWH5vbSTfjm2nCWt_pblWH7kJso8nMVuE,592
9
+ struai/models/drawings.py,sha256=oTvN0ZID1TdUsR1BkJ-6M87nFbQwsYyePd0xoxKns9I,1634
10
+ struai/models/entities.py,sha256=4MHfhCn4g0n6d-6asASklbcmbJcIPg91rMBc5f56Zm8,1348
11
+ struai/models/projects.py,sha256=_VYinZrwWCmv3SQy7iZFPBtHcGsZe3XxI1i9SCk0EfY,1654
12
+ struai/models/search.py,sha256=UL3RYsAidS1zdV4lKiwNcHcAIjzyi8dgAQodowjzh58,1324
13
+ struai/resources/__init__.py,sha256=_JT_8OrvVcJ4yHmvZnsqYj4ej8pVg5lKSPKIcgQTCZk,183
14
+ struai/resources/drawings.py,sha256=b3C7CHS-p8CpGBtKl_dvLeUFDJ3g8HjlLBA4II0HsT4,3957
15
+ struai/resources/projects.py,sha256=_KRHO6ByBN_W-STlaAhVljLt9F3Yh9xzV5Piahc50FQ,19501
16
+ struai-1.0.1.dist-info/METADATA,sha256=pVsCT9ov986VJ3flFsJhCGwMIGrYlO_ITbdoPJ1_gV8,4460
17
+ struai-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ struai-1.0.1.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- struai/__init__.py,sha256=FsxjwfQOKDXzcnX8TqfWgTOqEbe-rW7zf8hwsqSMq0c,2190
2
- struai/_base.py,sha256=IPhrlgoqf7RZOh9xy2O6o9qMazEj0-RkI-dXPGzk76U,10795
3
- struai/_client.py,sha256=AmxmJ0ZSPlYlfqf4WHRaR_PQ1u5m3DCE59pLrf8j-7s,3483
4
- struai/_exceptions.py,sha256=GK0aVnOdkmcFaO16e0lzj9S2NmnTjQiy5H6nH6yDV24,2021
5
- struai/_version.py,sha256=PrqPeNLB_vK7o-1a2bBnGr1qOX4JyRedkdthES3yNbw,49
6
- struai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- struai/models/__init__.py,sha256=ddFoRRUY32xeHnjI0twEIRtXNh1huyWwggE1sY-y2e4,1144
8
- struai/models/common.py,sha256=qRehKPxDWPG34_LHhxu7T93Wqz1uRRQzIErQhraYAt4,598
9
- struai/models/drawings.py,sha256=oTvN0ZID1TdUsR1BkJ-6M87nFbQwsYyePd0xoxKns9I,1634
10
- struai/models/entities.py,sha256=dlQJqnY1GbVZOy74Rtt7J2JGxtUI7pWyifAO3whmNP0,753
11
- struai/models/projects.py,sha256=_VYinZrwWCmv3SQy7iZFPBtHcGsZe3XxI1i9SCk0EfY,1654
12
- struai/models/search.py,sha256=UL3RYsAidS1zdV4lKiwNcHcAIjzyi8dgAQodowjzh58,1324
13
- struai/resources/__init__.py,sha256=_JT_8OrvVcJ4yHmvZnsqYj4ej8pVg5lKSPKIcgQTCZk,183
14
- struai/resources/drawings.py,sha256=b3C7CHS-p8CpGBtKl_dvLeUFDJ3g8HjlLBA4II0HsT4,3957
15
- struai/resources/projects.py,sha256=3HMLbZmhcPQq51uTT3Y21Rx48PDKXF_-ESPDJYBd5tw,19453
16
- struai-0.1.0.dist-info/METADATA,sha256=hVEKAZoNtYqtqz_c95ouj7GOmY7jKPVvrfn2tYe3iS0,4309
17
- struai-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
- struai-0.1.0.dist-info/RECORD,,
File without changes