diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index a07b58d9e..01e9e11e6 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -4,7 +4,8 @@ import re from collections.abc import Mapping 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 core.agent.plugin_entities import AgentStrategyProviderEntity @@ -71,10 +72,21 @@ class PluginDeclaration(BaseModel): endpoints: Optional[list[str]] = Field(default_factory=list[str]) 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: 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}$") name: str = Field(..., pattern=r"^[a-z0-9_-]{1,128}$") description: I18nObject @@ -94,6 +106,15 @@ class PluginDeclaration(BaseModel): agent_strategy: Optional[AgentStrategyProviderEntity] = None 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") @classmethod def validate_category(cls, values: dict) -> dict: diff --git a/api/pyproject.toml b/api/pyproject.toml index 8f5a6a44a..c59140e24 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -84,10 +84,11 @@ dependencies = [ "weave~=0.51.0", "yarl~=1.18.3", "webvtt-py~=0.5.1", - "sseclient-py>=1.8.0", - "httpx-sse>=0.4.0", + "sseclient-py~=1.8.0", + "httpx-sse~=0.4.0", "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 # alphabet order (a-z) and suitable group. diff --git a/api/tests/test_containers_integration_tests/services/test_webapp_auth_service.py b/api/tests/test_containers_integration_tests/services/test_webapp_auth_service.py index 666b083ba..429056f5e 100644 --- a/api/tests/test_containers_integration_tests/services/test_webapp_auth_service.py +++ b/api/tests/test_containers_integration_tests/services/test_webapp_auth_service.py @@ -57,10 +57,12 @@ class TestWebAppAuthService: tuple: (account, tenant) - Created account and tenant instances """ 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( - email=fake.email(), + email=unique_email, name=fake.name(), interface_language="en-US", status="active", @@ -109,8 +111,11 @@ class TestWebAppAuthService: password = fake.password(length=12) # Create account with password + import uuid + + unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com" account = Account( - email=fake.email(), + email=unique_email, name=fake.name(), interface_language="en-US", status="active", @@ -322,9 +327,12 @@ class TestWebAppAuthService: """ # Arrange: Create account without password fake = Faker() + import uuid + + unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com" account = Account( - email=fake.email(), + email=unique_email, name=fake.name(), interface_language="en-US", status="active", @@ -431,9 +439,12 @@ class TestWebAppAuthService: """ # Arrange: Create banned account fake = Faker() + import uuid + + unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com" account = Account( - email=fake.email(), + email=unique_email, name=fake.name(), interface_language="en-US", status=AccountStatus.BANNED.value, diff --git a/api/uv.lock b/api/uv.lock index 1d872087c..54c408336 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1318,6 +1318,7 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, { name = "opik" }, + { name = "packaging" }, { name = "pandas", extra = ["excel", "output-formatting", "performance"] }, { name = "pandoc" }, { name = "psycogreen" }, @@ -1469,7 +1470,7 @@ requires-dist = [ { name = "flask-login", specifier = "~=0.6.3" }, { name = "flask-migrate", specifier = "~=4.0.7" }, { 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 = "gevent", specifier = "~=24.11.1" }, { name = "gmpy2", specifier = "~=2.2.1" }, @@ -1481,7 +1482,7 @@ requires-dist = [ { name = "googleapis-common-protos", specifier = "==1.63.0" }, { name = "gunicorn", specifier = "~=23.0.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 = "json-repair", specifier = ">=0.41.1" }, { name = "langfuse", specifier = "~=2.51.3" }, @@ -1509,6 +1510,7 @@ requires-dist = [ { name = "opentelemetry-semantic-conventions", specifier = "==0.48b0" }, { name = "opentelemetry-util-http", specifier = "==0.48b0" }, { name = "opik", specifier = "~=1.7.25" }, + { name = "packaging", specifier = "~=23.2" }, { name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" }, { name = "pandoc", specifier = "~=2.4" }, { name = "psycogreen", specifier = "~=1.0.2" }, @@ -1528,7 +1530,7 @@ requires-dist = [ { name = "sendgrid", specifier = "~=6.12.3" }, { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.28.0" }, { 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 = "tiktoken", specifier = "~=0.9.0" }, { name = "transformers", specifier = "~=4.53.0" },