mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-22 02:42:22 +03:00
feat(api): add Snapshot.created and type NodeFile timestamps as date-time
This commit is contained in:
parent
2fb7056cb9
commit
ead44787f3
@ -480,11 +480,13 @@ class Project:
|
|||||||
stat_info = await wait_run_in_executor(lambda e=entry: e.stat())
|
stat_info = await wait_run_in_executor(lambda e=entry: e.stat())
|
||||||
is_dir = await wait_run_in_executor(lambda e=entry: e.is_dir())
|
is_dir = await wait_run_in_executor(lambda e=entry: e.is_dir())
|
||||||
if is_dir:
|
if is_dir:
|
||||||
|
created_at: str | None = None
|
||||||
|
modified_at: str | None = None
|
||||||
try:
|
try:
|
||||||
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
||||||
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
||||||
except (OSError, OverflowError, ValueError):
|
except (OSError, OverflowError, ValueError):
|
||||||
created_at = modified_at = ""
|
created_at = modified_at = None
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": rel_path,
|
"path": rel_path,
|
||||||
@ -506,7 +508,7 @@ class Project:
|
|||||||
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
||||||
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
||||||
except (OSError, OverflowError, ValueError):
|
except (OSError, OverflowError, ValueError):
|
||||||
created_at = modified_at = ""
|
created_at = modified_at = None
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": rel_path,
|
"path": rel_path,
|
||||||
@ -537,7 +539,7 @@ class Project:
|
|||||||
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
created_at = datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat()
|
||||||
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
||||||
except (OSError, OverflowError, ValueError):
|
except (OSError, OverflowError, ValueError):
|
||||||
created_at = modified_at = ""
|
created_at = modified_at = None
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": os.path.relpath(dir_full_path, base_path),
|
"path": os.path.relpath(dir_full_path, base_path),
|
||||||
@ -567,7 +569,7 @@ class Project:
|
|||||||
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
modified_at = datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
|
||||||
except (OSError, OverflowError, ValueError) as e:
|
except (OSError, OverflowError, ValueError) as e:
|
||||||
log.warning(f"Invalid timestamp for '{rel_path}': {e}")
|
log.warning(f"Invalid timestamp for '{rel_path}': {e}")
|
||||||
created_at = modified_at = ""
|
created_at = modified_at = None
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": rel_path,
|
"path": rel_path,
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -111,8 +112,8 @@ class NodeFile(BaseModel):
|
|||||||
|
|
||||||
path: str = Field(..., description="File name")
|
path: str = Field(..., description="File name")
|
||||||
size: int = Field(..., description="File size in bytes")
|
size: int = Field(..., description="File size in bytes")
|
||||||
created_at: str = Field(..., description="File creation time (ISO 8601)")
|
created_at: Optional[datetime] = Field(None, description="File creation time (ISO 8601)")
|
||||||
modified_at: str = Field(..., description="File modification time (ISO 8601)")
|
modified_at: Optional[datetime] = Field(None, description="File modification time (ISO 8601)")
|
||||||
file_type: str = Field(..., description="File type determined by the file command")
|
file_type: str = Field(..., description="File type determined by the file command")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -15,10 +15,11 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, model_validator
|
||||||
|
|
||||||
|
|
||||||
class SnapshotBase(BaseModel):
|
class SnapshotBase(BaseModel):
|
||||||
@ -44,4 +45,14 @@ class Snapshot(SnapshotBase):
|
|||||||
name: str = Field(..., description="Name of the snapshot")
|
name: str = Field(..., description="Name of the snapshot")
|
||||||
filename: str = Field(..., description="Filename of the snapshot")
|
filename: str = Field(..., description="Filename of the snapshot")
|
||||||
description: str = Field(..., description="Description of the snapshot")
|
description: str = Field(..., description="Description of the snapshot")
|
||||||
created_at: int = Field(..., description="Date of the snapshot (UTC timestamp)")
|
created_at: int = Field(
|
||||||
|
..., description="Date of the snapshot (UTC timestamp). Deprecated: use created", deprecated=True
|
||||||
|
)
|
||||||
|
created: datetime = Field(..., description="Date of the snapshot (ISO 8601, UTC)")
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _derive_created(cls, data):
|
||||||
|
if isinstance(data, dict) and "created" not in data and data.get("created_at") is not None:
|
||||||
|
data = {**data, "created": datetime.fromtimestamp(data["created_at"], tz=timezone.utc)}
|
||||||
|
return data
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
@ -54,6 +55,9 @@ class TestSnapshotRoutes:
|
|||||||
response = await client.get(app.url_path_for("get_snapshots", project_id=project.id))
|
response = await client.get(app.url_path_for("get_snapshots", project_id=project.id))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert len(response.json()) == 1
|
assert len(response.json()) == 1
|
||||||
|
data = response.json()[0]
|
||||||
|
assert isinstance(data["created_at"], int)
|
||||||
|
assert datetime.fromisoformat(data["created"]) == datetime.fromtimestamp(data["created_at"], tz=timezone.utc)
|
||||||
|
|
||||||
async def test_delete_snapshot(
|
async def test_delete_snapshot(
|
||||||
self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot
|
self, app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot
|
||||||
@ -80,3 +84,15 @@ class TestSnapshotRoutes:
|
|||||||
response = await client.post(app.url_path_for("create_snapshot", project_id=project.id), json={"name": "snap1"})
|
response = await client.post(app.url_path_for("create_snapshot", project_id=project.id), json={"name": "snap1"})
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
assert len(os.listdir(os.path.join(project.path, "snapshots"))) == 2
|
assert len(os.listdir(os.path.join(project.path, "snapshots"))) == 2
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data["created_at"], int)
|
||||||
|
assert datetime.fromisoformat(data["created"]) == datetime.fromtimestamp(data["created_at"], tz=timezone.utc)
|
||||||
|
|
||||||
|
async def test_snapshot_schema_timestamps(self, app: FastAPI) -> None:
|
||||||
|
|
||||||
|
schemas = app.openapi()["components"]["schemas"]
|
||||||
|
assert schemas["Snapshot"]["properties"]["created"]["format"] == "date-time"
|
||||||
|
assert schemas["Snapshot"]["properties"]["created_at"]["type"] == "integer"
|
||||||
|
assert schemas["Snapshot"]["properties"]["created_at"]["deprecated"] is True
|
||||||
|
for field in ("created_at", "modified_at"):
|
||||||
|
assert {"type": "string", "format": "date-time"} in schemas["NodeFile"]["properties"][field]["anyOf"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user