rosetta-cli 3.0.4__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.4/rosetta_cli.egg-info → rosetta_cli-3.0.6}/PKG-INFO +1 -1
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/pyproject.toml +1 -1
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/cli.py +2 -1
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/cleanup_command.py +10 -1
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/parse_command.py +43 -44
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/ragflow_client.py +33 -15
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_config.py +49 -15
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_publisher.py +8 -3
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/services/dataset_service.py +16 -3
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6/rosetta_cli.egg-info}/PKG-INFO +1 -1
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/MANIFEST.in +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/README.md +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/env.template +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/__init__.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/__main__.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/__init__.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/base_command.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/list_command.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/publish_command.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/commands/verify_command.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_auth.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/rosetta_utils.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/services/__init__.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/services/auth_service.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/services/document_data.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/services/document_service.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli/typing_utils.py +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/SOURCES.txt +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/dependency_links.txt +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/entry_points.txt +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/requires.txt +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/rosetta_cli.egg-info/top_level.txt +0 -0
- {rosetta_cli-3.0.4 → rosetta_cli-3.0.6}/setup.cfg +0 -0
|
@@ -378,7 +378,8 @@ Frontmatter Metadata (publish flow):
|
|
|
378
378
|
chunk_method=config.chunk_method,
|
|
379
379
|
parser_config=config.parser_config,
|
|
380
380
|
page_size=config.page_size,
|
|
381
|
-
|
|
381
|
+
dataset_default=config.dataset_default,
|
|
382
|
+
timeout=config.timeout,
|
|
382
383
|
)
|
|
383
384
|
|
|
384
385
|
return execute_command(args.command, args, client, config)
|
|
@@ -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
|
|
|
@@ -116,7 +116,8 @@ class RAGFlowClient:
|
|
|
116
116
|
embedding_model: str | None = None,
|
|
117
117
|
chunk_method: str = "naive",
|
|
118
118
|
parser_config: JsonDict | None = None,
|
|
119
|
-
page_size: int = 1000
|
|
119
|
+
page_size: int = 1000,
|
|
120
|
+
dataset_default: str = "aia",
|
|
120
121
|
):
|
|
121
122
|
"""
|
|
122
123
|
Initialize RAGFlow client.
|
|
@@ -130,6 +131,7 @@ class RAGFlowClient:
|
|
|
130
131
|
chunk_method: Chunking method (default: naive)
|
|
131
132
|
parser_config: Parser configuration dict for chunk_method settings
|
|
132
133
|
page_size: Default page size for list operations (default: 1000)
|
|
134
|
+
dataset_default: Default dataset name when no release-specific or explicit dataset name is available
|
|
133
135
|
|
|
134
136
|
Raises:
|
|
135
137
|
ValueError: If api_key or base_url is empty
|
|
@@ -147,6 +149,7 @@ class RAGFlowClient:
|
|
|
147
149
|
self.chunk_method = chunk_method
|
|
148
150
|
self.parser_config = parser_config or {}
|
|
149
151
|
self.page_size = page_size
|
|
152
|
+
self.dataset_default = dataset_default
|
|
150
153
|
|
|
151
154
|
# Initialize RAGFlow SDK client
|
|
152
155
|
self._client = RAGFlow(api_key=api_key, base_url=base_url, version=version)
|
|
@@ -469,26 +472,38 @@ class RAGFlowClient:
|
|
|
469
472
|
# Dataset doesn't exist, create it (gated by dry_run)
|
|
470
473
|
return self.create_dataset(name, description, dry_run=dry_run)
|
|
471
474
|
|
|
472
|
-
def _resolve_dataset_name(
|
|
475
|
+
def _resolve_dataset_name(
|
|
476
|
+
self,
|
|
477
|
+
dataset_template: str,
|
|
478
|
+
release: str | None,
|
|
479
|
+
dataset_name: str | None,
|
|
480
|
+
) -> str:
|
|
473
481
|
"""
|
|
474
|
-
Resolve dataset name
|
|
482
|
+
Resolve dataset name using release + dataset_template, explicit dataset_name or default.
|
|
475
483
|
|
|
476
484
|
Args:
|
|
477
|
-
|
|
485
|
+
dataset_template: Name template (e.g., "aia-{release}")
|
|
478
486
|
release: Release identifier (e.g., "r1")
|
|
487
|
+
dataset_name: Explicit dataset name, if provided (e.g., "aia-r1")
|
|
479
488
|
|
|
480
489
|
Returns:
|
|
481
490
|
Resolved dataset name
|
|
482
491
|
|
|
483
492
|
Examples:
|
|
484
|
-
>>> _resolve_dataset_name("aia-{release}", "r1")
|
|
493
|
+
>>> _resolve_dataset_name("aia-{release}", "r1", "aia-r2")
|
|
485
494
|
"aia-r1"
|
|
486
|
-
>>> _resolve_dataset_name("aia", None)
|
|
487
|
-
"aia"
|
|
495
|
+
>>> _resolve_dataset_name("aia-{release}", None, "aia-r2")
|
|
496
|
+
"aia-r2"
|
|
488
497
|
"""
|
|
489
|
-
if release and "{release}" in
|
|
490
|
-
|
|
491
|
-
|
|
498
|
+
if release and "{release}" in dataset_template:
|
|
499
|
+
resolved_name = dataset_template.format(release=release)
|
|
500
|
+
else:
|
|
501
|
+
resolved_name = dataset_name or self.dataset_default
|
|
502
|
+
|
|
503
|
+
if "{release}" in resolved_name:
|
|
504
|
+
raise ValueError(f"Unresolved release placeholder in dataset name: {resolved_name}")
|
|
505
|
+
|
|
506
|
+
return resolved_name
|
|
492
507
|
|
|
493
508
|
def _build_title_with_tags(self, tags: list[str], filename: str) -> str:
|
|
494
509
|
"""
|
|
@@ -521,7 +536,7 @@ class RAGFlowClient:
|
|
|
521
536
|
OPTIMIZED: Now accepts pre-read content to avoid redundant file I/O.
|
|
522
537
|
|
|
523
538
|
This method:
|
|
524
|
-
1. Resolves dataset name
|
|
539
|
+
1. Resolves the target dataset name
|
|
525
540
|
2. Ensures dataset exists
|
|
526
541
|
3. Builds title with tag prefixes
|
|
527
542
|
4. Checks if document exists (by ims_doc_id)
|
|
@@ -529,6 +544,12 @@ class RAGFlowClient:
|
|
|
529
544
|
6. Deletes existing document if changed
|
|
530
545
|
7. Uploads new document with metadata
|
|
531
546
|
|
|
547
|
+
Dataset name resolution precedence:
|
|
548
|
+
1. If metadata.release is present, and dataset_template contains "{release}",
|
|
549
|
+
resolve dataset_template with the release.
|
|
550
|
+
2. Otherwise, use dataset_name when provided.
|
|
551
|
+
3. Otherwise, fall back to dataset_default.
|
|
552
|
+
|
|
532
553
|
Args:
|
|
533
554
|
file_path: Path to file (for filename, backward compatibility)
|
|
534
555
|
metadata: Document metadata with pre-calculated hash
|
|
@@ -567,10 +588,7 @@ class RAGFlowClient:
|
|
|
567
588
|
actual_hash = metadata.content_hash
|
|
568
589
|
|
|
569
590
|
# Resolve dataset name
|
|
570
|
-
resolved_name = self._resolve_dataset_name(
|
|
571
|
-
dataset_template if "{release}" in dataset_template else (dataset_name or dataset_template),
|
|
572
|
-
metadata.release
|
|
573
|
-
)
|
|
591
|
+
resolved_name = self._resolve_dataset_name(dataset_template, metadata.release, dataset_name)
|
|
574
592
|
|
|
575
593
|
# Ensure dataset exists (dry_run gates the underlying create_dataset call)
|
|
576
594
|
dataset = self._ensure_dataset(
|
|
@@ -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
|
|
@@ -27,14 +27,27 @@ class DatasetService:
|
|
|
27
27
|
|
|
28
28
|
def resolve_dataset_name(self, args_dataset: Optional[str]) -> tuple[Optional[str], bool]:
|
|
29
29
|
"""
|
|
30
|
-
Resolve dataset
|
|
31
|
-
|
|
30
|
+
Resolve which dataset to act on.
|
|
31
|
+
|
|
32
|
+
How the name is picked:
|
|
33
|
+
1. --dataset given -> use it (False)
|
|
34
|
+
2. else list server datasets, keep those starting with the template
|
|
35
|
+
prefix ("aia-" from "aia-{release}")
|
|
36
|
+
3. exactly one match -> use it (True)
|
|
37
|
+
4. several matches -> give up, ask user (None, False)
|
|
38
|
+
5. no matches -> use dataset_default (True)
|
|
39
|
+
|
|
32
40
|
Args:
|
|
33
41
|
args_dataset: Dataset name from arguments (can be None)
|
|
34
|
-
|
|
42
|
+
|
|
35
43
|
Returns:
|
|
36
44
|
Tuple of (dataset_name: str or None, auto_detected: bool)
|
|
37
45
|
Returns (None, False) if resolution fails
|
|
46
|
+
|
|
47
|
+
auto_detected answers "did the caller supply this name?", not "did
|
|
48
|
+
we find it on the server". False only for step 1. True for steps 3
|
|
49
|
+
and 5 alike: the caller supplied nothing and this method chose.
|
|
50
|
+
True on step 5 is correct.
|
|
38
51
|
"""
|
|
39
52
|
# Explicit dataset provided
|
|
40
53
|
if args_dataset:
|
|
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
|