Skip to content

Commit 48a46a4

Browse files
committed
chore: release version 0.5.0
- Renamed internal API methods to public: `_generate_input_model` and `_generate_output_model` to `generate_input_model` and `generate_output_model`. - Updated parameter names for clarity: `format` to `output_format` in `context_logger`. - Improved type annotations across various modules for better type safety. - Refactored code to enhance quality and reduce duplication, including extraction of validation error handling. - Added new async middleware methods for improved middleware management. - Changed `Identity.roles` from a mutable list to an immutable tuple for memory safety. - Ensured security by specifying encoding when opening YAML files. - Updated documentation and tests to reflect changes in API and functionality.
1 parent eee1257 commit 48a46a4

File tree

20 files changed

+268
-229
lines changed

20 files changed

+268
-229
lines changed

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88

9+
## [0.5.0] - 2026-02-21
10+
11+
### Changed
12+
13+
#### API Naming
14+
- **decorator** - Renamed `_generate_input_model` / `_generate_output_model` to `generate_input_model` / `generate_output_model` as public API
15+
- **context_logger** - Renamed `format` parameter to `output_format` to avoid shadowing Python builtin
16+
- **registry** - Renamed `_write_lock` to `_lock` for clearer intent
17+
18+
#### Type Annotations
19+
- **decorator** - Replaced bare `dict` with `dict[str, Any]` in `_normalize_result`, `annotations`, `metadata`, `_async_execute`, `_sync_execute`
20+
- **bindings** - Fixed `_build_model_from_json_schema` parameter type from `dict` to `dict[str, Any]`
21+
- **scanner** - Fixed `roots` parameter type from `list[dict]` to `list[dict[str, Any]]`
22+
- **metrics** - Fixed `snapshot` return type from `dict` to `dict[str, Any]`
23+
- **executor** - Removed redundant string-quoted forward references in `from_registry`; fixed `middlewares` parameter type to `list[Middleware] | None`
24+
25+
#### Code Quality
26+
- **executor** - Extracted `_convert_validation_errors()` helper to eliminate 6 duplicated validation error conversion patterns
27+
- **executor** - Refactored `call_async()` and `stream()` to use new async middleware manager methods
28+
- **executor** - Removed internal `_execute_on_error_async` method (replaced by `MiddlewareManager.execute_on_error_async`)
29+
- **loader** - Use `self._resolver.clear_cache()` instead of accessing private `_file_cache` directly
30+
- **tracing** - Replaced `print()` with `sys.stdout.write()` in `StdoutExporter`
31+
- **acl / loader** - Changed hardcoded logger names to `logging.getLogger(__name__)`
32+
33+
### Added
34+
35+
#### Async Middleware
36+
- **MiddlewareManager** - Added `execute_before_async()`, `execute_after_async()`, `execute_on_error_async()` for proper async middleware dispatch with `inspect.iscoroutinefunction` detection
37+
- **RefResolver** - Added `clear_cache()` public method for cache management
38+
- **Executor** - Added `clear_async_cache()` public method
39+
40+
### Fixed
41+
42+
#### Memory Safety
43+
- **context** - Changed `Identity.roles` from mutable `list[str]` to immutable `tuple[str, ...]` in frozen dataclass
44+
45+
#### Security
46+
- **acl** - Added explicit `encoding="utf-8"` to YAML file open
47+
48+
949
## [0.4.0] - 2026-02-20
1050

1151
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ src/apcore/
116116
acl.py # Access control
117117
errors.py # Error hierarchy
118118
module.py # Module annotations & metadata
119-
middleware/ # Middleware system
119+
middleware/ # Middleware system
120120
observability/ # Tracing, metrics, logging
121121
registry/ # Module discovery & registration
122122
schema/ # Schema loading, validation, export

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "apcore"
7-
version = "0.4.0"
7+
version = "0.5.0"
88
description = "Schema-driven module development framework for AI-perceivable interfaces"
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/apcore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
TracingMiddleware,
6262
)
6363

64-
__version__ = "0.4.0"
64+
__version__ = "0.5.0"
6565

6666
__all__ = [
6767
# Core

src/apcore/acl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(self, rules: list[ACLRule], default_effect: str = "deny") -> None:
5757
self._default_effect: str = default_effect
5858
self._yaml_path: str | None = None
5959
self.debug: bool = False
60-
self._logger: logging.Logger = logging.getLogger("apcore.acl")
60+
self._logger: logging.Logger = logging.getLogger(__name__)
6161
self._lock = threading.Lock()
6262

6363
@classmethod
@@ -77,7 +77,7 @@ def load(cls, yaml_path: str) -> ACL:
7777
if not os.path.isfile(yaml_path):
7878
raise ConfigNotFoundError(config_path=yaml_path)
7979

80-
with open(yaml_path) as f:
80+
with open(yaml_path, encoding="utf-8") as f:
8181
try:
8282
data = yaml.safe_load(f)
8383
except yaml.YAMLError as e:

src/apcore/bindings.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
from apcore.decorator import (
1313
FunctionModule,
14-
_generate_input_model,
15-
_generate_output_model,
14+
generate_input_model,
15+
generate_output_model,
1616
)
1717
from apcore.errors import (
1818
BindingCallableNotFoundError,
@@ -40,7 +40,7 @@
4040
_UNSUPPORTED_KEYS = {"oneOf", "anyOf", "allOf", "$ref", "format"}
4141

4242

43-
def _build_model_from_json_schema(schema: dict, model_name: str = "DynamicModel") -> type[BaseModel]:
43+
def _build_model_from_json_schema(schema: dict[str, Any], model_name: str = "DynamicModel") -> type[BaseModel]:
4444
"""Build a Pydantic model from a simple JSON Schema dict."""
4545
# Check for unsupported top-level features
4646
if _UNSUPPORTED_KEYS & schema.keys():
@@ -174,8 +174,8 @@ def _create_module_from_binding(self, binding: dict, binding_file_dir: str) -> F
174174
# Determine schema mode
175175
if binding.get("auto_schema"):
176176
try:
177-
input_schema = _generate_input_model(func)
178-
output_schema = _generate_output_model(func)
177+
input_schema = generate_input_model(func)
178+
output_schema = generate_output_model(func)
179179
except (FuncMissingTypeHintError, FuncMissingReturnTypeError) as exc:
180180
raise BindingSchemaMissingError(target=binding["target"]) from exc
181181
elif "input_schema" in binding or "output_schema" in binding:
@@ -204,8 +204,8 @@ def _create_module_from_binding(self, binding: dict, binding_file_dir: str) -> F
204204
else:
205205
# No schema mode specified, try auto_schema as default
206206
try:
207-
input_schema = _generate_input_model(func)
208-
output_schema = _generate_output_model(func)
207+
input_schema = generate_input_model(func)
208+
output_schema = generate_output_model(func)
209209
except (FuncMissingTypeHintError, FuncMissingReturnTypeError) as exc:
210210
raise BindingSchemaMissingError(target=binding["target"]) from exc
211211

src/apcore/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Identity:
1515

1616
id: str
1717
type: str = "user"
18-
roles: list[str] = field(default_factory=list)
18+
roles: tuple[str, ...] = field(default_factory=tuple)
1919
attrs: dict[str, Any] = field(default_factory=dict)
2020

2121

src/apcore/decorator.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from apcore.errors import FuncMissingReturnTypeError, FuncMissingTypeHintError
1414

1515

16-
def _generate_input_model(func: Any) -> type[BaseModel]:
16+
def generate_input_model(func: Any) -> type[BaseModel]:
1717
"""Convert a function's parameter signature into a dynamic Pydantic BaseModel.
1818
1919
Skips self/cls, *args, **kwargs, and Context-typed parameters.
@@ -71,7 +71,7 @@ def _generate_input_model(func: Any) -> type[BaseModel]:
7171
return create_model("InputModel", **field_dict)
7272

7373

74-
def _generate_output_model(func: Any) -> type[BaseModel]:
74+
def generate_output_model(func: Any) -> type[BaseModel]:
7575
"""Convert a function's return type annotation into a Pydantic BaseModel.
7676
7777
- dict / dict[str, T] -> permissive model (extra="allow")
@@ -124,7 +124,7 @@ def _has_context_param(func: Any) -> tuple[bool, str | None]:
124124
return (False, None)
125125

126126

127-
def _normalize_result(result: Any) -> dict:
127+
def _normalize_result(result: Any) -> dict[str, Any]:
128128
"""Normalize a function's return value to a dict for the executor pipeline."""
129129
if result is None:
130130
return {}
@@ -151,15 +151,15 @@ def __init__(
151151
documentation: str | None = None,
152152
tags: list[str] | None = None,
153153
version: str = "1.0.0",
154-
annotations: dict | None = None,
155-
metadata: dict | None = None,
154+
annotations: dict[str, Any] | None = None,
155+
metadata: dict[str, Any] | None = None,
156156
input_schema: type[BaseModel] | None = None,
157157
output_schema: type[BaseModel] | None = None,
158158
) -> None:
159159
self._func = func
160160
self.module_id = module_id
161-
self.input_schema = input_schema if input_schema is not None else _generate_input_model(func)
162-
self.output_schema = output_schema if output_schema is not None else _generate_output_model(func)
161+
self.input_schema = input_schema if input_schema is not None else generate_input_model(func)
162+
self.output_schema = output_schema if output_schema is not None else generate_output_model(func)
163163

164164
has_context, context_param_name = _has_context_param(func)
165165

@@ -181,7 +181,7 @@ def __init__(
181181
# inspect.iscoroutinefunction returns the correct value.
182182
if inspect.iscoroutinefunction(func):
183183

184-
async def _async_execute(inputs: dict, context: Context) -> dict:
184+
async def _async_execute(inputs: dict[str, Any], context: Context) -> dict[str, Any]:
185185
call_kwargs = dict(inputs)
186186
if has_context:
187187
call_kwargs[context_param_name] = context
@@ -191,7 +191,7 @@ async def _async_execute(inputs: dict, context: Context) -> dict:
191191
self.execute = _async_execute
192192
else:
193193

194-
def _sync_execute(inputs: dict, context: Context) -> dict:
194+
def _sync_execute(inputs: dict[str, Any], context: Context) -> dict[str, Any]:
195195
call_kwargs = dict(inputs)
196196
if has_context:
197197
call_kwargs[context_param_name] = context
@@ -219,10 +219,10 @@ def module(
219219
id: str | None = None, # noqa: A002
220220
description: str | None = None,
221221
documentation: str | None = None,
222-
annotations: dict | None = None,
222+
annotations: dict[str, Any] | None = None,
223223
tags: list[str] | None = None,
224224
version: str = "1.0.0",
225-
metadata: dict | None = None,
225+
metadata: dict[str, Any] | None = None,
226226
registry: Any = None,
227227
) -> Any:
228228
"""Wrap a Python function as an apcore module.

0 commit comments

Comments
 (0)