contentgrid-extension-helpers 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .exceptions import LLMDenyException, ExtensionHelperException, IllegalActivityError, MissingInputError, InjectionError, MalformedInputError, NotRelatedError, SensitiveInformationError, SecurityError # noqa: F401
2
+ from .middleware.exception_middleware import catch_exceptions_middleware # noqa: F401
3
+ from .structured_output.model_deny import DenyReason, ModelDeny # noqa: F401
@@ -1,2 +1,2 @@
1
- from .oidc import create_current_user_dependency, decode_and_verify_token, create_oauth2_scheme, get_jwks_client
2
- from .user import ContentGridUser
1
+ from .oidc import create_current_user_dependency, decode_and_verify_token, create_oauth2_scheme, get_oauth_jwks_client, get_oidc_jwks_client # noqa: F401
2
+ from .user import ContentGridUser # noqa: F401
@@ -0,0 +1,48 @@
1
+
2
+ class ExtensionHelperException(Exception):
3
+ """Base class for all custom exceptions in the extension helpers library."""
4
+ def __init__(self, *args: object) -> None:
5
+ super().__init__(*args)
6
+
7
+ class LLMDenyException(ExtensionHelperException):
8
+ """
9
+ Raised when the LLM denies a request based on its rules.
10
+ This is a base class for more specific denial reasons.
11
+ """
12
+ def __init__(self, *args: object) -> None:
13
+ super().__init__(*args)
14
+
15
+ class NotRelatedError(LLMDenyException):
16
+ """Raised when the input is unrelated to the required extraction."""
17
+ def __init__(self, *args: object) -> None:
18
+ super().__init__(*args)
19
+
20
+ class MissingInputError(LLMDenyException):
21
+ """Raised when required input is missing or empty."""
22
+ def __init__(self, *args: object) -> None:
23
+ super().__init__(*args)
24
+
25
+ class MalformedInputError(LLMDenyException):
26
+ """Raised when input is present but malformed or contains only strange characters."""
27
+ def __init__(self, *args: object) -> None:
28
+ super().__init__(*args)
29
+
30
+ class IllegalActivityError(LLMDenyException):
31
+ """Raised when the input describes illegal activities."""
32
+ def __init__(self, *args: object) -> None:
33
+ super().__init__(*args)
34
+
35
+ class SensitiveInformationError(LLMDenyException):
36
+ """Raised when the input contains sensitive information."""
37
+ def __init__(self, *args: object) -> None:
38
+ super().__init__(*args)
39
+
40
+ class SecurityError(LLMDenyException):
41
+ """Raised when the input attempts to compromise system security."""
42
+ def __init__(self, *args: object) -> None:
43
+ super().__init__(*args)
44
+
45
+ class InjectionError(SecurityError):
46
+ """Raised when the input is a prompt injection attack."""
47
+ def __init__(self, *args: object) -> None:
48
+ super().__init__(*args)
@@ -1 +1 @@
1
- from .json_logging import setup_json_logging, XenitJsonFormatter
1
+ from .json_logging import setup_json_logging, XenitJsonFormatter # noqa: F401
@@ -2,7 +2,7 @@ import datetime
2
2
  import json
3
3
  import logging
4
4
  import time
5
- from typing import List, Optional, Any, Dict
5
+ from typing import Optional, Any, Dict
6
6
 
7
7
  class XenitJsonFormatter(logging.Formatter):
8
8
  def __init__(
@@ -54,7 +54,7 @@ class XenitJsonFormatter(logging.Formatter):
54
54
 
55
55
  def setup_json_logging(
56
56
  component: str = "cg-extension",
57
- additional_keys: dict[str, str] = [],
57
+ additional_keys: dict[str,str] = {},
58
58
  log_level: int = logging.DEBUG
59
59
  ) -> logging.Logger:
60
60
  """
@@ -0,0 +1,124 @@
1
+ from fastapi import Request, status
2
+ import logging
3
+ from contentgrid_extension_helpers.problem_response import ProblemResponse
4
+ from contentgrid_hal_client.exceptions import (
5
+ NotFound,
6
+ Unauthorized,
7
+ BadRequest,
8
+ IncorrectAttributeType,
9
+ NonExistantAttribute,
10
+ MissingRequiredAttribute,
11
+ MissingHALTemplate,
12
+ )
13
+ from requests.exceptions import HTTPError
14
+ from contentgrid_extension_helpers.exceptions import LLMDenyException
15
+
16
+ async def catch_exceptions_middleware(request: Request, call_next, problem_base_url: str = "https://docs.contentgrid.com"):
17
+ """
18
+ Catches exceptions and returns ProblemResponse objects.
19
+
20
+ Args:
21
+ request: The incoming request.
22
+ call_next: The next middleware or route handler.
23
+ problem_base_url: The base URL for problem type URIs.
24
+ """
25
+
26
+ def format_type(type_name: str) -> str:
27
+ """Formats problem type URIs."""
28
+ return f"{problem_base_url}/{type_name}"
29
+
30
+ cors_headers = {
31
+ "Access-Control-Allow-Origin": "*",
32
+ "Access-Control-Allow-Methods": "*",
33
+ "Access-Control-Allow-Headers": "*, Authorization",
34
+ }
35
+
36
+ try:
37
+ return await call_next(request)
38
+ except LLMDenyException as e:
39
+ logging.error(f"LLM denied request: {e}")
40
+ return ProblemResponse(
41
+ title="Request Denied",
42
+ problem_type=format_type("request-denied"),
43
+ detail=str(e),
44
+ status=status.HTTP_400_BAD_REQUEST,
45
+ headers=cors_headers,
46
+ )
47
+ except NotFound:
48
+ return ProblemResponse(
49
+ title="Not found",
50
+ problem_type=format_type("not-found"),
51
+ detail="Resource not found",
52
+ status=status.HTTP_404_NOT_FOUND,
53
+ headers=cors_headers,
54
+ )
55
+ except Unauthorized:
56
+ return ProblemResponse(
57
+ title="Unauthorized",
58
+ problem_type=format_type("unauthorized"),
59
+ detail="user is not authorized for requested resource",
60
+ status=status.HTTP_401_UNAUTHORIZED,
61
+ headers=cors_headers,
62
+ )
63
+ except BadRequest:
64
+ return ProblemResponse(
65
+ title="Bad Request",
66
+ problem_type=format_type("bad-request"),
67
+ detail="The request was malformed or invalid.",
68
+ status=status.HTTP_400_BAD_REQUEST,
69
+ headers=cors_headers,
70
+ )
71
+ except IncorrectAttributeType as e:
72
+ return ProblemResponse(
73
+ title="Incorrect Attribute Type",
74
+ problem_type=format_type("incorrect-attribute-type"),
75
+ detail=str(e),
76
+ status=status.HTTP_400_BAD_REQUEST,
77
+ headers=cors_headers,
78
+ )
79
+ except NonExistantAttribute as e:
80
+ return ProblemResponse(
81
+ title="Non-Existent Attribute",
82
+ problem_type=format_type("non-existent-attribute"),
83
+ detail=str(e),
84
+ status=status.HTTP_404_NOT_FOUND,
85
+ headers=cors_headers,
86
+ )
87
+ except MissingRequiredAttribute as e:
88
+ return ProblemResponse(
89
+ title="Missing Required Attribute",
90
+ problem_type=format_type("missing-required-attribute"),
91
+ detail=str(e),
92
+ status=status.HTTP_400_BAD_REQUEST,
93
+ headers=cors_headers,
94
+ )
95
+ except MissingHALTemplate as e:
96
+ return ProblemResponse(
97
+ title="Missing HAL Template",
98
+ problem_type=format_type("missing-hal-template"),
99
+ detail=str(e),
100
+ status=status.HTTP_404_NOT_FOUND,
101
+ headers=cors_headers,
102
+ )
103
+ except HTTPError as e:
104
+ logging.exception(f"HTTP Error: {str(e)}", exc_info=True)
105
+ return ProblemResponse(
106
+ title="HTTP Error",
107
+ problem_type=format_type("http-error"),
108
+ detail=f"An HTTP error occurred: {str(e)}",
109
+ status=e.response.status_code if e.response else status.HTTP_500_INTERNAL_SERVER_ERROR,
110
+ headers=cors_headers,
111
+ )
112
+ except Exception as e:
113
+ logging.exception(
114
+ f"Untyped exception caught in backend request...: {str(e)}",
115
+ exc_info=True,
116
+ stack_info=True,
117
+ )
118
+ return ProblemResponse(
119
+ title="Internal server error",
120
+ problem_type=format_type("unknown"),
121
+ detail=f"An unexpected error occurred: {str(e)}",
122
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
123
+ headers=cors_headers,
124
+ )
@@ -0,0 +1,45 @@
1
+ from typing import Any, Dict, Mapping
2
+ from fastapi.responses import JSONResponse
3
+
4
+
5
+ def get_problem_dict(title: str | None, problem_type: str | None, status: int | None, detail: str | None, instance: str | None, **kwargs) -> Dict[str, Any]:
6
+ result: Dict[str, Any] = dict()
7
+ if title is not None:
8
+ result["title"] = title
9
+ if problem_type is not None:
10
+ result["type"] = problem_type
11
+ if status is not None:
12
+ result["status"] = status
13
+ if detail is not None:
14
+ result["detail"] = detail
15
+ if instance is not None:
16
+ result["instance"] = instance
17
+
18
+ for key, value in kwargs.items():
19
+ if key == "type":
20
+ raise ValueError(f"{key} is a reserved property name!")
21
+ result[key] = value
22
+
23
+ return result
24
+
25
+
26
+ class ProblemResponse(JSONResponse):
27
+ def __init__(
28
+ self,
29
+ title: str | None = None,
30
+ problem_type: str | None = "about:blank", # not 'type' because it is a built-in function
31
+ status: int = 400,
32
+ detail: str | None = None,
33
+ instance: str | None = None,
34
+ headers: Mapping[str, str] | None = None,
35
+ media_type: str | None = "application/problem+json",
36
+ background: Any | None = None,
37
+ **kwargs
38
+ ):
39
+ super().__init__(
40
+ get_problem_dict(title, problem_type, status, detail, instance, **kwargs),
41
+ status_code=status,
42
+ headers=headers,
43
+ media_type=media_type,
44
+ background=background
45
+ )
@@ -0,0 +1,45 @@
1
+ from enum import Enum
2
+ from pydantic import BaseModel, Field
3
+
4
+ from contentgrid_extension_helpers import exceptions
5
+
6
+
7
+ class DenyReason(Enum):
8
+ NOT_RELATED = "not_related"
9
+ MISSING_INPUT = "missing_input"
10
+ MALFORMED_INPUT = "malformed_input"
11
+ ILLEGAL_ACTIVITY = "illegal_activity"
12
+ SENSITIVE_INFORMATION = "sensitive_information"
13
+ SECURITY_ERROR = "security_error"
14
+ INJECTION_ATTEMPT = "injection_attempt"
15
+
16
+ def to_exception(self) -> type[exceptions.LLMDenyException]:
17
+ """Maps a DenyReason to its corresponding exception class."""
18
+ match self:
19
+ case DenyReason.NOT_RELATED:
20
+ return exceptions.NotRelatedError
21
+ case DenyReason.MISSING_INPUT:
22
+ return exceptions.MissingInputError
23
+ case DenyReason.MALFORMED_INPUT:
24
+ return exceptions.MalformedInputError
25
+ case DenyReason.ILLEGAL_ACTIVITY:
26
+ return exceptions.IllegalActivityError
27
+ case DenyReason.SENSITIVE_INFORMATION:
28
+ return exceptions.SensitiveInformationError
29
+ case DenyReason.SECURITY_ERROR:
30
+ return exceptions.SecurityError
31
+ case DenyReason.INJECTION_ATTEMPT:
32
+ return exceptions.InjectionError
33
+
34
+
35
+ class ModelDeny(BaseModel):
36
+ deny_type: DenyReason = Field(description="Type of denial")
37
+ reason: str = Field(
38
+ description="Reason for denying the request. Should be a short sentence that can be shown to the user. Not longer than two lines"
39
+ )
40
+
41
+ def __init__(self, **data):
42
+ super().__init__(**data)
43
+ # Raise the corresponding exception immediately
44
+ exception_class = self.deny_type.to_exception()
45
+ raise exception_class(self.reason)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: contentgrid-extension-helpers
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Helper functions for contentgrid extensions.
5
5
  Author-email: Ranec Belpaire <ranec.belpaire@xenit.eu>
6
6
  License: Copyright 2024 Xenit Solutions
@@ -26,6 +26,7 @@ Requires-Dist: requests<3,>=2.20.0
26
26
  Requires-Dist: uri-template<2
27
27
  Requires-Dist: fastapi>=0.111
28
28
  Requires-Dist: PyJWT>2
29
+ Requires-Dist: contentgrid_hal_client>=0.0.10
29
30
 
30
31
  ### ContentGrid-Extension-Helpers
31
32
 
@@ -0,0 +1,15 @@
1
+ contentgrid_extension_helpers/__init__.py,sha256=Mw64JI29DW5HOzmL1UA8grdyrOqAQeHlllR_zxjagy8,380
2
+ contentgrid_extension_helpers/exceptions.py,sha256=GJOIwDC_51pyhiPaq6wRrkmS3X1aU_PQoEE4sPTgopQ,1812
3
+ contentgrid_extension_helpers/problem_response.py,sha256=v2z_hx92pHWyqMZfnJDi6TaGVIeoGh2AXVr1F9VA00w,1542
4
+ contentgrid_extension_helpers/authentication/__init__.py,sha256=XnupVcladC7H4LudetZ46da9IP69pWKhtRJvikhmIKM,200
5
+ contentgrid_extension_helpers/authentication/oidc.py,sha256=14XEmp_WWDVygb3oBKB9S29UlgJV8wnm_1lw36U4xxc,9820
6
+ contentgrid_extension_helpers/authentication/user.py,sha256=pr3DVZKchLxpJXU6k2uUNLquwr9LjWfr_sArdAjUjZU,185
7
+ contentgrid_extension_helpers/logging/__init__.py,sha256=15tz-g0fLdBjJto8kWcjCYhfXCJg0qrwSXVuXRH9i3Q,77
8
+ contentgrid_extension_helpers/logging/json_logging.py,sha256=NrzoBfEUAwQT7mmCHK0GoFJv5t9rrgjomDTXzZ-vgFI,3112
9
+ contentgrid_extension_helpers/middleware/exception_middleware.py,sha256=RXbdTzdtfufrGBfViTIQZw691jgXE3rmogKF28Vlhzs,4462
10
+ contentgrid_extension_helpers/structured_output/model_deny.py,sha256=n2sEls0kyhvL8hHUKeo3_JQ_ZssyknIbIoDAOUvMVxc,1748
11
+ contentgrid_extension_helpers-0.0.2.dist-info/LICENSE,sha256=tk6n-p8lEmzLJg-O4052CkMgfUtt1q2Zoh1QLAyL7S8,555
12
+ contentgrid_extension_helpers-0.0.2.dist-info/METADATA,sha256=VIMjWfLhujv6cT5eJvnUozPrKy1G4yVwo8XsNPpmRGM,1393
13
+ contentgrid_extension_helpers-0.0.2.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
14
+ contentgrid_extension_helpers-0.0.2.dist-info/top_level.txt,sha256=yJGGofrNVsl5psVGO0vLFHO1610ob88GtB9zpvS8iIk,30
15
+ contentgrid_extension_helpers-0.0.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +0,0 @@
1
- contentgrid_extension_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- contentgrid_extension_helpers/authentication/__init__.py,sha256=b6Bp8jCg2-T7bcTGid7pCk5PfNYwH-52UFUX2Op8ArQ,146
3
- contentgrid_extension_helpers/authentication/oidc.py,sha256=14XEmp_WWDVygb3oBKB9S29UlgJV8wnm_1lw36U4xxc,9820
4
- contentgrid_extension_helpers/authentication/user.py,sha256=pr3DVZKchLxpJXU6k2uUNLquwr9LjWfr_sArdAjUjZU,185
5
- contentgrid_extension_helpers/logging/__init__.py,sha256=u6edUMrsULUinBNN94yJywZyINz4azhIAXO2LMbmtaY,64
6
- contentgrid_extension_helpers/logging/json_logging.py,sha256=QRoDHZjtMXM-JGPR6JWqJnCljwSQ912wjsZ38XMA7Tw,3119
7
- contentgrid_extension_helpers-0.0.1.dist-info/LICENSE,sha256=tk6n-p8lEmzLJg-O4052CkMgfUtt1q2Zoh1QLAyL7S8,555
8
- contentgrid_extension_helpers-0.0.1.dist-info/METADATA,sha256=eP5YG_IsyyRtfonDnTiwnLlKPuQHZ7JWkB2s4aFaO2k,1347
9
- contentgrid_extension_helpers-0.0.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
10
- contentgrid_extension_helpers-0.0.1.dist-info/top_level.txt,sha256=yJGGofrNVsl5psVGO0vLFHO1610ob88GtB9zpvS8iIk,30
11
- contentgrid_extension_helpers-0.0.1.dist-info/RECORD,,