struai 0.1.0__tar.gz → 1.0.1__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.
Files changed (31) hide show
  1. struai-1.0.1/.github/workflows/release.yml +139 -0
  2. struai-1.0.1/.pre-commit-config.yaml +13 -0
  3. struai-1.0.1/CHANGELOG.md +15 -0
  4. {struai-0.1.0 → struai-1.0.1}/PKG-INFO +4 -1
  5. {struai-0.1.0 → struai-1.0.1}/README.md +3 -0
  6. {struai-0.1.0 → struai-1.0.1}/js/README.md +7 -0
  7. {struai-0.1.0 → struai-1.0.1}/js/package.json +1 -1
  8. {struai-0.1.0 → struai-1.0.1}/js/src/index.ts +130 -2
  9. {struai-0.1.0 → struai-1.0.1}/pyproject.toml +13 -1
  10. {struai-0.1.0 → struai-1.0.1}/src/struai/__init__.py +29 -25
  11. {struai-0.1.0 → struai-1.0.1}/src/struai/_base.py +17 -3
  12. {struai-0.1.0 → struai-1.0.1}/src/struai/_client.py +4 -2
  13. {struai-0.1.0 → struai-1.0.1}/src/struai/_version.py +1 -1
  14. {struai-0.1.0 → struai-1.0.1}/src/struai/models/__init__.py +3 -1
  15. {struai-0.1.0 → struai-1.0.1}/src/struai/models/common.py +1 -1
  16. struai-1.0.1/src/struai/models/entities.py +62 -0
  17. {struai-0.1.0 → struai-1.0.1}/src/struai/resources/projects.py +5 -5
  18. struai-0.1.0/.github/workflows/publish-npm.yml +0 -68
  19. struai-0.1.0/.github/workflows/publish-python.yml +0 -54
  20. struai-0.1.0/src/struai/models/entities.py +0 -37
  21. {struai-0.1.0 → struai-1.0.1}/.github/workflows/ci.yml +0 -0
  22. {struai-0.1.0 → struai-1.0.1}/.gitignore +0 -0
  23. {struai-0.1.0 → struai-1.0.1}/js/.gitignore +0 -0
  24. {struai-0.1.0 → struai-1.0.1}/js/tsconfig.json +0 -0
  25. {struai-0.1.0 → struai-1.0.1}/src/struai/_exceptions.py +0 -0
  26. {struai-0.1.0 → struai-1.0.1}/src/struai/models/drawings.py +0 -0
  27. {struai-0.1.0 → struai-1.0.1}/src/struai/models/projects.py +0 -0
  28. {struai-0.1.0 → struai-1.0.1}/src/struai/models/search.py +0 -0
  29. {struai-0.1.0 → struai-1.0.1}/src/struai/py.typed +0 -0
  30. {struai-0.1.0 → struai-1.0.1}/src/struai/resources/__init__.py +0 -0
  31. {struai-0.1.0 → struai-1.0.1}/src/struai/resources/drawings.py +0 -0
@@ -0,0 +1,139 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ publish_only:
7
+ description: 'Just publish current version (skip version bump)'
8
+ required: false
9
+ default: 'false'
10
+ type: choice
11
+ options:
12
+ - 'false'
13
+ - 'true'
14
+ push:
15
+ branches: [main]
16
+
17
+ permissions:
18
+ contents: write
19
+ id-token: write
20
+
21
+ jobs:
22
+ release:
23
+ runs-on: ubuntu-latest
24
+ concurrency: release
25
+ if: github.repository_owner == 'bhoshaga' && inputs.publish_only != 'true'
26
+ outputs:
27
+ released: ${{ steps.version.outputs.release }}
28
+ version: ${{ steps.version.outputs.version }}
29
+
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0
34
+ token: ${{ secrets.GITHUB_TOKEN }}
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@v5
38
+ with:
39
+ python-version: '3.11'
40
+
41
+ - name: Set up Node.js
42
+ uses: actions/setup-node@v4
43
+ with:
44
+ node-version: '20'
45
+
46
+ - name: Install python-semantic-release
47
+ run: pip install python-semantic-release
48
+
49
+ - name: Get next version
50
+ id: version
51
+ run: |
52
+ # Get the next version without making changes
53
+ NEXT_VERSION=$(semantic-release version --print 2>/dev/null || echo "")
54
+ if [ -z "$NEXT_VERSION" ]; then
55
+ echo "No release needed"
56
+ echo "release=false" >> $GITHUB_OUTPUT
57
+ else
58
+ echo "Next version: $NEXT_VERSION"
59
+ echo "release=true" >> $GITHUB_OUTPUT
60
+ echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT
61
+ fi
62
+
63
+ - name: Update npm package version
64
+ if: steps.version.outputs.release == 'true'
65
+ run: |
66
+ cd js
67
+ npm version ${{ steps.version.outputs.version }} --no-git-tag-version
68
+
69
+ - name: Python Semantic Release
70
+ if: steps.version.outputs.release == 'true'
71
+ env:
72
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73
+ run: |
74
+ git config user.name "github-actions[bot]"
75
+ git config user.email "github-actions[bot]@users.noreply.github.com"
76
+
77
+ # Add the npm package.json change
78
+ git add js/package.json
79
+
80
+ # Run semantic-release (will update Python version, commit, tag, and create release)
81
+ semantic-release version
82
+
83
+ # Push changes and tags
84
+ git push origin main --follow-tags
85
+
86
+ publish-python:
87
+ needs: release
88
+ if: always() && (needs.release.outputs.released == 'true' || inputs.publish_only == 'true')
89
+ runs-on: ubuntu-latest
90
+ environment: pypi
91
+ permissions:
92
+ id-token: write
93
+
94
+ steps:
95
+ - uses: actions/checkout@v4
96
+
97
+ - name: Set up Python
98
+ uses: actions/setup-python@v5
99
+ with:
100
+ python-version: '3.11'
101
+
102
+ - name: Install build tools
103
+ run: pip install build
104
+
105
+ - name: Build package
106
+ run: python -m build
107
+
108
+ - name: Publish to PyPI
109
+ uses: pypa/gh-action-pypi-publish@release/v1
110
+ with:
111
+ skip-existing: true
112
+
113
+ publish-npm:
114
+ needs: release
115
+ if: always() && (needs.release.outputs.released == 'true' || inputs.publish_only == 'true')
116
+ runs-on: ubuntu-latest
117
+ defaults:
118
+ run:
119
+ working-directory: js
120
+
121
+ steps:
122
+ - uses: actions/checkout@v4
123
+
124
+ - name: Set up Node.js
125
+ uses: actions/setup-node@v4
126
+ with:
127
+ node-version: '20'
128
+ registry-url: 'https://registry.npmjs.org'
129
+
130
+ - name: Install dependencies
131
+ run: npm install
132
+
133
+ - name: Build
134
+ run: npm run build
135
+
136
+ - name: Publish to npm
137
+ run: npm publish --access public
138
+ env:
139
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,13 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.9.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/compilerla/conventional-pre-commit
10
+ rev: v3.6.0
11
+ hooks:
12
+ - id: conventional-pre-commit
13
+ stages: [commit-msg]
@@ -0,0 +1,15 @@
1
+ # CHANGELOG
2
+
3
+ <!-- version list -->
4
+
5
+ ## v1.0.1 (2026-02-04)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **ci**: Include publish jobs directly in release workflow
10
+ ([`02436eb`](https://github.com/bhoshaga/struai/commit/02436eb55c63cbaa54eac8ca456a54d30125fada))
11
+
12
+
13
+ ## v1.0.0 (2026-02-04)
14
+
15
+ - Initial Release
@@ -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)
@@ -14,6 +14,9 @@ pip install struai
14
14
  from struai import StruAI
15
15
 
16
16
  client = StruAI(api_key="sk-xxx") # or set STRUAI_API_KEY env var
17
+
18
+ # Optional: override base URL (http://localhost:8000 or http://localhost:8000/v1)
19
+ client = StruAI(api_key="sk-xxx", base_url="http://localhost:8000")
17
20
  ```
18
21
 
19
22
  ## Tier 1: Raw Detection ($0.02/page)
@@ -16,6 +16,9 @@ npm install struai
16
16
  import { StruAI } from 'struai';
17
17
 
18
18
  const client = new StruAI({ apiKey: 'sk-xxx' });
19
+
20
+ // Optional: override base URL (http://localhost:8000 or http://localhost:8000/v1)
21
+ const local = new StruAI({ apiKey: 'sk-xxx', baseUrl: 'http://localhost:8000' });
19
22
  ```
20
23
 
21
24
  ## Tier 1: Raw Detection ($0.02/page)
@@ -59,6 +62,10 @@ for (const hit of results.results) {
59
62
  // Natural language query
60
63
  const answer = await project.query('What beams connect to column C3?');
61
64
  console.log(answer.answer);
65
+
66
+ // Browse entities
67
+ const entities = await project.entities.list({ type: 'Component', limit: 50 });
68
+ const entity = await project.entities.get('ent_abc123');
62
69
  ```
63
70
 
64
71
  ## License
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "struai",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "StruAI Drawing Analysis SDK - AI-powered construction drawing analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -163,7 +163,71 @@ export interface QueryResponse {
163
163
  confidence: number;
164
164
  }
165
165
 
166
- const DEFAULT_BASE_URL = 'https://api.stru.ai/v1';
166
+ export interface EntityLocation {
167
+ sheet_id: string;
168
+ sheet_title?: string;
169
+ page?: number | null;
170
+ }
171
+
172
+ export interface EntityRelation {
173
+ uuid: string;
174
+ type: string;
175
+ fact: string;
176
+ source_id?: string;
177
+ source_label?: string;
178
+ target_id?: string;
179
+ target_label?: string;
180
+ }
181
+
182
+ export interface Fact {
183
+ id: string;
184
+ type: string;
185
+ fact: string;
186
+ source_id: string;
187
+ target_id: string;
188
+ source_label?: string;
189
+ target_label?: string;
190
+ }
191
+
192
+ export interface EntityListItem {
193
+ id: string;
194
+ type: string;
195
+ label: string;
196
+ description?: string;
197
+ sheet_id?: string;
198
+ bbox?: [number, number, number, number];
199
+ attributes?: string;
200
+ }
201
+
202
+ export interface Entity {
203
+ id: string;
204
+ type: string;
205
+ label: string;
206
+ description?: string;
207
+ outgoing: EntityRelation[];
208
+ incoming: EntityRelation[];
209
+ locations: EntityLocation[];
210
+ }
211
+
212
+ const DEFAULT_BASE_URL = 'https://api.stru.ai';
213
+
214
+ function normalizeBaseUrl(raw: string): string {
215
+ const trimmed = raw.replace(/\/$/, '');
216
+ try {
217
+ const parsed = new URL(trimmed);
218
+ const path = parsed.pathname.replace(/\/$/, '');
219
+ if (path === '' || path === '/') {
220
+ parsed.pathname = '/v1';
221
+ return parsed.toString().replace(/\/$/, '');
222
+ }
223
+ return trimmed;
224
+ } catch {
225
+ if (trimmed.endsWith('/v1')) {
226
+ return trimmed;
227
+ }
228
+ return `${trimmed}/v1`;
229
+ }
230
+ }
167
231
 
168
232
  class APIError extends Error {
169
233
  constructor(
@@ -284,14 +348,78 @@ class Sheets {
284
348
  }
285
349
  }
286
350
 
351
+ class Entities {
352
+ constructor(
353
+ private client: StruAI,
354
+ private projectId: string
355
+ ) {}
356
+
357
+ async list(options?: {
358
+ sheetId?: string;
359
+ type?: string;
360
+ limit?: number;
361
+ }): Promise<EntityListItem[]> {
362
+ const params = new URLSearchParams();
363
+ if (options?.limit) params.set('limit', options.limit.toString());
364
+ if (options?.sheetId) params.set('sheet_id', options.sheetId);
365
+ if (options?.type) params.set('type', options.type);
366
+ const query = params.toString();
367
+
368
+ const response = await this.client.request<{ entities: EntityListItem[] }>(
369
+ query
370
+ ? `/projects/${this.projectId}/entities?${query}`
371
+ : `/projects/${this.projectId}/entities`
372
+ );
373
+ return response.entities;
374
+ }
375
+
376
+ async get(entityId: string): Promise<Entity> {
377
+ return this.client.request<Entity>(
378
+ `/projects/${this.projectId}/entities/${entityId}`
379
+ );
380
+ }
381
+ }
382
+
383
+ class Relationships {
384
+ constructor(
385
+ private client: StruAI,
386
+ private projectId: string
387
+ ) {}
388
+
389
+ async list(options?: {
390
+ sourceId?: string;
391
+ targetId?: string;
392
+ type?: string;
393
+ limit?: number;
394
+ }): Promise<Fact[]> {
395
+ const params = new URLSearchParams();
396
+ if (options?.limit) params.set('limit', options.limit.toString());
397
+ if (options?.sourceId) params.set('source_id', options.sourceId);
398
+ if (options?.targetId) params.set('target_id', options.targetId);
399
+ if (options?.type) params.set('type', options.type);
400
+ const query = params.toString();
401
+
402
+ const response = await this.client.request<{ relationships: Fact[] }>(
403
+ query
404
+ ? `/projects/${this.projectId}/relationships?${query}`
405
+ : `/projects/${this.projectId}/relationships`
406
+ );
407
+ return response.relationships;
408
+ }
409
+ }
410
+
287
411
  class ProjectInstance {
288
412
  public readonly sheets: Sheets;
413
+ public readonly entities: Entities;
414
+ public readonly relationships: Relationships;
289
415
 
290
416
  constructor(
291
417
  private client: StruAI,
292
418
  private project: Project
293
419
  ) {
294
420
  this.sheets = new Sheets(client, project.id);
421
+ this.entities = new Entities(client, project.id);
422
+ this.relationships = new Relationships(client, project.id);
295
423
  }
296
424
 
297
425
  get id() {
@@ -378,7 +506,7 @@ export class StruAI {
378
506
 
379
507
  constructor(options: StruAIOptions) {
380
508
  this.apiKey = options.apiKey;
381
- this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
509
+ this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
382
510
  this.timeout = options.timeout ?? 60000;
383
511
 
384
512
  this.drawings = new Drawings(this);
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "struai"
7
- version = "0.1.0"
7
+ version = "1.0.1"
8
8
  description = "StruAI Drawing Analysis SDK - AI-powered construction drawing analysis"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -72,3 +72,15 @@ warn_unused_ignores = true
72
72
 
73
73
  [tool.pytest.ini_options]
74
74
  asyncio_mode = "auto"
75
+
76
+ [tool.semantic_release]
77
+ version_toml = ["pyproject.toml:project.version"]
78
+ version_variables = ["src/struai/_version.py:__version__"]
79
+ branch = "main"
80
+ build_command = "pip install build && python -m build"
81
+ commit_message = "chore(release): {version}\n\nAutomatically generated by python-semantic-release"
82
+
83
+ [tool.semantic_release.commit_parser_options]
84
+ allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"]
85
+ minor_tags = ["feat"]
86
+ patch_tags = ["fix", "perf"]
@@ -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
  ]
@@ -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
@@ -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
 
@@ -1,2 +1,2 @@
1
1
  """Version information."""
2
- __version__ = "0.1.0"
2
+ __version__ = "1.0.1"
@@ -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
  ]
@@ -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
 
@@ -0,0 +1,62 @@
1
+ """Entity and Relationship models."""
2
+ from typing import List, Optional
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from .common import BBox
7
+
8
+
9
+ class EntityLocation(BaseModel):
10
+ """Where an entity appears."""
11
+
12
+ sheet_id: str
13
+ sheet_title: Optional[str] = None
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
27
+
28
+
29
+ class Fact(BaseModel):
30
+ """Relationship between entities (list endpoint)."""
31
+
32
+ id: str
33
+ type: str
34
+ fact: str
35
+ source_id: str
36
+ target_id: str
37
+ source_label: Optional[str] = None
38
+ target_label: Optional[str] = None
39
+
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
+
53
+ class Entity(BaseModel):
54
+ """Full entity with relationships."""
55
+
56
+ id: str
57
+ type: str
58
+ label: str
59
+ description: Optional[str] = None
60
+ outgoing: List[EntityRelation] = []
61
+ incoming: List[EntityRelation] = []
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,68 +0,0 @@
1
- name: Publish npm Package
2
-
3
- on:
4
- workflow_dispatch:
5
- push:
6
- branches: [main]
7
- paths:
8
- - 'js/**'
9
- - '.github/workflows/publish-npm.yml'
10
- release:
11
- types: [published]
12
-
13
- jobs:
14
- build:
15
- runs-on: ubuntu-latest
16
- defaults:
17
- run:
18
- working-directory: js
19
- steps:
20
- - uses: actions/checkout@v4
21
-
22
- - name: Set up Node.js
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: '20'
26
- registry-url: 'https://registry.npmjs.org'
27
-
28
- - name: Install dependencies
29
- run: npm install
30
-
31
- - name: Build
32
- run: npm run build
33
-
34
- - name: Upload artifacts
35
- uses: actions/upload-artifact@v4
36
- with:
37
- name: npm-dist
38
- path: js/dist/
39
-
40
- publish:
41
- needs: build
42
- runs-on: ubuntu-latest
43
- if: github.event_name == 'release' || github.ref == 'refs/heads/main'
44
- defaults:
45
- run:
46
- working-directory: js
47
- steps:
48
- - uses: actions/checkout@v4
49
-
50
- - name: Set up Node.js
51
- uses: actions/setup-node@v4
52
- with:
53
- node-version: '20'
54
- registry-url: 'https://registry.npmjs.org'
55
-
56
- - name: Install dependencies
57
- run: npm install
58
-
59
- - name: Download artifacts
60
- uses: actions/download-artifact@v4
61
- with:
62
- name: npm-dist
63
- path: js/dist/
64
-
65
- - name: Publish to npm
66
- run: npm publish --access public
67
- env:
68
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,54 +0,0 @@
1
- name: Publish Python Package
2
-
3
- on:
4
- workflow_dispatch:
5
- push:
6
- branches: [main]
7
- paths:
8
- - 'src/**'
9
- - 'pyproject.toml'
10
- - '.github/workflows/publish-python.yml'
11
- release:
12
- types: [published]
13
-
14
- jobs:
15
- build:
16
- runs-on: ubuntu-latest
17
- steps:
18
- - uses: actions/checkout@v4
19
-
20
- - name: Set up Python
21
- uses: actions/setup-python@v5
22
- with:
23
- python-version: '3.11'
24
-
25
- - name: Install build tools
26
- run: pip install build twine
27
-
28
- - name: Build package
29
- run: python -m build
30
-
31
- - name: Upload artifacts
32
- uses: actions/upload-artifact@v4
33
- with:
34
- name: python-dist
35
- path: dist/
36
-
37
- publish:
38
- needs: build
39
- runs-on: ubuntu-latest
40
- if: github.event_name == 'release' || github.ref == 'refs/heads/main'
41
- environment: pypi
42
- permissions:
43
- id-token: write
44
- steps:
45
- - name: Download artifacts
46
- uses: actions/download-artifact@v4
47
- with:
48
- name: python-dist
49
- path: dist/
50
-
51
- - name: Publish to PyPI
52
- uses: pypa/gh-action-pypi-publish@release/v1
53
- with:
54
- skip-existing: true
@@ -1,37 +0,0 @@
1
- """Entity and Relationship models."""
2
- from typing import List, Optional
3
-
4
- from pydantic import BaseModel
5
-
6
-
7
- class EntityLocation(BaseModel):
8
- """Where an entity appears."""
9
-
10
- sheet_id: str
11
- sheet_title: Optional[str] = None
12
- page: int
13
-
14
-
15
- class Fact(BaseModel):
16
- """Relationship between entities."""
17
-
18
- id: str
19
- fact: str
20
- edge_type: str
21
- source_id: str
22
- source_label: Optional[str] = None
23
- target_id: str
24
- target_label: Optional[str] = None
25
-
26
-
27
- class Entity(BaseModel):
28
- """Full entity with relationships."""
29
-
30
- id: str
31
- type: str
32
- label: str
33
- description: Optional[str] = None
34
- group_id: str
35
- outgoing_facts: List[Fact] = []
36
- incoming_facts: List[Fact] = []
37
- locations: List[EntityLocation] = []
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes