Skip to content

Commit b42b239

Browse files
Merge pull request #1152 from datajoint/dev-1150-underscores
Fix #1150
2 parents f357955 + f54cd5c commit b42b239

File tree

4 files changed

+69
-3
lines changed

4 files changed

+69
-3
lines changed

datajoint/table.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .condition import make_condition
1616
from .expression import QueryExpression
1717
from . import blob
18-
from .utils import user_choice, get_master
18+
from .utils import user_choice, get_master, is_camel_case
1919
from .heading import Heading
2020
from .errors import (
2121
DuplicateError,
@@ -75,6 +75,10 @@ class Table(QueryExpression):
7575
def table_name(self):
7676
return self._table_name
7777

78+
@property
79+
def class_name(self):
80+
return self.__class__.__name__
81+
7882
@property
7983
def definition(self):
8084
raise NotImplementedError(
@@ -93,6 +97,14 @@ def declare(self, context=None):
9397
"Cannot declare new tables inside a transaction, "
9498
"e.g. from inside a populate/make call"
9599
)
100+
# Enforce strict CamelCase #1150
101+
if not is_camel_case(self.class_name):
102+
raise DataJointError(
103+
"Table class name `{name}` is invalid. Please use CamelCase. ".format(
104+
name=self.class_name
105+
)
106+
+ "Classes defining tables should be formatted in strict CamelCase."
107+
)
96108
sql, external_stores = declare(self.full_table_name, self.definition, context)
97109
sql = sql.format(database=self.database)
98110
try:

datajoint/utils.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ def get_master(full_table_name: str) -> str:
5353
return match["master"] + "`" if match else ""
5454

5555

56+
def is_camel_case(s):
57+
"""
58+
Check if a string is in CamelCase notation.
59+
60+
:param s: string to check
61+
:returns: True if the string is in CamelCase notation, False otherwise
62+
Example:
63+
>>> is_camel_case("TableName") # returns True
64+
>>> is_camel_case("table_name") # returns False
65+
"""
66+
return bool(re.match(r"^[A-Z][A-Za-z0-9]*$", s))
67+
68+
5669
def to_camel_case(s):
5770
"""
5871
Convert names with under score (_) separation into camel case names.
@@ -82,7 +95,7 @@ def from_camel_case(s):
8295
def convert(match):
8396
return ("_" if match.groups()[0] else "") + match.group(0).lower()
8497

85-
if not re.match(r"[A-Z][a-zA-Z0-9]*", s):
98+
if not is_camel_case(s):
8699
raise DataJointError(
87100
"ClassName must be alphanumeric in CamelCase, begin with a capital letter"
88101
)

tests/test_declare.py

+23
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,26 @@ class WithSuchALongPartNameThatItCrashesMySQL(dj.Part):
337337

338338
with pytest.raises(dj.DataJointError):
339339
schema_any(WhyWouldAnyoneCreateATableNameThisLong)
340+
341+
342+
def test_table_name_with_underscores(schema_any):
343+
"""
344+
Test issue #1150 -- Reject table names containing underscores. Tables should be in strict
345+
CamelCase.
346+
"""
347+
348+
class TableNoUnderscores(dj.Manual):
349+
definition = """
350+
id : int
351+
"""
352+
353+
class Table_With_Underscores(dj.Manual):
354+
definition = """
355+
id : int
356+
"""
357+
358+
schema_any(TableNoUnderscores)
359+
with pytest.raises(
360+
dj.DataJointError, match="must be alphanumeric in CamelCase"
361+
) as e:
362+
schema_any(Table_With_Underscores)

tests/test_utils.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,28 @@
33
"""
44

55
from datajoint import DataJointError
6-
from datajoint.utils import from_camel_case, to_camel_case
6+
from datajoint.utils import (
7+
from_camel_case,
8+
to_camel_case,
9+
is_camel_case,
10+
)
711
import pytest
812

913

14+
def test_is_camel_case():
15+
assert is_camel_case("AllGroups")
16+
assert not is_camel_case("All_Groups")
17+
assert not is_camel_case("All_Groups_")
18+
assert not is_camel_case("_AllGroups")
19+
assert not is_camel_case("allGroups")
20+
assert not is_camel_case("repNames")
21+
assert not is_camel_case("10_all")
22+
assert not is_camel_case("hello world")
23+
assert not is_camel_case("#baisc_names")
24+
assert not is_camel_case("alphaBeta")
25+
assert not is_camel_case("TestΣ")
26+
27+
1028
def test_from_camel_case():
1129
assert from_camel_case("AllGroups") == "all_groups"
1230
with pytest.raises(DataJointError):

0 commit comments

Comments
 (0)