Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
bb40e7e
Removing 2.7 support and switching everything to new practices
xiancg May 7, 2023
270e3cf
New package setup
xiancg Aug 3, 2024
f9dc598
Update logger
xiancg Aug 3, 2024
29228ad
Logger update and removed six calls
xiancg Aug 3, 2024
3944c62
Updating format and removing eval statements
xiancg Aug 4, 2024
0e74614
Adding pathlib and making sure TokenNumber and Token are taken in loa…
xiancg Aug 4, 2024
721f128
Working on typing hints to improve readability
xiancg Aug 4, 2024
1c143f6
Typing hints
xiancg Aug 4, 2024
78eced5
Parameterize explicit solve test
xiancg Aug 4, 2024
72316fc
Parameterize defaults tests
xiancg Aug 4, 2024
02aebeb
Parameterize implicit solving test
xiancg Aug 4, 2024
4ef373d
Parameterize parsing tests
xiancg Aug 4, 2024
ab7ad6d
Parameterize repeated tokens
xiancg Aug 4, 2024
c828ed0
Parameterize repeating tokens
xiancg Aug 4, 2024
2a38c90
Parameterization of anchor tests
xiancg Aug 4, 2024
ba8372d
More parameterization on naming tests
xiancg Aug 4, 2024
db14be8
Remove comment
xiancg Aug 4, 2024
f535ba3
WIP rules tests parameterization
xiancg Aug 4, 2024
aa898fc
Validate basic rule creation parameters
xiancg Aug 5, 2024
638c895
Rules tests parametrization
xiancg Aug 5, 2024
584c456
Wip token tests parametrization
xiancg Aug 6, 2024
3ded37b
Tokens tests parametrization finished
xiancg Aug 6, 2024
870da79
Update docs
xiancg Aug 6, 2024
7cf4ddf
Fixing outdated tox
xiancg Aug 6, 2024
df05641
Merge branch 'master' into refactor
xiancg Aug 7, 2024
ef92972
Remove old travis
xiancg Aug 7, 2024
34f456d
Merge branch 'master' into refactor
xiancg Aug 7, 2024
cb57fb5
Setup shell before invoking ruff
xiancg Aug 7, 2024
4906430
Using pipenv run
xiancg Aug 7, 2024
ebdcf82
Badges
xiancg Aug 7, 2024
4bdded2
Fixing docs buils
xiancg Aug 7, 2024
0794381
docs requirements
xiancg Aug 7, 2024
4930a59
Update yml with requirements
xiancg Aug 7, 2024
8e01a76
Merge branch 'master' into refactor
xiancg Aug 7, 2024
93611b4
Update badges in docs
xiancg Aug 7, 2024
96dfd41
Merge branch 'master' into refactor
xiancg Aug 7, 2024
879181d
Update coverage
xiancg Aug 10, 2024
a06c8a4
Adds coverage badge
xiancg Aug 10, 2024
8fce812
Testing environment
xiancg Aug 10, 2024
583e488
Add lcov report
xiancg Aug 10, 2024
057036f
wrong token placement
xiancg Aug 10, 2024
9122ca7
Merge branch 'master' into refactor
xiancg Aug 10, 2024
50c3fbe
Update get_repo logic and adds validation functions
xiancg Aug 11, 2024
60dc4ec
Adds tokens and rules validations. Refactor save_session
xiancg Aug 11, 2024
be5a88c
Update load function conf naming
xiancg Aug 11, 2024
f0c10d6
Not templates, rules
xiancg Aug 11, 2024
bb8d379
Limit to pull requests
xiancg Aug 11, 2024
e4aa558
Adding referenced rules
xiancg Aug 11, 2024
55ad519
Working on new referenced functionality an tests
xiancg Aug 11, 2024
a243b9d
Adds validation function and test
xiancg Dec 23, 2024
1b31d15
All tests passing
xiancg Dec 23, 2024
aba19d4
Validations for token numbers
xiancg Dec 23, 2024
4fd1bd2
Padding validation and more docs for it
xiancg Dec 23, 2024
5f3b565
Version up
xiancg Dec 23, 2024
023de7d
Merge branch 'master' into refactor
xiancg Dec 23, 2024
52126a3
Version up
xiancg Dec 23, 2024
36305df
Ignore flake8 for now
xiancg Dec 23, 2024
7d54804
Fix .conf file naming in logs
xiancg Dec 23, 2024
61ceac2
Merge branch 'master' into refactor
xiancg Dec 23, 2024
6c60b27
Missing log info in raise statement
xiancg Jan 6, 2025
b027a75
Merge branch 'master' into refactor
xiancg Jan 6, 2025
98f45dc
Let the user know about casing mismatches
xiancg Jan 6, 2025
24c8be4
Merge branch 'master' into refactor
xiancg Jan 6, 2025
dfb9dda
Improve error info if casing is the problem
xiancg Jan 7, 2025
c0049f4
Implements validation against data passed by the user
xiancg Jan 7, 2025
e4156af
Version up
xiancg Jan 7, 2025
7026449
Merge branch 'master' into refactor
xiancg Jan 7, 2025
64a0c65
Implements fallback value for required tokens
xiancg Jan 8, 2025
74c110a
Make sure we only enforce this on Token and not TokenNumber
xiancg Jan 8, 2025
6af78cb
Update docs to include new fallback attribute of Token
xiancg Jan 8, 2025
9c4c69f
Version up
xiancg Jan 8, 2025
8e9613d
Merge branch 'master' into refactor
xiancg Jan 8, 2025
bd159e7
WIP picking rules
xiancg Jan 8, 2025
f18ec2c
WIP validate many rules
xiancg Jan 10, 2025
bf5f13b
Merge branch 'master' into refactor
xiancg Jan 10, 2025
9d0b0fd
Validate against certain rules. Validation now returns a list of vali…
xiancg Jan 10, 2025
82861dd
Update docs
xiancg Jan 10, 2025
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
7 changes: 7 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
================================

1.5.1-beta
---------------------------------------

**Improvements:**
- Adds strict validation option. If strict is True, the name must match exactly the casing the rule.
- If no rule names are passed to with_rules, only the active rule is validated.

1.4.5-beta
---------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions docs/source/usage/validating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Many times the only thing we need is to know if a name is valid or not for a giv
- The number of expected separators must match with the rule.
- If tokens have options, the given name must use one of those options.
- If token is a number, validates suffix, prefix and padding.
- If strict is passed as True to the validate function, the name must match exactly the casing the rule.
- If no rule names are passed to with_rules, only the active rule is validated.

Let's set these Tokens and Rule.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "vfxnaming"
version = "1.5.0-beta"
version = "1.5.1-beta"
authors = [
{ name="Chris Granados", email="info@chrisgranados.com" },
]
Expand Down
114 changes: 72 additions & 42 deletions src/vfxnaming/naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import vfxnaming.rules as rules
import vfxnaming.tokens as tokens
from pathlib import Path
from typing import AnyStr, Dict, Union
from typing import AnyStr, Dict, Union, Iterable

from vfxnaming.logger import logger
from vfxnaming.error import SolvingError, RepoError
Expand Down Expand Up @@ -138,9 +138,12 @@ def solve(*args, **kwargs) -> AnyStr:
return rule.solve(**values)


def validate(name: AnyStr, **kwargs) -> bool:
"""Validates a name string against the currently active rule and its
tokens if passed as keyword arguments.
def validate( # noqa: C901
name: AnyStr, with_rules: Iterable[str] = [], strict: bool = False, **kwargs
) -> Iterable[rules.Rule]:
"""Validates a name string against the currently active rule if no rules are passed or
against the list of specific rules passed in with_rules.
It also validates its tokens if passed as keyword arguments.

-For rules with repeated tokens:

Expand All @@ -162,50 +165,77 @@ def validate(name: AnyStr, **kwargs) -> bool:
Args:
name (str): Name string e.g.: C_helmet_001_MSH

with_rules (list, optional): List of rule names to validate against. Defaults to [].

strict (bool, optional): If False, it'll try to accept casing mismatches.

kwargs (dict): Keyword arguments with token names and values.

Returns:
bool: True if the name is valid, False otherwise.
list: List of validated rules. Empty list if no rule could be validated.
"""
rule = rules.get_active_rule()
# * This accounts for those cases where a token is used more than once in a rule
repeated_fields = dict()
for each in rule.fields:
if each not in repeated_fields.keys():
if rule.fields.count(each) > 1:
repeated_fields[each] = 1
fields_with_digits = list()
for each in rule.fields:
if each in repeated_fields.keys():
counter = repeated_fields.get(each)
repeated_fields[each] = counter + 1
fields_with_digits.append(f"{each}{counter}")
else:
fields_with_digits.append(each)
values = {}
fields_inc = 0
for f in fields_with_digits:
token = tokens.get_token(rule.fields[fields_inc])
if token:
# Explicitly passed as keyword argument
if kwargs.get(f) is not None:
values[f] = token.solve(kwargs.get(f))
fields_inc += 1
continue
# Explicitly passed as keyword argument without repetitive digits
# Use passed argument for all field repetitions
elif kwargs.get(rule.fields[fields_inc]) is not None:
values[f] = token.solve(kwargs.get(rule.fields[fields_inc]))
fields_inc += 1
continue
elif token.required and isinstance(token, tokens.Token):
if len(token.fallback):
values[f] = token.fallback
previously_active_rule = rules.get_active_rule()
if not len(with_rules):
with_rules = [previously_active_rule.name]
validated: Iterable[rules.Rule] = []
for with_rule in with_rules:
rule = rules.get_rule(with_rule)
if not rule:
logger.warning(f"Rule {with_rule} not found.")

rules.set_active_rule(rule)
# * This accounts for those cases where a token is used more than once in a rule
repeated_fields = dict()
for each in rule.fields:
if each not in repeated_fields.keys():
if rule.fields.count(each) > 1:
repeated_fields[each] = 1
fields_with_digits = list()
for each in rule.fields:
if each in repeated_fields.keys():
counter = repeated_fields.get(each)
repeated_fields[each] = counter + 1
fields_with_digits.append(f"{each}{counter}")
else:
fields_with_digits.append(each)
values = {}
fields_inc = 0
for f in fields_with_digits:
token = tokens.get_token(rule.fields[fields_inc])
if token:
# Explicitly passed as keyword argument
if kwargs.get(f) is not None:
values[f] = token.solve(kwargs.get(f))
fields_inc += 1
continue
fields_inc += 1
logger.debug(f"Validating rule '{rule.name}' with values {values}")
return rule.validate(name, **values)
# Explicitly passed as keyword argument without repetitive digits
# Use passed argument for all field repetitions
elif kwargs.get(rule.fields[fields_inc]) is not None:
values[f] = token.solve(kwargs.get(rule.fields[fields_inc]))
fields_inc += 1
continue
elif token.required and isinstance(token, tokens.Token):
if len(token.fallback):
values[f] = token.fallback
fields_inc += 1
continue
fields_inc += 1
logger.debug(f"Validating rule '{rule.name}' with values {values}")
validation = rule.validate(name, strict, **values)
if validation:
rules.set_active_rule(previously_active_rule)
validated.append(rule)
rules.set_active_rule(previously_active_rule)
if not len(validated):
logger.warning(
f"Could not validate {name} with any of the given "
f"rules {', '.join([rule for rule in with_rules])}."
)
else:
logger.info(
f"Name {name} validated with rules: {', '.join([rule.name for rule in validated])}."
)
return validated


def validate_repo(repo: Path) -> bool:
Expand Down
15 changes: 7 additions & 8 deletions src/vfxnaming/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def parse(self, name: AnyStr) -> Union[Dict, None]:
f"and rule's pattern '{self._pattern}':'{len(expected_separators)}'."
)

def validate(self, name: AnyStr, **validate_values) -> bool: # noqa: C901
def validate(self, name: AnyStr, strict: bool = False, **validate_values) -> bool: # noqa: C901
"""Validate if given name matches the rule pattern.

Args:
Expand All @@ -184,14 +184,10 @@ def validate(self, name: AnyStr, **validate_values) -> bool: # noqa: C901
)
return False

regex = self.__build_regex()
regex = self.__build_regex(strict)
match = regex.search(name)
if not match:
logger.warning(f"Name {name} does not match rule pattern '{self._pattern}'")
if regex.search(name.lower()):
logger.warning(
f"Name {name} has casing mismatches with '{self._pattern}'"
)
return False

match_dict = match.groupdict()
Expand Down Expand Up @@ -297,7 +293,7 @@ def validate(self, name: AnyStr, **validate_values) -> bool: # noqa: C901

return matching_options

def __build_regex(self) -> re.Pattern:
def __build_regex(self, strict: bool = False) -> re.Pattern:
# ? Taken from Lucidity by Martin Pengelly-Phillips
# Escape non-placeholder components
expression = re.sub(
Expand All @@ -320,7 +316,10 @@ def __build_regex(self) -> re.Pattern:
expression = f"{expression}$"
# Compile expression
try:
compiled = re.compile(expression)
if strict:
compiled = re.compile(expression)
else:
compiled = re.compile(expression, re.IGNORECASE)
except re.error as error:
if any(
[
Expand Down
74 changes: 54 additions & 20 deletions tests/naming_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from pathlib import Path
import pytest
import tempfile
from typing import Dict, List
import types

Check failure on line 4 in tests/naming_test.py

View workflow job for this annotation

GitHub Actions / build (3.10)

Ruff (F401)

tests/naming_test.py:4:8: F401 `types` imported but unused
from typing import Dict, List, Union

Check failure on line 5 in tests/naming_test.py

View workflow job for this annotation

GitHub Actions / build (3.10)

Ruff (F401)

tests/naming_test.py:5:32: F401 `typing.Union` imported but unused

from vfxnaming import naming as n
import vfxnaming.rules as rules
Expand Down Expand Up @@ -236,60 +237,92 @@
[
(
"dramatic_bounce_chars_001_LGT",
True,
1,
),
(
"dramatic_bounce_chars_001",
False,
0,
),
(
"whatEver_bounce_chars_001_LGT",
False,
0,
),
(
"dramatic_bounce_chars_01_LGT",
False,
0,
),
(
"dramatic_bounce_chars_v001_LGT",
False,
0,
),
(
"dramatic_bounce_chars_1000_LGT",
True,
1,
),
],
)
def test_valid(self, name: str, expected: bool):
assert n.validate(name) is expected
def test_valid(self, name: str, expected: int):
validated = n.validate(name)
assert len(validated) == expected

@pytest.mark.parametrize(
"name,validate_values,expected",
[
(
"dramatic_bounce_chars_001_LGT",
{"category": "dramatic"},
True,
1,
),
(
"dramatic_bounce_chars_001_LGT",
{"whatAffects": "chars"},
True,
1,
),
(
"dramatic_bounce_chars_001_LGT",
{"category": "practical"},
False,
0,
),
(
"dramatic_bounce_chars_001_LGT",
{"whatAffects": "anything"},
False,
0,
),
],
)
def test_valid_with_tokens(self, name: str, validate_values: dict, expected: bool):
assert n.validate(name, **validate_values) is expected
def test_valid_with_tokens(self, name: str, validate_values: dict, expected: int):
validated = n.validate(name, **validate_values)
assert len(validated) == expected


class Test_ValidateHarcodedValues:
@pytest.fixture(autouse=True)
def setup(self):
tokens.reset_tokens()
rules.reset_rules()
tokens.add_token("side", center="C", left="L", right="R", default="center")
tokens.add_token(
"region",
orbital="ORBI",
parotidmasseter="PAROT",
mental="MENT",
frontal="FRONT",
zygomatic="ZYGO",
retromandibularfossa="RETMAND",
)
rules.add_rule("filename", "{side}-ALWAYS_{side}-This_{side}-{region}")

@pytest.mark.parametrize(
"name,strict,expected",
[
("C-ALWAYS_C-This_C-ORBI", False, 1),
("C-always_C-This_C-ORBI", False, 1),
("C-always_C-this_C-ORBI", True, 0),
],
)
def test_valid_harcoded(self, name: str, strict: bool, expected: int):
validated = n.validate(name, strict=strict)
assert len(validated) == expected


class Test_ValidateWithRepetitions:
Expand Down Expand Up @@ -322,28 +355,29 @@
"side3": "right",
"region3": "zygomatic",
},
True,
1,
),
(
"R-MENT_C-PAROT_L-RETMAND",
{
"side2": "center",
},
True,
1,
),
(
"R-MENT_C-PAROT_L-RETMAND",
{
"side": "center",
},
False,
0,
),
],
)
def test_valid_with_repetitions(
self, name: str, validate_values: dict, expected: bool
self, name: str, validate_values: dict, expected: int
):
assert n.validate(name, **validate_values) is expected
validated = n.validate(name, **validate_values)
assert len(validated) == expected


class Test_RuleWithRepetitions:
Expand Down
Loading