Skip to content

Commit 486a9fc

Browse files
authored
Merge pull request #1327 from Sage-Bionetworks/SYNPY-1777
[SYNPY-1777] Add fix_schema_name
2 parents 5463edc + aa2c5d2 commit 486a9fc

5 files changed

Lines changed: 193 additions & 6 deletions

File tree

docs/tutorials/command_line_client.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -569,12 +569,13 @@ Register a JSON Schema to a Synapse organization for later binding to entities.
569569
synapse register-json-schema [-h] [--schema-version VERSION] schema_path organization_name schema_name
570570
```
571571
572-
| Name | Type | Description | Default |
573-
|-----------------------|------------|-------------------------------------------------------------------------------------|---------|
574-
| `schema_path` | Positional | Path to the JSON schema file to register | |
575-
| `organization_name` | Positional | Name of the organization to register the schema under | |
576-
| `schema_name` | Positional | The name of the JSON schema | |
577-
| `--schema-version` | Named | Version of the schema to register (e.g., '0.0.1'). If not specified, auto-generated | None |
572+
| Name | Type | Description | Default |
573+
|-----------------------|------------|--------------------------------------------------------------------------------------------------------|---------|
574+
| `schema_path` | Positional | Path to the JSON schema file to register | |
575+
| `organization_name` | Positional | Name of the organization to register the schema under | |
576+
| `schema_name` | Positional | The name of the JSON schema | |
577+
| `--schema-version` | Named | Version of the schema to register (e.g., '0.0.1'). If not specified, auto-generated | None |
578+
| `--fix-schema-name` | Named | Replaces dashes and underscores with periods in the schema name ('my-schema_name' -> 'my.schema.name') | False |
578579
579580
### `bind-json-schema`
580581

synapseclient/__main__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ def register_json_schema(args, syn):
824824
schema_path=args.schema_path,
825825
organization_name=args.organization_name,
826826
schema_name=args.schema_name,
827+
fix_schema_name=args.fix_schema_name,
827828
schema_version=args.schema_version,
828829
synapse_client=syn,
829830
)
@@ -1892,6 +1893,12 @@ def build_parser():
18921893
type=str,
18931894
help="The name of the JSON schema",
18941895
)
1896+
parser_register_json_schema.add_argument(
1897+
"--fix-schema-name",
1898+
action="store_true",
1899+
default=False,
1900+
help="Replaces dashes and underscores with periods in the schema name (e.g., 'my-schema_name' becomes 'my.schema.name')",
1901+
)
18951902
parser_register_json_schema.add_argument(
18961903
"--schema-version",
18971904
dest="schema_version",

synapseclient/extensions/curator/schema_management.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import json
9+
import re
910
from typing import TYPE_CHECKING, Optional
1011

1112
from synapseclient.core.async_utils import wrap_async_to_sync
@@ -20,6 +21,7 @@ def register_jsonschema(
2021
schema_path: str,
2122
organization_name: str,
2223
schema_name: str,
24+
fix_schema_name: bool = False,
2325
schema_version: Optional[str] = None,
2426
synapse_client: Optional["Synapse"] = None,
2527
) -> "JSONSchema":
@@ -33,6 +35,8 @@ def register_jsonschema(
3335
schema_path: Path to the JSON schema file to register
3436
organization_name: Name of the organization to register the schema under
3537
schema_name: Name of the JSON schema
38+
fix_schema_name: If True, fixes the schema name to meet Synapse requirements by replacing
39+
dashes and underscores with periods. Defaults to False.
3640
schema_version: Optional version of the schema (e.g., '0.0.1').
3741
If not specified, a version will be auto-generated.
3842
synapse_client: If not passed in and caching was not disabled by
@@ -54,6 +58,7 @@ def register_jsonschema(
5458
schema_path="/path/to/schema.json",
5559
organization_name="my.org",
5660
schema_name="my.schema",
61+
fix_schema_name=True,
5762
schema_version="0.0.1",
5863
synapse_client=syn
5964
)
@@ -65,6 +70,7 @@ def register_jsonschema(
6570
coroutine=register_jsonschema_async(
6671
schema_path=schema_path,
6772
organization_name=organization_name,
73+
fix_schema_name=fix_schema_name,
6874
schema_name=schema_name,
6975
schema_version=schema_version,
7076
synapse_client=synapse_client,
@@ -76,6 +82,7 @@ async def register_jsonschema_async(
7682
schema_path: str,
7783
organization_name: str,
7884
schema_name: str,
85+
fix_schema_name: bool = False,
7986
schema_version: Optional[str] = None,
8087
synapse_client: Optional["Synapse"] = None,
8188
) -> "JSONSchema":
@@ -89,6 +96,8 @@ async def register_jsonschema_async(
8996
schema_path: Path to the JSON schema file to register
9097
organization_name: Name of the organization to register the schema under
9198
schema_name: The name of the JSON schema
99+
fix_schema_name: If True, fixes the schema name to meet Synapse requirements by replacing
100+
dashes and underscores with periods. Defaults to False.
92101
schema_version: Optional version of the schema (e.g., '0.0.1').
93102
If not specified, a version will be auto-generated.
94103
synapse_client: If not passed in and caching was not disabled by
@@ -111,6 +120,7 @@ async def register_jsonschema_async(
111120
schema_path="/path/to/schema.json",
112121
organization_name="my.org",
113122
schema_name="my.schema",
123+
fix_schema_name=True,
114124
schema_version="0.0.1",
115125
synapse_client=syn
116126
))
@@ -123,6 +133,11 @@ async def register_jsonschema_async(
123133

124134
syn = Synapse.get_client(synapse_client=synapse_client)
125135

136+
if fix_schema_name:
137+
old_name = schema_name
138+
schema_name = fix_name(schema_name)
139+
syn.logger.info(f"Changed schema name from '{old_name}' to '{schema_name}' ")
140+
126141
with open(schema_path, "r") as f:
127142
schema_body = json.load(f)
128143

@@ -142,6 +157,24 @@ async def register_jsonschema_async(
142157
return json_schema
143158

144159

160+
def fix_name(name: str) -> str:
161+
"""
162+
Fixes a schema name to meet Synapse requirements by:
163+
- replacing dashes and underscores with periods.
164+
- collapsing multiple consecutive periods into a single period.
165+
166+
Arguments:
167+
name: The original schema name
168+
169+
Returns:
170+
The fixed schema name
171+
172+
"""
173+
name = name.replace("-", ".").replace("_", ".")
174+
name = re.sub(r"\.+", ".", name)
175+
return name
176+
177+
145178
def bind_jsonschema(
146179
entity_id: str,
147180
json_schema_uri: str,

tests/integration/synapseclient/test_command_line_client.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,30 @@ def test_register_json_schema(self, test_state, schema_organization, schema_file
13341334
assert schema_name in output
13351335
assert schema_organization.name in output
13361336

1337+
def test_register_json_schema_fix_schema_name(
1338+
self, test_state, schema_organization, schema_file
1339+
):
1340+
"""Test register-json-schema CLI command"""
1341+
schema_name = f"test-schema_id{str(uuid.uuid4())[:8]}"
1342+
fixed_schema_name = schema_name.replace("-", ".").replace("_", ".")
1343+
1344+
output = run(
1345+
test_state,
1346+
"synapse",
1347+
"--skip-checks",
1348+
"register-json-schema",
1349+
schema_file,
1350+
schema_organization.name,
1351+
schema_name,
1352+
"--schema-version",
1353+
"1.0.0",
1354+
"--fix-schema-name",
1355+
)
1356+
1357+
assert "Successfully registered schema" in output
1358+
assert fixed_schema_name in output
1359+
assert schema_organization.name in output
1360+
13371361
def test_bind_json_schema(self, test_state, schema_organization, schema_file):
13381362
"""Test bind-json-schema CLI command"""
13391363
from synapseclient.models import Folder
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import json
2+
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
3+
4+
import pytest
5+
6+
from synapseclient.extensions.curator import register_jsonschema_async
7+
from synapseclient.extensions.curator.schema_management import fix_name
8+
9+
10+
@pytest.fixture
11+
def mock_synapse_client():
12+
mock_client = MagicMock()
13+
mock_client.logger = MagicMock()
14+
return mock_client
15+
16+
17+
@pytest.fixture
18+
def mock_jsonschema():
19+
with patch("synapseclient.models.schema_organization.JSONSchema") as MockSchema:
20+
instance = MockSchema.return_value
21+
instance.store_async = AsyncMock()
22+
instance.uri = "syn123.456"
23+
yield MockSchema
24+
25+
26+
@pytest.mark.asyncio
27+
async def test_register_jsonschema_async(mock_synapse_client, mock_jsonschema):
28+
schema_path = "mock_path.json"
29+
schema_content = {"$id": "test_schema", "type": "object"}
30+
org_name = "test.org"
31+
schema_name = "my-schema_name"
32+
version = "1.0.0"
33+
34+
m_open = mock_open(read_data=json.dumps(schema_content))
35+
36+
with patch("builtins.open", m_open), patch(
37+
"synapseclient.Synapse.get_client", return_value=mock_synapse_client
38+
), patch("json.load", return_value=schema_content):
39+
result = await register_jsonschema_async(
40+
schema_path=schema_path,
41+
organization_name=org_name,
42+
schema_name=schema_name,
43+
schema_version=version,
44+
synapse_client=mock_synapse_client,
45+
)
46+
47+
# Verify the name was used as-is
48+
mock_jsonschema.assert_called_once_with(
49+
name=schema_name, organization_name=org_name
50+
)
51+
52+
result.store_async.assert_awaited_once_with(
53+
schema_body=schema_content,
54+
version=version,
55+
synapse_client=mock_synapse_client,
56+
)
57+
58+
assert result.uri == "syn123.456"
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_register_jsonschema_async_fix_schema_name(
63+
mock_synapse_client, mock_jsonschema
64+
):
65+
schema_path = "mock_path.json"
66+
schema_content = {"$id": "test_schema", "type": "object"}
67+
org_name = "test.org"
68+
schema_name = "my-schema_name"
69+
fixed_schema_name = "my.schema.name"
70+
version = "1.0.0"
71+
72+
m_open = mock_open(read_data=json.dumps(schema_content))
73+
74+
with patch("builtins.open", m_open), patch(
75+
"synapseclient.Synapse.get_client", return_value=mock_synapse_client
76+
), patch("json.load", return_value=schema_content):
77+
result = await register_jsonschema_async(
78+
schema_path=schema_path,
79+
organization_name=org_name,
80+
schema_name=schema_name,
81+
fix_schema_name=True,
82+
schema_version=version,
83+
synapse_client=mock_synapse_client,
84+
)
85+
86+
# Verify the name was fixed (dashes/underscores to dots)
87+
mock_jsonschema.assert_called_once_with(
88+
name=fixed_schema_name, organization_name=org_name
89+
)
90+
91+
result.store_async.assert_awaited_once_with(
92+
schema_body=schema_content,
93+
version=version,
94+
synapse_client=mock_synapse_client,
95+
)
96+
97+
assert result.uri == "syn123.456"
98+
99+
100+
@pytest.mark.parametrize(
101+
"name, expected_fixed_name",
102+
[
103+
("name", "name"),
104+
("name.name", "name.name"),
105+
("name..name", "name.name"),
106+
("name-name", "name.name"),
107+
("name--name", "name.name"),
108+
("name_name", "name.name"),
109+
("name-_name", "name.name"),
110+
],
111+
ids=[
112+
"No special characters",
113+
"One period",
114+
"Multiple periods",
115+
"One dash",
116+
"Multiple dashes",
117+
"Underscore",
118+
"Mixed special characters",
119+
],
120+
)
121+
def test_fix_name(name, expected_fixed_name):
122+
assert fix_name(name) == expected_fixed_name

0 commit comments

Comments
 (0)