Skip to content

Commit fa29862

Browse files
dwolfsonJunie
andcommitted
chore(release): bump version to 6.1.15
Co-authored-by: Junie <junie@egeria-project.org>
1 parent c984668 commit fa29862

23 files changed

Lines changed: 90 additions & 56 deletions

‎PYEGERIA_ISSUES.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ read. Full draft: trellis session scratch `egeria-issue-engine-host-403-loop.md`
225225

226226
### ISSUE-93: `declassify_metadata_element` raises `AttributeError` when `body` is omitted, though the parameter is Optional — and the classification is silently left in place
227227

228-
**Layer:** Pyegeria · **Status:** open · **Found:** 2026-09-08 (Resource Explorer, swapping a Project's kind classification during investigation reclassification).
228+
**Layer:** Pyegeria · **Status:** fixed (2026-09-13) · **Found:** 2026-09-08 (Resource Explorer, swapping a Project's kind classification during investigation reclassification).
229229

230230
`MetadataExpert.declassify_metadata_element(metadata_element_guid, classification_name, body=None)`
231231
declares `body` as `Optional[dict | MetadataSourceRequestBody] = None`, but the
@@ -282,7 +282,7 @@ rather than only that the new one is present.
282282

283283
### ISSUE-92: `Project Type`'s description in `commands_project_compact.json` lists only 4 of its 6 `valid_values` — omits `Project` and `Experiment`, and the stale text is baked into 24 generated files
284284

285-
**Layer:** Pyegeria · **Status:** open · **Found:** 2026-09-07 (Resource
285+
**Layer:** Pyegeria · **Status:** fixed (2026-09-13) · **Found:** 2026-09-07 (Resource
286286
Explorer, designing investigation → Egeria Project classification mapping).
287287

288288
`md_processing/data/compact_commands/commands_project_compact.json`, the
@@ -369,7 +369,7 @@ check that flags a `description` naming a subset of its own `valid_values`.
369369

370370
### ISSUE-91: `pyegeria.core.mcp_server` imports `mcp.server.mcpserver` (mcp 2.x only) but `pyproject.toml` declares `mcp >=0.1` — any consumer that resolves mcp 1.x gets a server that dies at import
371371

372-
**Layer:** pyegeria packaging · **Status:** open · **Found:** 2026-09-05 (Egeria Advisor dev startup on the M3 Max)
372+
**Layer:** pyegeria packaging · **Status:** fixed (2026-09-13) · **Found:** 2026-09-05 (Egeria Advisor dev startup on the M3 Max)
373373

374374
Commit 2b39ba06 (2026-07-30, "migrate mcp_server.py to mcp 2.0.0's MCPServer") changed the
375375
server's import to:
@@ -965,7 +965,7 @@ into the entry now, so it isn't rediscovered from scratch later.
965965

966966
### ISSUE-96: get_guid_for_name (and every other sync wrapper using asyncio.get_event_loop().run_until_complete(...)) breaks when a client instance is reused across threads — surfaces as a misleading CLIENT_ERROR_400 "unable to connect"
967967

968-
Layer: pyegeria core (_base_server_client.py, _base_platform_client.py, every OMVS class's sync wrappers) · Status: open · Found: 2026-09-12 (Resource Explorer, SurveyDefinitionReader._lookup_question_guid)
968+
Layer: pyegeria core (_base_server_client.py, _base_platform_client.py, every OMVS class's sync wrappers) · Status: fixed (2026-09-13) · Found: 2026-09-12 (Resource Explorer, SurveyDefinitionReader._lookup_question_guid)
969969

970970
A single client instance (e.g. ClassificationExplorer) constructed once
971971
and reused from multiple threads — a natural pattern for any caller with a

‎md_processing/data/compact_commands/commands_project_compact.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2621,7 +2621,7 @@
26212621
"Experiment"
26222622
],
26232623
"existing_element": "",
2624-
"description": "A string classifying the project. Supported values are Campaign, Task, PersonalProject and StudyProject.",
2624+
"description": "A string classifying the project. `Project` (the default) applies no classification. `Campaign` is a long-term strategic initiative delivered through multiple projects; `Task` a self-contained short activity; `PersonalProject` an informal project an individual creates to organize their own work; `StudyProject` a focused analysis of a topic, person, object or situation; `Experiment` a project testing a hypothesis, which is recorded in the `hypothesis` attribute.",
26252625
"generated": false,
26262626
"style": "Enum",
26272627
"user_specified": true,

‎pyegeria/core/_base_platform_client.py‎

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import inspect
1212
import json
1313
import os
14+
import threading
1415

1516
import httpcore
1617
import httpx
@@ -125,15 +126,8 @@ def __init__(
125126
self.headers["Authorization"] = f"Bearer {self.token}"
126127
self.text_headers["Authorization"] = f"Bearer {self.token}"
127128

128-
self.session = AsyncClient(
129-
verify=enable_ssl_check,
130-
timeout=httpx.Timeout(timeout=float(self.timeout), connect=10.0),
131-
limits=httpx.Limits(
132-
max_connections=10,
133-
max_keepalive_connections=5,
134-
keepalive_expiry=20.0, # stay under typical reverse-proxy idle timeout (60-75 s)
135-
),
136-
)
129+
self._thread_local = threading.local()
130+
137131
self.command_root: str = f"{self.platform_url}/servers/{self.server_name}/api/open-metadata/"
138132

139133
try:
@@ -142,6 +136,26 @@ def __init__(
142136
except PyegeriaConnectionException as e:
143137
raise
144138

139+
@property
140+
def session(self) -> AsyncClient:
141+
"""Get the httpx session for the current thread."""
142+
if not hasattr(self._thread_local, "session"):
143+
self._thread_local.session = AsyncClient(
144+
verify=enable_ssl_check,
145+
timeout=httpx.Timeout(timeout=float(self.timeout), connect=10.0),
146+
limits=httpx.Limits(
147+
max_connections=10,
148+
max_keepalive_connections=5,
149+
keepalive_expiry=20.0, # stay under typical reverse-proxy idle timeout (60-75 s)
150+
),
151+
)
152+
return self._thread_local.session
153+
154+
@session.setter
155+
def session(self, value: AsyncClient):
156+
"""Allow setting the session for the current thread."""
157+
self._thread_local.session = value
158+
145159
async def _async_check_connection(self) -> str:
146160
"""Check if the connection is working. Async version.
147161

‎pyegeria/core/_base_server_client.py‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import inspect
1212
import json
1313
import os
14+
import threading
1415

1516
import httpcore
1617
import httpx
@@ -124,10 +125,8 @@ def __init__(
124125
self.headers["Authorization"] = f"Bearer {self.token}"
125126
self.text_headers["Authorization"] = f"Bearer {self.token}"
126127

127-
self.session = AsyncClient(
128-
verify=enable_ssl_check,
129-
timeout=httpx.Timeout(timeout=float(self.timeout), connect=10.0),
130-
)
128+
self._thread_local = threading.local()
129+
131130
self.command_root: str = f"{self.platform_url}/servers/{self.server_name}/api/open-metadata/"
132131

133132
try:
@@ -136,6 +135,21 @@ def __init__(
136135
except PyegeriaConnectionException:
137136
raise
138137

138+
@property
139+
def session(self) -> AsyncClient:
140+
"""Get the httpx session for the current thread."""
141+
if not hasattr(self._thread_local, "session"):
142+
self._thread_local.session = AsyncClient(
143+
verify=enable_ssl_check,
144+
timeout=httpx.Timeout(timeout=float(self.timeout), connect=10.0),
145+
)
146+
return self._thread_local.session
147+
148+
@session.setter
149+
def session(self, value: AsyncClient):
150+
"""Allow setting the session for the current thread."""
151+
self._thread_local.session = value
152+
139153
async def _async_check_connection(self) -> str:
140154
"""Check if the connection is working. Async version.
141155

‎pyegeria/core/_server_client.py‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6248,33 +6248,36 @@ def validate_update_element_request(self, body: dict | UpdateElementRequestBody
62486248
return validated_body
62496249

62506250
@dynamic_catch
6251-
def validate_update_properties_request(self, body: dict | UpdatePropertiesRequestBody) -> UpdatePropertiesRequestBody | None:
6251+
def validate_update_properties_request(self, body: Optional[dict | UpdatePropertiesRequestBody] = None) -> UpdatePropertiesRequestBody | None:
62526252
if isinstance(body, UpdatePropertiesRequestBody):
62536253
validated_body = body
62546254
elif isinstance(body, dict):
62556255
validated_body = self._validate_body(self._update_properties_request_adapter.validate_python, body)
62566256
else:
6257-
validated_body = None
6257+
body = {"class": "UpdatePropertiesRequestBody"}
6258+
validated_body = self._validate_body(UpdatePropertiesRequestBody.model_validate, body)
62586259
return validated_body
62596260

62606261
@dynamic_catch
6261-
def validate_metadata_source_request(self, body: dict | MetadataSourceRequestBody) -> MetadataSourceRequestBody | None:
6262+
def validate_metadata_source_request(self, body: Optional[dict | MetadataSourceRequestBody] = None) -> MetadataSourceRequestBody | None:
62626263
if isinstance(body, MetadataSourceRequestBody):
62636264
validated_body = body
62646265
elif isinstance(body, dict):
62656266
validated_body = self._validate_body(self._metadata_source_request_adapter.validate_python, body)
62666267
else:
6267-
validated_body = None
6268+
body = {"class": "MetadataSourceRequestBody"}
6269+
validated_body = self._validate_body(MetadataSourceRequestBody.model_validate, body)
62686270
return validated_body
62696271

62706272
@dynamic_catch
6271-
def validate_update_effectivity_dates_request(self, body: dict | UpdateEffectivityDatesRequestBody) -> UpdateEffectivityDatesRequestBody | None:
6273+
def validate_update_effectivity_dates_request(self, body: Optional[dict | UpdateEffectivityDatesRequestBody] = None) -> UpdateEffectivityDatesRequestBody | None:
62726274
if isinstance(body, UpdateEffectivityDatesRequestBody):
62736275
validated_body = body
62746276
elif isinstance(body, dict):
62756277
validated_body = self._validate_body(self._update_effectivity_dates_request_adapter.validate_python, body)
62766278
else:
6277-
validated_body = None
6279+
body = {"class": "UpdateEffectivityDatesRequestBody"}
6280+
validated_body = self._validate_body(UpdateEffectivityDatesRequestBody.model_validate, body)
62786281
return validated_body
62796282

62806283
@dynamic_catch
@@ -6289,33 +6292,36 @@ def validate_open_metadata_delete_request(self, body: Optional[dict | OpenMetada
62896292
return validated_body
62906293

62916294
@dynamic_catch
6292-
def validate_archive_request(self, body: dict | DeleteElementRequestBody) -> DeleteElementRequestBody | None:
6295+
def validate_archive_request(self, body: Optional[dict | DeleteElementRequestBody] = None) -> DeleteElementRequestBody | None:
62936296
if isinstance(body, DeleteElementRequestBody):
62946297
validated_body = body
62956298
elif isinstance(body, dict):
62966299
validated_body = self._validate_body(self._delete_element_request_adapter.validate_python, body)
62976300
else:
6298-
validated_body = None
6301+
body = {"class": "DeleteElementRequestBody"}
6302+
validated_body = self._validate_body(DeleteElementRequestBody.model_validate, body)
62996303
return validated_body
63006304

63016305
@dynamic_catch
6302-
def validate_new_open_metadata_element_request(self, body: dict | NewOpenMetadataElementRequestBody) -> NewOpenMetadataElementRequestBody | None:
6306+
def validate_new_open_metadata_element_request(self, body: Optional[dict | NewOpenMetadataElementRequestBody] = None) -> NewOpenMetadataElementRequestBody | None:
63036307
if isinstance(body, NewOpenMetadataElementRequestBody):
63046308
validated_body = body
63056309
elif isinstance(body, dict):
63066310
validated_body = self._validate_body(self._new_open_metadata_element_request_adapter.validate_python, body)
63076311
else:
6308-
validated_body = None
6312+
body = {"class": "NewOpenMetadataElementRequestBody"}
6313+
validated_body = self._validate_body(NewOpenMetadataElementRequestBody.model_validate, body)
63096314
return validated_body
63106315

63116316
@dynamic_catch
6312-
def validate_new_related_elements_request(self, body: dict | NewRelatedElementsRequestBody) -> NewRelatedElementsRequestBody | None:
6317+
def validate_new_related_elements_request(self, body: Optional[dict | NewRelatedElementsRequestBody] = None) -> NewRelatedElementsRequestBody | None:
63136318
if isinstance(body, NewRelatedElementsRequestBody):
63146319
validated_body = body
63156320
elif isinstance(body, dict):
63166321
validated_body = self._validate_body(self._new_related_elements_request_adapter.validate_python, body)
63176322
else:
6318-
validated_body = None
6323+
body = {"class": "NewRelatedElementsRequestBody"}
6324+
validated_body = self._validate_body(NewRelatedElementsRequestBody.model_validate, body)
63196325
return validated_body
63206326

63216327
@dynamic_catch

‎pyproject.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
[project]
55
name = "pyegeria"
6-
version = "6.1.14"
6+
version = "6.1.15"
77
license = 'Apache-2.0'
88
license-files = ["LICENSE"]
99
authors = [{name = "Dan Wolfson", email = "dan.wolfson@pdr-associates.com"},]
@@ -36,7 +36,7 @@ dependencies = [
3636
"pydevd-pycharm >=253.27642.35",
3737
"wcwidth",
3838
"altair==6.2.2",
39-
"mcp >=0.1",
39+
"mcp >=2.1.1",
4040
"markers>=0.3.0",
4141
"pytest-asyncio>=1.2.0",
4242
"python-dotenv>=1.1.1",

‎sample-data/templates/advanced/Projects/Create_Campaign.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ ___
130130
131131
> **Attribute Type**: Enum
132132
133-
> **Description**: A string classifying the project. Supported values are Campaign, Task, PersonalProject and StudyProject.
133+
> **Description**: A string classifying the project. `Project` (the default) applies no classification. `Campaign` is a long-term strategic initiative delivered through multiple projects; `Task` a self-contained short activity; `PersonalProject` an informal project an individual creates to organize their own work; `StudyProject` a focused analysis of a topic, person, object or situation; `Experiment` a project testing a hypothesis, which is recorded in the `hypothesis` attribute.
134134
135135
> **Valid Values**: Project,Campaign,Task,PersonalProject,StudyProject,Experiment
136136

‎sample-data/templates/advanced/Projects/Create_Experiment.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ ___
130130
131131
> **Attribute Type**: Enum
132132
133-
> **Description**: A string classifying the project. Supported values are Campaign, Task, PersonalProject and StudyProject.
133+
> **Description**: A string classifying the project. `Project` (the default) applies no classification. `Campaign` is a long-term strategic initiative delivered through multiple projects; `Task` a self-contained short activity; `PersonalProject` an informal project an individual creates to organize their own work; `StudyProject` a focused analysis of a topic, person, object or situation; `Experiment` a project testing a hypothesis, which is recorded in the `hypothesis` attribute.
134134
135135
> **Valid Values**: Project,Campaign,Task,PersonalProject,StudyProject,Experiment
136136

‎sample-data/templates/advanced/Projects/Create_Glossary_Project.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ ___
130130
131131
> **Attribute Type**: Enum
132132
133-
> **Description**: A string classifying the project. Supported values are Campaign, Task, PersonalProject and StudyProject.
133+
> **Description**: A string classifying the project. `Project` (the default) applies no classification. `Campaign` is a long-term strategic initiative delivered through multiple projects; `Task` a self-contained short activity; `PersonalProject` an informal project an individual creates to organize their own work; `StudyProject` a focused analysis of a topic, person, object or situation; `Experiment` a project testing a hypothesis, which is recorded in the `hypothesis` attribute.
134134
135135
> **Valid Values**: Project,Campaign,Task,PersonalProject,StudyProject,Experiment
136136

‎sample-data/templates/advanced/Projects/Create_Governance_Project.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ ___
130130
131131
> **Attribute Type**: Enum
132132
133-
> **Description**: A string classifying the project. Supported values are Campaign, Task, PersonalProject and StudyProject.
133+
> **Description**: A string classifying the project. `Project` (the default) applies no classification. `Campaign` is a long-term strategic initiative delivered through multiple projects; `Task` a self-contained short activity; `PersonalProject` an informal project an individual creates to organize their own work; `StudyProject` a focused analysis of a topic, person, object or situation; `Experiment` a project testing a hypothesis, which is recorded in the `hypothesis` attribute.
134134
135135
> **Valid Values**: Project,Campaign,Task,PersonalProject,StudyProject,Experiment
136136

0 commit comments

Comments
 (0)