rosetta-cli 3.0.5__tar.gz → 3.0.6__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.
- {rosetta_cli-3.0.5/rosetta_cli.egg-info → rosetta_cli-3.0.6}/PKG-INFO +1 -1
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/pyproject.toml +1 -1
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/cleanup_command.py +10 -1
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/parse_command.py +43 -44
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_config.py +49 -15
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_publisher.py +8 -3
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6/rosetta_cli.egg-info}/PKG-INFO +1 -1
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/MANIFEST.in +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/README.md +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/env.template +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/__init__.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/__main__.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/cli.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/__init__.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/base_command.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/list_command.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/publish_command.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/commands/verify_command.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/ragflow_client.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_auth.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_utils.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/services/__init__.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/services/auth_service.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/services/dataset_service.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/services/document_data.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/services/document_service.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli/typing_utils.py +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/SOURCES.txt +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/dependency_links.txt +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/entry_points.txt +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/requires.txt +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/top_level.txt +0 -0
- {rosetta_cli-3.0.5 → rosetta_cli-3.0.6}/setup.cfg +0 -0
|
@@ -87,7 +87,16 @@ class CleanupCommand(BaseCommand):
|
|
|
87
87
|
dataset_service: DatasetService,
|
|
88
88
|
args: CommandArgs,
|
|
89
89
|
) -> list[DocumentLike]:
|
|
90
|
-
"""Get documents to delete based on filters.
|
|
90
|
+
"""Get documents to delete based on filters.
|
|
91
|
+
|
|
92
|
+
Accepted limitation: every branch below reads one page, bounded by
|
|
93
|
+
RAGFLOW_PAGE_SIZE (default 1000). On a dataset larger than that, this
|
|
94
|
+
command deletes the documents on the first page and reports success -
|
|
95
|
+
the remainder is left in place with no warning. The instruction set is
|
|
96
|
+
not expected to approach the cap, and this command serves the optional
|
|
97
|
+
MCP publishing pipeline rather than plugin delivery. Anyone pointing
|
|
98
|
+
`--dataset` at a larger dataset should know the delete is partial.
|
|
99
|
+
"""
|
|
91
100
|
document_service = DocumentService(self.client)
|
|
92
101
|
|
|
93
102
|
if args.tags:
|
|
@@ -11,7 +11,7 @@ from ..services.auth_service import AuthService
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
from .base_command import BaseCommand
|
|
14
|
-
from ..typing_utils import CommandArgs, DatasetLike, JsonDict
|
|
14
|
+
from ..typing_utils import CommandArgs, DatasetLike, DocumentLike, JsonDict
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ParseCommand(BaseCommand):
|
|
@@ -102,58 +102,57 @@ class ParseCommand(BaseCommand):
|
|
|
102
102
|
traceback.print_exc()
|
|
103
103
|
return 1
|
|
104
104
|
|
|
105
|
+
# RAGFlow `run` values that mean a document still needs parsing.
|
|
106
|
+
PARSE_REQUIRED_STATUSES = ("FAIL", "UNSTART", "CANCEL")
|
|
107
|
+
|
|
105
108
|
def _get_documents_to_parse(self, dataset: DatasetLike, args: CommandArgs) -> tuple[list[JsonDict], dict[str, int]]:
|
|
106
109
|
"""Get documents that need parsing."""
|
|
107
|
-
document_service = DocumentService(self.client)
|
|
108
110
|
docs_to_parse: list[JsonDict] = []
|
|
109
111
|
status_counts = {'done': 0, 'running': 0}
|
|
110
112
|
|
|
111
113
|
print(f"Checking parsing status for documents...")
|
|
112
114
|
|
|
115
|
+
# One unfiltered list call serves both modes: force selects everything,
|
|
116
|
+
# default partitions the same list client-side on `run`. Default mode used
|
|
117
|
+
# to make two calls - a server-side run= filter, plus a full list purely to
|
|
118
|
+
# tally done/running for the summary.
|
|
119
|
+
#
|
|
120
|
+
# Deliberate tradeoff, not a no-op: the server-filtered call surfaced up to
|
|
121
|
+
# page_size documents *needing parsing*, whereas page 1 of an unfiltered
|
|
122
|
+
# list surfaces only the needing-parse documents that happen to fall inside
|
|
123
|
+
# it. Once a dataset exceeds RAGFLOW_PAGE_SIZE this can therefore select
|
|
124
|
+
# fewer documents than before.
|
|
125
|
+
#
|
|
126
|
+
# Accepted limitation, not a deferred fix: every list path in this command
|
|
127
|
+
# reads page 1 only, bounded by RAGFLOW_PAGE_SIZE (default 1000). The
|
|
128
|
+
# instruction set is not expected to approach that cap - it is roughly 470
|
|
129
|
+
# files - and this command serves the optional MCP publishing pipeline,
|
|
130
|
+
# which is secondary to plugin delivery. The page-1-only read is also
|
|
131
|
+
# pre-existing: it already applied to the force branch and to the
|
|
132
|
+
# done/running tally.
|
|
133
|
+
documents = dataset.list_documents(page_size=self.config.page_size)
|
|
134
|
+
|
|
135
|
+
selected: list[tuple[DocumentLike, str]] = []
|
|
136
|
+
for document in documents:
|
|
137
|
+
status = self._count_parse_status(status_counts, document)
|
|
138
|
+
if args.force or status in self.PARSE_REQUIRED_STATUSES:
|
|
139
|
+
selected.append((document, status))
|
|
140
|
+
|
|
113
141
|
if args.force:
|
|
114
|
-
|
|
115
|
-
documents = dataset.list_documents(page_size=self.config.page_size)
|
|
116
|
-
|
|
117
|
-
print(f"Found {len(documents)} document(s) (force mode - parsing all)\n")
|
|
118
|
-
|
|
119
|
-
for document in documents:
|
|
120
|
-
status = self._count_parse_status(status_counts, document)
|
|
121
|
-
doc_id = getattr(document, 'id', None)
|
|
122
|
-
if doc_id:
|
|
123
|
-
docs_to_parse.append({
|
|
124
|
-
"id": doc_id,
|
|
125
|
-
"name": getattr(document, 'name', 'Untitled'),
|
|
126
|
-
"dataset_id": dataset.id,
|
|
127
|
-
"folder": ".",
|
|
128
|
-
"status": status
|
|
129
|
-
})
|
|
142
|
+
print(f"Found {len(selected)} document(s) (force mode - parsing all)\n")
|
|
130
143
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
print(f"Found {len(documents_needing_parse)} document(s) needing parsing\n")
|
|
145
|
-
|
|
146
|
-
for document in documents_needing_parse:
|
|
147
|
-
status = self._count_parse_status(status_counts, document)
|
|
148
|
-
doc_id = getattr(document, 'id', None)
|
|
149
|
-
if doc_id:
|
|
150
|
-
docs_to_parse.append({
|
|
151
|
-
"id": doc_id,
|
|
152
|
-
"name": getattr(document, 'name', 'Untitled'),
|
|
153
|
-
"dataset_id": dataset.id,
|
|
154
|
-
"folder": ".",
|
|
155
|
-
"status": status
|
|
156
|
-
})
|
|
144
|
+
print(f"Found {len(selected)} document(s) needing parsing\n")
|
|
145
|
+
|
|
146
|
+
for document, status in selected:
|
|
147
|
+
doc_id = getattr(document, 'id', None)
|
|
148
|
+
if doc_id:
|
|
149
|
+
docs_to_parse.append({
|
|
150
|
+
"id": doc_id,
|
|
151
|
+
"name": getattr(document, 'name', 'Untitled'),
|
|
152
|
+
"dataset_id": dataset.id,
|
|
153
|
+
"folder": ".",
|
|
154
|
+
"status": status
|
|
155
|
+
})
|
|
157
156
|
|
|
158
157
|
return docs_to_parse, status_counts
|
|
159
158
|
|
|
@@ -9,7 +9,6 @@ import logging
|
|
|
9
9
|
import os
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from urllib.parse import urlparse
|
|
13
12
|
|
|
14
13
|
from dotenv import load_dotenv
|
|
15
14
|
|
|
@@ -69,14 +68,40 @@ def find_env_file(env_name: str | None = None) -> Path | None:
|
|
|
69
68
|
return None
|
|
70
69
|
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
# Environment keywords searched for in the RAGFlow URL, in scan order.
|
|
72
|
+
#
|
|
73
|
+
# ORDER IS LOAD-BEARING - do not sort or alphabetize this tuple. The scan takes the
|
|
74
|
+
# first keyword that appears anywhere in the URL, so a keyword that contains another
|
|
75
|
+
# keyword must be listed before the one it contains. "prod" is a substring of
|
|
76
|
+
# "preprod" - the only such pair in this set - so putting "prod" first would make
|
|
77
|
+
# preprod.example.com resolve to "prod". test_environment_keyword_order_is_safe in
|
|
78
|
+
# tests/test_rosetta_config_environment.py enforces this invariant.
|
|
79
|
+
ENVIRONMENT_KEYWORDS: tuple[str, ...] = (
|
|
80
|
+
"preprod", # must precede "prod", which it contains
|
|
81
|
+
"prod",
|
|
82
|
+
"stag",
|
|
83
|
+
"qa",
|
|
84
|
+
"dev",
|
|
85
|
+
"test",
|
|
86
|
+
"uat",
|
|
87
|
+
"sandbox",
|
|
88
|
+
"perf",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _environment_from_url(url: str) -> str | None:
|
|
93
|
+
"""Detect an environment name from keywords in a RAGFlow URL.
|
|
94
|
+
|
|
95
|
+
Case-insensitive substring scan over the URL exactly as given (scheme, host,
|
|
96
|
+
port and path all count). The first keyword in ENVIRONMENT_KEYWORDS to appear
|
|
97
|
+
wins, and the resolved value is the keyword itself, not the surrounding label:
|
|
98
|
+
'https://ims-dev.example.com/' -> 'dev'. Returns None when no keyword appears.
|
|
99
|
+
"""
|
|
100
|
+
lowered = (url or "").lower()
|
|
101
|
+
for keyword in ENVIRONMENT_KEYWORDS:
|
|
102
|
+
if keyword in lowered:
|
|
103
|
+
return keyword
|
|
104
|
+
return None
|
|
80
105
|
|
|
81
106
|
|
|
82
107
|
@dataclass
|
|
@@ -98,7 +123,12 @@ class RosettaConfig:
|
|
|
98
123
|
RAGFLOW_PAGE_SIZE: Page size for listing operations (default: 1000)
|
|
99
124
|
RAGFLOW_PARSE_TIMEOUT: Timeout for parsing operations in seconds (default: 1200)
|
|
100
125
|
RAGFLOW_TIMEOUT: HTTP request timeout in seconds (default: 30)
|
|
101
|
-
ENVIRONMENT: Environment
|
|
126
|
+
ENVIRONMENT: Environment display label. Takes precedence over URL keyword
|
|
127
|
+
detection, but not over an explicit --env argument. When unset, the
|
|
128
|
+
label is detected from RAGFLOW_BASE_URL by a case-insensitive scan for
|
|
129
|
+
the first of preprod, prod, stag, qa, dev, test, uat, sandbox, perf to
|
|
130
|
+
appear in it (the resolved value is that keyword itself, so
|
|
131
|
+
"https://ims-dev.example.com" yields "dev"); "local" if none appear.
|
|
102
132
|
|
|
103
133
|
Examples:
|
|
104
134
|
>>> config = RosettaConfig.from_env(".env")
|
|
@@ -194,9 +224,11 @@ class RosettaConfig:
|
|
|
194
224
|
Args:
|
|
195
225
|
environment: Optional explicit environment name (e.g., "local",
|
|
196
226
|
"dev", "remote"). If provided, this value is used and takes
|
|
197
|
-
precedence over
|
|
198
|
-
|
|
199
|
-
|
|
227
|
+
precedence over everything else. If not provided, the
|
|
228
|
+
ENVIRONMENT variable is used; failing that, the label is
|
|
229
|
+
detected from a keyword in RAGFLOW_BASE_URL (see
|
|
230
|
+
ENVIRONMENT_KEYWORDS), defaulting to "local" when no keyword
|
|
231
|
+
appears.
|
|
200
232
|
|
|
201
233
|
Returns:
|
|
202
234
|
RosettaConfig instance
|
|
@@ -213,9 +245,11 @@ class RosettaConfig:
|
|
|
213
245
|
api_key = os.getenv("RAGFLOW_API_KEY", "")
|
|
214
246
|
dataset_default = os.getenv("RAGFLOW_DATASET_DEFAULT", "aia")
|
|
215
247
|
dataset_template = os.getenv("RAGFLOW_DATASET_TEMPLATE", "aia-{release}")
|
|
216
|
-
#
|
|
248
|
+
# Environment is a display label only - nothing branches on its value.
|
|
249
|
+
# Precedence: explicit argument (--env) > ENVIRONMENT > keyword detected in
|
|
250
|
+
# the RAGFlow URL > "local".
|
|
217
251
|
if not environment:
|
|
218
|
-
environment = os.getenv("ENVIRONMENT") or
|
|
252
|
+
environment = os.getenv("ENVIRONMENT") or _environment_from_url(base_url) or "local"
|
|
219
253
|
|
|
220
254
|
# Dataset creation settings
|
|
221
255
|
embedding_model = os.getenv("RAGFLOW_EMBEDDING_MODEL") or None
|
|
@@ -641,13 +641,16 @@ class ContentPublisher:
|
|
|
641
641
|
return name[: -len(path.name)] + new_name
|
|
642
642
|
|
|
643
643
|
# Collect all docs to delete into one list: (doc, label)
|
|
644
|
-
# Start with
|
|
644
|
+
# Start with managed docs carrying incomplete metadata: original_path is
|
|
645
|
+
# set (so the doc is ours) but ims_doc_id is absent. A doc with no
|
|
646
|
+
# original_path is custom/unmanaged and is never touched - see the
|
|
647
|
+
# docstring guarantee, which _cleanup_orphans enforces the same way.
|
|
645
648
|
duplicates: list[tuple[DocumentLike, str]] = []
|
|
646
649
|
for doc in all_docs:
|
|
647
650
|
meta = getattr(doc, "meta_fields", {}) or {}
|
|
648
651
|
ims_doc_id = meta.get("ims_doc_id") if isinstance(meta, dict) else getattr(meta, "ims_doc_id", None)
|
|
649
652
|
doc_original_path = meta.get("original_path", "") if isinstance(meta, dict) else getattr(meta, "original_path", "")
|
|
650
|
-
if
|
|
653
|
+
if doc_original_path and not ims_doc_id:
|
|
651
654
|
doc_name = getattr(doc, "name", "") or doc.id
|
|
652
655
|
duplicates.append((doc, doc_name))
|
|
653
656
|
|
|
@@ -674,8 +677,10 @@ class ContentPublisher:
|
|
|
674
677
|
duplicates.append((doc, original_path))
|
|
675
678
|
|
|
676
679
|
# Name duplicates: foo.md + foo(1).md + foo(2).md ...
|
|
680
|
+
# Grouped over managed_docs, not all_docs: a custom doc whose stripped
|
|
681
|
+
# name happens to collide with a managed one must not be deleted.
|
|
677
682
|
name_groups: dict[str, list[DocumentLike]] = defaultdict(list)
|
|
678
|
-
for doc in
|
|
683
|
+
for doc in managed_docs:
|
|
679
684
|
doc_name = getattr(doc, "name", "") or ""
|
|
680
685
|
if not doc_name:
|
|
681
686
|
continue
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|