Clean up the application registry

This commit is contained in:
jeremystretch 2023-02-19 13:58:01 -05:00
parent 1c2f55668a
commit f2c9d6e4eb
6 changed files with 53 additions and 65 deletions

View File

@ -1,14 +1,2 @@
# Webhook content types # Webhook content types
HTTP_CONTENT_TYPE_JSON = 'application/json' HTTP_CONTENT_TYPE_JSON = 'application/json'
# Registerable extras features
EXTRAS_FEATURES = [
'custom_fields',
'custom_links',
'export_templates',
'job_results',
'journaling',
'synced_data',
'tags',
'webhooks'
]

View File

@ -14,13 +14,13 @@ from .registration import *
from .templates import * from .templates import *
# Initialize plugin registry # Initialize plugin registry
registry['plugins'] = { registry['plugins'].update({
'graphql_schemas': [], 'graphql_schemas': [],
'menus': [], 'menus': [],
'menu_items': {}, 'menu_items': {},
'preferences': {}, 'preferences': {},
'template_extensions': collections.defaultdict(list), 'template_extensions': collections.defaultdict(list),
} })
DEFAULT_RESOURCE_PATHS = { DEFAULT_RESOURCE_PATHS = {
'search_indexes': 'search.indexes', 'search_indexes': 'search.indexes',

View File

@ -2,7 +2,6 @@ from django.db.models import Q
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
from taggit.managers import _TaggableManager from taggit.managers import _TaggableManager
from extras.constants import EXTRAS_FEATURES
from netbox.registry import registry from netbox.registry import registry
@ -18,7 +17,7 @@ def is_taggable(obj):
def image_upload(instance, filename): def image_upload(instance, filename):
""" """
Return a path for uploading image attchments. Return a path for uploading image attachments.
""" """
path = 'image-attachments/' path = 'image-attachments/'
@ -56,8 +55,14 @@ class FeatureQuery:
def register_features(model, features): def register_features(model, features):
"""
Register model features in the application registry.
"""
app_label, model_name = model._meta.label_lower.split('.')
for feature in features: for feature in features:
if feature not in EXTRAS_FEATURES: try:
raise ValueError(f"{feature} is not a valid extras feature!") registry['model_features'][feature][app_label].add(model_name)
app_label, model_name = model._meta.label_lower.split('.') except KeyError:
registry['model_features'][feature][app_label].add(model_name) raise KeyError(
f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
)

View File

@ -12,6 +12,7 @@ from taggit.managers import TaggableManager
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
from extras.utils import is_taggable, register_features from extras.utils import is_taggable, register_features
from netbox.registry import registry
from netbox.signals import post_clean from netbox.signals import post_clean
from utilities.json import CustomFieldJSONEncoder from utilities.json import CustomFieldJSONEncoder
from utilities.utils import serialize_object from utilities.utils import serialize_object
@ -388,22 +389,26 @@ class SyncedDataMixin(models.Model):
raise NotImplementedError(f"{self.__class__} must implement a sync_data() method.") raise NotImplementedError(f"{self.__class__} must implement a sync_data() method.")
FEATURES_MAP = ( FEATURES_MAP = {
('custom_fields', CustomFieldsMixin), 'custom_fields': CustomFieldsMixin,
('custom_links', CustomLinksMixin), 'custom_links': CustomLinksMixin,
('export_templates', ExportTemplatesMixin), 'export_templates': ExportTemplatesMixin,
('job_results', JobResultsMixin), 'job_results': JobResultsMixin,
('journaling', JournalingMixin), 'journaling': JournalingMixin,
('synced_data', SyncedDataMixin), 'synced_data': SyncedDataMixin,
('tags', TagsMixin), 'tags': TagsMixin,
('webhooks', WebhooksMixin), 'webhooks': WebhooksMixin,
) }
registry['model_features'].update({
feature: defaultdict(set) for feature in FEATURES_MAP.keys()
})
@receiver(class_prepared) @receiver(class_prepared)
def _register_features(sender, **kwargs): def _register_features(sender, **kwargs):
features = { features = {
feature for feature, cls in FEATURES_MAP if issubclass(sender, cls) feature for feature, cls in FEATURES_MAP.items() if issubclass(sender, cls)
} }
register_features(sender, features) register_features(sender, features)

View File

@ -1,12 +1,10 @@
import collections import collections
from extras.constants import EXTRAS_FEATURES
class Registry(dict): class Registry(dict):
""" """
Central registry for registration of functionality. Once a store (key) is defined, it cannot be overwritten or Central registry for registration of functionality. Once a Registry is initialized, keys cannot be added or
deleted (although its value may be manipulated). removed (though the value of each key is mutable).
""" """
def __getitem__(self, key): def __getitem__(self, key):
try: try:
@ -15,20 +13,18 @@ class Registry(dict):
raise KeyError(f"Invalid store: {key}") raise KeyError(f"Invalid store: {key}")
def __setitem__(self, key, value): def __setitem__(self, key, value):
if key in self: raise TypeError("Cannot add stores to registry after initialization")
raise KeyError(f"Store already set: {key}")
super().__setitem__(key, value)
def __delitem__(self, key): def __delitem__(self, key):
raise TypeError("Cannot delete stores from registry") raise TypeError("Cannot delete stores from registry")
# Initialize the global registry # Initialize the global registry
registry = Registry() registry = Registry({
registry['data_backends'] = dict() 'data_backends': dict(),
registry['denormalized_fields'] = collections.defaultdict(list) 'denormalized_fields': collections.defaultdict(list),
registry['model_features'] = { 'model_features': dict(),
feature: collections.defaultdict(set) for feature in EXTRAS_FEATURES 'plugins': dict(),
} 'search': dict(),
registry['search'] = dict() 'views': collections.defaultdict(dict),
registry['views'] = collections.defaultdict(dict) })

View File

@ -5,29 +5,23 @@ from netbox.registry import Registry
class RegistryTest(TestCase): class RegistryTest(TestCase):
def test_add_store(self): def test_set_store(self):
reg = Registry() reg = Registry({
reg['foo'] = 123 'foo': 123,
})
with self.assertRaises(TypeError):
reg['bar'] = 456
self.assertEqual(reg['foo'], 123) def test_mutate_store(self):
reg = Registry({
def test_manipulate_store(self): 'foo': [1, 2],
reg = Registry() })
reg['foo'] = [1, 2]
reg['foo'].append(3) reg['foo'].append(3)
self.assertListEqual(reg['foo'], [1, 2, 3]) self.assertListEqual(reg['foo'], [1, 2, 3])
def test_overwrite_store(self):
reg = Registry()
reg['foo'] = 123
with self.assertRaises(KeyError):
reg['foo'] = 456
def test_delete_store(self): def test_delete_store(self):
reg = Registry() reg = Registry({
reg['foo'] = 123 'foo': 123,
})
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
del reg['foo'] del reg['foo']