djresttoolkit 0.1.1__py3-none-any.whl → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: A collection of Django and DRF utilities to simplify API development.
5
5
  Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
6
6
  Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
@@ -33,7 +33,7 @@ License: # MIT License
33
33
  OTHER DEALINGS IN THE SOFTWARE.
34
34
  License-File: LICENSE
35
35
  Keywords: api-development,django,django-authentication,django-email,django-helpers,django-mixins,django-pagination,django-rest,django-rest-framework-utilities,django-rest-utilities,django-serializers,django-shortcuts,django-utilities,djangorestframework,drf,python-package,rest-api
36
- Classifier: Development Status :: 1 - Planning
36
+ Classifier: Development Status :: 3 - Alpha
37
37
  Classifier: Framework :: Django
38
38
  Classifier: Framework :: Django :: 4.0
39
39
  Classifier: Intended Audience :: Developers
@@ -46,6 +46,7 @@ Classifier: Topic :: Software Development :: Libraries
46
46
  Classifier: Topic :: Utilities
47
47
  Classifier: Typing :: Typed
48
48
  Requires-Python: >=3.13
49
+ Requires-Dist: pydantic>=2.11.7
49
50
  Provides-Extra: dev
50
51
  Requires-Dist: mypy; extra == 'dev'
51
52
  Requires-Dist: pytest; extra == 'dev'
@@ -0,0 +1,17 @@
1
+ LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
2
+ README.md,sha256=P0hcRXIpkEZZIiyWKsvuCkOIlqPxQ4tRkdb5R_mTARQ,1464
3
+ src/djresttoolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ src/djresttoolkit/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ src/djresttoolkit/apps.py,sha256=nKb5GUIEhAB3IL3lTmEXNc5XuvvaZupH-1CCuYKFrEQ,158
6
+ src/djresttoolkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ src/djresttoolkit/mail/__init__.py,sha256=tB9SdMlhfWQ640q4aobZ0H1c7fTWalpDL2I-onkr2VI,268
8
+ src/djresttoolkit/mail/_email_sender.py,sha256=vvTPZzSAfX2FXHv1IY0ST8dEsq8M4wAxkihm0JbRh1Y,3136
9
+ src/djresttoolkit/mail/_models.py,sha256=_41pH3xC0jP8SHSty2FkxvRh2_ddKk-4peT11OHcBBE,1462
10
+ src/djresttoolkit/mail/_types.py,sha256=est1mrN80vB_4-j-2yuAr_l_7rNzO4AJlqpq74zO5ow,682
11
+ src/djresttoolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ src/djresttoolkit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ djresttoolkit-0.2.0.dist-info/METADATA,sha256=Xr6JLGRsN0NlwnQ_Nu0HcdBv6Sfs7RKeIdYOq4GVoL8,4374
14
+ djresttoolkit-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ djresttoolkit-0.2.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
16
+ djresttoolkit-0.2.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
17
+ djresttoolkit-0.2.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- def main() -> None:
2
- print("Hello from drutils!")
File without changes
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjresttoolkitConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "djresttoolkit"
@@ -0,0 +1,11 @@
1
+ from ._email_sender import EmailSender
2
+ from ._models import EmailContent, EmailTemplate
3
+ from ._types import EmailContentDict, EmailTemplateDict
4
+
5
+ __all__ = [
6
+ "EmailSender",
7
+ "EmailContent",
8
+ "EmailTemplate",
9
+ "EmailContentDict",
10
+ "EmailTemplateDict",
11
+ ]
@@ -0,0 +1,94 @@
1
+ import logging
2
+ from smtplib import SMTPException
3
+ from typing import cast
4
+
5
+ from django.core.exceptions import ValidationError
6
+ from django.core.mail import EmailMultiAlternatives
7
+ from django.template.loader import render_to_string
8
+ from pydantic import EmailStr
9
+
10
+ from ._models import EmailContent
11
+ from ._types import EmailContentDict
12
+
13
+ # Set up logger
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class EmailSender:
18
+ """
19
+ Sends templated emails using Django's EmailMultiAlternatives.
20
+
21
+ Supports:
22
+ - Plain text and HTML templates
23
+ - Template rendering with context
24
+ - Optional silent failure handling via `exceptions`
25
+
26
+ Parameters
27
+ ----------
28
+ email_content : EmailContent | EmailContentDict
29
+ The email data, including subject, sender, templates, and context.
30
+
31
+ Methods
32
+ -------
33
+ send(to: list[EmailStr], exceptions: bool = False) -> bool
34
+ Sends the email to the given recipients.
35
+ - `exceptions=True` riase exceptions on failure and returns False if `exceptions=False`.
36
+
37
+ Example
38
+ -------
39
+ >>> from mypackage._models import EmailContent, EmailTemplate
40
+ >>> content = EmailContent(
41
+ ... subject="Hello",
42
+ ... from_email="noreply@example.com",
43
+ ... context={"username": "Alice"},
44
+ ... template=EmailTemplate(text="emails/welcome.txt", html="emails/welcome.html")
45
+ ... )
46
+ >>> sender = EmailSender(content)
47
+ >>> sender.send(to=["user@example.com"]) # --> True/False
48
+
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ email_content: EmailContent | EmailContentDict,
54
+ ) -> None:
55
+ """Initialize email sender class."""
56
+ self._email_content = email_content
57
+
58
+ @property
59
+ def email_content(self) -> EmailContentDict:
60
+ """Convert pydantic mode to python dict."""
61
+ if isinstance(self.email_content, EmailContent):
62
+ return cast(EmailContentDict, self.email_content.model_dump())
63
+ return self.email_content
64
+
65
+ def send(
66
+ self,
67
+ to: list[EmailStr],
68
+ exceptions: bool = False,
69
+ ) -> bool:
70
+ """Send email to recipients."""
71
+ unique_recipients = list(set(to))
72
+ try:
73
+ logger.info("Starting email sending process.")
74
+ email = EmailMultiAlternatives(
75
+ subject=self.email_content["subject"],
76
+ body=render_to_string(
77
+ self.email_content["template"]["text"],
78
+ self.email_content["context"],
79
+ ),
80
+ from_email=self.email_content["from_email"],
81
+ to=unique_recipients,
82
+ )
83
+ email.attach_alternative(
84
+ content=self.email_content["template"]["html"],
85
+ mimetype="text/html",
86
+ )
87
+ email.send()
88
+ logger.info(f"Email sent successfully to: {', '.join(unique_recipients)}")
89
+ return True
90
+ except (SMTPException, ValidationError) as error:
91
+ logger.error(f"Error during sending email\nErrors: {error}")
92
+ if exceptions:
93
+ raise ValidationError("Error during sending email") from error
94
+ return False
@@ -0,0 +1,53 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, EmailStr, field_validator
4
+
5
+
6
+ class EmailTemplate(BaseModel):
7
+ """Template for rendering email content."""
8
+
9
+ text: str # plain text version
10
+ html: str # HTML version
11
+
12
+ @field_validator("text")
13
+ @classmethod
14
+ def validate_text_template(cls, v: str) -> str:
15
+ if not v.endswith(".txt"):
16
+ raise ValueError("Text template must end with .txt")
17
+ return v
18
+
19
+ @field_validator("html")
20
+ @classmethod
21
+ def validate_html_template(cls, v: str) -> str:
22
+ if not v.endswith(".html"):
23
+ raise ValueError("HTML template must end with .html")
24
+ return v
25
+
26
+
27
+ class EmailContent(BaseModel):
28
+ """
29
+ Represents the content of an email ready for rendering.
30
+
31
+ This model combines the key parts of an email, including:
32
+ - subject line
33
+ - sender email (from_email)
34
+ - rendering context for template variables
35
+ - associated text and/or HTML templates
36
+
37
+ Attributes
38
+ ----------
39
+ subject : str
40
+ The subject line of the email.
41
+ from_email : EmailStr | None
42
+ Sender's email address.
43
+ context : dict[str, Any] | None
44
+ Optional context data used to render templates (e.g., {"username": "Alice"}).
45
+ template : EmailTemplate
46
+ Templates for the email body (.txt and/or .html).
47
+
48
+ """
49
+
50
+ subject: str
51
+ from_email: EmailStr | None
52
+ context: dict[str, Any] | None = None
53
+ template: EmailTemplate
@@ -0,0 +1,28 @@
1
+ from typing import Any, TypedDict
2
+
3
+ from pydantic import EmailStr
4
+
5
+
6
+ class EmailTemplateDict(TypedDict):
7
+ """Template for rendering email content."""
8
+
9
+ text: str # plain text version
10
+ html: str # HTML version html: str # HTML version
11
+
12
+
13
+ class EmailContentDict(TypedDict):
14
+ """
15
+ Represents a fully configured email message.
16
+
17
+ This model combines the essential parts of an email, including:
18
+ - subject line
19
+ - from_email the sender email
20
+ - a rendering context for template variables
21
+ - associated text and/or HTML templates
22
+
23
+ """
24
+
25
+ subject: str
26
+ from_email: EmailStr | None
27
+ context: dict[str, Any] | None
28
+ template: EmailTemplateDict
File without changes
File without changes
@@ -1,9 +0,0 @@
1
- LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
2
- README.md,sha256=P0hcRXIpkEZZIiyWKsvuCkOIlqPxQ4tRkdb5R_mTARQ,1464
3
- src/djresttoolkit/__init__.py,sha256=ENteKzaWCshHx06bXsudOKG4J4PqQrCELdx4Gx6p7hU,53
4
- src/djresttoolkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- djresttoolkit-0.1.1.dist-info/METADATA,sha256=bc5dN5p213RvTBTCgi1rqLtGGN0GTcqIfF0DozqxSwU,4345
6
- djresttoolkit-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- djresttoolkit-0.1.1.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
8
- djresttoolkit-0.1.1.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
9
- djresttoolkit-0.1.1.dist-info/RECORD,,