lr-gladiator 0.3.0__tar.gz → 0.5.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.

Potentially problematic release.


This version of lr-gladiator might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lr-gladiator
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: CLI and Python client for Arena PLM (app.bom.com): login, get revisions, list/download attachments, and upload to working revisions.
5
5
  Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lr-gladiator"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "CLI and Python client for Arena PLM (app.bom.com): login, get revisions, list/download attachments, and upload to working revisions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -185,7 +185,45 @@ class ArenaClient:
185
185
  edition=edition,
186
186
  )
187
187
 
188
- # ---------- API-mode (HTTP) ----------
188
+ def get_bom(self, item_number: str, revision: Optional[str] = None) -> List[Dict]:
189
+ """
190
+ Return a normalized list of BOM lines for the given item.
191
+
192
+ By default this fetches the EFFECTIVE (approved) revision's BOM.
193
+ Use revision="WORKING" or a specific label (e.g., "B2") to override.
194
+ """
195
+ # 1) Resolve the exact revision GUID we want the BOM for
196
+ target_guid = self._api_resolve_revision_guid(item_number, revision or "EFFECTIVE")
197
+
198
+ # 2) GET /items/{guid}/bom
199
+ url = f"{self._api_base()}/items/{target_guid}/bom"
200
+ self._log(f"GET {url}")
201
+ r = self.session.get(url)
202
+ r.raise_for_status()
203
+ data = self._ensure_json(r)
204
+
205
+ rows = data.get("results", data if isinstance(data, list) else [])
206
+ norm: List[Dict] = []
207
+ for row in rows:
208
+ itm = row.get("item", {}) if isinstance(row, dict) else {}
209
+ norm.append({
210
+ # association/line
211
+ "guid": row.get("guid"),
212
+ "lineNumber": row.get("lineNumber"),
213
+ "notes": row.get("notes"),
214
+ "quantity": row.get("quantity"),
215
+ "refDes": row.get("refDes") or row.get("referenceDesignators") or "",
216
+ # child item
217
+ "itemGuid": itm.get("guid") or itm.get("id"),
218
+ "itemNumber": itm.get("number"),
219
+ "itemName": itm.get("name"),
220
+ "itemRevision": itm.get("revisionNumber"),
221
+ "itemRevisionStatus": itm.get("revisionStatus"),
222
+ "itemUrl": (itm.get("url") or {}).get("api"),
223
+ "itemAppUrl": (itm.get("url") or {}).get("app"),
224
+ })
225
+ return norm
226
+
189
227
  def _api_base(self) -> str:
190
228
  return self.cfg.base_url.rstrip("/")
191
229
 
@@ -242,7 +280,7 @@ class ArenaClient:
242
280
  file_guid = f.get("guid") or f.get("id")
243
281
  norm.append({
244
282
  "id": row.get("guid") or row.get("id"), # association id
245
- "fileGuid": file_guid, # actual file id
283
+ "fileGuid": file_guid, # actual file id
246
284
  "name": f.get("name") or f.get("title"),
247
285
  "filename": f.get("name") or f.get("title"),
248
286
  "size": f.get("size"),
@@ -287,7 +325,7 @@ class ArenaClient:
287
325
  # Prefer the one not superseded
288
326
  eff = [rv for rv in revs if (rv.get("revisionStatus") or "").upper() == "EFFECTIVE" or rv.get("status") == 1]
289
327
  if not eff:
290
- raise ArenaError("No approved/effective revision exists for this item.")
328
+ raise ArenaError("No approved/effective revision exists for this item. Try using revision 'WORKING'.")
291
329
  current = next((rv for rv in eff if not rv.get("supersededDateTime")), eff[-1])
292
330
  return current.get("guid")
293
331
 
@@ -431,14 +469,11 @@ class ArenaClient:
431
469
  title = title or file_path.stem
432
470
  file_format = file_format or (file_path.suffix[1:].lower() if file_path.suffix else "bin")
433
471
  description = description or "Uploaded via gladiator"
472
+ files = {"content": (file_path.name, open(file_path, "rb"), "application/octet-stream")}
434
473
 
435
- files = {
436
- "content": (file_path.name, open(file_path, "rb"), "application/octet-stream"),
437
- }
438
474
  # NOTE: nested field names are sent in `data`, not `files`
439
475
  data_form = {
440
476
  "file.title": title,
441
- "file.name": filename,
442
477
  "file.description": description,
443
478
  "file.category.guid": cat_guid,
444
479
  "file.format": file_format,
@@ -83,6 +83,9 @@ def latest_approved(
83
83
  sys.stdout.write("\n")
84
84
  else:
85
85
  print(rev)
86
+ except requests.HTTPError as e:
87
+ typer.secho(f"Arena request failed: {e}", fg=typer.colors.RED, err=True)
88
+ raise typer.Exit(2)
86
89
  except ArenaError as e:
87
90
  typer.secho(str(e), fg=typer.colors.RED, err=True)
88
91
  raise typer.Exit(2)
@@ -108,10 +111,48 @@ def list_files(
108
111
  for f in files:
109
112
  table.add_row(str(f.get("filename")), str(f.get("size")), str(f.get("checksum")))
110
113
  print(table)
114
+ except requests.HTTPError as e:
115
+ typer.secho(f"Arena request failed: {e}", fg=typer.colors.RED, err=True)
116
+ raise typer.Exit(2)
111
117
  except ArenaError as e:
112
118
  typer.secho(str(e), fg=typer.colors.RED, err=True)
113
119
  raise typer.Exit(2)
114
120
 
121
+ @app.command("bom")
122
+ def bom(
123
+ item: str = typer.Argument(..., help="Item/article number (e.g., 890-1001)"),
124
+ revision: Optional[str] = typer.Option(None, "--rev", help='Revision selector: WORKING, EFFECTIVE (default), or label (e.g., "B2")'),
125
+ output: str = typer.Option("table", "--output", help='Output format: "table" (default) or "json"'),
126
+ ):
127
+ """List the BOM lines for an item revision."""
128
+ try:
129
+ lines = _client().get_bom(item, revision)
130
+ if output.lower() == "json":
131
+ print(json.dumps({"count": len(lines), "results": lines}, indent=2))
132
+ return
133
+
134
+ title_rev = revision or "(latest approved)"
135
+ table = Table(title=f"BOM for {item} rev {title_rev}")
136
+ table.add_column("Line", justify="right")
137
+ table.add_column("Qty", justify="right")
138
+ table.add_column("Number")
139
+ table.add_column("Name")
140
+ table.add_column("RefDes")
141
+ for ln in lines:
142
+ table.add_row(
143
+ str(ln.get("lineNumber") or ""),
144
+ str(ln.get("quantity") or ""),
145
+ str(ln.get("itemNumber") or ""),
146
+ str(ln.get("itemName") or ""),
147
+ str(ln.get("refDes") or ""),
148
+ )
149
+ print(table)
150
+ except requests.HTTPError as e:
151
+ typer.secho(f"Arena request failed: {e}", fg=typer.colors.RED, err=True)
152
+ raise typer.Exit(2)
153
+ except ArenaError as e:
154
+ typer.secho(str(e), fg=typer.colors.RED, err=True)
155
+ raise typer.Exit(2)
115
156
 
116
157
  @app.command("get-files")
117
158
  def get_files(
@@ -123,6 +164,9 @@ def get_files(
123
164
  paths = _client().download_files(item, revision, out_dir=out)
124
165
  for p in paths:
125
166
  print(str(p))
167
+ except requests.HTTPError as e:
168
+ typer.secho(f"Arena request failed: {e}", fg=typer.colors.RED, err=True)
169
+ raise typer.Exit(2)
126
170
  except ArenaError as e:
127
171
  typer.secho(str(e), fg=typer.colors.RED, err=True)
128
172
  raise typer.Exit(2)
@@ -137,7 +181,7 @@ def upload_file(
137
181
  category: str = typer.Option("CAD Data", "--category", help='File category name (default: "CAD Data")'),
138
182
  file_format: Optional[str] = typer.Option(None, "--format", help="File format (default: file extension)"),
139
183
  description: Optional[str] = typer.Option(None, "--desc", help="Optional description"),
140
- primary: bool = typer.Option(True, "--primary/--no-primary", help="Mark association as primary"),
184
+ primary: bool = typer.Option(False, "--primary/--no-primary", help="Mark association as primary"),
141
185
  edition: str = typer.Option("1", "--edition", help="Edition number when creating a new association (default: 1)"),
142
186
  ):
143
187
  """If a file with the same filename exists: update its content (new edition).
@@ -149,6 +193,9 @@ def upload_file(
149
193
  description=description, primary=primary, edition=edition
150
194
  )
151
195
  print(json.dumps(result, indent=2))
196
+ except requests.HTTPError as e:
197
+ typer.secho(f"Arena request failed: {e}", fg=typer.colors.RED, err=True)
198
+ raise typer.Exit(2)
152
199
  except ArenaError as e:
153
200
  typer.secho(str(e), fg=typer.colors.RED, err=True)
154
201
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lr-gladiator
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: CLI and Python client for Arena PLM (app.bom.com): login, get revisions, list/download attachments, and upload to working revisions.
5
5
  Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
6
6
  License: MIT
File without changes
File without changes
File without changes