Skip to content

Commit

Permalink
Merge pull request #12 from Ousret/develop
Browse files Browse the repository at this point in the history
Minors revisions for 2.0.0
  • Loading branch information
Ousret authored Apr 9, 2020
2 parents b47f4f3 + 8998204 commit 55ac222
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 28 deletions.
61 changes: 57 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<p align="center">
<img alt="Temporary logo" src="https://user-images.githubusercontent.com/9326700/76708477-64a96600-66f7-11ea-9d4a-8cc07866e185.png"/><br>
<sup>Combine advantages of many representations, with auto-completion!</sup><br>
<sup>Object oriented headers, pythonic, with auto-completion!</sup><br>
<a href="https://travis-ci.org/Ousret/kiss-headers">
<img src="https://travis-ci.org/Ousret/kiss-headers.svg?branch=master"/>
</a>
Expand Down Expand Up @@ -45,7 +45,7 @@ charset = headers['Content-Type'].split(';')[-1].split('=')[-1].replace('"', '')

## 🔪 Features

`kiss-headers` is a basic library that allow you to handle headers with great care.
`kiss-headers` is a basic library that allow you to handle headers as objects.

* A backwards-compatible syntax using bracket style.
* Capability to alter headers using simple, human-readable operator notation `+` and `-`.
Expand All @@ -70,7 +70,7 @@ Plus all the features that you would expect from handling headers...

Whatever you like, use `pipenv` or `pip`, it simply works. Requires Python 3.6+ installed.
```sh
pip install kiss-headers
pip install kiss-headers --upgrade
```

### 🍰 Usage
Expand All @@ -85,6 +85,8 @@ response = get('https://www.google.fr')
headers = parse_it(response)

headers.content_type.charset # output: ISO-8859-1
# Its the same as
headers["content-type"]["charset"] # output: ISO-8859-1
```

Do not forget that headers are not OneToOne. One header can be repeated multiple times and attributes can have multiple values within the same header.
Expand All @@ -109,10 +111,61 @@ 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..
# or.. just using :
headers['from']
```

## 🛠️ The builder

Introduced in the version 2.0, kiss-headers now allow you to create headers with more than 40+ ready-to-use, fully documented, header objects.

```python
from kiss_headers import *

headers = (
Host("developer.mozilla.org")
+ UserAgent(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0"
)
+ Accept("text/html")
+ Accept("application/xhtml+xml")
+ Accept("application/xml", qualifier=0.9)
+ Accept(qualifier=0.8)
+ AcceptLanguage("en-US")
+ AcceptLanguage("en", qualifier=0.5)
+ AcceptEncoding("gzip")
+ AcceptEncoding("deflate")
+ AcceptEncoding("br")
+ Referer("https://developer.mozilla.org/testpage.html")
+ Connection(should_keep_alive=True)
+ UpgradeInsecureRequests()
+ IfModifiedSince("Mon, 18 Jul 2016 02:36:04 GMT")
+ IfNoneMatch("c561c68d0ba92bbeb8b0fff2a9199f722e3a621a")
+ CacheControl(max_age=0)
)

raw_headers = str(headers)
```

`raw_headers` now retain the following :

```
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html, application/xhtml+xml, application/xml; q="0.9", */*; q="0.8"
Accept-Language: en-US, en; q="0.5"
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
Cache-Control: max-age="0"
```

See the complete list of available header class in the full documentation.
Also, you can create your own custom header object using the class `kiss_headers.CustomHeader`.

## 📜 Documentation

See the full documentation for advanced usages : [www.kiss-headers.tech](https://www.kiss-headers.tech/)
Expand Down
2 changes: 2 additions & 0 deletions kiss_headers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
~~~~~~~~~~~~~~
Kiss-Headers is a headers, HTTP or IMAP4 flavour, utility, written in Python, for humans.
Object oriented headers. Keep it simple and stupid.
Basic usage:
>>> import requests
Expand Down Expand Up @@ -83,5 +84,6 @@
IfMatch,
IfNoneMatch,
Server,
Vary,
)
from kiss_headers.version import __version__, VERSION
4 changes: 2 additions & 2 deletions kiss_headers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def parse_it(raw_headers: Any) -> Headers:
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))
elif r == "httpx._models.Response":
elif r in ["httpx._models.Response", "urllib3.response.HTTPResponse"]:
headers = raw_headers.headers.items()

if headers is None:
Expand Down Expand Up @@ -85,7 +85,7 @@ def parse_it(raw_headers: Any) -> Headers:

def explain(headers: Headers) -> CaseInsensitiveDict:
"""
Return an brief explanation of each header present in headers if available.
Return a brief explanation of each header present in headers if available.
"""
if not Header.__subclasses__():
raise LookupError(
Expand Down
50 changes: 38 additions & 12 deletions kiss_headers/builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from re import fullmatch

from kiss_headers.models import Header
from kiss_headers.utils import class_to_header_name
from kiss_headers.utils import class_to_header_name, prettify_header_name, quote
from typing import Optional, Union, Dict, List

from datetime import datetime, timezone
Expand Down Expand Up @@ -517,7 +517,15 @@ def __init__(

method = method.lower()

if method not in ["chunked", "compress", "deflate", "gzip", "identity", "br"]:
if method not in [
"chunked",
"compress",
"deflate",
"gzip",
"identity",
"br",
"*",
]:
raise ValueError(
"You should choose between 'chunked', 'compress', 'deflate', 'gzip', 'identity' or 'br' for the encoding method."
)
Expand Down Expand Up @@ -554,12 +562,17 @@ class AcceptEncoding(TransferEncoding):

__tags__ = ["request"]

def __init__(self, method: str, **kwargs):
def __init__(self, method: str, qualifier=1.0, **kwargs):
"""
:param method:
:param method: Either chunked, compress, deflate, gzip, identity, br or a wildcard.
:param qualifier: Any value used is placed in an order of preference expressed using relative quality value called the weight.
:param kwargs:
"""
super().__init__(method, **kwargs)
args: Dict = {"q": qualifier if qualifier != 1.0 else None}

args.update(kwargs)

super().__init__(method, **args)


class Dnt(CustomHeader):
Expand Down Expand Up @@ -778,9 +791,9 @@ def __init__(self, etag_value: str, is_a_weak_validator: bool = False, **kwargs)
:param kwargs:
"""
super().__init__(
'{weak_validation_cond}"{etag}"'.format(
"{weak_validation_cond}{etag}".format(
weak_validation_cond="W/" if is_a_weak_validator else "",
etag=etag_value,
etag=quote(etag_value),
),
**kwargs,
)
Expand Down Expand Up @@ -1073,7 +1086,7 @@ def __init__(self, etag_value: str, **kwargs):
:param etag_value: Entity tags uniquely representing the requested resources. They are a string of ASCII characters placed between double quotes (like "675af34563dc-tr34").
:param kwargs:
"""
super().__init__(etag_value, **kwargs)
super().__init__(quote(etag_value), **kwargs)


class IfNoneMatch(IfMatch):
Expand All @@ -1089,10 +1102,8 @@ def __init__(self, etag_value: str, **kwargs):


class Server(CustomHeader):
"""
The Server header describes the software used by the origin server that handled the request —
that is, the server that generated the response.
"""
"""The Server header describes the software used by the origin server that handled the request —
that is, the server that generated the response."""

__tags__ = ["response"]

Expand All @@ -1102,3 +1113,18 @@ def __init__(self, product: str, **kwargs):
:param kwargs:
"""
super().__init__(product, **kwargs)


class Vary(CustomHeader):
"""The Vary HTTP response header determines how to match future request headers to decide whether a cached response
can be used rather than requesting a fresh one from the origin server."""

__squash__ = True
__tags__ = ["response"]

def __init__(self, header_name, **kwargs):
"""
:param header_name: An header name to take into account when deciding whether or not a cached response can be used.
:param kwargs:
"""
super().__init__(prettify_header_name(header_name), **kwargs)
26 changes: 20 additions & 6 deletions kiss_headers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,18 @@ def pretty_name(self) -> str:
def content(self) -> str:
"""
Output associated content to header as it was captured initially.
>>> header = Header("ETag", '"33a64df551425fcc55e4d42a148795d9f25f89d4"')
>>> header.content
'33a64df551425fcc55e4d42a148795d9f25f89d4'
"""
# Unquote content if their is only one value/attribute in it. Like the ETag header.
if len(self.attrs) == 1:
if self._content.startswith('"') and self._content.endswith('"'):
return self._content[1:-1]
return unquote(self._content)

return self._content

def __deepcopy__(self, memodict: Dict) -> "Header":
"""Simply provide a deepcopy of an Header object. Pointer/Reference free of the initial reference."""
return Header(deepcopy(self.name), deepcopy(self.content))

def __iadd__(self, other: Union[str, "Header"]) -> "Header":
Expand Down Expand Up @@ -185,10 +189,18 @@ def __isub__(self, other: str) -> "Header":
This method should allow you to remove attribute or member from header.
"""
if not isinstance(other, str):
raise TypeError
raise TypeError(
"You cannot subtract {type_} to an Header.".format(
type_=str(type(other))
)
)

if other not in self:
raise ValueError
raise ValueError(
"You cannot subtract '{element}' from '{header_name}' Header because its not there.".format(
element=other, header_name=self.pretty_name
)
)

other = normalize_str(other)

Expand Down Expand Up @@ -354,6 +366,8 @@ def __delattr__(self, item: str):
del self[item]

def __iter__(self) -> Iterator[Tuple[str, Optional[Union[str, List[str]]]]]:
"""Provide a way to iter over an Header object. This will yield a Tuple of key, value.
Value would be None if the key is a member without associated value."""
for key in self._valued_attrs:
yield key, self[key]
for adjective in self._not_valued_attrs:
Expand Down Expand Up @@ -501,7 +515,7 @@ def __contains__(self, item: str) -> bool:
class Headers(object):
"""
Object oriented representation for Headers. Contains a list of Header with some level of abstraction.
Combine advantages of dict, CaseInsensibleDict and objects.
Combine advantages of dict, CaseInsensibleDict and native objects.
"""

# Most common headers that you may or may not find. This should be appreciated when having auto-completion.
Expand Down Expand Up @@ -632,7 +646,7 @@ def to_dict(self) -> CaseInsensitiveDict:

def __deepcopy__(self, memodict: Dict) -> "Headers":
"""
Just provide a deepcopy of current Headers object. Pointer linked free of current instance.
Just provide a deepcopy of current Headers object. Pointer/reference free of the current instance.
"""
return Headers(deepcopy(self._headers))

Expand Down
13 changes: 13 additions & 0 deletions kiss_headers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,16 @@ def unquote(string: str) -> str:
return string[1:-1]

return string


def quote(string: str) -> str:
"""
Surround string by double quote.
>>> quote("hello")
'"hello"'
>>> quote('"hello')
'""hello"'
>>> quote('"hello"')
'"hello"'
"""
return '"' + unquote(string) + '"'
5 changes: 1 addition & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ def get_version():

# Package meta-data.
NAME = "kiss-headers"
DESCRIPTION = (
"Headers: Keep-It=Simple; And=Stupid. Library for HTTP and IMAP headers reading and processing, "
"dead simple. "
)
DESCRIPTION = "Python package for headers, object oriented, http or imap."
URL = "https://github.com/ousret/kiss-headers"
EMAIL = "[email protected]"
AUTHOR = "Ahmed TAHRI @Ousret"
Expand Down

0 comments on commit 55ac222

Please sign in to comment.