Skip to content
This repository has been archived by the owner on Nov 2, 2024. It is now read-only.

Commit

Permalink
[WIP] Adding docstrings in IntelOwl Codebase. (intelowlproject#2430)
Browse files Browse the repository at this point in the history
* Added docstrings in Authentication

Signed-off-by: aryan <aryan1bhokare@gmail.com>

* Added docstrings in api_app module.

Signed-off-by: aryan <aryan1bhokare@gmail.com>

* fixed linters

Signed-off-by: aryan <aryan1bhokare@gmail.com>

---------

Signed-off-by: aryan <aryan1bhokare@gmail.com>
  • Loading branch information
aryan-bhokare authored and vaclavbartos committed Oct 13, 2024
1 parent 6455b42 commit 270c629
Show file tree
Hide file tree
Showing 22 changed files with 1,833 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
max-line-length = 88
max-line-length = 140
ignore =
W503,
E231,
Expand Down
15 changes: 15 additions & 0 deletions api_app/analyzers_manager/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,24 @@ class BaseAnalyzerMixin(Plugin, metaclass=ABCMeta):
@classmethod
@property
def config_exception(cls):
"""Returns the AnalyzerConfigurationException class."""
return AnalyzerConfigurationException

@property
def analyzer_name(self) -> str:
"""Returns the name of the analyzer."""
return self._config.name

@classmethod
@property
def report_model(cls):
"""Returns the AnalyzerReport model."""
return AnalyzerReport

@classmethod
@property
def config_model(cls):
"""Returns the AnalyzerConfig model."""
return AnalyzerConfig

def get_exceptions_to_catch(self):
Expand Down Expand Up @@ -98,6 +102,12 @@ def _validate_result(self, result, level=0, max_recursion=190):
return result

def after_run_success(self, content):
"""
Handles actions after a successful run.
Args:
content (any): The content to process after a successful run.
"""
super().after_run_success(self._validate_result(content, max_recursion=15))


Expand Down Expand Up @@ -194,6 +204,11 @@ def read_file_bytes(self) -> bytes:

@property
def filepath(self) -> str:
"""Returns the file path, retrieving the file from storage if necessary.
Returns:
str: The file path.
"""
if not self.__filepath:
self.__filepath = self._job.file.storage.retrieve(
file=self._job.file, analyzer=self.analyzer_name
Expand Down
125 changes: 118 additions & 7 deletions api_app/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@

class Plugin(metaclass=ABCMeta):
"""
Abstract Base class for plugins.
For internal use only.
Abstract Base class for plugins. Provides a framework for defining and running
plugins within a specified configuration.
Attributes:
config (PythonConfig): Configuration for the plugin.
kwargs: Additional keyword arguments.
"""

def __init__(
Expand All @@ -40,6 +44,12 @@ def __init__(

@property
def name(self):
"""
Get the name of the plugin.
Returns:
str: The name of the plugin.
"""
return self._config.name

@classmethod
Expand All @@ -50,6 +60,12 @@ def python_base_path(cls) -> PosixPath:

@classmethod
def all_subclasses(cls):
"""
Retrieve all subclasses of the plugin class.
Returns:
list: Sorted list of plugin subclasses.
"""
posix_dir = PosixPath(str(cls.python_base_path).replace(".", "/"))
for plugin in posix_dir.rglob("*.py"):
if plugin.stem == "__init__":
Expand All @@ -65,30 +81,72 @@ def all_subclasses(cls):

@cached_property
def _job(self) -> "Job":
"""
Get the job associated with the plugin.
Returns:
Job: The job instance.
"""
return Job.objects.get(pk=self.job_id)

@property
def job_id(self) -> int:
"""
Get the job ID.
Returns:
int: The job ID.
"""
return self._job_id

@job_id.setter
def job_id(self, value):
"""
Set the job ID.
Args:
value (int): The job ID.
"""
self._job_id = value

@cached_property
def _user(self):
"""
Get the user associated with the job.
Returns:
User: The user instance.
"""
return self._job.user

def __repr__(self):
"""
Get the string representation of the plugin.
Returns:
str: The string representation of the plugin.
"""
return str(self)

def __str__(self):
"""
Get the string representation of the plugin.
Returns:
str: The string representation of the plugin.
"""
try:
return f"({self.__class__.__name__}, job: #{self.job_id})"
except AttributeError:
return f"{self.__class__.__name__}"

def config(self, runtime_configuration: typing.Dict):
"""
Configure the plugin with runtime parameters.
Args:
runtime_configuration (dict): Runtime configuration parameters.
"""
self.__parameters = self._config.read_configured_params(
self._user, runtime_configuration
)
Expand All @@ -104,25 +162,33 @@ def config(self, runtime_configuration: typing.Dict):

def before_run(self):
"""
function called directly before run function.
Function called directly before the run function.
"""

@abstractmethod
def run(self) -> dict:
"""
Called from *start* fn and wrapped in a try-catch block.
Should be overwritten in child class
:returns report
Called from *start* function and wrapped in a try-catch block.
Should be overwritten in child class.
Returns:
dict: Report generated by the plugin.
"""

def after_run(self):
"""
function called after run function.
Function called after the run function.
"""
self.report.end_time = timezone.now()
self.report.save()

def after_run_success(self, content: typing.Any):
"""
Handle the successful completion of the run function.
Args:
content (Any): Content generated by the plugin.
"""
# avoiding JSON serialization errors for types: File and bytes
report_content = content
if isinstance(report_content, typing.List):
Expand All @@ -140,6 +206,12 @@ def after_run_success(self, content: typing.Any):
self.report.save(update_fields=["status", "report"])

def log_error(self, e):
"""
Log an error encountered during the run function.
Args:
e (Exception): The exception to log.
"""
if isinstance(
e, (*self.get_exceptions_to_catch(), SoftTimeLimitExceeded, HTTPError)
):
Expand All @@ -151,6 +223,12 @@ def log_error(self, e):
logger.exception(error_message)

def after_run_failed(self, e: Exception):
"""
Handle the failure of the run function.
Args:
e (Exception): The exception that caused the failure.
"""
self.report.errors.append(str(e))
self.report.status = self.report.Status.FAILED
self.report.save(update_fields=["status", "errors"])
Expand Down Expand Up @@ -246,6 +324,12 @@ def _monkeypatch(cls, patches: list = None) -> None:
@classmethod
@property
def python_module(cls) -> PythonModule:
"""
Get the Python module associated with the plugin.
Returns:
PythonModule: The Python module instance.
"""
valid_module = cls.__module__.replace(str(cls.python_base_path), "")
# remove the starting dot
valid_module = valid_module[1:]
Expand All @@ -255,9 +339,24 @@ def python_module(cls) -> PythonModule:

@classmethod
def update(cls) -> bool:
"""
Update the plugin. Must be implemented by subclasses.
Returns:
bool: Whether the update was successful.
"""
raise NotImplementedError("No update implemented")

def _get_health_check_url(self, user: User = None) -> typing.Optional[str]:
"""
Get the URL for performing a health check.
Args:
user (User): The user instance.
Returns:
typing.Optional[str]: The health check URL.
"""
params = (
self._config.parameters.annotate_configured(self._config, user)
.annotate_value_for_user(self._config, user)
Expand All @@ -274,6 +373,15 @@ def _get_health_check_url(self, user: User = None) -> typing.Optional[str]:
return None

def health_check(self, user: User = None) -> bool:
"""
Perform a health check for the plugin.
Args:
user (User): The user instance.
Returns:
bool: Whether the health check was successful.
"""
url = self._get_health_check_url(user)
if url and url.startswith("http"):
if settings.STAGE_CI or settings.MOCK_CONNECTIONS:
Expand All @@ -296,6 +404,9 @@ def health_check(self, user: User = None) -> bool:
raise NotImplementedError()

def disable_for_rate_limit(self):
"""
Disable the plugin due to rate limiting.
"""
logger.info(f"Trying to disable for rate limit {self}")
if self._user.has_membership():
org_configuration = self._config.get_or_create_org_configuration(
Expand Down
16 changes: 16 additions & 0 deletions api_app/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def decorator_deprecated(func):

@functools.wraps(func)
def wrapper_deprecated(*args, **kwargs):
"""
Wrapper function to log a warning and amend the response with
deprecation headers.
"""
# do something before handling the request, could e.g. issue a django signal
logger.warning("Deprecated endpoint %s called", func.__name__)

Expand All @@ -39,6 +43,18 @@ def wrapper_deprecated(*args, **kwargs):


def prevent_signal_recursion(func):
"""
Decorator to prevent recursion issues when saving Django model instances.
It ensures that the decorated function does not cause a recursion loop
during model save operations.
Args:
func (function): The function to be decorated.
Returns:
function: The wrapper function that prevents recursion.
"""

@functools.wraps(func)
def no_recursion(sender, instance=None, **kwargs):
if not instance:
Expand Down
Loading

0 comments on commit 270c629

Please sign in to comment.