feat: Better exceptions and parsing.
parent
8ba4302b64
commit
8220491d50
142
blext/cli.py
142
blext/cli.py
|
@ -19,12 +19,13 @@
|
|||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import typing as typ
|
||||
from pathlib import Path
|
||||
|
||||
import cyclopts
|
||||
import pydantic as pyd
|
||||
import rich
|
||||
|
||||
from . import exceptions as exc
|
||||
from . import finders, pack, spec, supported, wheels
|
||||
|
||||
console = rich.console.Console()
|
||||
|
@ -42,6 +43,39 @@ PATH_BL_INIT_PY: Path = (
|
|||
)
|
||||
|
||||
|
||||
def _parse(
|
||||
*,
|
||||
bl_platform: supported.BLPlatform | None = None,
|
||||
proj_path: Path | None = None,
|
||||
release_profile: supported.ReleaseProfile = supported.ReleaseProfile.Release,
|
||||
detect_bl_platform: bool = True,
|
||||
) -> tuple[supported.BLPlatform | None, spec.BLExtSpec]:
|
||||
# Parse CLI
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
path_proj_spec = finders.find_proj_spec(proj_path)
|
||||
|
||||
# Parse Blender Extension Specification
|
||||
with exc.handle(exc.pretty, ValueError, pyd.ValidationError):
|
||||
universal_blext_spec = spec.BLExtSpec.from_proj_spec(
|
||||
path_proj_spec=path_proj_spec,
|
||||
release_profile=release_profile,
|
||||
)
|
||||
|
||||
# Deduce BLPlatform
|
||||
if bl_platform is not None:
|
||||
_bl_platform = bl_platform
|
||||
elif bl_platform is None and detect_bl_platform:
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
_bl_platform = finders.detect_local_blplatform()
|
||||
else:
|
||||
return (None, universal_blext_spec)
|
||||
|
||||
return (
|
||||
_bl_platform,
|
||||
universal_blext_spec.constrain_to_bl_platform(_bl_platform),
|
||||
)
|
||||
|
||||
|
||||
####################
|
||||
# - Command: Build
|
||||
####################
|
||||
|
@ -50,8 +84,7 @@ def build(
|
|||
bl_platform: supported.BLPlatform | None = None,
|
||||
proj_path: Path | None = None,
|
||||
release_profile: supported.ReleaseProfile = supported.ReleaseProfile.Release,
|
||||
_return_blext_spec: typ.Annotated[bool, cyclopts.Parameter(show=False)] = False,
|
||||
) -> spec.BLExtSpec | None:
|
||||
) -> None:
|
||||
"""[Build] the extension to an installable `.zip` file.
|
||||
|
||||
Parameters:
|
||||
|
@ -60,25 +93,23 @@ def build(
|
|||
release_profile: The release profile to bake into the extension.
|
||||
"""
|
||||
# Parse CLI
|
||||
if bl_platform is None:
|
||||
bl_platform = finders.detect_local_blplatform()
|
||||
path_proj_spec = finders.find_proj_spec(proj_path)
|
||||
|
||||
# Parse Blender Extension Specification
|
||||
blext_spec = spec.BLExtSpec.from_proj_spec(
|
||||
path_proj_spec=path_proj_spec,
|
||||
release_profile=release_profile,
|
||||
).constrain_to_bl_platform(bl_platform)
|
||||
|
||||
# Download Wheels
|
||||
wheels.download_wheels(
|
||||
blext_spec,
|
||||
bl_platform, blext_spec = _parse(
|
||||
bl_platform=bl_platform,
|
||||
no_prompt=False,
|
||||
proj_path=proj_path,
|
||||
release_profile=release_profile,
|
||||
)
|
||||
|
||||
# Download Wheels
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
wheels.download_wheels(
|
||||
blext_spec,
|
||||
bl_platform=bl_platform,
|
||||
no_prompt=False,
|
||||
)
|
||||
|
||||
# Pack the Blender Extension
|
||||
pack.pack_bl_extension(blext_spec, overwrite=True)
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
pack.pack_bl_extension(blext_spec, overwrite=True)
|
||||
|
||||
# Validate the Blender Extension
|
||||
console.print()
|
||||
|
@ -110,12 +141,11 @@ def build(
|
|||
if return_code == 0:
|
||||
console.print('[✔] Blender Extension Validation Succeeded')
|
||||
else:
|
||||
msg = 'Packed extension did not validate'
|
||||
raise ValueError(msg)
|
||||
|
||||
if _return_blext_spec:
|
||||
return blext_spec
|
||||
return None
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
msgs = [
|
||||
'Blender failed to validate the packed extension. For more information, see the validation logs (above).',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
|
||||
####################
|
||||
|
@ -126,16 +156,24 @@ def dev(
|
|||
proj_path: Path | None = None,
|
||||
) -> None:
|
||||
"""Launch the local [dev] extension w/Blender."""
|
||||
# Parse CLI
|
||||
release_profile = supported.ReleaseProfile.Dev
|
||||
bl_platform, blext_spec = _parse(
|
||||
bl_platform=None,
|
||||
proj_path=proj_path,
|
||||
release_profile=release_profile,
|
||||
)
|
||||
|
||||
# Build the Extension
|
||||
blext_spec = build(
|
||||
build(
|
||||
bl_platform=None,
|
||||
proj_path=proj_path,
|
||||
release_profile=supported.ReleaseProfile.Dev,
|
||||
_return_blext_spec=True,
|
||||
)
|
||||
|
||||
# Find Blender
|
||||
blender_exe = finders.find_blender_exe()
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
blender_exe = finders.find_blender_exe()
|
||||
|
||||
# Launch Blender
|
||||
## - Same as running 'blender --python ./blender_scripts/bl_init.py'
|
||||
|
@ -188,17 +226,14 @@ def show_spec(
|
|||
proj_path: Path to a `pyproject.toml` or a folder containing a `pyproject.toml`, which specifies the Blender extension.
|
||||
release_profile: The release profile to bake into the extension.
|
||||
"""
|
||||
# Parse BLExtSpec
|
||||
path_proj_spec = finders.find_proj_spec(proj_path)
|
||||
blext_spec = spec.BLExtSpec.from_proj_spec(
|
||||
path_proj_spec=path_proj_spec,
|
||||
# Parse CLI
|
||||
_, blext_spec = _parse(
|
||||
bl_platform=bl_platform,
|
||||
proj_path=proj_path,
|
||||
release_profile=release_profile,
|
||||
detect_bl_platform=False,
|
||||
)
|
||||
|
||||
# Constrain BLExtSpec to BLPlatform
|
||||
if bl_platform is not None:
|
||||
blext_spec = blext_spec.constrain_to_bl_platform(bl_platform)
|
||||
|
||||
# Show BLExtSpec
|
||||
rich.print(blext_spec)
|
||||
|
||||
|
@ -206,8 +241,8 @@ def show_spec(
|
|||
####################
|
||||
# - Command: Show Manifest
|
||||
####################
|
||||
@app_show.command(name='bl_manifest', group='Information')
|
||||
def show_bl_manifest(
|
||||
@app_show.command(name='manifest', group='Information')
|
||||
def show_manifest(
|
||||
bl_platform: supported.BLPlatform | None = None,
|
||||
proj_path: Path | None = None,
|
||||
release_profile: supported.ReleaseProfile = supported.ReleaseProfile.Release,
|
||||
|
@ -220,20 +255,13 @@ def show_bl_manifest(
|
|||
release_profile: The release profile to bake into the extension.
|
||||
"""
|
||||
# Parse CLI
|
||||
if bl_platform is None:
|
||||
bl_platform = finders.detect_local_blplatform()
|
||||
path_proj_spec = finders.find_proj_spec(proj_path)
|
||||
|
||||
# Parse Blender Extension Specification
|
||||
blext_spec = spec.BLExtSpec.from_proj_spec(
|
||||
path_proj_spec=path_proj_spec,
|
||||
_, blext_spec = _parse(
|
||||
bl_platform=bl_platform,
|
||||
proj_path=proj_path,
|
||||
release_profile=release_profile,
|
||||
detect_bl_platform=False,
|
||||
)
|
||||
|
||||
# [Maybe] Constrain BLExtSpec to BLPlatform
|
||||
if bl_platform is not None:
|
||||
blext_spec = blext_spec.constrain_to_bl_platform(bl_platform)
|
||||
|
||||
# Show BLExtSpec
|
||||
rich.print(blext_spec.manifest_str)
|
||||
|
||||
|
@ -252,20 +280,13 @@ def show_init_settings(
|
|||
release_profile: The release profile to bake into the extension.
|
||||
"""
|
||||
# Parse CLI
|
||||
if bl_platform is None:
|
||||
bl_platform = finders.detect_local_blplatform()
|
||||
path_proj_spec = finders.find_proj_spec(proj_path)
|
||||
|
||||
# Parse Blender Extension Specification
|
||||
blext_spec = spec.BLExtSpec.from_proj_spec(
|
||||
path_proj_spec=path_proj_spec,
|
||||
_, blext_spec = _parse(
|
||||
bl_platform=bl_platform,
|
||||
proj_path=proj_path,
|
||||
release_profile=release_profile,
|
||||
detect_bl_platform=False,
|
||||
)
|
||||
|
||||
# [Maybe] Constrain BLExtSpec to BLPlatform
|
||||
if bl_platform is not None:
|
||||
blext_spec = blext_spec.constrain_to_bl_platform(bl_platform)
|
||||
|
||||
# Show BLExtSpec
|
||||
rich.print(blext_spec.init_settings_str)
|
||||
|
||||
|
@ -276,7 +297,8 @@ def show_init_settings(
|
|||
@app_show.command(name='path_blender', group='Paths')
|
||||
def show_path_blender() -> None:
|
||||
"""Display the found path to the Blender executable."""
|
||||
blender_exe = finders.find_blender_exe()
|
||||
with exc.handle(exc.pretty, ValueError):
|
||||
blender_exe = finders.find_blender_exe()
|
||||
|
||||
rich.print(blender_exe)
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# blext
|
||||
# Copyright (C) 2025 blext Project Contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Exception handling for enhancing the end-user experience of dealing with errors."""
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import rich
|
||||
import rich.markdown
|
||||
import rich.padding
|
||||
import pydantic as pyd
|
||||
|
||||
|
||||
def sys_exit_1():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def handle(
|
||||
ex_handler,
|
||||
*exceptions,
|
||||
after_ex=sys_exit_1,
|
||||
):
|
||||
try:
|
||||
yield
|
||||
except exceptions as ex:
|
||||
ex_handler(ex)
|
||||
after_ex()
|
||||
|
||||
|
||||
####################
|
||||
# - Exception Handlers
|
||||
####################
|
||||
def pretty(ex: Exception, show_filename_lineno: bool = False) -> None:
|
||||
# Parse ValidationError
|
||||
if isinstance(ex, pyd.ValidationError):
|
||||
model_name = ex.title
|
||||
ex_name = f'ValidationError{"s" if ex.error_count() > 1 else ""}'
|
||||
|
||||
ex = ValueError(
|
||||
*[
|
||||
f'{model_name}.'
|
||||
+ ', '.join([str(el) for el in err['loc']])
|
||||
+ '='
|
||||
+ str(err['input'])
|
||||
+ ': '
|
||||
+ err['msg']
|
||||
for err in ex.errors()
|
||||
]
|
||||
)
|
||||
else:
|
||||
ex_name = ex.__class__.__name__
|
||||
|
||||
# Parse Filename and Line#
|
||||
if show_filename_lineno:
|
||||
_, exc_obj, exc_tb = sys.exc_info()
|
||||
filename = (
|
||||
Path(exc_tb.tb_frame.f_code.co_filename).name
|
||||
if exc_tb is not None
|
||||
else None
|
||||
)
|
||||
lineno = exc_tb.tb_lineno if exc_tb is not None else None
|
||||
info = f' ({filename}|{lineno}):' if exc_tb is not None else ':'
|
||||
else:
|
||||
info = ':'
|
||||
|
||||
# Format Message Tuple
|
||||
messages = [
|
||||
rich.padding.Padding(rich.markdown.Markdown(arg), pad=(0, 0, 0, 4))
|
||||
for arg in ex.args
|
||||
]
|
||||
|
||||
# Present
|
||||
rich.print(
|
||||
f'[bold red]{ex_name}[/bold red]',
|
||||
info,
|
||||
*messages,
|
||||
sep='',
|
||||
)
|
|
@ -66,21 +66,21 @@ def find_proj_spec(proj_path: Path | None) -> Path:
|
|||
proj_path: Search for a `pyproject.toml` project specification at this path.
|
||||
When `None`, search in the current working directory.
|
||||
"""
|
||||
# Deduce Project Path
|
||||
if proj_path is None:
|
||||
path_proj_spec = Path.cwd() / 'pyproject.toml'
|
||||
if not path_proj_spec.is_file():
|
||||
symptom = (
|
||||
'does not exist' if not path_proj_spec.exists() else 'is not a file'
|
||||
)
|
||||
msg = "Couldn't find 'pyproject.toml' in the current working directory"
|
||||
elif proj_path.exists():
|
||||
if proj_path.is_file():
|
||||
path_proj_spec = proj_path
|
||||
elif proj_path.is_dir():
|
||||
path_proj_spec = proj_path / 'pyproject.toml'
|
||||
|
||||
elif proj_path.is_dir() and (proj_path / 'pyproject.toml').is_file():
|
||||
path_proj_spec = proj_path / 'pyproject.toml'
|
||||
|
||||
elif not proj_path.is_file():
|
||||
symptom = 'does not exist' if not proj_path.exists() else 'is not a file'
|
||||
msg = f"Provided project specification {symptom} (tried to load '{proj_path}')"
|
||||
raise ValueError(msg)
|
||||
if not path_proj_spec.is_file():
|
||||
msgs = [
|
||||
f'No project specification found at `{path_proj_spec}`.',
|
||||
'Please verify the project path.',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
return path_proj_spec.resolve()
|
||||
|
||||
|
|
202
blext/spec.py
202
blext/spec.py
|
@ -27,6 +27,40 @@ import tomli_w
|
|||
|
||||
from . import supported
|
||||
|
||||
ValidBLTags: typ.TypeAlias = typ.Literal[
|
||||
'3D View',
|
||||
'Add Curve',
|
||||
'Add Mesh',
|
||||
'Animation',
|
||||
'Bake',
|
||||
'Camera',
|
||||
'Compositing',
|
||||
'Development',
|
||||
'Game Engine',
|
||||
'Geometry Nodes',
|
||||
'Grease Pencil',
|
||||
'Import-Export',
|
||||
'Lighting',
|
||||
'Material',
|
||||
'Modeling',
|
||||
'Mesh',
|
||||
'Node',
|
||||
'Object',
|
||||
'Paint',
|
||||
'Pipeline',
|
||||
'Physics',
|
||||
'Render',
|
||||
'Rigging',
|
||||
'Scene',
|
||||
'Sculpt',
|
||||
'Sequencer',
|
||||
'System',
|
||||
'Text Editor',
|
||||
'Tracking',
|
||||
'User Interface',
|
||||
'UV',
|
||||
]
|
||||
|
||||
|
||||
####################
|
||||
# - Path Mangling
|
||||
|
@ -164,39 +198,7 @@ class BLExtSpec(pyd.BaseModel):
|
|||
|
||||
# Addon Tags
|
||||
tags: tuple[
|
||||
typ.Literal[
|
||||
'3D View',
|
||||
'Add Curve',
|
||||
'Add Mesh',
|
||||
'Animation',
|
||||
'Bake',
|
||||
'Camera',
|
||||
'Compositing',
|
||||
'Development',
|
||||
'Game Engine',
|
||||
'Geometry Nodes',
|
||||
'Grease Pencil',
|
||||
'Import-Export',
|
||||
'Lighting',
|
||||
'Material',
|
||||
'Modeling',
|
||||
'Mesh',
|
||||
'Node',
|
||||
'Object',
|
||||
'Paint',
|
||||
'Pipeline',
|
||||
'Physics',
|
||||
'Render',
|
||||
'Rigging',
|
||||
'Scene',
|
||||
'Sculpt',
|
||||
'Sequencer',
|
||||
'System',
|
||||
'Text Editor',
|
||||
'Tracking',
|
||||
'User Interface',
|
||||
'UV',
|
||||
],
|
||||
ValidBLTags,
|
||||
...,
|
||||
] = ()
|
||||
license: tuple[str, ...]
|
||||
|
@ -389,60 +391,83 @@ class BLExtSpec(pyd.BaseModel):
|
|||
ValueError: If the `pyproject.toml` file does not contain the required tables and/or fields.
|
||||
|
||||
"""
|
||||
# Parse Sections
|
||||
## Parse [project]
|
||||
####################
|
||||
# - Parsing: Stage 1
|
||||
####################
|
||||
###: Determine whether all fields are accessible.
|
||||
|
||||
# Parse [project]
|
||||
if proj_spec.get('project') is not None:
|
||||
project = proj_spec['project']
|
||||
else:
|
||||
msg = "'pyproject.toml' MUST define '[project]' table"
|
||||
raise ValueError(msg)
|
||||
msgs = [
|
||||
f'In `{path_proj_root / "pyproject.toml"}`:',
|
||||
'- `[project]` table is not defined.',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
## Parse [tool.blext]
|
||||
# Parse [tool.blext]
|
||||
if (
|
||||
proj_spec.get('tool') is not None
|
||||
or proj_spec['tool'].get('blext') is not None
|
||||
and proj_spec['tool'].get('blext') is not None
|
||||
):
|
||||
blext_spec = proj_spec['tool']['blext']
|
||||
else:
|
||||
msg = "'pyproject.toml' MUST define '[tool.blext]' table"
|
||||
raise ValueError(msg)
|
||||
msgs = [
|
||||
f'In `{path_proj_root / "pyproject.toml"}`:',
|
||||
'- `[tool.blext]` table is not defined.',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
## Parse [tool.blext.profiles]
|
||||
# Parse [tool.blext.profiles]
|
||||
if proj_spec['tool']['blext'].get('profiles') is not None:
|
||||
release_profiles = blext_spec['profiles']
|
||||
if release_profile in release_profiles:
|
||||
release_profile_spec = release_profiles[release_profile]
|
||||
else:
|
||||
msg = f"To parse the profile '{release_profile}' from 'pyproject.toml', it MUST be defined as a key in '[tool.blext.profiles]'"
|
||||
raise ValueError(msg)
|
||||
msgs = [
|
||||
f'In `{path_proj_root / "pyproject.toml"}`:',
|
||||
f'- `[tool.blext.profiles.{release_profile}]` table is not defined, yet `{release_profile}` is requested.',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
else:
|
||||
msg = "'pyproject.toml' MUST define '[tool.blext.profiles]'"
|
||||
raise ValueError(msg)
|
||||
msgs = [
|
||||
f'In `{path_proj_root / "pyproject.toml"}`:',
|
||||
'- `[tool.blext.profiles]` table is not defined.',
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
# Parse Values
|
||||
## Parse project.requires-python
|
||||
####################
|
||||
# - Parsing: Stage 2
|
||||
####################
|
||||
###: Parse values that require transformations.
|
||||
|
||||
field_parse_errs = []
|
||||
|
||||
# Parse project.requires-python
|
||||
if project.get('requires-python') is not None:
|
||||
project_requires_python = project['requires-python'].replace('~= ', '')
|
||||
else:
|
||||
msg = "'pyproject.toml' MUST define 'project.requires-python'"
|
||||
raise ValueError(msg)
|
||||
field_parse_errs.append('- `project.requires-python` is not defined.')
|
||||
|
||||
## Parse project.maintainers[0]
|
||||
if project.get('maintainers') is not None:
|
||||
# Parse project.maintainers[0]
|
||||
if project.get('maintainers') is not None and len(project['maintainers']) > 0:
|
||||
first_maintainer = project.get('maintainers')[0]
|
||||
else:
|
||||
first_maintainer = {'name': None, 'email': None}
|
||||
|
||||
## Parse project.license
|
||||
# Parse project.license
|
||||
if (
|
||||
project.get('license') is not None
|
||||
and project['license'].get('text') is not None
|
||||
):
|
||||
_license = project['license']['text']
|
||||
else:
|
||||
msg = "'pyproject.toml' MUST define 'project.license.text'"
|
||||
raise ValueError(msg)
|
||||
field_parse_errs.append('- `project.license.text` is not defined.')
|
||||
field_parse_errs.append(
|
||||
'- Please note that all Blender addons MUST have a GPL-compatible license: <https://docs.blender.org/manual/en/latest/advanced/extensions/licenses.html>'
|
||||
)
|
||||
|
||||
## Parse project.urls.homepage
|
||||
if (
|
||||
|
@ -453,13 +478,54 @@ class BLExtSpec(pyd.BaseModel):
|
|||
else:
|
||||
homepage = None
|
||||
|
||||
# Conform to BLExt Specification
|
||||
####################
|
||||
# - Parsing: Stage 3
|
||||
####################
|
||||
###: Parse field availability and provide for descriptive errors
|
||||
|
||||
if blext_spec.get('platforms') is None:
|
||||
field_parse_errs += ['- `[tool.blext.platforms]` is not defined.']
|
||||
if project.get('name') is None:
|
||||
field_parse_errs += ['- `project.name` is not defined.']
|
||||
if blext_spec.get('pretty_name') is None:
|
||||
field_parse_errs += ['- `tool.blext.pretty_name` is not defined.']
|
||||
if project.get('version') is None:
|
||||
field_parse_errs += ['- `project.version` is not defined.']
|
||||
if project.get('description') is None:
|
||||
field_parse_errs += ['- `project.description` is not defined.']
|
||||
if blext_spec.get('blender_version_min') is None:
|
||||
field_parse_errs += ['- `tool.blext.blender_version_min` is not defined.']
|
||||
if blext_spec.get('blender_version_max') is None:
|
||||
field_parse_errs += ['- `tool.blext.blender_version_max` is not defined.']
|
||||
if blext_spec.get('bl_tags') is None:
|
||||
field_parse_errs += ['- `tool.blext.bl_tags` is not defined.']
|
||||
field_parse_errs += [
|
||||
'- Valid `bl_tags` values are: '
|
||||
+ ', '.join([f'"{el}"' for el in typ.get_args(ValidBLTags)])
|
||||
]
|
||||
if blext_spec.get('copyright') is None:
|
||||
field_parse_errs += ['- `tool.blext.copyright` is not defined.']
|
||||
field_parse_errs += [
|
||||
'- Example: `copyright = ["<current_year> <proj_name> Contributors`'
|
||||
]
|
||||
|
||||
if field_parse_errs:
|
||||
msgs = [
|
||||
f'In `{path_proj_root / "pyproject.toml"}`:',
|
||||
*field_parse_errs,
|
||||
]
|
||||
raise ValueError(*msgs)
|
||||
|
||||
####################
|
||||
# - Parsing: Stage 4
|
||||
####################
|
||||
###: With guaranteed existance, do qualitative parsing w/pydantic.
|
||||
return cls(
|
||||
path_proj_root=path_proj_root,
|
||||
req_python_version=project_requires_python,
|
||||
bl_platform_pypa_tags=blext_spec.get('platforms'),
|
||||
bl_platform_pypa_tags=blext_spec['platforms'],
|
||||
# Path Locality
|
||||
use_path_local=release_profile_spec.get('use_path_local'),
|
||||
use_path_local=release_profile_spec.get('use_path_local', False),
|
||||
# File Logging
|
||||
use_log_file=release_profile_spec.get('use_log_file', False),
|
||||
log_file_path=release_profile_spec.get('log_file_path', 'addon.log'),
|
||||
|
@ -468,20 +534,20 @@ class BLExtSpec(pyd.BaseModel):
|
|||
use_log_console=release_profile_spec.get('use_log_console', True),
|
||||
log_console_level=release_profile_spec.get('log_console_level', 'DEBUG'),
|
||||
# Basics
|
||||
id=project.get('name'),
|
||||
name=blext_spec.get('pretty_name'),
|
||||
version=project.get('version'),
|
||||
tagline=project.get('description'),
|
||||
id=project['name'],
|
||||
name=blext_spec['pretty_name'],
|
||||
version=project['version'],
|
||||
tagline=project['description'],
|
||||
maintainer=f'{first_maintainer["name"]} <{first_maintainer["email"]}>',
|
||||
# Blender Compatibility
|
||||
blender_version_min=blext_spec.get('blender_version_min'),
|
||||
blender_version_max=blext_spec.get('blender_version_max'),
|
||||
blender_version_min=blext_spec['blender_version_min'],
|
||||
blender_version_max=blext_spec['blender_version_max'],
|
||||
# Permissions
|
||||
permissions=blext_spec.get('permissions', {}),
|
||||
# Addon Tags
|
||||
tags=blext_spec.get('bl_tags'),
|
||||
tags=blext_spec['bl_tags'],
|
||||
license=(f'SPDX:{_license}',),
|
||||
copyright=blext_spec.get('copyright'),
|
||||
copyright=blext_spec['copyright'],
|
||||
website=homepage,
|
||||
)
|
||||
|
||||
|
@ -499,14 +565,14 @@ class BLExtSpec(pyd.BaseModel):
|
|||
release_profile: The profile to load initial settings for.
|
||||
|
||||
Raises:
|
||||
ValueError: If the `pyproject.toml` file does not contain the required tables and/or fields.
|
||||
ValueError: If the `pyproject.toml` file cannot be loaded, or it does not contain the required tables and/or fields.
|
||||
"""
|
||||
# Load File
|
||||
if path_proj_spec.is_file():
|
||||
with path_proj_spec.open('rb') as f:
|
||||
proj_spec = tomllib.load(f)
|
||||
else:
|
||||
msg = f"Could not load 'pyproject.toml' at '{path_proj_spec}"
|
||||
msg = f'Could not load file: `{path_proj_spec}`'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Parse Extension Specification
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
These settings are transported via a special TOML file that is packed into the extension zip-file when packaging for release.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import tomllib
|
||||
import typing as typ
|
||||
from pathlib import Path
|
||||
|
||||
import pydantic as pyd
|
||||
|
||||
from .. import __package__ as base_package
|
||||
|
||||
####################
|
||||
# - Constants
|
||||
####################
|
||||
|
@ -46,18 +47,8 @@ StrLogLevel: typ.TypeAlias = typ.Literal[
|
|||
]
|
||||
|
||||
|
||||
def str_to_loglevel(obj: typ.Any) -> LogLevel | typ.Any:
|
||||
if isinstance(obj, str) and obj in STR_LOG_LEVEL:
|
||||
return STR_LOG_LEVEL[obj]
|
||||
return obj
|
||||
|
||||
|
||||
class InitSettings(pyd.BaseModel):
|
||||
"""Model describing addon initialization settings, describing default settings baked into the release.
|
||||
|
||||
Each parameter
|
||||
|
||||
"""
|
||||
"""Model describing addon initialization settings, describing default settings baked into the release."""
|
||||
|
||||
use_log_file: bool
|
||||
log_file_path: Path
|
||||
|
@ -66,6 +57,20 @@ class InitSettings(pyd.BaseModel):
|
|||
use_log_console: bool
|
||||
log_console_level: StrLogLevel
|
||||
|
||||
@pyd.field_validator('log_file_path', mode='after')
|
||||
@classmethod
|
||||
def add_bpy_to_log_file_path(cls, value: Path, _: pyd.ValidationInfo) -> Path:
|
||||
import bpy
|
||||
|
||||
addon_dir = Path(
|
||||
bpy.utils.extension_path_user(
|
||||
base_package,
|
||||
path='',
|
||||
create=True,
|
||||
)
|
||||
)
|
||||
return addon_dir / value
|
||||
|
||||
|
||||
####################
|
||||
# - Init Settings Management
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "blext"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "A fast, convenient project manager for Blender extensions."
|
||||
authors = [
|
||||
{ name = "Sofus Albert Høgsbro Rose", email = "blext@sofusrose.com" },
|
||||
|
@ -49,6 +49,9 @@ blext = "blext.cli:app"
|
|||
[tool.setuptools]
|
||||
packages = ["blext"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["blext"]
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
|
|
Loading…
Reference in New Issue