pmflow-shared 0.1.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.
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ *.egg
9
+
10
+ # Node
11
+ node_modules/
12
+ .next/
13
+ out/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+
19
+ # Environment
20
+ .env
21
+ .env.local
22
+ .env.*.local
23
+
24
+ # OS
25
+ .DS_Store
26
+
27
+ # uv
28
+ uv.lock
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmflow-shared
3
+ Version: 0.1.0
4
+ Summary: Shared models, schemas, and enums for pmflow
5
+ Project-URL: Repository, https://github.com/zhanglaixian/pmflow
6
+ Author-email: zhanglaixian <zhanglaixian@zhuanzhuan.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: email-validator>=2.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: sqlmodel>=0.0.22
15
+ Description-Content-Type: text/markdown
16
+
17
+ # pmflow-shared
18
+
19
+ Shared models, schemas, and enums for [pmflow](https://github.com/zhanglaixian/pmflow) — an integrated project management and AI-assisted development platform.
20
+
21
+ This package is primarily used as a dependency by `pmflow-cli` and `pmflow-server`. You typically don't need to install it directly.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install pmflow-shared
27
+ ```
@@ -0,0 +1,11 @@
1
+ # pmflow-shared
2
+
3
+ Shared models, schemas, and enums for [pmflow](https://github.com/zhanglaixian/pmflow) — an integrated project management and AI-assisted development platform.
4
+
5
+ This package is primarily used as a dependency by `pmflow-cli` and `pmflow-server`. You typically don't need to install it directly.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install pmflow-shared
11
+ ```
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "pmflow-shared"
3
+ version = "0.1.0"
4
+ description = "Shared models, schemas, and enums for pmflow"
5
+ requires-python = ">=3.12"
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ authors = [{name = "zhanglaixian", email = "zhanglaixian@zhuanzhuan.com"}]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ ]
14
+ dependencies = [
15
+ "sqlmodel>=0.0.22",
16
+ "pydantic>=2.0",
17
+ "email-validator>=2.0",
18
+ ]
19
+
20
+ [project.urls]
21
+ Repository = "https://github.com/zhanglaixian/pmflow"
22
+
23
+ [build-system]
24
+ requires = ["hatchling"]
25
+ build-backend = "hatchling.build"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/pmflow_shared"]
@@ -0,0 +1 @@
1
+ """pmflow-shared: Shared models, schemas, and enums."""
@@ -0,0 +1,10 @@
1
+ API_V1_PREFIX = "/api/v1"
2
+
3
+ TOKEN_PREFIX = "pmf_"
4
+ TOKEN_BYTES = 32
5
+
6
+ PRIORITY_NONE = 0
7
+ PRIORITY_LOW = 1
8
+ PRIORITY_MEDIUM = 2
9
+ PRIORITY_HIGH = 3
10
+ PRIORITY_URGENT = 4
@@ -0,0 +1,37 @@
1
+ from enum import Enum
2
+
3
+
4
+ class IssueStatus(str, Enum):
5
+ TODO = "todo"
6
+ DOING = "doing"
7
+ DONE = "done"
8
+ BLOCKED = "blocked"
9
+
10
+
11
+ class IssueType(str, Enum):
12
+ FEATURE = "feature"
13
+ BUG = "bug"
14
+ TASK = "task"
15
+ EPIC = "epic"
16
+
17
+
18
+ class ContextType(str, Enum):
19
+ FEISHU_DOC = "feishu_doc"
20
+ IMAGE = "image"
21
+ AUDIO = "audio"
22
+ MARKDOWN = "markdown"
23
+ FEISHU_SHEET = "feishu_sheet"
24
+ EXCEL = "excel"
25
+ HTML_PROTOTYPE = "html_prototype"
26
+ OTHER_FILE = "other_file"
27
+
28
+
29
+ class WorkspaceRole(str, Enum):
30
+ OWNER = "owner"
31
+ ADMIN = "admin"
32
+ MEMBER = "member"
33
+
34
+
35
+ class MessageRole(str, Enum):
36
+ USER = "user"
37
+ ASSISTANT = "assistant"
@@ -0,0 +1,17 @@
1
+ from pmflow_shared.models.user import User
2
+ from pmflow_shared.models.workspace import Workspace, WorkspaceMember
3
+ from pmflow_shared.models.issue import Issue
4
+ from pmflow_shared.models.context import Context
5
+ from pmflow_shared.models.conversation import Conversation, Message
6
+ from pmflow_shared.models.token import ApiToken
7
+
8
+ __all__ = [
9
+ "User",
10
+ "Workspace",
11
+ "WorkspaceMember",
12
+ "Issue",
13
+ "Context",
14
+ "Conversation",
15
+ "Message",
16
+ "ApiToken",
17
+ ]
@@ -0,0 +1,13 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from sqlmodel import Field, SQLModel
5
+
6
+
7
+ class TimestampMixin(SQLModel):
8
+ created_at: datetime = Field(default_factory=datetime.utcnow)
9
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
10
+
11
+
12
+ def new_uuid() -> uuid.UUID:
13
+ return uuid.uuid4()
@@ -0,0 +1,33 @@
1
+ import uuid
2
+
3
+ from sqlmodel import Field, SQLModel
4
+
5
+ from pmflow_shared.enums import ContextType
6
+ from pmflow_shared.models.base import TimestampMixin, new_uuid
7
+
8
+
9
+ class Context(TimestampMixin, SQLModel, table=True):
10
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
11
+ issue_id: uuid.UUID | None = Field(default=None, foreign_key="issue.id", index=True)
12
+ workspace_id: uuid.UUID | None = Field(default=None, foreign_key="workspace.id", index=True)
13
+ type: ContextType
14
+ title: str
15
+ description: str | None = None
16
+
17
+ # type=feishu_doc / feishu_sheet
18
+ feishu_doc_token: str | None = None
19
+ feishu_doc_url: str | None = None
20
+
21
+ # type=image / audio / excel / other_file
22
+ storage_key: str | None = None
23
+ file_name: str | None = None
24
+ file_size: int | None = None
25
+ mime_type: str | None = None
26
+
27
+ # all types: optional external URL
28
+ url: str | None = None
29
+
30
+ # type=markdown
31
+ content: str | None = None
32
+
33
+ created_by: uuid.UUID = Field(foreign_key="user.id")
@@ -0,0 +1,28 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from sqlalchemy import Column
5
+ from sqlalchemy.types import JSON
6
+ from sqlmodel import Field, SQLModel
7
+
8
+ from pmflow_shared.enums import MessageRole
9
+ from pmflow_shared.models.base import new_uuid
10
+
11
+
12
+ class Conversation(SQLModel, table=True):
13
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
14
+ issue_id: uuid.UUID | None = Field(default=None, foreign_key="issue.id", index=True)
15
+ workspace_id: uuid.UUID | None = Field(default=None, foreign_key="workspace.id", index=True)
16
+ title: str
17
+ purpose: str = Field(default="prd")
18
+ created_by: uuid.UUID = Field(foreign_key="user.id")
19
+ created_at: datetime = Field(default_factory=datetime.utcnow)
20
+
21
+
22
+ class Message(SQLModel, table=True):
23
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
24
+ conversation_id: uuid.UUID = Field(foreign_key="conversation.id", index=True)
25
+ role: MessageRole
26
+ content: str
27
+ extra_data: dict | None = Field(default=None, sa_column=Column("extra_data", JSON))
28
+ created_at: datetime = Field(default_factory=datetime.utcnow)
@@ -0,0 +1,20 @@
1
+ import uuid
2
+
3
+ from sqlmodel import Field, SQLModel
4
+
5
+ from pmflow_shared.enums import IssueStatus, IssueType
6
+ from pmflow_shared.models.base import TimestampMixin, new_uuid
7
+
8
+
9
+ class Issue(TimestampMixin, SQLModel, table=True):
10
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
11
+ workspace_id: uuid.UUID = Field(foreign_key="workspace.id", index=True)
12
+ title: str
13
+ description: str | None = None
14
+ type: IssueType = Field(default=IssueType.TASK)
15
+ status: IssueStatus = Field(default=IssueStatus.TODO, index=True)
16
+ priority: int = Field(default=0)
17
+ assignee_id: uuid.UUID | None = Field(default=None, foreign_key="user.id")
18
+ parent_id: uuid.UUID | None = Field(default=None, foreign_key="issue.id")
19
+ sequence_num: int
20
+ created_by: uuid.UUID = Field(foreign_key="user.id")
@@ -0,0 +1,20 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from sqlmodel import Field, SQLModel
5
+
6
+ from pmflow_shared.models.base import new_uuid
7
+
8
+
9
+ class ApiToken(SQLModel, table=True):
10
+ __tablename__ = "api_token"
11
+
12
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
13
+ user_id: uuid.UUID = Field(foreign_key="user.id", index=True)
14
+ name: str
15
+ token_hash: str = Field(unique=True, index=True)
16
+ token_prefix: str
17
+ last_used_at: datetime | None = None
18
+ expires_at: datetime | None = None
19
+ is_active: bool = Field(default=True)
20
+ created_at: datetime = Field(default_factory=datetime.utcnow)
@@ -0,0 +1,14 @@
1
+ import uuid
2
+
3
+ from sqlmodel import Field, SQLModel
4
+
5
+ from pmflow_shared.models.base import TimestampMixin, new_uuid
6
+
7
+
8
+ class User(TimestampMixin, SQLModel, table=True):
9
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
10
+ email: str = Field(unique=True, index=True)
11
+ name: str
12
+ hashed_password: str
13
+ avatar_url: str | None = None
14
+ is_active: bool = Field(default=True)
@@ -0,0 +1,25 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from sqlmodel import Field, SQLModel
5
+
6
+ from pmflow_shared.enums import WorkspaceRole
7
+ from pmflow_shared.models.base import TimestampMixin, new_uuid
8
+
9
+
10
+ class Workspace(TimestampMixin, SQLModel, table=True):
11
+ id: uuid.UUID = Field(default_factory=new_uuid, primary_key=True)
12
+ name: str = Field(index=True)
13
+ slug: str = Field(unique=True, index=True)
14
+ description: str | None = None
15
+ created_by: uuid.UUID = Field(foreign_key="user.id")
16
+ issue_counter: int = Field(default=0)
17
+
18
+
19
+ class WorkspaceMember(SQLModel, table=True):
20
+ __tablename__ = "workspace_member"
21
+
22
+ workspace_id: uuid.UUID = Field(foreign_key="workspace.id", primary_key=True)
23
+ user_id: uuid.UUID = Field(foreign_key="user.id", primary_key=True)
24
+ role: WorkspaceRole = Field(default=WorkspaceRole.MEMBER)
25
+ joined_at: datetime = Field(default_factory=datetime.utcnow)
@@ -0,0 +1,34 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from pmflow_shared.enums import MessageRole
7
+
8
+
9
+ class ConversationCreate(BaseModel):
10
+ title: str
11
+ purpose: str = "prd"
12
+
13
+
14
+ class ConversationRead(BaseModel):
15
+ id: uuid.UUID
16
+ issue_id: uuid.UUID | None
17
+ workspace_id: uuid.UUID | None
18
+ title: str
19
+ purpose: str
20
+ created_by: uuid.UUID
21
+ created_at: datetime
22
+
23
+
24
+ class SendMessage(BaseModel):
25
+ content: str
26
+
27
+
28
+ class MessageRead(BaseModel):
29
+ id: uuid.UUID
30
+ conversation_id: uuid.UUID
31
+ role: MessageRole
32
+ content: str
33
+ extra_data: dict | None
34
+ created_at: datetime
@@ -0,0 +1,36 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from pmflow_shared.enums import ContextType
7
+
8
+
9
+ class ContextCreate(BaseModel):
10
+ type: ContextType
11
+ title: str
12
+ description: str | None = None
13
+ feishu_doc_token: str | None = None
14
+ feishu_doc_url: str | None = None
15
+ url: str | None = None
16
+ content: str | None = None
17
+
18
+
19
+ class ContextRead(BaseModel):
20
+ id: uuid.UUID
21
+ issue_id: uuid.UUID | None
22
+ workspace_id: uuid.UUID | None
23
+ type: ContextType
24
+ title: str
25
+ description: str | None
26
+ feishu_doc_token: str | None
27
+ feishu_doc_url: str | None
28
+ storage_key: str | None
29
+ file_name: str | None
30
+ file_size: int | None
31
+ mime_type: str | None
32
+ url: str | None
33
+ content: str | None
34
+ created_by: uuid.UUID
35
+ created_at: datetime
36
+ updated_at: datetime
@@ -0,0 +1,44 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from pmflow_shared.enums import IssueStatus, IssueType
7
+
8
+
9
+ class IssueCreate(BaseModel):
10
+ title: str
11
+ description: str | None = None
12
+ type: IssueType = IssueType.TASK
13
+ priority: int = 0
14
+ assignee_id: uuid.UUID | None = None
15
+ parent_id: uuid.UUID | None = None
16
+
17
+
18
+ class IssueUpdate(BaseModel):
19
+ title: str | None = None
20
+ description: str | None = None
21
+ type: IssueType | None = None
22
+ priority: int | None = None
23
+ assignee_id: uuid.UUID | None = None
24
+ parent_id: uuid.UUID | None = None
25
+
26
+
27
+ class IssueStatusUpdate(BaseModel):
28
+ status: IssueStatus
29
+
30
+
31
+ class IssueRead(BaseModel):
32
+ id: uuid.UUID
33
+ workspace_id: uuid.UUID
34
+ title: str
35
+ description: str | None
36
+ type: IssueType
37
+ status: IssueStatus
38
+ priority: int
39
+ assignee_id: uuid.UUID | None
40
+ parent_id: uuid.UUID | None
41
+ sequence_num: int
42
+ created_by: uuid.UUID
43
+ created_at: datetime
44
+ updated_at: datetime
@@ -0,0 +1,56 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel, EmailStr
5
+
6
+
7
+ class UserCreate(BaseModel):
8
+ email: EmailStr
9
+ name: str
10
+ password: str
11
+
12
+
13
+ class UserLogin(BaseModel):
14
+ email: EmailStr
15
+ password: str
16
+
17
+
18
+ class UserRead(BaseModel):
19
+ id: uuid.UUID
20
+ email: str
21
+ name: str
22
+ avatar_url: str | None
23
+ is_active: bool
24
+ created_at: datetime
25
+
26
+
27
+ # --- Auth response schemas ---
28
+
29
+
30
+ class AuthResponse(BaseModel):
31
+ user: UserRead
32
+ message: str = "ok"
33
+
34
+
35
+ # --- API Token schemas ---
36
+
37
+
38
+ class TokenCreate(BaseModel):
39
+ name: str
40
+ expires_in_days: int | None = None
41
+
42
+
43
+ class TokenRead(BaseModel):
44
+ id: uuid.UUID
45
+ name: str
46
+ token_prefix: str
47
+ last_used_at: datetime | None
48
+ expires_at: datetime | None
49
+ is_active: bool
50
+ created_at: datetime
51
+
52
+
53
+ class TokenCreated(TokenRead):
54
+ """Only returned once at creation time — includes the plain-text token."""
55
+
56
+ plain_token: str
@@ -0,0 +1,38 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from pmflow_shared.enums import WorkspaceRole
7
+
8
+
9
+ class WorkspaceCreate(BaseModel):
10
+ name: str
11
+ slug: str
12
+ description: str | None = None
13
+
14
+
15
+ class WorkspaceUpdate(BaseModel):
16
+ name: str | None = None
17
+ description: str | None = None
18
+
19
+
20
+ class WorkspaceRead(BaseModel):
21
+ id: uuid.UUID
22
+ name: str
23
+ slug: str
24
+ description: str | None
25
+ created_by: uuid.UUID
26
+ created_at: datetime
27
+ updated_at: datetime
28
+
29
+
30
+ class WorkspaceMemberAdd(BaseModel):
31
+ user_id: uuid.UUID
32
+ role: WorkspaceRole = WorkspaceRole.MEMBER
33
+
34
+
35
+ class WorkspaceMemberRead(BaseModel):
36
+ user_id: uuid.UUID
37
+ role: WorkspaceRole
38
+ joined_at: datetime