Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/tutorials/command_line_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,12 +569,13 @@ Register a JSON Schema to a Synapse organization for later binding to entities.
synapse register-json-schema [-h] [--schema-version VERSION] schema_path organization_name schema_name
```

| Name | Type | Description | Default |
|-----------------------|------------|-------------------------------------------------------------------------------------|---------|
| `schema_path` | Positional | Path to the JSON schema file to register | |
| `organization_name` | Positional | Name of the organization to register the schema under | |
| `schema_name` | Positional | The name of the JSON schema | |
| `--schema-version` | Named | Version of the schema to register (e.g., '0.0.1'). If not specified, auto-generated | None |
| Name | Type | Description | Default |
|-----------------------|------------|--------------------------------------------------------------------------------------------------------|---------|
| `schema_path` | Positional | Path to the JSON schema file to register | |
| `organization_name` | Positional | Name of the organization to register the schema under | |
| `schema_name` | Positional | The name of the JSON schema | |
| `--schema-version` | Named | Version of the schema to register (e.g., '0.0.1'). If not specified, auto-generated | None |
| `--fix-schema-name` | Named | Replaces dashes and underscores with periods in the schema name ('my-schema_name' -> 'my.schema.name') | False |

### `bind-json-schema`

Expand Down
7 changes: 7 additions & 0 deletions synapseclient/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,7 @@ def register_json_schema(args, syn):
schema_path=args.schema_path,
organization_name=args.organization_name,
schema_name=args.schema_name,
fix_schema_name=args.fix_schema_name,
schema_version=args.schema_version,
synapse_client=syn,
)
Expand Down Expand Up @@ -1892,6 +1893,12 @@ def build_parser():
type=str,
help="The name of the JSON schema",
)
parser_register_json_schema.add_argument(
"--fix-schema-name",
action="store_true",
default=False,
help="Replaces dashes and underscores with periods in the schema name (e.g., 'my-schema_name' becomes 'my.schema.name')",
)
parser_register_json_schema.add_argument(
"--schema-version",
dest="schema_version",
Expand Down
33 changes: 33 additions & 0 deletions synapseclient/extensions/curator/schema_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

import json
import re
from typing import TYPE_CHECKING, Optional

from synapseclient.core.async_utils import wrap_async_to_sync
Expand All @@ -20,6 +21,7 @@ def register_jsonschema(
schema_path: str,
organization_name: str,
schema_name: str,
fix_schema_name: bool = False,
schema_version: Optional[str] = None,
synapse_client: Optional["Synapse"] = None,
) -> "JSONSchema":
Expand All @@ -33,6 +35,8 @@ def register_jsonschema(
schema_path: Path to the JSON schema file to register
organization_name: Name of the organization to register the schema under
schema_name: Name of the JSON schema
fix_schema_name: If True, fixes the schema name to meet Synapse requirements by replacing
dashes and underscores with periods. Defaults to False.
schema_version: Optional version of the schema (e.g., '0.0.1').
If not specified, a version will be auto-generated.
synapse_client: If not passed in and caching was not disabled by
Expand All @@ -54,6 +58,7 @@ def register_jsonschema(
schema_path="/path/to/schema.json",
organization_name="my.org",
schema_name="my.schema",
fix_schema_name=True,
schema_version="0.0.1",
synapse_client=syn
)
Expand All @@ -65,6 +70,7 @@ def register_jsonschema(
coroutine=register_jsonschema_async(
schema_path=schema_path,
organization_name=organization_name,
fix_schema_name=fix_schema_name,
schema_name=schema_name,
schema_version=schema_version,
synapse_client=synapse_client,
Expand All @@ -76,6 +82,7 @@ async def register_jsonschema_async(
schema_path: str,
organization_name: str,
schema_name: str,
fix_schema_name: bool = False,
schema_version: Optional[str] = None,
synapse_client: Optional["Synapse"] = None,
) -> "JSONSchema":
Expand All @@ -89,6 +96,8 @@ async def register_jsonschema_async(
schema_path: Path to the JSON schema file to register
organization_name: Name of the organization to register the schema under
schema_name: The name of the JSON schema
fix_schema_name: If True, fixes the schema name to meet Synapse requirements by replacing
dashes and underscores with periods. Defaults to False.
schema_version: Optional version of the schema (e.g., '0.0.1').
If not specified, a version will be auto-generated.
synapse_client: If not passed in and caching was not disabled by
Expand All @@ -111,6 +120,7 @@ async def register_jsonschema_async(
schema_path="/path/to/schema.json",
organization_name="my.org",
schema_name="my.schema",
fix_schema_name=True,
schema_version="0.0.1",
synapse_client=syn
))
Expand All @@ -123,6 +133,11 @@ async def register_jsonschema_async(

syn = Synapse.get_client(synapse_client=synapse_client)

if fix_schema_name:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewelamb I just noticed there's a bug in the code. The log should only be printed if there's actually a name change. I am seeing such as below when there's no name changes:

Changed schema name from 'ComputationalToolsTemplate' to 'ComputationalToolsTemplate'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, that's when fix_schema_names is turned on, but no change actually gets made. That's a quick fix.

old_name = schema_name
schema_name = fix_name(schema_name)
syn.logger.info(f"Changed schema name from '{old_name}' to '{schema_name}' ")

with open(schema_path, "r") as f:
schema_body = json.load(f)

Expand All @@ -142,6 +157,24 @@ async def register_jsonschema_async(
return json_schema


def fix_name(name: str) -> str:
"""
Fixes a schema name to meet Synapse requirements by:
- replacing dashes and underscores with periods.
- collapsing multiple consecutive periods into a single period.

Arguments:
name: The original schema name

Returns:
The fixed schema name

"""
name = name.replace("-", ".").replace("_", ".")
name = re.sub(r"\.+", ".", name)
return name


def bind_jsonschema(
entity_id: str,
json_schema_uri: str,
Expand Down
24 changes: 24 additions & 0 deletions tests/integration/synapseclient/test_command_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,30 @@ def test_register_json_schema(self, test_state, schema_organization, schema_file
assert schema_name in output
assert schema_organization.name in output

def test_register_json_schema_fix_schema_name(
self, test_state, schema_organization, schema_file
):
"""Test register-json-schema CLI command"""
schema_name = f"test-schema_id{str(uuid.uuid4())[:8]}"
fixed_schema_name = schema_name.replace("-", ".").replace("_", ".")

output = run(
test_state,
"synapse",
"--skip-checks",
"register-json-schema",
schema_file,
schema_organization.name,
schema_name,
"--schema-version",
"1.0.0",
"--fix-schema-name",
)

assert "Successfully registered schema" in output
assert fixed_schema_name in output
assert schema_organization.name in output

def test_bind_json_schema(self, test_state, schema_organization, schema_file):
"""Test bind-json-schema CLI command"""
from synapseclient.models import Folder
Expand Down
122 changes: 122 additions & 0 deletions tests/unit/synapseclient/extensions/test_schema_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import json
from unittest.mock import AsyncMock, MagicMock, mock_open, patch

import pytest

from synapseclient.extensions.curator import register_jsonschema_async
from synapseclient.extensions.curator.schema_management import fix_name


@pytest.fixture
def mock_synapse_client():
mock_client = MagicMock()
mock_client.logger = MagicMock()
return mock_client


@pytest.fixture
def mock_jsonschema():
with patch("synapseclient.models.schema_organization.JSONSchema") as MockSchema:
instance = MockSchema.return_value
instance.store_async = AsyncMock()
instance.uri = "syn123.456"
yield MockSchema


@pytest.mark.asyncio
async def test_register_jsonschema_async(mock_synapse_client, mock_jsonschema):
schema_path = "mock_path.json"
schema_content = {"$id": "test_schema", "type": "object"}
org_name = "test.org"
schema_name = "my-schema_name"
version = "1.0.0"

m_open = mock_open(read_data=json.dumps(schema_content))

with patch("builtins.open", m_open), patch(
"synapseclient.Synapse.get_client", return_value=mock_synapse_client
), patch("json.load", return_value=schema_content):
result = await register_jsonschema_async(
schema_path=schema_path,
organization_name=org_name,
schema_name=schema_name,
schema_version=version,
synapse_client=mock_synapse_client,
)

# Verify the name was used as-is
mock_jsonschema.assert_called_once_with(
name=schema_name, organization_name=org_name
)

result.store_async.assert_awaited_once_with(
schema_body=schema_content,
version=version,
synapse_client=mock_synapse_client,
)

assert result.uri == "syn123.456"


@pytest.mark.asyncio
async def test_register_jsonschema_async_fix_schema_name(
mock_synapse_client, mock_jsonschema
):
schema_path = "mock_path.json"
schema_content = {"$id": "test_schema", "type": "object"}
org_name = "test.org"
schema_name = "my-schema_name"
fixed_schema_name = "my.schema.name"
version = "1.0.0"

m_open = mock_open(read_data=json.dumps(schema_content))

with patch("builtins.open", m_open), patch(
"synapseclient.Synapse.get_client", return_value=mock_synapse_client
), patch("json.load", return_value=schema_content):
result = await register_jsonschema_async(
schema_path=schema_path,
organization_name=org_name,
schema_name=schema_name,
fix_schema_name=True,
schema_version=version,
synapse_client=mock_synapse_client,
)

# Verify the name was fixed (dashes/underscores to dots)
mock_jsonschema.assert_called_once_with(
name=fixed_schema_name, organization_name=org_name
)

result.store_async.assert_awaited_once_with(
schema_body=schema_content,
version=version,
synapse_client=mock_synapse_client,
)

assert result.uri == "syn123.456"


@pytest.mark.parametrize(
"name, expected_fixed_name",
[
("name", "name"),
("name.name", "name.name"),
("name..name", "name.name"),
("name-name", "name.name"),
("name--name", "name.name"),
("name_name", "name.name"),
("name-_name", "name.name"),
],
ids=[
"No special characters",
"One period",
"Multiple periods",
"One dash",
"Multiple dashes",
"Underscore",
"Mixed special characters",
],
)
def test_fix_name(name, expected_fixed_name):
assert fix_name(name) == expected_fixed_name
Loading