refactor: improve plugin version validation to support full semantic versioning (#25161)

Signed-off-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
-LAN-
2025-09-05 12:39:48 +08:00
committed by GitHub
parent 432f89cf33
commit e78f1cdc6a
4 changed files with 49 additions and 14 deletions

View File

@@ -4,7 +4,8 @@ import re
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any, Optional from typing import Any, Optional
from pydantic import BaseModel, Field, model_validator from packaging.version import InvalidVersion, Version
from pydantic import BaseModel, Field, field_validator, model_validator
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from core.agent.plugin_entities import AgentStrategyProviderEntity from core.agent.plugin_entities import AgentStrategyProviderEntity
@@ -71,10 +72,21 @@ class PluginDeclaration(BaseModel):
endpoints: Optional[list[str]] = Field(default_factory=list[str]) endpoints: Optional[list[str]] = Field(default_factory=list[str])
class Meta(BaseModel): class Meta(BaseModel):
minimum_dify_version: Optional[str] = Field(default=None, pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$") minimum_dify_version: Optional[str] = Field(default=None)
version: Optional[str] = Field(default=None) version: Optional[str] = Field(default=None)
version: str = Field(..., pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$") @field_validator("minimum_dify_version")
@classmethod
def validate_minimum_dify_version(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return v
try:
Version(v)
return v
except InvalidVersion as e:
raise ValueError(f"Invalid version format: {v}") from e
version: str = Field(...)
author: Optional[str] = Field(..., pattern=r"^[a-zA-Z0-9_-]{1,64}$") author: Optional[str] = Field(..., pattern=r"^[a-zA-Z0-9_-]{1,64}$")
name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$") name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$")
description: I18nObject description: I18nObject
@@ -94,6 +106,15 @@ class PluginDeclaration(BaseModel):
agent_strategy: Optional[AgentStrategyProviderEntity] = None agent_strategy: Optional[AgentStrategyProviderEntity] = None
meta: Meta meta: Meta
@field_validator("version")
@classmethod
def validate_version(cls, v: str) -> str:
try:
Version(v)
return v
except InvalidVersion as e:
raise ValueError(f"Invalid version format: {v}") from e
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
def validate_category(cls, values: dict) -> dict: def validate_category(cls, values: dict) -> dict:

View File

@@ -84,10 +84,11 @@ dependencies = [
"weave~=0.51.0", "weave~=0.51.0",
"yarl~=1.18.3", "yarl~=1.18.3",
"webvtt-py~=0.5.1", "webvtt-py~=0.5.1",
"sseclient-py>=1.8.0", "sseclient-py~=1.8.0",
"httpx-sse>=0.4.0", "httpx-sse~=0.4.0",
"sendgrid~=6.12.3", "sendgrid~=6.12.3",
"flask-restx>=1.3.0", "flask-restx~=1.3.0",
"packaging~=23.2",
] ]
# Before adding new dependency, consider place it in # Before adding new dependency, consider place it in
# alphabet order (a-z) and suitable group. # alphabet order (a-z) and suitable group.

View File

@@ -57,10 +57,12 @@ class TestWebAppAuthService:
tuple: (account, tenant) - Created account and tenant instances tuple: (account, tenant) - Created account and tenant instances
""" """
fake = Faker() fake = Faker()
import uuid
# Create account # Create account with unique email to avoid collisions
unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com"
account = Account( account = Account(
email=fake.email(), email=unique_email,
name=fake.name(), name=fake.name(),
interface_language="en-US", interface_language="en-US",
status="active", status="active",
@@ -109,8 +111,11 @@ class TestWebAppAuthService:
password = fake.password(length=12) password = fake.password(length=12)
# Create account with password # Create account with password
import uuid
unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com"
account = Account( account = Account(
email=fake.email(), email=unique_email,
name=fake.name(), name=fake.name(),
interface_language="en-US", interface_language="en-US",
status="active", status="active",
@@ -322,9 +327,12 @@ class TestWebAppAuthService:
""" """
# Arrange: Create account without password # Arrange: Create account without password
fake = Faker() fake = Faker()
import uuid
unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com"
account = Account( account = Account(
email=fake.email(), email=unique_email,
name=fake.name(), name=fake.name(),
interface_language="en-US", interface_language="en-US",
status="active", status="active",
@@ -431,9 +439,12 @@ class TestWebAppAuthService:
""" """
# Arrange: Create banned account # Arrange: Create banned account
fake = Faker() fake = Faker()
import uuid
unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com"
account = Account( account = Account(
email=fake.email(), email=unique_email,
name=fake.name(), name=fake.name(),
interface_language="en-US", interface_language="en-US",
status=AccountStatus.BANNED.value, status=AccountStatus.BANNED.value,

8
api/uv.lock generated
View File

@@ -1318,6 +1318,7 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" }, { name = "opentelemetry-util-http" },
{ name = "opik" }, { name = "opik" },
{ name = "packaging" },
{ name = "pandas", extra = ["excel", "output-formatting", "performance"] }, { name = "pandas", extra = ["excel", "output-formatting", "performance"] },
{ name = "pandoc" }, { name = "pandoc" },
{ name = "psycogreen" }, { name = "psycogreen" },
@@ -1469,7 +1470,7 @@ requires-dist = [
{ name = "flask-login", specifier = "~=0.6.3" }, { name = "flask-login", specifier = "~=0.6.3" },
{ name = "flask-migrate", specifier = "~=4.0.7" }, { name = "flask-migrate", specifier = "~=4.0.7" },
{ name = "flask-orjson", specifier = "~=2.0.0" }, { name = "flask-orjson", specifier = "~=2.0.0" },
{ name = "flask-restx", specifier = ">=1.3.0" }, { name = "flask-restx", specifier = "~=1.3.0" },
{ name = "flask-sqlalchemy", specifier = "~=3.1.1" }, { name = "flask-sqlalchemy", specifier = "~=3.1.1" },
{ name = "gevent", specifier = "~=24.11.1" }, { name = "gevent", specifier = "~=24.11.1" },
{ name = "gmpy2", specifier = "~=2.2.1" }, { name = "gmpy2", specifier = "~=2.2.1" },
@@ -1481,7 +1482,7 @@ requires-dist = [
{ name = "googleapis-common-protos", specifier = "==1.63.0" }, { name = "googleapis-common-protos", specifier = "==1.63.0" },
{ name = "gunicorn", specifier = "~=23.0.0" }, { name = "gunicorn", specifier = "~=23.0.0" },
{ name = "httpx", extras = ["socks"], specifier = "~=0.27.0" }, { name = "httpx", extras = ["socks"], specifier = "~=0.27.0" },
{ name = "httpx-sse", specifier = ">=0.4.0" }, { name = "httpx-sse", specifier = "~=0.4.0" },
{ name = "jieba", specifier = "==0.42.1" }, { name = "jieba", specifier = "==0.42.1" },
{ name = "json-repair", specifier = ">=0.41.1" }, { name = "json-repair", specifier = ">=0.41.1" },
{ name = "langfuse", specifier = "~=2.51.3" }, { name = "langfuse", specifier = "~=2.51.3" },
@@ -1509,6 +1510,7 @@ requires-dist = [
{ name = "opentelemetry-semantic-conventions", specifier = "==0.48b0" }, { name = "opentelemetry-semantic-conventions", specifier = "==0.48b0" },
{ name = "opentelemetry-util-http", specifier = "==0.48b0" }, { name = "opentelemetry-util-http", specifier = "==0.48b0" },
{ name = "opik", specifier = "~=1.7.25" }, { name = "opik", specifier = "~=1.7.25" },
{ name = "packaging", specifier = "~=23.2" },
{ name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" }, { name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" },
{ name = "pandoc", specifier = "~=2.4" }, { name = "pandoc", specifier = "~=2.4" },
{ name = "psycogreen", specifier = "~=1.0.2" }, { name = "psycogreen", specifier = "~=1.0.2" },
@@ -1528,7 +1530,7 @@ requires-dist = [
{ name = "sendgrid", specifier = "~=6.12.3" }, { name = "sendgrid", specifier = "~=6.12.3" },
{ name = "sentry-sdk", extras = ["flask"], specifier = "~=2.28.0" }, { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.28.0" },
{ name = "sqlalchemy", specifier = "~=2.0.29" }, { name = "sqlalchemy", specifier = "~=2.0.29" },
{ name = "sseclient-py", specifier = ">=1.8.0" }, { name = "sseclient-py", specifier = "~=1.8.0" },
{ name = "starlette", specifier = "==0.47.2" }, { name = "starlette", specifier = "==0.47.2" },
{ name = "tiktoken", specifier = "~=0.9.0" }, { name = "tiktoken", specifier = "~=0.9.0" },
{ name = "transformers", specifier = "~=4.53.0" }, { name = "transformers", specifier = "~=4.53.0" },