scitex 2.14.0__py3-none-any.whl → 2.15.3__py3-none-any.whl
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.
- scitex/__init__.py +71 -17
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +210 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +27 -0
- scitex/_mcp_tools/template.py +24 -0
- scitex/_mcp_tools/writer.py +17 -210
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +129 -61
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +7 -2
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +160 -41
- scitex/cli/capture.py +133 -20
- scitex/cli/introspect.py +488 -0
- scitex/cli/main.py +200 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/plt.py +414 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +154 -8
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +355 -0
- scitex/cli/stats.py +136 -11
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +49 -299
- scitex/cloud/__init__.py +41 -2
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +256 -0
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +82 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +41 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
- scitex/introspect/_mcp/__init__.py +41 -0
- scitex/introspect/_mcp/handlers.py +233 -0
- scitex/introspect/_members.py +155 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/_save.py +1 -2
- scitex/io/bundle/README.md +1 -1
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +98 -5
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +245 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/README.md +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +155 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/README.md +1 -1
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/template/clone_writer_directory.py +5 -5
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- scitex/writer/README.md +1 -1
- scitex/writer/__init__.py +43 -34
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.3.dist-info/METADATA +667 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/scholar/storage/_mixins/_symlink_handlers.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Symlink handling mixin for LibraryManager.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from scitex import logging
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SymlinkHandlersMixin:
|
|
22
|
+
"""Mixin providing symlink handling methods."""
|
|
23
|
+
|
|
24
|
+
def _generate_readable_name(
|
|
25
|
+
self,
|
|
26
|
+
comprehensive_metadata: Dict,
|
|
27
|
+
master_storage_path: Path,
|
|
28
|
+
authors: Optional[List[str]] = None,
|
|
29
|
+
year: Optional[int] = None,
|
|
30
|
+
journal: Optional[str] = None,
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Generate readable symlink name from metadata."""
|
|
33
|
+
from scitex.dict import DotDict
|
|
34
|
+
|
|
35
|
+
# Extract author
|
|
36
|
+
first_author = "Unknown"
|
|
37
|
+
if authors and len(authors) > 0:
|
|
38
|
+
author_parts = authors[0].split()
|
|
39
|
+
first_author = (
|
|
40
|
+
author_parts[-1] if len(author_parts) > 1 else author_parts[0]
|
|
41
|
+
)
|
|
42
|
+
first_author = "".join(c for c in first_author if c.isalnum() or c == "-")[
|
|
43
|
+
:20
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Format year
|
|
47
|
+
if isinstance(year, DotDict):
|
|
48
|
+
year = None
|
|
49
|
+
|
|
50
|
+
if isinstance(year, str) and year.isdigit():
|
|
51
|
+
year = int(year)
|
|
52
|
+
|
|
53
|
+
year_str = f"{year:04d}" if isinstance(year, int) else "0000"
|
|
54
|
+
|
|
55
|
+
# Clean journal name
|
|
56
|
+
journal_clean = "Unknown"
|
|
57
|
+
if journal:
|
|
58
|
+
journal_clean = self.config.path_manager._sanitize_filename(journal)[:30]
|
|
59
|
+
if not journal_clean:
|
|
60
|
+
journal_clean = "Unknown"
|
|
61
|
+
|
|
62
|
+
# Get citation count and impact factor
|
|
63
|
+
cc, if_val = self._extract_cc_and_if(comprehensive_metadata)
|
|
64
|
+
|
|
65
|
+
# Count PDFs
|
|
66
|
+
pdf_files = list(master_storage_path.glob("*.pdf"))
|
|
67
|
+
n_pdfs = len(pdf_files)
|
|
68
|
+
|
|
69
|
+
readable_name = f"PDF-{n_pdfs:02d}_CC-{cc:06d}_IF-{int(if_val):03d}_{year_str}_{first_author}_{journal_clean}"
|
|
70
|
+
return readable_name
|
|
71
|
+
|
|
72
|
+
def _extract_cc_and_if(self, comprehensive_metadata: Dict) -> tuple:
|
|
73
|
+
"""Extract citation count and impact factor from metadata."""
|
|
74
|
+
if "metadata" in comprehensive_metadata:
|
|
75
|
+
metadata_section = comprehensive_metadata.get("metadata", {})
|
|
76
|
+
cc_val = metadata_section.get("citation_count", {})
|
|
77
|
+
if isinstance(cc_val, dict):
|
|
78
|
+
cc = cc_val.get("total", 0) or 0
|
|
79
|
+
else:
|
|
80
|
+
cc = cc_val or 0
|
|
81
|
+
|
|
82
|
+
publication_section = metadata_section.get("publication", {})
|
|
83
|
+
if_val = publication_section.get("impact_factor", 0.0) or 0.0
|
|
84
|
+
else:
|
|
85
|
+
cc_val = comprehensive_metadata.get("citation_count", 0)
|
|
86
|
+
if isinstance(cc_val, dict):
|
|
87
|
+
cc = cc_val.get("total", 0) or 0
|
|
88
|
+
else:
|
|
89
|
+
cc = cc_val or 0
|
|
90
|
+
|
|
91
|
+
if_val = (
|
|
92
|
+
comprehensive_metadata.get("journal_impact_factor")
|
|
93
|
+
or comprehensive_metadata.get("impact_factor")
|
|
94
|
+
or comprehensive_metadata.get("publication", {}).get("impact_factor")
|
|
95
|
+
)
|
|
96
|
+
if isinstance(if_val, dict):
|
|
97
|
+
if_val = if_val.get("value", 0.0) or 0.0
|
|
98
|
+
else:
|
|
99
|
+
if_val = if_val or 0.0
|
|
100
|
+
|
|
101
|
+
return cc, if_val
|
|
102
|
+
|
|
103
|
+
def update_symlink(
|
|
104
|
+
self,
|
|
105
|
+
master_storage_path: Path,
|
|
106
|
+
project: str,
|
|
107
|
+
metadata: Optional[Dict] = None,
|
|
108
|
+
) -> Optional[Path]:
|
|
109
|
+
"""Update project symlink to reflect current paper status."""
|
|
110
|
+
try:
|
|
111
|
+
if metadata is None:
|
|
112
|
+
metadata_file = master_storage_path / "metadata.json"
|
|
113
|
+
if metadata_file.exists():
|
|
114
|
+
with open(metadata_file) as f:
|
|
115
|
+
metadata = json.load(f)
|
|
116
|
+
else:
|
|
117
|
+
logger.warning(f"No metadata found for {master_storage_path.name}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# Extract metadata from nested structure if needed
|
|
121
|
+
if "metadata" in metadata:
|
|
122
|
+
meta_section = metadata.get("metadata", {})
|
|
123
|
+
basic_section = meta_section.get("basic", {})
|
|
124
|
+
pub_section = meta_section.get("publication", {})
|
|
125
|
+
authors = basic_section.get("authors")
|
|
126
|
+
year = basic_section.get("year")
|
|
127
|
+
journal = pub_section.get("journal")
|
|
128
|
+
else:
|
|
129
|
+
authors = metadata.get("authors")
|
|
130
|
+
year = metadata.get("year")
|
|
131
|
+
journal = metadata.get("journal")
|
|
132
|
+
|
|
133
|
+
readable_name = self._generate_readable_name(
|
|
134
|
+
comprehensive_metadata=metadata,
|
|
135
|
+
master_storage_path=master_storage_path,
|
|
136
|
+
authors=authors,
|
|
137
|
+
year=year,
|
|
138
|
+
journal=journal,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return self._create_project_symlink(
|
|
142
|
+
master_storage_path=master_storage_path,
|
|
143
|
+
project=project,
|
|
144
|
+
readable_name=readable_name,
|
|
145
|
+
)
|
|
146
|
+
except Exception as exc_:
|
|
147
|
+
logger.error(
|
|
148
|
+
f"Failed to update symlink for {master_storage_path.name}: {exc_}"
|
|
149
|
+
)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _create_project_symlink(
|
|
153
|
+
self, master_storage_path: Path, project: str, readable_name: str
|
|
154
|
+
) -> Optional[Path]:
|
|
155
|
+
"""Create symlink in project directory pointing to master storage."""
|
|
156
|
+
try:
|
|
157
|
+
project_dir = self.config.path_manager.get_library_project_dir(project)
|
|
158
|
+
symlink_path = project_dir / readable_name
|
|
159
|
+
master_id = master_storage_path.name
|
|
160
|
+
|
|
161
|
+
# Remove old symlinks pointing to the same master entry
|
|
162
|
+
for existing_link in project_dir.iterdir():
|
|
163
|
+
if not existing_link.is_symlink():
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
target = existing_link.resolve()
|
|
168
|
+
if target.name == master_id and existing_link.name != readable_name:
|
|
169
|
+
logger.debug(f"Removing old symlink: {existing_link.name}")
|
|
170
|
+
existing_link.unlink()
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.debug(f"Skipping broken symlink {existing_link.name}: {e}")
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# Create new symlink
|
|
176
|
+
if not symlink_path.exists():
|
|
177
|
+
relative_path = os.path.relpath(master_storage_path, project_dir)
|
|
178
|
+
symlink_path.symlink_to(relative_path)
|
|
179
|
+
logger.success(
|
|
180
|
+
f"Created project symlink: {symlink_path} -> {relative_path}"
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
logger.debug(f"Project symlink already exists: {symlink_path}")
|
|
184
|
+
|
|
185
|
+
return symlink_path
|
|
186
|
+
|
|
187
|
+
except Exception as exc_:
|
|
188
|
+
logger.warning(f"Failed to create project symlink: {exc_}")
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def _ensure_project_symlink(
|
|
192
|
+
self,
|
|
193
|
+
title: str,
|
|
194
|
+
year: Optional[int] = None,
|
|
195
|
+
authors: Optional[List[str]] = None,
|
|
196
|
+
paper_id: str = None,
|
|
197
|
+
master_storage_path: Path = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Ensure project symlink exists for paper in master library."""
|
|
200
|
+
try:
|
|
201
|
+
if not paper_id or not master_storage_path:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
project_lib_path = (
|
|
205
|
+
self.config.path_manager.get_scholar_library_path() / self.project
|
|
206
|
+
)
|
|
207
|
+
project_lib_path.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
|
|
209
|
+
paper_info = {"title": title, "year": year, "authors": authors or []}
|
|
210
|
+
readable_paths = self._call_path_manager_get_storage_paths(
|
|
211
|
+
paper_info=paper_info, collection_name=self.project
|
|
212
|
+
)
|
|
213
|
+
readable_name = readable_paths["readable_name"]
|
|
214
|
+
symlink_path = project_lib_path / readable_name
|
|
215
|
+
relative_path = f"../MASTER/{paper_id}"
|
|
216
|
+
|
|
217
|
+
if not symlink_path.exists():
|
|
218
|
+
symlink_path.symlink_to(relative_path)
|
|
219
|
+
logger.info(
|
|
220
|
+
f"Created project symlink: {readable_name} -> {relative_path}"
|
|
221
|
+
)
|
|
222
|
+
except Exception as exc_:
|
|
223
|
+
logger.debug(f"Error creating project symlink: {exc_}")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# EOF
|
scitex/security/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Reusable security utilities for the SciTeX ecosystem. Works everywhere: local, c
|
|
|
14
14
|
The module is part of the `scitex` package (editable install):
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
cd ~/proj/scitex-
|
|
17
|
+
cd ~/proj/scitex-python
|
|
18
18
|
pip install -e .
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -162,7 +162,7 @@ Total open alerts: 2
|
|
|
162
162
|
```yaml
|
|
163
163
|
- name: Check Security
|
|
164
164
|
run: |
|
|
165
|
-
pip install -e ~/proj/scitex-
|
|
165
|
+
pip install -e ~/proj/scitex-python
|
|
166
166
|
python -m scitex.security.cli check --save
|
|
167
167
|
```
|
|
168
168
|
|
|
@@ -245,7 +245,7 @@ Get path to latest alerts file.
|
|
|
245
245
|
## Testing
|
|
246
246
|
|
|
247
247
|
```bash
|
|
248
|
-
cd ~/proj/scitex-
|
|
248
|
+
cd ~/proj/scitex-python
|
|
249
249
|
pytest tests/test_security.py
|
|
250
250
|
```
|
|
251
251
|
|
scitex/session/README.md
CHANGED
scitex/session/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-08-21 20:36:45 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/SciTeX-Code/src/scitex/session/__init__.py
|
|
5
4
|
# ----------------------------------------
|
|
6
5
|
from __future__ import annotations
|
|
6
|
+
|
|
7
7
|
import os
|
|
8
8
|
|
|
9
9
|
__FILE__ = __file__
|
|
@@ -20,27 +20,46 @@ Usage:
|
|
|
20
20
|
import sys
|
|
21
21
|
import matplotlib.pyplot as plt
|
|
22
22
|
from scitex import session
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Start a session
|
|
25
25
|
CONFIG, sys.stdout, sys.stderr, plt, COLORS, rng = session.start(sys, plt)
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Your experiment code here
|
|
28
|
-
|
|
29
|
-
# Close the session
|
|
28
|
+
|
|
29
|
+
# Close the session
|
|
30
30
|
session.close(CONFIG)
|
|
31
31
|
|
|
32
32
|
# Session manager for advanced use cases
|
|
33
33
|
manager = session.SessionManager()
|
|
34
34
|
active_sessions = manager.get_active_sessions()
|
|
35
|
+
|
|
36
|
+
# Using INJECTED sentinel for decorator parameters
|
|
37
|
+
@stx.session
|
|
38
|
+
def main(CONFIG=stx.session.INJECTED, plt=stx.session.INJECTED):
|
|
39
|
+
...
|
|
35
40
|
"""
|
|
36
41
|
|
|
42
|
+
|
|
43
|
+
# Sentinel object for decorator-injected parameters
|
|
44
|
+
class _InjectedSentinel:
|
|
45
|
+
"""Sentinel value indicating a parameter will be injected by a decorator."""
|
|
46
|
+
|
|
47
|
+
def __repr__(self):
|
|
48
|
+
return "<INJECTED>"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
INJECTED = _InjectedSentinel()
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
# Import session management functionality
|
|
55
|
+
from ._decorator import run, session
|
|
56
|
+
from ._lifecycle import close, running2finished, start
|
|
38
57
|
from ._manager import SessionManager
|
|
39
|
-
from ._lifecycle import start, close, running2finished
|
|
40
|
-
from ._decorator import session, run
|
|
41
58
|
|
|
42
59
|
# Export public API
|
|
43
60
|
__all__ = [
|
|
61
|
+
# Sentinel for injected parameters
|
|
62
|
+
"INJECTED",
|
|
44
63
|
# Session lifecycle (main functions)
|
|
45
64
|
"start",
|
|
46
65
|
"close",
|
scitex/session/_decorator.py
CHANGED
|
@@ -21,7 +21,7 @@ import sys as sys_module
|
|
|
21
21
|
|
|
22
22
|
from ._lifecycle import start, close
|
|
23
23
|
from scitex.logging import getLogger
|
|
24
|
-
from
|
|
24
|
+
from . import INJECTED # Use local INJECTED from session module
|
|
25
25
|
|
|
26
26
|
# Internal logger for the decorator itself
|
|
27
27
|
_decorator_logger = getLogger(__name__)
|
scitex/sh/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- ---
|
|
2
2
|
!-- Timestamp: 2025-10-29 07:23:56
|
|
3
3
|
!-- Author: ywatanabe
|
|
4
|
-
!-- File: /home/ywatanabe/proj/scitex-
|
|
4
|
+
!-- File: /home/ywatanabe/proj/scitex-python/src/scitex/sh/README.md
|
|
5
5
|
!-- --- -->
|
|
6
6
|
|
|
7
7
|
# scitex.sh - Shell Command Execution Module
|
scitex/sh/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
from __future__ import annotations
|
|
3
|
+
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
6
|
__FILE__ = __file__
|
|
@@ -8,9 +8,9 @@ __DIR__ = os.path.dirname(__FILE__)
|
|
|
8
8
|
|
|
9
9
|
from typing import Union
|
|
10
10
|
|
|
11
|
-
from ._types import CommandInput, ReturnFormat, ShellResult
|
|
12
|
-
from ._security import quote, validate_command
|
|
13
11
|
from ._execute import execute
|
|
12
|
+
from ._security import quote, validate_command
|
|
13
|
+
from ._types import CommandInput, ReturnFormat, ShellResult
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def sh(
|
|
@@ -88,6 +88,9 @@ def sh_run(command: CommandInput, verbose: bool = True) -> ShellResult:
|
|
|
88
88
|
return execute(command, verbose=verbose)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
# Legacy functions moved from gen module
|
|
92
|
+
from ._shell_legacy import run_shellcommand, run_shellscript
|
|
93
|
+
|
|
94
|
+
__all__ = ["sh", "sh_run", "quote", "run_shellcommand", "run_shellscript"]
|
|
92
95
|
|
|
93
96
|
# EOF
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-22
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/social/__init__.py
|
|
4
|
+
|
|
5
|
+
"""SciTeX Social - Unified social media management.
|
|
6
|
+
|
|
7
|
+
This module provides a thin wrapper around socialia, the core social media
|
|
8
|
+
integration package. It uses scitex branding and environment variable prefixes.
|
|
9
|
+
|
|
10
|
+
Features
|
|
11
|
+
--------
|
|
12
|
+
- Twitter/X posting and analytics
|
|
13
|
+
- LinkedIn posting
|
|
14
|
+
- Reddit posting
|
|
15
|
+
- YouTube analytics
|
|
16
|
+
- Google Analytics integration
|
|
17
|
+
|
|
18
|
+
Environment Variables
|
|
19
|
+
---------------------
|
|
20
|
+
Credentials use SCITEX_SOCIAL_ prefix (falls back to SOCIALIA_):
|
|
21
|
+
- SCITEX_SOCIAL_X_CONSUMER_KEY, SCITEX_SOCIAL_X_CONSUMER_KEY_SECRET
|
|
22
|
+
- SCITEX_SOCIAL_X_ACCESS_TOKEN, SCITEX_SOCIAL_X_ACCESS_TOKEN_SECRET
|
|
23
|
+
- SCITEX_SOCIAL_X_BEARER_TOKEN
|
|
24
|
+
- SCITEX_SOCIAL_LINKEDIN_CLIENT_ID, SCITEX_SOCIAL_LINKEDIN_CLIENT_SECRET
|
|
25
|
+
- SCITEX_SOCIAL_LINKEDIN_ACCESS_TOKEN
|
|
26
|
+
- SCITEX_SOCIAL_REDDIT_CLIENT_ID, SCITEX_SOCIAL_REDDIT_CLIENT_SECRET
|
|
27
|
+
- SCITEX_SOCIAL_YOUTUBE_API_KEY
|
|
28
|
+
- SCITEX_SOCIAL_GOOGLE_ANALYTICS_PROPERTY_ID
|
|
29
|
+
|
|
30
|
+
Usage
|
|
31
|
+
-----
|
|
32
|
+
import scitex as stx
|
|
33
|
+
|
|
34
|
+
# Twitter/X
|
|
35
|
+
x = stx.social.Twitter()
|
|
36
|
+
x.post("Hello from SciTeX!")
|
|
37
|
+
|
|
38
|
+
# LinkedIn
|
|
39
|
+
linkedin = stx.social.LinkedIn()
|
|
40
|
+
linkedin.post("Research update", visibility="public")
|
|
41
|
+
|
|
42
|
+
# YouTube analytics
|
|
43
|
+
yt = stx.social.YouTube()
|
|
44
|
+
stats = yt.get_channel_stats()
|
|
45
|
+
|
|
46
|
+
# Google Analytics
|
|
47
|
+
ga = stx.social.GoogleAnalytics()
|
|
48
|
+
report = ga.get_report(start_date="7daysAgo")
|
|
49
|
+
|
|
50
|
+
See Also
|
|
51
|
+
--------
|
|
52
|
+
- socialia: https://github.com/ywatanabe1989/socialia
|
|
53
|
+
- scitex: https://scitex.ai
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
import os as _os
|
|
57
|
+
|
|
58
|
+
# Set branding BEFORE importing socialia
|
|
59
|
+
_os.environ.setdefault("SOCIALIA_BRAND", "scitex.social")
|
|
60
|
+
_os.environ.setdefault("SOCIALIA_ENV_PREFIX", "SCITEX_SOCIAL")
|
|
61
|
+
|
|
62
|
+
# Check socialia availability
|
|
63
|
+
try:
|
|
64
|
+
import socialia as _socialia
|
|
65
|
+
|
|
66
|
+
# Re-export platform clients
|
|
67
|
+
from socialia import (
|
|
68
|
+
# Content strategies for MCP
|
|
69
|
+
PLATFORM_STRATEGIES,
|
|
70
|
+
# Base class
|
|
71
|
+
BasePoster,
|
|
72
|
+
GoogleAnalytics,
|
|
73
|
+
LinkedIn,
|
|
74
|
+
LinkedInPoster,
|
|
75
|
+
Reddit,
|
|
76
|
+
RedditPoster,
|
|
77
|
+
# Platform clients (preferred names)
|
|
78
|
+
Twitter,
|
|
79
|
+
# Backward compatibility aliases
|
|
80
|
+
TwitterPoster,
|
|
81
|
+
YouTube,
|
|
82
|
+
YouTubePoster,
|
|
83
|
+
)
|
|
84
|
+
from socialia import __version__ as _socialia_version
|
|
85
|
+
|
|
86
|
+
SOCIALIA_AVAILABLE = True
|
|
87
|
+
__socialia_version__ = _socialia_version
|
|
88
|
+
|
|
89
|
+
except ImportError:
|
|
90
|
+
SOCIALIA_AVAILABLE = False
|
|
91
|
+
__socialia_version__ = None
|
|
92
|
+
|
|
93
|
+
# Provide helpful error on access
|
|
94
|
+
class _SocialiaNotAvailable:
|
|
95
|
+
"""Placeholder when socialia is not installed."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, *args, **kwargs):
|
|
98
|
+
raise ImportError(
|
|
99
|
+
"socialia is required for scitex.social. "
|
|
100
|
+
"Install with: pip install socialia"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def __getattr__(self, name):
|
|
104
|
+
raise ImportError(
|
|
105
|
+
"socialia is required for scitex.social. "
|
|
106
|
+
"Install with: pip install socialia"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
BasePoster = _SocialiaNotAvailable
|
|
110
|
+
Twitter = _SocialiaNotAvailable
|
|
111
|
+
LinkedIn = _SocialiaNotAvailable
|
|
112
|
+
Reddit = _SocialiaNotAvailable
|
|
113
|
+
YouTube = _SocialiaNotAvailable
|
|
114
|
+
GoogleAnalytics = _SocialiaNotAvailable
|
|
115
|
+
TwitterPoster = _SocialiaNotAvailable
|
|
116
|
+
LinkedInPoster = _SocialiaNotAvailable
|
|
117
|
+
RedditPoster = _SocialiaNotAvailable
|
|
118
|
+
YouTubePoster = _SocialiaNotAvailable
|
|
119
|
+
PLATFORM_STRATEGIES = ""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def has_socialia() -> bool:
|
|
123
|
+
"""Check if socialia is available.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
bool
|
|
128
|
+
True if socialia is installed and importable.
|
|
129
|
+
"""
|
|
130
|
+
return SOCIALIA_AVAILABLE
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = [
|
|
134
|
+
# Availability check
|
|
135
|
+
"SOCIALIA_AVAILABLE",
|
|
136
|
+
"has_socialia",
|
|
137
|
+
"__socialia_version__",
|
|
138
|
+
# Base class
|
|
139
|
+
"BasePoster",
|
|
140
|
+
# Platform clients (preferred names)
|
|
141
|
+
"Twitter",
|
|
142
|
+
"LinkedIn",
|
|
143
|
+
"Reddit",
|
|
144
|
+
"YouTube",
|
|
145
|
+
"GoogleAnalytics",
|
|
146
|
+
# Backward compatibility aliases
|
|
147
|
+
"TwitterPoster",
|
|
148
|
+
"LinkedInPoster",
|
|
149
|
+
"RedditPoster",
|
|
150
|
+
"YouTubePoster",
|
|
151
|
+
# Content strategies
|
|
152
|
+
"PLATFORM_STRATEGIES",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# EOF
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# External Package Branding Guide
|
|
2
|
+
|
|
3
|
+
When scitex wraps external packages (like `figrecipe` for `scitex.plt` or `crossref-local` for `scitex.scholar`), those packages should support configurable branding so documentation and error messages show the scitex namespace.
|
|
4
|
+
|
|
5
|
+
## When to Use Branding
|
|
6
|
+
|
|
7
|
+
- **Use branding**: External packages that scitex wraps (figrecipe, crossref-local, etc.)
|
|
8
|
+
- **Don't use branding**: Internal scitex modules (scitex.audio, scitex.stats, etc.) - just hardcode `SCITEX_*` prefix
|
|
9
|
+
|
|
10
|
+
## Pattern Overview
|
|
11
|
+
|
|
12
|
+
The external package provides a `_branding.py` module that:
|
|
13
|
+
1. Reads brand name from environment variable
|
|
14
|
+
2. Derives environment variable prefix from brand name
|
|
15
|
+
3. Provides helper functions for rebranding text/docstrings
|
|
16
|
+
|
|
17
|
+
## Implementation Template
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# external_package/_branding.py
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
# Environment variables for branding
|
|
27
|
+
# Parent package sets these before importing
|
|
28
|
+
BRAND_NAME = os.environ.get("{PACKAGE}_BRAND", "{package}")
|
|
29
|
+
BRAND_ALIAS = os.environ.get("{PACKAGE}_ALIAS", "{alias}")
|
|
30
|
+
|
|
31
|
+
# Original values for replacement
|
|
32
|
+
_ORIGINAL_NAME = "{package}"
|
|
33
|
+
_ORIGINAL_ALIAS = "{alias}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _brand_to_env_prefix(brand: str) -> str:
|
|
37
|
+
"""Convert brand name to environment variable prefix.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
"figrecipe" -> "FIGRECIPE"
|
|
41
|
+
"scitex.plt" -> "SCITEX_PLT"
|
|
42
|
+
"crossref-local" -> "CROSSREF_LOCAL"
|
|
43
|
+
"""
|
|
44
|
+
return brand.upper().replace(".", "_").replace("-", "_")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Environment variable prefix based on brand
|
|
48
|
+
ENV_PREFIX = _brand_to_env_prefix(BRAND_NAME)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_env(key: str, default: Optional[str] = None) -> Optional[str]:
|
|
52
|
+
"""Get environment variable with brand-aware prefix.
|
|
53
|
+
|
|
54
|
+
Checks {ENV_PREFIX}_{key} first, then falls back to original prefix.
|
|
55
|
+
"""
|
|
56
|
+
value = os.environ.get(f"{ENV_PREFIX}_{key}")
|
|
57
|
+
if value is not None:
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
# Fall back to original prefix if different
|
|
61
|
+
original_prefix = _brand_to_env_prefix(_ORIGINAL_NAME)
|
|
62
|
+
if ENV_PREFIX != original_prefix:
|
|
63
|
+
value = os.environ.get(f"{original_prefix}_{key}")
|
|
64
|
+
if value is not None:
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
return default
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def rebrand_text(text: Optional[str]) -> Optional[str]:
|
|
71
|
+
"""Apply branding to a text string (docstrings, error messages)."""
|
|
72
|
+
if text is None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
if BRAND_NAME == _ORIGINAL_NAME and BRAND_ALIAS == _ORIGINAL_ALIAS:
|
|
76
|
+
return text
|
|
77
|
+
|
|
78
|
+
result = text
|
|
79
|
+
|
|
80
|
+
# Replace import statements
|
|
81
|
+
result = re.sub(
|
|
82
|
+
rf"import\s+{_ORIGINAL_NAME}\s+as\s+{_ORIGINAL_ALIAS}",
|
|
83
|
+
f"import {BRAND_NAME} as {BRAND_ALIAS}",
|
|
84
|
+
result,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Replace "from package" statements
|
|
88
|
+
result = re.sub(
|
|
89
|
+
rf"from\s+{_ORIGINAL_NAME}(\s+import|\s*\.)",
|
|
90
|
+
lambda m: f"from {BRAND_NAME}{m.group(1)}",
|
|
91
|
+
result,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_mcp_server_name() -> str:
|
|
98
|
+
"""Get the MCP server name based on branding."""
|
|
99
|
+
return BRAND_NAME.replace(".", "-")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Usage in Parent Package (scitex)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# scitex/plt/__init__.py
|
|
106
|
+
import os
|
|
107
|
+
|
|
108
|
+
# Set branding BEFORE importing the external package
|
|
109
|
+
os.environ["FIGRECIPE_BRAND"] = "scitex.plt"
|
|
110
|
+
os.environ["FIGRECIPE_ALIAS"] = "plt"
|
|
111
|
+
|
|
112
|
+
# Now import - docstrings will show scitex.plt instead of figrecipe
|
|
113
|
+
from figrecipe import *
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Port Scheme
|
|
117
|
+
|
|
118
|
+
SciTeX uses port scheme 3129X (TEX → te-ku-su → 2-9-3 in Japanese):
|
|
119
|
+
|
|
120
|
+
| Port | Service |
|
|
121
|
+
|-------|------------------|
|
|
122
|
+
| 31290 | scitex-cloud |
|
|
123
|
+
| 31291 | crossref-local |
|
|
124
|
+
| 31292 | openalex |
|
|
125
|
+
| 31293 | scitex-audio |
|
|
126
|
+
|
|
127
|
+
## Environment Variable Pattern
|
|
128
|
+
|
|
129
|
+
External packages should use `{ENV_PREFIX}_{SETTING}`:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
SCITEX_PLT_MODE=local
|
|
133
|
+
CROSSREF_LOCAL_API_URL=http://localhost:8333
|
|
134
|
+
SCITEX_AUDIO_RELAY_URL=http://localhost:31293
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Example: crossref-local
|
|
138
|
+
|
|
139
|
+
See GitHub Issue: https://github.com/ywatanabe1989/crossref-local/issues/11
|
|
140
|
+
|
|
141
|
+
The crossref-local package should implement:
|
|
142
|
+
- `CROSSREF_LOCAL_BRAND` / `CROSSREF_LOCAL_ALIAS` env vars
|
|
143
|
+
- Dynamic `ENV_PREFIX` derived from brand name
|
|
144
|
+
- When used via scitex.scholar, shows `scitex.scholar` in docs
|
|
145
|
+
|
|
146
|
+
## References
|
|
147
|
+
|
|
148
|
+
- figrecipe/_branding.py - Reference implementation
|
|
149
|
+
- scitex/audio/_branding.py - Simple internal module (no rebranding needed)
|