Skip to content

Commit

Permalink
Merge pull request #4 from Ousret/patch-3
Browse files Browse the repository at this point in the history
Bring new minors features and fix minors bugs
  • Loading branch information
Ousret authored Mar 18, 2020
2 parents 855f74d + a6244ea commit 5f711c7
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 13 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ charset = headers['Content-Type'].split(';')[-1].split('=')[-1].replace('"', '')

### Your support

Please ⭐ this repository if this project helped you!
Please ⭐ this repository if this project helped you! ✨ That would be very much appreciated ✨

### ✨ Installation

Expand Down Expand Up @@ -75,6 +75,11 @@ type(headers.set_cookie) # output: list
'Secur' in headers.set_cookie[0] # output: False
'domain' in headers.set_cookie[0] # output: True
headers.set_cookie[0].domain # output: .google.fr

# Hell, you can do this if you wish to remove all 'Set-Cookies' entries.
headers -= 'Set-Cookies'
# Or this, if you must !
headers = headers - 'Set-Cookies'
```

Do not forget that headers are not 1 TO 1. One header can be repeated multiple time and attribute can have multiple value within the same header.
Expand All @@ -91,6 +96,16 @@ type(headers.set_cookie) # output: list
headers.set_cookie[0].expires # output Wed, 15-Apr-2020 21:27:31 GMT
```

Just a note to inform you that accessing a header that have the same name as a reserved keyword must be done this way :
```python
headers = parse_it('From: Ousret; origin=www.github.com\nIS: 1\nWhile: Not-True')

# this flavour
headers.from_ # to access From, just add a single underscore to it
# or..
headers['from']
```

## 👤 Contributing

Contributions, issues and feature requests are very much welcome.<br />
Expand Down
90 changes: 79 additions & 11 deletions kiss_headers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@
from email.header import decode_header
from cached_property import cached_property
from requests import Response
from copy import deepcopy, copy


RESERVED_KEYWORD = [
'and_', 'assert_', 'in_', 'not_',
'pass_', 'finally_', 'while_',
'yield_', 'is_', 'as_', 'break_',
'return_', 'elif_', 'except_', 'def_',
'from_'
]

class Header(object):

charset: Union['Header', str]
format: Union['Header', str]
boundary: Union['Header', str]
expires: Union['Header', str]
timeout: Union['Header', str]
max: Union['Header', str]
path: Union['Header', str]
samesite: Union['Header', str]
domain: Union['Header', str]
charset: str
format: str
boundary: str
expires: str
timeout: str
max: str
path: str
samesite: str
domain: str

def __init__(self, head: str, content: str):

Expand Down Expand Up @@ -83,13 +92,16 @@ def content(self) -> str:

return self._content

def __deepcopy__(self, memodict: Dict) -> 'Header':
return Header(deepcopy(self.name), deepcopy(self.content))

def __iter__(self) -> Iterator[Tuple[str, Optional[str]]]:
for key, value in self._valued_attrs.items():
yield key, self[key]
for adjective in self._not_valued_attrs:
yield adjective, None

def __eq__(self, other) -> bool:
def __eq__(self, other: Union[str, 'Header']) -> bool:
if isinstance(other, str):
return self.content == other or other in self._not_valued_attrs
if isinstance(other, Header):
Expand Down Expand Up @@ -250,6 +262,9 @@ def to_dict(self) -> Dict[str, str]:
]
)

def __deepcopy__(self, memodict: Dict) -> 'Headers':
return Headers(deepcopy(self._headers))

def __eq__(self, other: 'Headers') -> bool:
if len(other) != len(self):
return False
Expand All @@ -269,6 +284,52 @@ def __str__(self):
def __repr__(self) -> str:
return '\n'.join([header.__repr__() for header in self])

def __add__(self, other: Header) -> 'Headers':
"""
Add using syntax c = a + b. The result is a newly created object.
"""
headers = deepcopy(self)
headers += other

return headers

def __sub__(self, other: Union[Header, str]) -> 'Headers':
"""
Subtract using syntax c = a - b. The result is a newly created object.
"""
headers = deepcopy(self)
headers -= other

return headers

def __iadd__(self, other: Header) -> 'Headers':
if isinstance(other, Header):
self._headers.append(other)
return self

raise TypeError('Cannot add type "{type_}" to Headers.'.format(type_=str(type(other))))

def __isub__(self, other: Union[Header, str]) -> 'Headers':
if isinstance(other, str):
other_normalized = Header.normalize_name(other)
to_be_removed = list()

for header in self:
if other_normalized == header.normalized_name:
to_be_removed.append(header)

for header in to_be_removed:
self._headers.remove(header)

return self

if isinstance(other, Header):
if other in self:
self._headers.remove(other)
return self

raise TypeError('Cannot subtract type "{type_}" to Headers.'.format(type_=str(type(other))))

def __getitem__(self, item: Union[str, int]) -> Union[Header, List[Header]]:
item = Header.normalize_name(item)

Expand All @@ -284,6 +345,13 @@ def __getitem__(self, item: Union[str, int]) -> Union[Header, List[Header]]:
return headers if len(headers) > 1 else headers.pop()

def __getattr__(self, item: str) -> Union[Header, List[Header]]:

if item[0] == '_':
item = item[1:]

if item.lower() in RESERVED_KEYWORD:
item = item[:-1]

if item not in self:
raise AttributeError("'{item}' header is not defined in headers.".format(item=item))

Expand All @@ -306,7 +374,7 @@ def __dir__(self) -> Iterable[str]:

def parse_it(raw_headers: Union[bytes, str, Dict[str, str], IOBase, Response]) -> Headers:
"""
Just decode anything that could represent headers. That simple PERIOD.
Just decode anything that could contain headers. That simple PERIOD.
"""
if isinstance(raw_headers, str):
headers = HeaderParser().parsestr(raw_headers, headersonly=True).items()
Expand Down
2 changes: 1 addition & 1 deletion kiss_headers/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
Expose version
"""

__version__ = "1.0.2"
__version__ = "1.0.3"
VERSION = __version__.split('.')
73 changes: 73 additions & 0 deletions tests/test_headers_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import unittest
from kiss_headers import Header, parse_it


class KissHeadersOperationTest(unittest.TestCase):
def test_isub(self):
headers = parse_it("""X-My-Testing: 1\nX-My-Second-Test: 1\nX-My-Second-Test: Precisly\nReceived: outpost\nReceived: outpost""")

self.assertEqual(
5,
len(headers)
)

headers -= 'X-My-Testing'

self.assertEqual(
4,
len(headers)
)

self.assertNotIn(
'X-My-Testing',
headers
)

headers -= 'Received'

self.assertEqual(
2,
len(headers)
)

self.assertNotIn(
'Received',
headers
)

headers -= Header('X-My-Second-Test', 'Precisly')

self.assertEqual(
1,
len(headers)
)

self.assertIn(
'X-My-Second-Test',
headers
)

def test_sub(self):
headers = parse_it(
"""X-My-Testing: 1\nX-My-Second-Test: 1\nX-My-Second-Test: Precisly\nReceived: outpost\nReceived: outpost""")

self.assertEqual(
5,
len(headers)
)

headers_two = headers - 'X-My-Testing'

self.assertEqual(
5,
len(headers)
)

self.assertEqual(
4,
len(headers_two)
)


if __name__ == '__main__':
unittest.main()
31 changes: 31 additions & 0 deletions tests/test_headers_reserved_keyword.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import unittest
from kiss_headers import parse_it


class MyKissHeadersReservedKeyword(unittest.TestCase):
def test_reserved_header_name_keyword(self):
headers = parse_it('From: Ousret; origin=www.github.com\nIS: 1\nWhile: Not-True')

self.assertIn(
'From',
headers
)

self.assertEqual(
'Ousret; origin=www.github.com',
headers.from_
)

self.assertIn(
'Ousret',
headers.from_
)

self.assertEqual(
'Not-True',
headers.while_
)


if __name__ == '__main__':
unittest.main()

0 comments on commit 5f711c7

Please sign in to comment.