Skip to content

Commit

Permalink
Merge pull request #3 from Ousret/patch-2
Browse files Browse the repository at this point in the history
Second batch of bug fixes and features add
  • Loading branch information
Ousret authored Mar 16, 2020
2 parents c9f03d7 + 596496f commit 7ece553
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 10 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ pip install kiss-headers

### 🍰 Usage

`parse_it()` method take `bytes`, `str`, `fp` or `dict` and give you back a `Headers` object.
`parse_it()` method take `bytes`, `str`, `fp`, `dict` or even `requests.Response` itself and give you back a `Headers` object.

```python
from requests import get
from kiss_headers import parse_it

response = get('https://www.google.fr')
headers = parse_it(response.headers)
headers = parse_it(response)

'Content-Type' in headers # output: True
'Content_type' in headers # output: True
Expand All @@ -64,10 +64,14 @@ str(headers.content_type) # output : text/html; charset=ISO-8859-1
'application/json' in headers.content_type # output: False
'text/html' in headers.content_type # output: True

str(headers.content_type.charset) # output : utf-8
str(headers.content_type.charset) # output : ISO-8859-1
type(headers.set_cookie) # output: list
'Secure' in headers.set_cookie[0] # output: True
'domain' in headers.set_cookie[0] # output: True
headers.set_cookie[0].domain # output: .google.fr
```

Do not forget that headers are not 1 TO 1. One header can be repeated multiple time and attribute can have multiple within the same header.
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.

```python
from kiss_headers import parse_it
Expand Down
30 changes: 25 additions & 5 deletions kiss_headers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List, Optional, Union, Dict, Mapping, Iterator, Tuple, Iterable
from email.header import decode_header
from cached_property import cached_property
from requests import Response


class Header(object):
Expand All @@ -14,6 +15,8 @@ class Header(object):
timeout: Union['Header', str]
max: Union['Header', str]
path: Union['Header', str]
samesite: Union['Header', str]
domain: Union['Header', str]

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

Expand All @@ -31,7 +34,7 @@ def __init__(self, head: str, content: str):
key, value = tuple(member.split('=', maxsplit=1))

# avoid confusing base64 look alike single value for (key, value)
if value.count('=') == len(value) or len(value) == 0:
if value.count('=') == len(value) or len(value) == 0 or ' ' in key:
self._not_valued_attrs.append(member)
continue

Expand Down Expand Up @@ -120,7 +123,7 @@ def get(self, attr: str) -> Optional[str]:
return None
return self._valued_attrs[attr]

def __getitem__(self, item: str) -> Union[str, List[str]]:
def __getitem__(self, item: Union[str, int]) -> Union[str, List[str]]:
"""
This method will allow you to retrieve attribute value using the bracket syntax, list-like.
"""
Expand All @@ -140,7 +143,7 @@ def __getitem__(self, item: str) -> Union[str, List[str]]:

return value

def __getattr__(self, item) -> str:
def __getattr__(self, item: str) -> str:
"""
All the magic happen here, this method should be invoked when trying to call (not declared) properties.
For instance, calling self.charset should end up here and be replaced by self['charset'].
Expand Down Expand Up @@ -266,7 +269,7 @@ def __str__(self):
def __repr__(self) -> str:
return '\n'.join([header.__repr__() for header in self])

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

if item not in self:
Expand Down Expand Up @@ -301,7 +304,7 @@ def __dir__(self) -> Iterable[str]:
return super().__dir__() + list(set([header.normalized_name for header in self]))


def parse_it(raw_headers: Union[bytes, str, Dict[str, str], IOBase]) -> Headers:
def parse_it(raw_headers: Union[bytes, str, Dict[str, str], IOBase, Response]) -> Headers:
"""
Just decode anything that could represent headers. That simple PERIOD.
"""
Expand All @@ -312,6 +315,13 @@ def parse_it(raw_headers: Union[bytes, str, Dict[str, str], IOBase]) -> Headers:
headers = BytesHeaderParser().parse(buf, headersonly=True).items()
elif isinstance(raw_headers, Mapping):
headers = raw_headers.items()
elif isinstance(raw_headers, Response):
headers = list()
for header_name in raw_headers.raw.headers:
for header_content in raw_headers.raw.headers.getlist(header_name):
headers.append(
(header_name, header_content)
)
else:
raise TypeError('Cannot parse type {type_} as it is not supported by kiss-header.'.format(type_=type(raw_headers)))

Expand All @@ -328,4 +338,14 @@ def parse_it(raw_headers: Union[bytes, str, Dict[str, str], IOBase]) -> Headers:

revised_headers.append((head, revised_content))

# Sometime raw content does not begin with headers. If that is the case, search for the next line.
if len(revised_headers) == 0 and len(raw_headers) > 0 and (isinstance(raw_headers, bytes) or isinstance(raw_headers, str)):
next_iter = raw_headers.split(
b'\n' if isinstance(raw_headers, bytes) else '\n',
maxsplit=1
)

if len(next_iter) >= 2:
return parse_it(next_iter[-1])

return Headers([Header(head, content) for head, content in revised_headers])
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.1"
__version__ = "1.0.2"
VERSION = __version__.split('.')
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_version():

REQUIRED = [
'cached_property',
'requests'
]

EXTRAS = {}
Expand Down

0 comments on commit 7ece553

Please sign in to comment.