@@ -59,24 +59,116 @@ pip install python-newtype
5959### Basic Usage
6060
6161``` python
62- from newtype import NewType
63-
64- # Create a wrapped string type with additional functionality
65- class EnhancedStr (NewType (str )):
66- def reverse (self ):
67- return self [::- 1 ]
68-
69- def count_words (self ):
70- return len (self .split())
71-
72- # Use the enhanced type
73- text = EnhancedStr(" Hello World" )
74- print (text.reverse()) # "dlroW olleH"
75- print (text.count_words()) # 2
76-
77- # Original string methods still work
78- print (text.upper()) # "HELLO WORLD"
79- print (text.split()) # ["Hello", "World"]
62+ import pytest
63+ import re
64+ from newtype import NewType, newtype_exclude
65+
66+
67+ class EmailStr (NewType (str )):
68+ # you can define `__slots__` to save space
69+ __slots__ = (
70+ ' _local_part' ,
71+ ' _domain_part' ,
72+ )
73+
74+ def __init__ (self , value : str ):
75+ super ().__init__ ()
76+ if " @" not in value:
77+ raise TypeError (" `EmailStr` requires a '@' symbol within" )
78+ self ._local_part, self ._domain_part = value.split(" @" )
79+
80+ @newtype_exclude
81+ def __str__ (self ):
82+ return f " <Email - Local Part: { self .local_part} ; Domain Part: { self .domain_part} > "
83+
84+ @ property
85+ def local_part (self ):
86+ """ Return the local part of the email address."""
87+ return self ._local_part
88+
89+ @ property
90+ def domain_part (self ):
91+ """ Return the domain part of the email address."""
92+ return self ._domain_part
93+
94+ @ property
95+ def full_email (self ):
96+ """ Return the full email address."""
97+ return str (self )
98+
99+ @ classmethod
100+ def from_string (cls , email : str ):
101+ """ Create an EmailStr instance from a string."""
102+ return cls (email)
103+
104+ @ staticmethod
105+ def is_valid_email (email : str ) -> bool :
106+ """ Check if the provided string is a valid email format."""
107+ email_regex = r " ^ [a-zA-Z0-9._%+- ]+ @[a-zA-Z0-9.- ]+ \. [a-zA-Z ]{2,} $ "
108+ return re.match(email_regex, email) is not None
109+
110+
111+ def test_emailstr_replace ():
112+ """ `EmailStr` uses `str.replace(..)` as its own method, returning an instance of `EmailStr`
113+ if the resultant `str` instance is a value `EmailStr`.
114+ """
115+ peter_email = EmailStr(" peter@gmail.com" )
116+ smith_email = EmailStr(" smith@gmail.com" )
117+
118+ with pytest.raises(Exception ):
119+ # this raises because `peter_email` is no longer an instance of `EmailStr`
120+ peter_email = peter_email.replace(" peter@gmail.com" , " petergmail.com" )
121+
122+ # this works because the entire email can be 'replaced'
123+ james_email = smith_email.replace(" smith@gmail.com" , " james@gmail.com" )
124+
125+ # comparison with `str` is built-in
126+ assert james_email == " james@gmail.com"
127+
128+ # `james_email` is still an `EmailStr`
129+ assert isinstance (james_email, EmailStr)
130+
131+ # this works because the local part can be 'replaced'
132+ jane_email = james_email.replace(" james" , " jane" )
133+
134+ # `jane_email` is still an `EmailStr`
135+ assert isinstance (jane_email, EmailStr)
136+ assert jane_email == " jane@gmail.com"
137+
138+
139+ def test_emailstr_properties_methods ():
140+ """ Test the property, class method, and static method of EmailStr."""
141+ # Test property
142+ email = EmailStr(" test@example.com" )
143+ # `property` is not coerced to `EmailStr`
144+ assert email.full_email == " <Email - Local Part: test; Domain Part: example.com>"
145+ assert isinstance (email.full_email, str )
146+ # `property` is not coerced to `EmailStr`
147+ assert not isinstance (email.full_email, EmailStr)
148+ assert email.local_part == " test"
149+ assert email.domain_part == " example.com"
150+
151+ # Test class method
152+ email_from_string = EmailStr.from_string(" classmethod@example.com" )
153+ # `property` is not coerced to `EmailStr`
154+ assert (
155+ email_from_string.full_email
156+ == " <Email - Local Part: classmethod; Domain Part: example.com>"
157+ )
158+ assert email_from_string.local_part == " classmethod"
159+ assert email_from_string.domain_part == " example.com"
160+
161+ # Test static method
162+ assert EmailStr.is_valid_email(" valid.email@example.com" ) is True
163+ assert EmailStr.is_valid_email(" invalid-email.com" ) is False
164+
165+
166+ def test_email_str__slots__ ():
167+ email = EmailStr(" test@example.com" )
168+
169+ with pytest.raises(AttributeError ):
170+ email.hi = " bye"
171+ assert email.hi == " bye"
80172```
81173
82174## Documentation
0 commit comments