vibetuner 2.7.1__py3-none-any.whl → 2.8.0__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.

Potentially problematic release.


This version of vibetuner might be problematic. Click here for more details.

vibetuner/config.py CHANGED
@@ -17,14 +17,19 @@ from pydantic import (
17
17
  from pydantic_extra_types.language_code import LanguageAlpha2
18
18
  from pydantic_settings import BaseSettings, SettingsConfigDict
19
19
 
20
- from vibetuner.paths import config_vars as config_vars_path
21
- from vibetuner.versioning import version
20
+ from .paths import config_vars as config_vars_path
21
+ from .versioning import version
22
22
 
23
23
 
24
24
  current_year: int = datetime.now().year
25
25
 
26
26
 
27
27
  def _load_project_config() -> "ProjectConfiguration":
28
+ if config_vars_path is None:
29
+ raise RuntimeError(
30
+ "Project root not detected. Cannot load project configuration. "
31
+ "Ensure you're running from within a project directory with .copier-answers.yml"
32
+ )
28
33
  if not config_vars_path.exists():
29
34
  return ProjectConfiguration()
30
35
  return ProjectConfiguration(
@@ -21,8 +21,8 @@ def register_router(router: APIRouter) -> None:
21
21
 
22
22
 
23
23
  try:
24
- import app.frontend.oauth as _app_oauth # noqa: F401
25
- import app.frontend.routes as _app_routes # noqa: F401
24
+ import app.frontend.oauth as _app_oauth # noqa: F401 # type: ignore[unresolved-import]
25
+ import app.frontend.routes as _app_routes # noqa: F401 # type: ignore[unresolved-import]
26
26
  except (ImportError, ModuleNotFoundError):
27
27
  pass
28
28
 
@@ -66,7 +66,7 @@ def user_preference_selector(conn: HTTPConnection) -> str | None:
66
66
 
67
67
 
68
68
  shared_translator = get_translator()
69
- if locales_path.exists() and locales_path.is_dir():
69
+ if locales_path is not None and locales_path.exists() and locales_path.is_dir():
70
70
  # Load translations from the locales directory
71
71
  shared_translator.load_from_directories([locales_path])
72
72
 
@@ -22,6 +22,10 @@ def health_ping():
22
22
  @router.get("/id")
23
23
  def health_instance_id():
24
24
  """Instance identification endpoint for distinguishing app instances"""
25
+ if root_path is None:
26
+ raise RuntimeError(
27
+ "Project root not detected. Cannot provide instance information."
28
+ )
25
29
  return {
26
30
  "app": settings.project.project_slug,
27
31
  "port": int(os.environ.get("PORT", 8000)),
vibetuner/mongo.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from beanie import init_beanie
2
2
  from pymongo import AsyncMongoClient
3
3
 
4
- from vibetuner.config import settings
5
- from vibetuner.models.registry import get_all_models
4
+ from .config import settings
5
+ from .models.registry import get_all_models
6
6
 
7
7
 
8
8
  async def init_models() -> None:
vibetuner/paths.py CHANGED
@@ -1,112 +1,229 @@
1
1
  from importlib.resources import files
2
2
  from pathlib import Path
3
+ from typing import Self
4
+
5
+ from pydantic import computed_field, model_validator
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
3
8
 
4
9
  # Package-relative paths (for bundled templates in the vibetuner package)
5
10
  _package_files = files("vibetuner")
6
11
  _package_templates_traversable = _package_files / "templates"
7
12
 
8
- # Convert to Path when actually used (handles both filesystem and zip-based packages)
13
+
9
14
  def _get_package_templates_path() -> Path:
10
15
  """Get package templates path, works for both installed and editable installs."""
11
- # For most cases, we can convert directly to Path
12
- # For zip files, importlib.resources handles extraction automatically
13
16
  try:
14
17
  return Path(str(_package_templates_traversable))
15
18
  except (TypeError, ValueError):
16
- # If we can't convert to Path, we're in a zip or similar
17
- # In this case, we'll need to use as_file() context manager when accessing
18
- # For now, raise an error - we can enhance this later if needed
19
19
  raise RuntimeError(
20
20
  "Package templates are in a non-filesystem location. "
21
21
  "This is not yet supported."
22
- )
22
+ ) from None
23
23
 
24
24
 
25
+ # Package templates always available
25
26
  package_templates = _get_package_templates_path()
27
+ core_templates = package_templates # Alias for backwards compatibility
26
28
 
27
- # Project root (set at runtime by the application using vibetuner)
28
- # When None, only package templates are available
29
- root: Path | None = None
30
- fallback_path = "defaults"
31
29
 
30
+ class PathSettings(BaseSettings):
31
+ """Path settings with lazy auto-detection of project root."""
32
32
 
33
- def set_project_root(project_root: Path) -> None:
34
- """Set the project root directory for the application using vibetuner.
33
+ model_config = SettingsConfigDict(
34
+ case_sensitive=False,
35
+ extra="ignore",
36
+ validate_default=True,
37
+ )
35
38
 
36
- This enables access to project-specific templates, assets, and locales.
37
- Must be called before accessing project-specific paths.
38
- """
39
- global root, templates, app_templates, locales, config_vars
40
- global assets, statics, css, js, favicons, img
41
- global frontend_templates, email_templates, markdown_templates
39
+ root: Path | None = None
40
+ fallback_path: str = "defaults"
41
+
42
+ @model_validator(mode="after")
43
+ def detect_project_root(self) -> Self:
44
+ """Auto-detect project root if not explicitly set."""
45
+ if self.root is None:
46
+ detected = self._find_project_root()
47
+ if detected is not None:
48
+ self.root = detected
49
+ return self
50
+
51
+ @staticmethod
52
+ def _find_project_root() -> Path | None:
53
+ """Find project root by searching for marker files."""
54
+ markers = [".copier-answers.yml", "pyproject.toml", ".git"]
55
+ current = Path.cwd()
56
+
57
+ for parent in [current, *current.parents]:
58
+ if any((parent / marker).exists() for marker in markers):
59
+ return parent
60
+
61
+ return None
62
+
63
+ # Project-specific paths (only available if root is set)
64
+ @computed_field
65
+ @property
66
+ def templates(self) -> Path | None:
67
+ """Project templates directory."""
68
+ return self.root / "templates" if self.root else None
69
+
70
+ @computed_field
71
+ @property
72
+ def app_templates(self) -> Path | None:
73
+ """Deprecated: use templates instead."""
74
+ return self.templates
75
+
76
+ @computed_field
77
+ @property
78
+ def locales(self) -> Path | None:
79
+ """Project locales directory."""
80
+ return self.root / "locales" if self.root else None
81
+
82
+ @computed_field
83
+ @property
84
+ def config_vars(self) -> Path | None:
85
+ """Copier answers file."""
86
+ return self.root / ".copier-answers.yml" if self.root else None
87
+
88
+ @computed_field
89
+ @property
90
+ def assets(self) -> Path | None:
91
+ """Project assets directory."""
92
+ return self.root / "assets" if self.root else None
93
+
94
+ @computed_field
95
+ @property
96
+ def statics(self) -> Path | None:
97
+ """Project static assets directory."""
98
+ return self.root / "assets" / "statics" if self.root else None
99
+
100
+ @computed_field
101
+ @property
102
+ def css(self) -> Path | None:
103
+ """Project CSS directory."""
104
+ return self.root / "assets" / "statics" / "css" if self.root else None
105
+
106
+ @computed_field
107
+ @property
108
+ def js(self) -> Path | None:
109
+ """Project JavaScript directory."""
110
+ return self.root / "assets" / "statics" / "js" if self.root else None
111
+
112
+ @computed_field
113
+ @property
114
+ def favicons(self) -> Path | None:
115
+ """Project favicons directory."""
116
+ return self.root / "assets" / "statics" / "favicons" if self.root else None
117
+
118
+ @computed_field
119
+ @property
120
+ def img(self) -> Path | None:
121
+ """Project images directory."""
122
+ return self.root / "assets" / "statics" / "img" if self.root else None
123
+
124
+ # Template paths (always return a list, project + package)
125
+ @computed_field
126
+ @property
127
+ def frontend_templates(self) -> list[Path]:
128
+ """Frontend template search paths (project overrides, then package)."""
129
+ paths = []
130
+ if self.root:
131
+ project_path = self.root / "templates" / "frontend"
132
+ if project_path.exists():
133
+ paths.append(project_path)
134
+ paths.append(package_templates / "frontend")
135
+ return paths
136
+
137
+ @computed_field
138
+ @property
139
+ def email_templates(self) -> list[Path]:
140
+ """Email template search paths (project overrides, then package)."""
141
+ paths = []
142
+ if self.root:
143
+ project_path = self.root / "templates" / "email"
144
+ if project_path.exists():
145
+ paths.append(project_path)
146
+ paths.append(package_templates / "email")
147
+ return paths
148
+
149
+ @computed_field
150
+ @property
151
+ def markdown_templates(self) -> list[Path]:
152
+ """Markdown template search paths (project overrides, then package)."""
153
+ paths = []
154
+ if self.root:
155
+ project_path = self.root / "templates" / "markdown"
156
+ if project_path.exists():
157
+ paths.append(project_path)
158
+ paths.append(package_templates / "markdown")
159
+ return paths
160
+
161
+ def set_root(self, project_root: Path) -> None:
162
+ """Explicitly set project root (overrides auto-detection)."""
163
+ self.root = project_root
164
+
165
+ def to_template_path_list(self, path: Path) -> list[Path]:
166
+ """Convert path to list with fallback."""
167
+ return [path, path / self.fallback_path]
168
+
169
+ def fallback_static_default(self, static_type: str, file_name: str) -> Path:
170
+ """Return a fallback path for a static file."""
171
+ if self.statics is None:
172
+ raise RuntimeError(
173
+ "Project root not detected. Cannot access static assets."
174
+ )
175
+
176
+ paths_to_check = [
177
+ self.statics / static_type / file_name,
178
+ self.statics / self.fallback_path / static_type / file_name,
179
+ ]
180
+
181
+ for path in paths_to_check:
182
+ if path.exists():
183
+ return path
184
+
185
+ raise FileNotFoundError(
186
+ f"Could not find {file_name} in any of the fallback paths: {paths_to_check}"
187
+ )
42
188
 
43
- root = project_root
44
189
 
45
- # Update project-specific paths
46
- templates = root / "templates"
47
- app_templates = templates # Deprecated: projects now use templates/ directly
48
- locales = root / "locales"
49
- config_vars = root / ".copier-answers.yml"
190
+ # Global settings instance with lazy auto-detection
191
+ _settings = PathSettings()
50
192
 
51
- # Update asset paths
52
- assets = root / "assets"
53
- statics = assets / "statics"
54
- css = statics / "css"
55
- js = statics / "js"
56
- favicons = statics / "favicons"
57
- img = statics / "img"
58
193
 
59
- # Update template lists to include project overrides
60
- frontend_templates = [templates / "frontend", package_templates / "frontend"]
61
- email_templates = [templates / "email", package_templates / "email"]
62
- markdown_templates = [templates / "markdown", package_templates / "markdown"]
194
+ # Backwards-compatible module-level API
195
+ def set_project_root(project_root: Path) -> None:
196
+ """Set the project root directory explicitly."""
197
+ _settings.set_root(project_root)
63
198
 
64
199
 
65
200
  def to_template_path_list(path: Path) -> list[Path]:
66
- return [
67
- path,
68
- path / fallback_path,
69
- ]
201
+ """Convert path to list with fallback."""
202
+ return _settings.to_template_path_list(path)
70
203
 
71
204
 
72
205
  def fallback_static_default(static_type: str, file_name: str) -> Path:
73
- """Return a fallback path for a file."""
74
- if root is None:
75
- raise RuntimeError(
76
- "Project root not set. Call set_project_root() before accessing assets."
77
- )
78
-
79
- paths_to_check = [
80
- statics / static_type / file_name,
81
- statics / fallback_path / static_type / file_name,
82
- ]
83
-
84
- for path in paths_to_check:
85
- if path.exists():
86
- return path
87
-
88
- raise FileNotFoundError(
89
- f"Could not find {file_name} in any of the fallback paths: {paths_to_check}"
90
- )
91
-
92
-
93
- # Core templates point to package (always available)
94
- core_templates = package_templates
95
-
96
- # Template paths - initially only package templates, updated when set_project_root() is called
97
- frontend_templates = [package_templates / "frontend"]
98
- email_templates = [package_templates / "email"]
99
- markdown_templates = [package_templates / "markdown"]
100
-
101
- # Project-specific paths - will be None until set_project_root() is called
102
- # These get updated by set_project_root()
103
- templates: Path | None = None
104
- app_templates: Path | None = None
105
- locales: Path | None = None
106
- config_vars: Path | None = None
107
- assets: Path | None = None
108
- statics: Path | None = None
109
- css: Path | None = None
110
- js: Path | None = None
111
- favicons: Path | None = None
112
- img: Path | None = None
206
+ """Return a fallback path for a static file."""
207
+ return _settings.fallback_static_default(static_type, file_name)
208
+
209
+
210
+ # Expose settings instance for direct access
211
+ paths = _settings
212
+
213
+ # Module-level variables that delegate to settings (backwards compatibility)
214
+ # Access like: from vibetuner.paths import frontend_templates
215
+ # Or better: from vibetuner.paths import paths; paths.frontend_templates
216
+ root = _settings.root
217
+ templates = _settings.templates
218
+ app_templates = _settings.app_templates
219
+ locales = _settings.locales
220
+ config_vars = _settings.config_vars
221
+ assets = _settings.assets
222
+ statics = _settings.statics
223
+ css = _settings.css
224
+ js = _settings.js
225
+ favicons = _settings.favicons
226
+ img = _settings.img
227
+ frontend_templates = _settings.frontend_templates
228
+ email_templates = _settings.email_templates
229
+ markdown_templates = _settings.markdown_templates
vibetuner/templates.py CHANGED
@@ -3,13 +3,7 @@ from typing import Any, Dict, Optional
3
3
 
4
4
  from jinja2 import Environment, FileSystemLoader, TemplateNotFound
5
5
 
6
- from .paths import (
7
- app_templates,
8
- core_templates,
9
- email_templates,
10
- frontend_templates,
11
- markdown_templates,
12
- )
6
+ from . import paths
13
7
 
14
8
 
15
9
  def _get_base_paths_for_namespace(
@@ -22,19 +16,19 @@ def _get_base_paths_for_namespace(
22
16
 
23
17
  # Map known namespaces to their predefined paths
24
18
  if namespace == "email":
25
- return email_templates
19
+ return paths.email_templates
26
20
  if namespace == "markdown":
27
- return markdown_templates
21
+ return paths.markdown_templates
28
22
  if namespace == "frontend":
29
- return frontend_templates
23
+ return paths.frontend_templates
30
24
 
31
25
  # Default for unknown or None namespace
32
26
  # Only include app_templates if project root has been set
33
- paths = []
34
- if app_templates is not None:
35
- paths.append(app_templates)
36
- paths.append(core_templates)
37
- return paths
27
+ path_list = []
28
+ if paths.app_templates is not None:
29
+ path_list.append(paths.app_templates)
30
+ path_list.append(paths.core_templates)
31
+ return path_list
38
32
 
39
33
 
40
34
  def _build_search_paths(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: vibetuner
3
- Version: 2.7.1
3
+ Version: 2.8.0
4
4
  Summary: Blessed Python dependencies for Vibetuner projects
5
5
  Requires-Dist: aioboto3>=15.5.0
6
6
  Requires-Dist: arel>=0.4.0
@@ -44,5 +44,5 @@ Requires-Dist: types-aioboto3[s3,ses]>=15.5.0 ; extra == 'dev'
44
44
  Requires-Dist: types-authlib>=1.6.5.20251005 ; extra == 'dev'
45
45
  Requires-Dist: types-pyyaml>=6.0.12.20250915 ; extra == 'dev'
46
46
  Requires-Dist: uv-bump>=0.3.1 ; extra == 'dev'
47
- Requires-Python: >=3.10
47
+ Requires-Python: >=3.11
48
48
  Provides-Extra: dev
@@ -2,22 +2,22 @@ vibetuner/__init__.py,sha256=rFIVCmxkKTT_g477V8biCw0lgpudyuUabXhYxg189lY,90
2
2
  vibetuner/__main__.py,sha256=Ye9oBAgXhcYQ4I4yZli3TIXF5lWQ9yY4tTPs4XnDDUY,29
3
3
  vibetuner/cli/__init__.py,sha256=-DM7TxG9FJKCjTXpNNVSKwtl_Nn3TEg_2OYRJNsLObY,1741
4
4
  vibetuner/cli/run.py,sha256=Cq7dkHBByTNVca7PY01R5ZNvtCqRbRM5J6ufTSh_rNo,5044
5
- vibetuner/config.py,sha256=kkGU0sht9YA_EOnqqg4MVNDykQQwV3mAFJRZPc8ERJM,3491
5
+ vibetuner/config.py,sha256=R3u23RHv5gcVuU27WEo-8MrSZYbqSNbALJJKR3ACaQQ,3714
6
6
  vibetuner/context.py,sha256=VdgfX-SFmVwiwKPFID4ElIRnFADTlagqsh9RKXNiLCs,725
7
7
  vibetuner/frontend/AGENTS.md,sha256=mds0nTl3RBblTA7EFhC9dxx86mn1FH5ESXNSj_1R1Jk,3253
8
8
  vibetuner/frontend/CLAUDE.md,sha256=mds0nTl3RBblTA7EFhC9dxx86mn1FH5ESXNSj_1R1Jk,3253
9
- vibetuner/frontend/__init__.py,sha256=7-zht4ssnuWyTy3Yv7SO3ikBhZq2H096em4C0TIJ7c8,2807
9
+ vibetuner/frontend/__init__.py,sha256=fbuXSpEFRGH9ahfEzAKBnaqiqLVjJdDQtXp88GEJo3c,2877
10
10
  vibetuner/frontend/context.py,sha256=yd9mJ8Cj9AUeHE533dofEoyCkw6oSPowdq397whfN_s,169
11
11
  vibetuner/frontend/deps.py,sha256=b3ocC_ryaK2Jp51SfcFqckrXiaL7V-chkFRqLjzgA_c,1296
12
12
  vibetuner/frontend/email.py,sha256=k0d7FCZCge5VYOKp3fLsbx7EA5_SrtBkpMs57o4W7u0,1119
13
13
  vibetuner/frontend/hotreload.py,sha256=Gl7FIKJaiCVVoyWQqdErBUOKDP1cGBFUpGzqHMiJd10,285
14
14
  vibetuner/frontend/lifespan.py,sha256=SDLcsfyQoa1H13KeISWPhe3B28oDF9L2TpZnyOos-N0,525
15
- vibetuner/frontend/middleware.py,sha256=dzurBZ6pWdterjbeypU-H2RT8Y9gDgWow9KxprmhAx8,4951
15
+ vibetuner/frontend/middleware.py,sha256=oKKAgqE4opAdP3WEk_PhKkQxdmAoWBvuW0PCsqRYVI8,4980
16
16
  vibetuner/frontend/oauth.py,sha256=EzEwoOZ_8xn_CiqAWpNoEdhV2NPxZKKwF2bA6W6Bkj0,5884
17
17
  vibetuner/frontend/routes/__init__.py,sha256=nHhiylHIUPZ2R-Bd7vXEGHLJBQ7fNuzPTJodjJR3lyc,428
18
18
  vibetuner/frontend/routes/auth.py,sha256=wq69axvOAqzU1QY6u-SFos2J3V_F4IOa-6cDIm0Z_8k,4062
19
19
  vibetuner/frontend/routes/debug.py,sha256=1YHM_y0FSfA6RON5acVHmnhtKCFiCj57eAzp5AkYWXc,12735
20
- vibetuner/frontend/routes/health.py,sha256=U5sA5E2ot-4qbCnNbJs98Djy0D5DvwKsofm68h6LQrI,817
20
+ vibetuner/frontend/routes/health.py,sha256=_XkMpdMNUemu7qzkGkqn5TBnZmGrArA3Xps5CWCcGlg,959
21
21
  vibetuner/frontend/routes/language.py,sha256=wHNfdewqWfK-2JLXwglu0Q0b_e00HFGd0A2-PYT44LE,1240
22
22
  vibetuner/frontend/routes/meta.py,sha256=pSyIxQsiB0QZSYwCQbS07KhkT5oHC5r9jvjUDIqZRGw,1409
23
23
  vibetuner/frontend/routes/user.py,sha256=DnewxYkiU0uiheNrzVyaAWfOk8jreJGTNrGuYf3DVCc,2658
@@ -33,8 +33,8 @@ vibetuner/models/oauth.py,sha256=BdOZbW47Das9ntHTtRmVdl1lB5zLCcXW2fyVJ-tQ_F8,150
33
33
  vibetuner/models/registry.py,sha256=O5YG7vOrWluqpH5N7m44v72wbscMhU_Pu3TJw_u0MTk,311
34
34
  vibetuner/models/types.py,sha256=Lj3ASEvx5eNgQMcVhNyKQHHolJqDxj2yH8S-M9oa4J8,402
35
35
  vibetuner/models/user.py,sha256=ttcSH4mVREPhA6bCFUWXKfJ9_8_Iq3lEYXe3rDrslw4,2696
36
- vibetuner/mongo.py,sha256=7Rszo86xJQSyElH5z_qA-Tc8Q0078nthPxG7heUG0gA,512
37
- vibetuner/paths.py,sha256=BYN59PKbcs8x7b6GD_P9K39pxmZdlnPZ5aRxV4TooNw,3931
36
+ vibetuner/mongo.py,sha256=mG4-78xjdNKWGV4PFddqdRL1zxFRH0P0fGgcmni4cx0,494
37
+ vibetuner/paths.py,sha256=WFHuaanFOBI7v7xOHjyLvNMYKwX0KqOyi63x62wLE-M,7470
38
38
  vibetuner/services/AGENTS.md,sha256=-0_QNcASfPv_a2dWMpnAQh4wdH7kgvskuRpxwF7d4Vo,2323
39
39
  vibetuner/services/CLAUDE.md,sha256=-0_QNcASfPv_a2dWMpnAQh4wdH7kgvskuRpxwF7d4Vo,2323
40
40
  vibetuner/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -76,9 +76,9 @@ vibetuner/templates/frontend/user/profile.html.jinja,sha256=A3Tqs6mCtXcCTirZ0ZNG
76
76
  vibetuner/templates/markdown/.placeholder,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  vibetuner/templates/markdown/AGENTS.md,sha256=jcvoQNazCj-t54s0gr-4_qyxLMP8iPf2urlaAj887n0,814
78
78
  vibetuner/templates/markdown/CLAUDE.md,sha256=jcvoQNazCj-t54s0gr-4_qyxLMP8iPf2urlaAj887n0,814
79
- vibetuner/templates.py,sha256=o4Y83mx-F64pd8oKFUaj_Cgn1a7WJIphld9puqGuDMo,5153
79
+ vibetuner/templates.py,sha256=xRoMb_oyAI5x4kxfpg56UcLKkT8e9HVn-o3KFAu9ISE,5094
80
80
  vibetuner/time.py,sha256=3_DtveCCzI20ocTnAlTh2u7FByUXtINaUoQZO-_uZow,1188
81
81
  vibetuner/versioning.py,sha256=vygq-fCbMoTMPTDtiUq8cd5LHkF0Sw6B2iTDbBF4bnE,154
82
- vibetuner-2.7.1.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
83
- vibetuner-2.7.1.dist-info/METADATA,sha256=Nde603sEfSkpxBR3MqPdik4-2jEDMAmeoXQ2p4uXX7g,1956
84
- vibetuner-2.7.1.dist-info/RECORD,,
82
+ vibetuner-2.8.0.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
83
+ vibetuner-2.8.0.dist-info/METADATA,sha256=PKx4zx0zk1Sw8w_tHy7ixa7rqJsRefWAsIVg-3-HODc,1956
84
+ vibetuner-2.8.0.dist-info/RECORD,,