opensees-cli 0.2.0__tar.gz → 0.2.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 (28) hide show
  1. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/PKG-INFO +1 -1
  2. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/pyproject.toml +1 -1
  3. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/__init__.py +1 -1
  4. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/files.py +31 -5
  5. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/run.py +15 -8
  6. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/PKG-INFO +1 -1
  7. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_files_commands.py +14 -0
  8. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/README.md +0 -0
  9. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/setup.cfg +0 -0
  10. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/__main__.py +0 -0
  11. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/admin.py +0 -0
  12. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/api.py +0 -0
  13. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/auth.py +0 -0
  14. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/config.py +0 -0
  15. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/main.py +0 -0
  16. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/path_setup.py +0 -0
  17. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli/version.py +0 -0
  18. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/SOURCES.txt +0 -0
  19. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/dependency_links.txt +0 -0
  20. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/entry_points.txt +0 -0
  21. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/requires.txt +0 -0
  22. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/src/opensees_cli.egg-info/top_level.txt +0 -0
  23. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_admin_commands.py +0 -0
  24. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_api.py +0 -0
  25. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_auth_commands.py +0 -0
  26. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_config.py +0 -0
  27. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_main_commands.py +0 -0
  28. {opensees_cli-0.2.0 → opensees_cli-0.2.1}/tests/test_run_commands.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensees_cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Run OpenSees simulations in the cloud from the command line.
5
5
  Author: Minjie Zhu
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensees_cli"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Run OpenSees simulations in the cloud from the command line."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """OpenSees CLI — run structural simulations in the cloud."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -28,14 +28,35 @@ def _files_callback(ctx: typer.Context) -> None:
28
28
  raise typer.Exit(0)
29
29
 
30
30
 
31
- def _files_list_items() -> list[dict]:
32
- """All files in the user file system."""
33
- r = api.get("/files/list")
31
+ def _files_list_items(folder: Optional[str] = None) -> list[dict]:
32
+ """List files in the user file system, optionally under ``folder``."""
33
+ params = {}
34
+ if folder:
35
+ params["folder"] = folder.strip().strip("/")
36
+ r = api.get("/files/list", params=params or None)
34
37
  return list(r.get("files", []))
35
38
 
36
39
 
40
+ def _filter_under_folder(items: list[dict], folder: str) -> list[dict]:
41
+ """Keep files at or under ``folder`` (client-side fallback)."""
42
+ root = folder.strip().strip("/")
43
+ if not root:
44
+ return items
45
+ prefix = root + "/"
46
+ out = []
47
+ for f in items:
48
+ path = str(f.get("path", ""))
49
+ if path == root or path.startswith(prefix):
50
+ out.append(f)
51
+ return out
52
+
53
+
37
54
  @app.command("list")
38
55
  def list_files(
56
+ folder: Optional[str] = typer.Argument(
57
+ None,
58
+ help="Only list files in this folder (and its subfolders), e.g. truss or examples/dambreak",
59
+ ),
39
60
  as_json: bool = typer.Option(
40
61
  False,
41
62
  "--json",
@@ -44,9 +65,12 @@ def list_files(
44
65
  is_flag=True,
45
66
  ),
46
67
  ):
47
- """List files. Prints nothing on stdout when there are no files (non-JSON)."""
68
+ """List files. With FOLDER, only show files under that path. Prints nothing when empty (non-JSON)."""
48
69
  try:
49
- items = _files_list_items()
70
+ items = _files_list_items(folder)
71
+ # Older APIs ignore ?folder= — filter locally so the CLI still works.
72
+ if folder:
73
+ items = _filter_under_folder(items, folder)
50
74
  except api.ApiError as e:
51
75
  console.print(f"[red]{e.message}[/red]")
52
76
  raise typer.Exit(1)
@@ -66,6 +90,8 @@ def list_files(
66
90
  "file_count": len(rows),
67
91
  "total_size": total,
68
92
  }
93
+ if folder:
94
+ obj["folder"] = folder.strip().strip("/")
69
95
  typer.echo(json.dumps(obj, indent=2).rstrip("\n"))
70
96
  return
71
97
 
@@ -1093,32 +1093,39 @@ def _week_range(week: int) -> tuple[datetime, datetime]:
1093
1093
  return start.astimezone(timezone.utc), end.astimezone(timezone.utc)
1094
1094
 
1095
1095
 
1096
- def _fmt_date_span(after_dt: datetime, before_dt: datetime | None) -> str:
1096
+ def _fmt_date_span(after_dt: datetime, before_dt: Optional[datetime]) -> str:
1097
1097
  """Return a compact human-readable date span string in local time.
1098
1098
 
1099
1099
  ``before_dt`` is exclusive (the day after the last day), so we display
1100
1100
  ``before_dt - 1 day`` as the end date. If ``before_dt`` is ``None`` the
1101
1101
  end is displayed as "today".
1102
1102
  Example: "Jan 5 – Mar 8, 2026" or "May 4–10, 2026" (same year/month).
1103
+
1104
+ Avoids ``%-d`` / ``%#d`` strftime flags (Unix vs Windows) by formatting
1105
+ the day with ``dt.day`` instead.
1103
1106
  """
1107
+ def _md(dt: datetime, *, year: bool = False) -> str:
1108
+ s = f"{dt.strftime('%b')} {dt.day}"
1109
+ return f"{s}, {dt.year}" if year else s
1110
+
1104
1111
  local = datetime.now().astimezone().tzinfo
1105
1112
  start = after_dt.astimezone(local)
1106
1113
 
1107
1114
  if before_dt is None:
1108
1115
  end = datetime.now().astimezone(local)
1109
- end_str = f"{end.strftime('%b %-d, %Y')}"
1116
+ end_str = _md(end, year=True)
1110
1117
  if start.year == end.year:
1111
1118
  if start.month == end.month:
1112
- return f"{start.strftime('%b %-d')}–{end.strftime('%-d, %Y')}"
1113
- return f"{start.strftime('%b %-d')} – {end_str}"
1114
- return f"{start.strftime('%b %-d, %Y')} – {end_str}"
1119
+ return f"{_md(start)}–{end.day}, {end.year}"
1120
+ return f"{_md(start)} – {end_str}"
1121
+ return f"{_md(start, year=True)} – {end_str}"
1115
1122
 
1116
1123
  end = (before_dt - timedelta(seconds=1)).astimezone(local)
1117
1124
  if start.year == end.year:
1118
1125
  if start.month == end.month:
1119
- return f"{start.strftime('%b %-d')}–{end.strftime('%-d, %Y')}"
1120
- return f"{start.strftime('%b %-d')} – {end.strftime('%b %-d, %Y')}"
1121
- return f"{start.strftime('%b %-d, %Y')} – {end.strftime('%b %-d, %Y')}"
1126
+ return f"{_md(start)}–{end.day}, {end.year}"
1127
+ return f"{_md(start)} – {_md(end, year=True)}"
1128
+ return f"{_md(start, year=True)} – {_md(end, year=True)}"
1122
1129
 
1123
1130
 
1124
1131
  def _week_span_range(w1: int, w2: int) -> tuple[datetime, datetime]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensees_cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Run OpenSees simulations in the cloud from the command line.
5
5
  Author: Minjie Zhu
6
6
  License: Proprietary
@@ -27,6 +27,20 @@ class TestFilesList:
27
27
  assert "model.py" in result.output
28
28
  assert "data.csv" in result.output
29
29
 
30
+ def test_list_folder_filters(self, cli_runner, logged_in_creds, patch_httpx, mock_router):
31
+ mock_router.add("GET", "/prod/files/list", json={
32
+ "files": [
33
+ {"path": "truss/Truss.py", "size": 100, "last_modified": "2025-01-01T00:00:00Z"},
34
+ {"path": "truss/data.csv", "size": 200, "last_modified": "2025-01-01T00:00:00Z"},
35
+ {"path": "other/model.py", "size": 300, "last_modified": "2025-01-01T00:00:00Z"},
36
+ ]
37
+ })
38
+ result = cli_runner.invoke(app, ["files", "list", "truss"])
39
+ assert result.exit_code == 0
40
+ assert "truss/Truss.py" in result.output
41
+ assert "truss/data.csv" in result.output
42
+ assert "other/model.py" not in result.output
43
+
30
44
 
31
45
  class TestFilesUpload:
32
46
  def test_upload_single_file(self, cli_runner, logged_in_creds, patch_httpx, mock_router, tmp_file):
File without changes
File without changes