nbshell.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import code
  2. import platform
  3. from collections import defaultdict
  4. from types import SimpleNamespace
  5. from colorama import Fore, Style
  6. from django import get_version
  7. from django.apps import apps
  8. from django.conf import settings
  9. from django.core.management.base import BaseCommand
  10. from django.utils.module_loading import import_string
  11. from netbox.constants import CORE_APPS
  12. from netbox.plugins.utils import get_installed_plugins
  13. def color(color: str, text: str):
  14. return getattr(Fore, color.upper()) + text + Style.RESET_ALL
  15. def bright(text: str):
  16. return Style.BRIGHT + text + Style.RESET_ALL
  17. def get_models(app_config):
  18. """
  19. Return a list of all non-private models within an app.
  20. """
  21. return [
  22. model for model in app_config.get_models()
  23. if not getattr(model, '_netbox_private', False)
  24. ]
  25. def get_constants(app_config):
  26. """
  27. Return a dictionary mapping of all constants defined within an app.
  28. """
  29. try:
  30. constants = import_string(f'{app_config.name}.constants')
  31. except ImportError:
  32. return {}
  33. return {
  34. name: value for name, value in vars(constants).items()
  35. }
  36. class Command(BaseCommand):
  37. help = "Start the Django shell with all NetBox models already imported"
  38. django_models = {}
  39. def add_arguments(self, parser):
  40. parser.add_argument(
  41. '-c', '--command',
  42. help='Python code to execute (instead of starting an interactive shell)',
  43. )
  44. def _lsapps(self):
  45. for app_label in self.django_models.keys():
  46. app_name = apps.get_app_config(app_label).verbose_name
  47. print(f'{app_label} - {app_name}')
  48. def _lsmodels(self, app_label=None):
  49. """
  50. Return a list of all models within each app.
  51. Args:
  52. app_label: The name of a specific app
  53. """
  54. if app_label:
  55. if app_label not in self.django_models:
  56. print(f"No models listed for {app_label}")
  57. return
  58. app_labels = [app_label]
  59. else:
  60. app_labels = self.django_models.keys() # All apps
  61. for app_label in app_labels:
  62. app_name = apps.get_app_config(app_label).verbose_name
  63. print(f'{app_name}:')
  64. for model in self.django_models[app_label]:
  65. print(f' {app_label}.{model}')
  66. def get_namespace(self):
  67. namespace = defaultdict(SimpleNamespace)
  68. # Iterate through all core apps & plugins to compile namespace of models and constants
  69. for app_name in [*CORE_APPS, *get_installed_plugins().keys()]:
  70. app_config = apps.get_app_config(app_name)
  71. # Populate models
  72. if models := get_models(app_config):
  73. for model in models:
  74. setattr(namespace[app_name], model.__name__, model)
  75. self.django_models[app_name] = sorted([
  76. model.__name__ for model in models
  77. ])
  78. # Populate constants
  79. for const_name, const_value in get_constants(app_config).items():
  80. setattr(namespace[app_name], const_name, const_value)
  81. return {
  82. **namespace,
  83. 'lsapps': self._lsapps,
  84. 'lsmodels': self._lsmodels,
  85. }
  86. @staticmethod
  87. def get_banner_text():
  88. lines = [
  89. '{title} ({hostname})'.format(
  90. title=bright('NetBox interactive shell'),
  91. hostname=platform.node(),
  92. ),
  93. '{python} | {django} | {netbox}'.format(
  94. python=color('green', f'Python v{platform.python_version()}'),
  95. django=color('green', f'Django v{get_version()}'),
  96. netbox=color('green', settings.RELEASE.name),
  97. ),
  98. ]
  99. if installed_plugins := get_installed_plugins():
  100. plugin_list = ', '.join([
  101. color('cyan', f'{name} v{version}') for name, version in installed_plugins.items()
  102. ])
  103. lines.append(
  104. 'Plugins: {plugin_list}'.format(
  105. plugin_list=plugin_list
  106. )
  107. )
  108. lines.append(
  109. 'lsapps() & lsmodels() will show available models. Use help(<model>) for more info.'
  110. )
  111. return '\n'.join([
  112. f'### {line}' for line in lines
  113. ])
  114. def handle(self, **options):
  115. namespace = self.get_namespace()
  116. # If Python code has been passed, execute it and exit.
  117. if options['command']:
  118. exec(options['command'], namespace)
  119. return
  120. # Try to enable tab-complete
  121. try:
  122. import readline
  123. import rlcompleter
  124. except ModuleNotFoundError:
  125. pass
  126. else:
  127. readline.set_completer(rlcompleter.Completer(namespace).complete)
  128. readline.parse_and_bind('tab: complete')
  129. # Run interactive shell
  130. return code.interact(banner=self.get_banner_text(), local=namespace)