From 126cc3e1d40e6964276451eee017ef8237a536df Mon Sep 17 00:00:00 2001 From: LavissaWoW Date: Wed, 20 Mar 2024 00:11:06 +0100 Subject: [PATCH] Alter pre-commit --- .gitignore | 2 +- .pre-commit-config.yaml | 13 +- LICENSE | 2 +- ipn_generator/generator.py | 149 +++++++------- ipn_generator/tests/test_IPNGenerator.py | 237 ++++++++--------------- 5 files changed, 171 insertions(+), 232 deletions(-) diff --git a/.gitignore b/.gitignore index 4f7ace7..6a9d296 100644 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,4 @@ venv.bak/ dmypy.json # Pyre type checker -.pyre/ \ No newline at end of file +.pyre/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05222ab..75fdd2d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,4 +6,15 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: mixed-line-ending - - id: check-toml \ No newline at end of file + - id: check-toml + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.3 + hooks: + - id: ruff-format + args: [--preview] + - id: ruff + args: [ + --fix, + --preview + ] diff --git a/LICENSE b/LICENSE index 3367210..723f5e8 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/ipn_generator/generator.py b/ipn_generator/generator.py index 727923b..431f956 100644 --- a/ipn_generator/generator.py +++ b/ipn_generator/generator.py @@ -9,7 +9,8 @@ import logging import re -logger = logging.getLogger('inventree') +logger = logging.getLogger("inventree") + def validate_pattern(pattern): """Validates pattern groups""" @@ -19,74 +20,77 @@ def validate_pattern(pattern): return True + class AutoGenIPNPlugin(EventMixin, SettingsMixin, InvenTreePlugin): """Plugin to generate IPN automatically""" - AUTHOR = 'Nichlas Walsøe' - DESCRIPTION = 'Plugin for automatically assigning IPN to parts created with empty IPN fields.' - VERSION = '0.1' + AUTHOR = "Nichlas Walsøe" + DESCRIPTION = ( + "Plugin for automatically assigning IPN to parts created with empty IPN fields." + ) + VERSION = "0.1" NAME = "IPNGenerator" SLUG = "ipngen" TITLE = "IPN Generator" SETTINGS = { - 'ACTIVE': { - 'name': ('Active'), - 'description': ('IPN generator is active'), - 'validator': bool, - 'default': True + "ACTIVE": { + "name": ("Active"), + "description": ("IPN generator is active"), + "validator": bool, + "default": True, }, - 'ON_CREATE': { - 'name': ('On Create'), - 'description': ('Active when creating new parts'), - 'validator': bool, - 'default': True + "ON_CREATE": { + "name": ("On Create"), + "description": ("Active when creating new parts"), + "validator": bool, + "default": True, }, - 'ON_CHANGE': { - 'name': ('On Edit'), - 'description': ('Active when editing existing parts'), - 'validator': bool, - 'default': True + "ON_CHANGE": { + "name": ("On Edit"), + "description": ("Active when editing existing parts"), + "validator": bool, + "default": True, }, - 'PATTERN': { - 'name': ('IPN pattern'), - 'description': ('Pattern for IPN generation'), - 'default': "(12)[a-z][a-d]", - 'validator': validate_pattern + "PATTERN": { + "name": ("IPN pattern"), + "description": ("Pattern for IPN generation"), + "default": "(12)[a-z][a-d]", + "validator": validate_pattern, }, } - min_pattern_char = ord('A') - max_pattern_char = ord('Z') - skip_chars = range(ord('['), ord('a')) + min_pattern_char = ord("A") + max_pattern_char = ord("Z") + skip_chars = range(ord("["), ord("a")) def wants_process_event(self, event): """Lets InvenTree know what events to listen for.""" - if not self.get_setting('ACTIVE'): + if not self.get_setting("ACTIVE"): return False - if (event == 'part_part.saved'): - return self.get_setting('ON_CHANGE') + if event == "part_part.saved": + return self.get_setting("ON_CHANGE") - if (event == 'part_part.created'): - return self.get_setting('ON_CREATE') + if event == "part_part.created": + return self.get_setting("ON_CREATE") return False def process_event(self, event, *args, **kwargs): """Main plugin handler function""" - if not self.get_setting('ACTIVE'): + if not self.get_setting("ACTIVE"): return False - id = kwargs.pop('id', None) - model = kwargs.pop('model', None) + id = kwargs.pop("id", None) + model = kwargs.pop("model", None) # Events can fire on unrelated models if model != "Part": - logger.debug('IPN Generator: Event Model is not part') + logger.debug("IPN Generator: Event Model is not part") return # Don't create IPNs for parts with IPNs @@ -95,7 +99,7 @@ def process_event(self, event, *args, **kwargs): return expression = self.construct_regex(True) - latest = Part.objects.filter(IPN__regex=expression).order_by('-IPN').first() + latest = Part.objects.filter(IPN__regex=expression).order_by("-IPN").first() if not latest: part.IPN = self.construct_first_ipn() @@ -107,51 +111,52 @@ def process_event(self, event, *args, **kwargs): return - def construct_regex(self, disable_groups=False): """Constructs a valid regex from provided IPN pattern. This regex is used to find the latest assigned IPN """ - regex = '^' + regex = "^" - m = re.findall(r"(\{\d+\+?\})|(\([\w\(\)]+\))|(\[(?:\w+|\w-\w)+\])", self.get_setting('PATTERN')) + m = re.findall( + r"(\{\d+\+?\})|(\([\w\(\)]+\))|(\[(?:\w+|\w-\w)+\])", + self.get_setting("PATTERN"), + ) for idx, group in enumerate(m): numeric, literal, character = group # Numeric, increment if numeric: start = "+" in numeric - r = '' g = numeric.strip("{}+") if start: - regex += '(' + regex += "(" if not disable_groups: - regex += f'?P' + regex += f"?P" for char in g: - regex += f'[{char}-9]' + regex += f"[{char}-9]" else: - regex += '(' + regex += "(" if not disable_groups: - regex += f'?P' - regex += f'\d{ {int(g)} }' - regex += ')' + regex += f"?P" + regex += f"\d{ {int(g)} }" + regex += ")" # Literal, won't change if literal: - l = literal.strip("()") - regex += '(' + lit = literal.strip("()") + regex += "(" if not disable_groups: - regex += f'?P' - regex += f'{re.escape(l)})' + regex += f"?P" + regex += f"{re.escape(lit)})" # Letters, a collection or sequence # Sequences incremented using ASCII if character: - regex += '(' + regex += "(" if not disable_groups: - regex += f'?P' regex += f'[{"".join(exp)}]' - regex += ')' + regex += ")" - regex += '$' + regex += "$" return regex @@ -183,17 +187,16 @@ def increment_ipn(self, exp, latest): incremented = False for key, val in reversed(m.groupdict().items()): + type, _ = key.split("i") - type, idx = key.split('i') - - if incremented or type == 'L': + if incremented or type == "L": ipn_list.append(val) continue - if type == 'N': - ipn_list.append(str(int(val)+1)) + if type == "N": + ipn_list.append(str(int(val) + 1)) incremented = True - elif type.startswith('C'): + elif type.startswith("C"): integerized_char = ord(val) choices = type[1:].split("_") @@ -222,13 +225,12 @@ def increment_ipn(self, exp, latest): incremented = True break - elif type.startswith('N'): - if type[1] == 'p': + elif type.startswith("N"): + if type[1] == "p": num = int(type[2:]) else: num = int(type[1:]) - if type[1] == 'p': - starts = int(type[2:]) + if type[1] == "p": next = int(val) + 1 if len(str(next)) > len(type[2:]): ipn_list.append(type[2:]) @@ -237,19 +239,20 @@ def increment_ipn(self, exp, latest): elif len(str(int(val) + 1)) > num: ipn_list.append(str(1).zfill(num)) else: - ipn_list.append(str(int(val)+1).zfill(num)) + ipn_list.append(str(int(val) + 1).zfill(num)) incremented = True ipn_list.reverse() return "".join(ipn_list) - - def construct_first_ipn(self): """No IPNs matching the pattern were found. Constructing the first IPN.""" - m = re.findall(r"(\{\d+\+?\})|(\([\w\(\)]+\))|(\[(?:\w+|(?:\w-\w)+)\])", self.get_setting('PATTERN')) + m = re.findall( + r"(\{\d+\+?\})|(\([\w\(\)]+\))|(\[(?:\w+|(?:\w-\w)+)\])", + self.get_setting("PATTERN"), + ) - ipn = '' + ipn = "" for group in m: numeric, literal, character = group diff --git a/ipn_generator/tests/test_IPNGenerator.py b/ipn_generator/tests/test_IPNGenerator.py index 6694b1b..f57d7ed 100644 --- a/ipn_generator/tests/test_IPNGenerator.py +++ b/ipn_generator/tests/test_IPNGenerator.py @@ -3,31 +3,33 @@ import logging from django.conf import settings -from ..generator import AutoGenIPNPlugin from part.models import Part, PartCategory from common.models import InvenTreeSetting from plugin import registry -logger = logging.getLogger('inventree') +logger = logging.getLogger("inventree") + def setup_func(cls): settings.PLUGIN_TESTING_EVENTS = True settings.TESTING_TABLE_EVENTS = True - InvenTreeSetting.set_setting('ENABLE_PLUGINS_EVENTS', True) - cls.plugin = registry.get_plugin('ipngen') + InvenTreeSetting.set_setting("ENABLE_PLUGINS_EVENTS", True) + cls.plugin = registry.get_plugin("ipngen") conf = cls.plugin.plugin_config() conf.active = True conf.save() + def teardown_func(): settings.PLUGIN_TESTING_EVENTS = False settings.TESTING_TABLE_EVENTS = False - InvenTreeSetting.set_setting('ENABLE_PLUGINS_EVENTS', False) + InvenTreeSetting.set_setting("ENABLE_PLUGINS_EVENTS", False) class IPNGeneratorPatternTests(TestCase): """Tests for verifying IPN pattern validation works properly""" + def setUp(self): """Set up test environment""" setup_func(self) @@ -39,103 +41,100 @@ def tearDown(self): def test_cannot_add_only_literal(self): """Verify that setting PATTERN to only literals fails validation""" with self.assertRaises(ValidationError): - self.plugin.set_setting('PATTERN', '(123)') + self.plugin.set_setting("PATTERN", "(123)") def test_cannot_add_only_random_string(self): """Verify that setting PATTERN to an invalid string""" with self.assertRaises(ValidationError): - self.plugin.set_setting('PATTERN', 'asldkferljgjtdS:DfS_D:fE_SD:FA_;G') + self.plugin.set_setting("PATTERN", "asldkferljgjtdS:DfS_D:fE_SD:FA_;G") def test_numeric_setting_length_1(self): """Verify that numeric regex accepts more than 1 int.""" # Single digit try: - self.plugin.set_setting('PATTERN', '{1}') + self.plugin.set_setting("PATTERN", "{1}") except ValidationError: self.fail("Correct numeric syntax raised a ValidationError") def test_numeric_setting_length_2(self): # Two digits try: - self.plugin.set_setting('PATTERN', '{15}') + self.plugin.set_setting("PATTERN", "{15}") except ValidationError: self.fail("Correct numeric syntax raised a ValidationError") - def test_numeric_setting_length_3(self): # Multiple digits try: - self.plugin.set_setting('PATTERN', '{125}') + self.plugin.set_setting("PATTERN", "{125}") except ValidationError: self.fail("Correct numeric syntax raised a ValidationError") - def text_numeric_setting_prefix_zero(self): """Zeroes should be filtered out when prefixed to numerics""" try: - self.plugin.set_setting('PATTERN', '{05}') + self.plugin.set_setting("PATTERN", "{05}") except ValidationError: self.fail("Numeric with 0 prefix raised a ValidationError") - def test_numeric_setting_with_start(self): """Appending a + to numerics should work""" try: - self.plugin.set_setting('PATTERN', '{25+}') + self.plugin.set_setting("PATTERN", "{25+}") except ValidationError: self.fail("Numeric with + suffix raised a ValidationError") def test_character_must_contain_more_than_one_character(self): """Verify that character groups must contain more than 1 character""" with self.assertRaises(ValidationError): - self.plugin.set_setting('PATTERN', '[a]') + self.plugin.set_setting("PATTERN", "[a]") def test_character_invalid_format(self): """Verify that character ranges are properly formatted""" with self.assertRaises(ValidationError): - self.plugin.set_setting('PATTERN', '[a-]') + self.plugin.set_setting("PATTERN", "[a-]") with self.assertRaises(ValidationError): - self.plugin.set_setting('PATTERN', '[aa-]') + self.plugin.set_setting("PATTERN", "[aa-]") def test_character_range_valid(self): """Verify that properly formatted character ranges are accepted""" try: - self.plugin.set_setting('PATTERN', '[a-b]') + self.plugin.set_setting("PATTERN", "[a-b]") except ValidationError: self.fail("Valid character group range raised a ValidationError") def test_character_list_valid(self): """Verify that list of individual characters are accepted""" try: - self.plugin.set_setting('PATTERN', '[abcsd]') + self.plugin.set_setting("PATTERN", "[abcsd]") except ValidationError: self.fail("Valid character list raised a ValidationError") def test_pattern_combinations(self): """""" try: - self.plugin.set_setting('PATTERN', '(1b)[a-b]{2}') + self.plugin.set_setting("PATTERN", "(1b)[a-b]{2}") except ValidationError: self.fail("Valid pattern (1b)[a-b]{2} raised a ValidationError") try: - self.plugin.set_setting('PATTERN', '[ab][a-d]{2}{3}') + self.plugin.set_setting("PATTERN", "[ab][a-d]{2}{3}") except ValidationError: self.fail("Valid pattern [ab][a-d]{2}{3} raised a ValidationError") try: - self.plugin.set_setting('PATTERN', '{2}[bc](a2)[a-c]') + self.plugin.set_setting("PATTERN", "{2}[bc](a2)[a-c]") except ValidationError: self.fail("Valid pattern {2}[bc](a2)[a-c] raised a ValidationError") try: - self.plugin.set_setting('PATTERN', '[a-b](1s){2}(3d)') + self.plugin.set_setting("PATTERN", "[a-b](1s){2}(3d)") except ValidationError: self.fail("Valid pattern [a-b](1s){2}(3d) raised a ValidationError") try: - self.plugin.set_setting('PATTERN', '{1}[aa]{2}(1r)') + self.plugin.set_setting("PATTERN", "{1}[aa]{2}(1r)") except ValidationError: self.fail("Valid pattern {1}[aa]{2}(1r) raised a ValidationError") @@ -154,130 +153,95 @@ def tearDown(self): def test_add_numeric(self): """Verify that numeric patterns work.""" - self.plugin.set_setting('PATTERN', '{1}') + self.plugin.set_setting("PATTERN", "{1}") cat = PartCategory.objects.all().first() - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=new_part.pk) self.assertIsNotNone(part.IPN) - self.assertEqual(part.IPN, '1') + self.assertEqual(part.IPN, "1") def test_add_numeric_with_start(self): """Verify that Numeric patterns with start number works.""" - self.plugin.set_setting('PATTERN', '{11+}') + self.plugin.set_setting("PATTERN", "{11+}") cat = PartCategory.objects.all().first() - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") - self.assertEqual(Part.objects.get(pk=new_part.pk).IPN, '11') + self.assertEqual(Part.objects.get(pk=new_part.pk).IPN, "11") def test_add_numeric_incrementing(self): """Verify that numeric patterns increment on subsequent parts.""" - self.plugin.set_setting('PATTERN', '{1}') + self.plugin.set_setting("PATTERN", "{1}") - self.assertEqual(self.plugin.get_setting('PATTERN'), '{1}') + self.assertEqual(self.plugin.get_setting("PATTERN"), "{1}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName' - ) + Part.objects.create(category=cat, name="PartName") - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=new_part.pk) - self.assertEqual(part.IPN, '2') + self.assertEqual(part.IPN, "2") def test_add_numeric_incrementing_with_start(self): """Verify that numeric patterns with start number increment on subsequent parts.""" - self.plugin.set_setting('PATTERN', '{11+}') + self.plugin.set_setting("PATTERN", "{11+}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName' - ) + Part.objects.create(category=cat, name="PartName") - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=new_part.pk) - self.assertEqual(part.IPN, '12') + self.assertEqual(part.IPN, "12") def test_add_numeric_with_prepend_zero(self): """Verify that numeric patterns work.""" - self.plugin.set_setting('PATTERN', '{3}') + self.plugin.set_setting("PATTERN", "{3}") cat = PartCategory.objects.all().first() - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=new_part.pk) self.assertIsNotNone(part.IPN) - self.assertEqual(part.IPN, '001') + self.assertEqual(part.IPN, "001") def test_numeric_rollover(self): """Verify that numeric groups rollover when reaching max""" - self.plugin.set_setting('PATTERN', '{2}') + self.plugin.set_setting("PATTERN", "{2}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name=f'PartName', - IPN='99' - ) + Part.objects.create(category=cat, name="PartName", IPN="99") - p = Part.objects.create( - category=cat, - name=f'PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, '01') + self.assertEqual(part.IPN, "01") def test_numeric_with_start_rollover(self): """Verify that numeric groups with start number rollover when reaching max""" - self.plugin.set_setting('PATTERN', '{26+}') + self.plugin.set_setting("PATTERN", "{26+}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name=f'PartName', - IPN='99' - ) + Part.objects.create(category=cat, name="PartName", IPN="99") - p = Part.objects.create( - category=cat, - name=f'PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, '26') + self.assertEqual(part.IPN, "26") class InvenTreeIPNGeneratorLiteralsTests(TestCase): @@ -294,23 +258,17 @@ def tearDown(self): def test_literal_persists(self): """Verify literals do not change""" - self.plugin.set_setting('PATTERN', '{1}(1v3)') + self.plugin.set_setting("PATTERN", "{1}(1v3)") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName' - ) + Part.objects.create(category=cat, name="PartName") - new_part = Part.objects.create( - category=cat, - name='PartName' - ) + new_part = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=new_part.pk) - self.assertEqual(part.IPN, '21v3') + self.assertEqual(part.IPN, "21v3") class InvenTreeIPNGeneratorCharacterTests(TestCase): @@ -327,86 +285,67 @@ def tearDown(self): def test_character_list(self): """Verify that lists of characters are looped through.""" - self.plugin.set_setting('PATTERN', '[abc]') + self.plugin.set_setting("PATTERN", "[abc]") cat = PartCategory.objects.all().first() def gen_part(expected_ipn): - p = Part.objects.create( - category=cat, - name='PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) self.assertEqual(part.IPN, expected_ipn) - gen_part('a') - gen_part('b') - gen_part('c') + gen_part("a") + gen_part("b") + gen_part("c") def test_character_list_rollover(self): """Verify that character lists restart after reaching the end""" - self.plugin.set_setting('PATTERN', '[abc]') + self.plugin.set_setting("PATTERN", "[abc]") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name=f'PartName', - IPN='c' - ) + Part.objects.create(category=cat, name="PartName", IPN="c") - p = Part.objects.create( - category=cat, - name=f'PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, 'a') + self.assertEqual(part.IPN, "a") def test_character_range(self): """Verify that ranges of characters are looped through.""" - self.plugin.set_setting('PATTERN', '[a-c]') + self.plugin.set_setting("PATTERN", "[a-c]") cat = PartCategory.objects.all().first() def gen_part(expected_ipn): - p = Part.objects.create( - category=cat, - name='PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) self.assertEqual(part.IPN, expected_ipn) - gen_part('a') - gen_part('b') - gen_part('c') + gen_part("a") + gen_part("b") + gen_part("c") def test_character_range_rollover(self): """Verify that character ranges loop around after reaching the end.""" - self.plugin.set_setting('PATTERN', '[a-c]') + self.plugin.set_setting("PATTERN", "[a-c]") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName', - IPN='c' - ) + Part.objects.create(category=cat, name="PartName", IPN="c") - p = Part.objects.create( - category=cat, - name='PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, 'a') + self.assertEqual(part.IPN, "a") class IPNGeneratorCombiningTests(TestCase): """Verify that combining different groups works properly""" + def setUp(self): """Set up test environment""" setup_func(self) @@ -418,39 +357,25 @@ def tearDown(self): def test_literal_and_number(self): """Verify literals and numbers work together""" - self.plugin.set_setting('PATTERN', '(AB){2}') + self.plugin.set_setting("PATTERN", "(AB){2}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName', - IPN='AB12' - ) + Part.objects.create(category=cat, name="PartName", IPN="AB12") - p = Part.objects.create( - category=cat, - name='PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, 'AB13') + self.assertEqual(part.IPN, "AB13") def test_only_last_incrementable_is_changed(self): """Verify that only last group in pattern gets incremented""" - self.plugin.set_setting('PATTERN', '[abc]{2}') + self.plugin.set_setting("PATTERN", "[abc]{2}") cat = PartCategory.objects.all().first() - Part.objects.create( - category=cat, - name='PartName', - IPN='a25' - ) + Part.objects.create(category=cat, name="PartName", IPN="a25") - p = Part.objects.create( - category=cat, - name='PartName' - ) + p = Part.objects.create(category=cat, name="PartName") part = Part.objects.get(pk=p.pk) - self.assertEqual(part.IPN, 'a26') + self.assertEqual(part.IPN, "a26")