Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔖 Release 2.4.3 #52

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: ["3.10"]
os: [ubuntu-latest]

steps:
Expand All @@ -38,4 +38,4 @@ jobs:
isort --check kiss_headers
- name: Code format (Black)
run: |
black --check --diff --target-version=py37 kiss_headers
black --check --diff kiss_headers
86 changes: 86 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Publish to PyPI

on:
release:
types:
- created

permissions:
contents: read

jobs:
build:
name: "Build dists"
runs-on: "ubuntu-latest"
outputs:
hashes: ${{ steps.hash.outputs.hashes }}

steps:
- name: "Checkout repository"
uses: "actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608"

- name: "Setup Python"
uses: "actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1"
with:
python-version: "3.x"

- name: "Install dependencies"
run: python -m pip install build

- name: "Build dists"
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \
python -m build

- name: "Generate hashes"
id: hash
run: |
cd dist && echo "::set-output name=hashes::$(sha256sum * | base64 -w0)"

- name: "Upload dists"
uses: "actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce"
with:
name: "dist"
path: "dist/"
if-no-files-found: error
retention-days: 5

provenance:
needs: [build]
permissions:
actions: read
contents: write
id-token: write # Needed to access the workflow's OIDC identity.
uses: "slsa-framework/slsa-github-generator/.github/workflows/[email protected]"
with:
base64-subjects: "${{ needs.build.outputs.hashes }}"
upload-assets: true
compile-generator: true # Workaround for https://github.com/slsa-framework/slsa-github-generator/issues/1163

publish:
name: "Publish"
if: startsWith(github.ref, 'refs/tags/')
environment:
name: pypi
url: https://pypi.org/p/kiss-headers
needs: ["build", "provenance"]
permissions:
contents: write
id-token: write # Needed for trusted publishing to PyPI.
runs-on: "ubuntu-latest"

steps:
- name: "Download dists"
uses: "actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a"
with:
name: "dist"
path: "dist/"

- name: "Upload dists to GitHub Release"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: |
gh release upload ${{ github.ref_name }} dist/* --repo ${{ github.repository }}

- name: "Publish dists to PyPI"
uses: "pypa/gh-action-pypi-publish@f8c70e705ffc13c3b4d1221169b84f12a75d6ca8" # v1.8.8
31 changes: 0 additions & 31 deletions .github/workflows/pythonpublish.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
os: [ubuntu-latest]

steps:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Plus all the features that you would expect from handling headers...

* Properties syntax for headers and attribute in a header.
* Supports headers and attributes OneToOne, OneToMany and ManySquashedIntoOne.
* Capable of parsing `bytes`, `fp`, `str`, `dict`, `email.Message`, `requests.Response`, `httpx._models.Response` and `urllib3.HTTPResponse`.
* Capable of parsing `bytes`, `fp`, `str`, `dict`, `email.Message`, `requests.Response`, `niquests.Response`, `httpx._models.Response` and `urllib3.HTTPResponse`.
* Automatically unquote and unfold the value of an attribute when retrieving it.
* Keep headers and attributes ordering.
* Case-insensitive with header name and attribute key.
Expand All @@ -73,6 +73,8 @@ Whatever you like, use `pipenv` or `pip`, it simply works. Requires Python 3.7+
pip install kiss-headers --upgrade
```

This project is included in [Niquests](https://github.com/jawah/niquests)! Your awesome drop-in replacement for Requests!

### 🍰 Usage

#### Quick start
Expand Down
10 changes: 7 additions & 3 deletions kiss_headers/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
from email.message import Message
from email.parser import HeaderParser
from io import BufferedReader, RawIOBase
Expand Down Expand Up @@ -27,17 +28,20 @@
def parse_it(raw_headers: Any) -> Headers:
"""
Just decode anything that could contain headers. That simple PERIOD.
:param raw_headers: Accept bytes, str, fp, dict, JSON, email.Message, requests.Response, urllib3.HTTPResponse and httpx.Response.
If passed with a Headers instance, return a deep copy of it.
:param raw_headers: Accept bytes, str, fp, dict, JSON, email.Message, requests.Response, niquests.Response, urllib3.HTTPResponse and httpx.Response.
:raises:
TypeError: If passed argument cannot be parsed to extract headers from it.
"""

if isinstance(raw_headers, Headers):
return deepcopy(raw_headers)

headers: Optional[Iterable[Tuple[Union[str, bytes], Union[str, bytes]]]] = None

if isinstance(raw_headers, str):
if raw_headers.startswith("{") and raw_headers.endswith("}"):
return decode(json_loads(raw_headers))

headers = HeaderParser().parsestr(raw_headers, headersonly=True).items()
elif (
isinstance(raw_headers, bytes)
Expand All @@ -54,7 +58,7 @@ def parse_it(raw_headers: Any) -> Headers:
r = extract_class_name(type(raw_headers))

if r:
if r == "requests.models.Response":
if r in ["requests.models.Response", "niquests.models.Response"]:
headers = []
for header_name in raw_headers.raw.headers:
for header_content in raw_headers.raw.headers.getlist(header_name):
Expand Down
6 changes: 3 additions & 3 deletions kiss_headers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
OUTPUT_LOCK_TYPE: bool = False


class Header(object):
class Header:
"""
Object representation of a single Header.
"""
Expand Down Expand Up @@ -606,7 +606,7 @@ def __contains__(self, item: str) -> bool:
return False


class Headers(object):
class Headers:
"""
Object-oriented representation for Headers. Contains a list of Header with some level of abstraction.
Combine advantages of dict, CaseInsensibleDict, list, multi-dict, and native objects.
Expand Down Expand Up @@ -1211,7 +1211,7 @@ def __dir__(self) -> Iterable[str]:
)


class Attributes(object):
class Attributes:
"""
Dedicated class to handle attributes within a Header. Wrap an AttributeBag and offer methods to manipulate it
with ease.
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__ = "2.4.2"
__version__ = "2.4.3"
VERSION = __version__.split(".")
39 changes: 39 additions & 0 deletions tests/test_headers_from_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,39 @@
"\n", "\r\n"
)

RAW_HEADERS_WITH_CONNECT = """HTTP/1.1 200 Connection established

HTTP/2 200
date: Tue, 28 Sep 2021 13:45:34 GMT
content-type: application/epub+zip
content-length: 3706401
content-disposition: filename=ipython-readthedocs-io-en-stable.epub
x-amz-id-2: 2PO2WHP4qGqkhyC1VbRE2KLN2g4uk38vYzaNJDU/OBSxh4lUtYgERD2FNAOPkKPD1a6rsNBMeKI=
x-amz-request-id: 21E21R71FAY4WQKT
last-modified: Sat, 25 Sep 2021 00:43:37 GMT
etag: "6f512f04591f7667486d044c54708448"
x-served: Nginx-Proxito-Sendfile
x-backend: web-i-078619706c1392c2c
x-rtd-project: ipython
x-rtd-version: stable
x-rtd-path: /proxito/epub/ipython/stable/ipython.epub
x-rtd-domain: ipython.readthedocs.io
x-rtd-version-method: path
x-rtd-project-method: subdomain
referrer-policy: no-referrer-when-downgrade
permissions-policy: interest-cohort=()
strict-transport-security: max-age=31536000; includeSubDomains; preload
cf-cache-status: HIT
age: 270
expires: Tue, 28 Sep 2021 15:45:34 GMT
cache-control: public, max-age=7200
accept-ranges: bytes
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 695d69b549330686-LHR""".replace(
"\n", "\r\n"
)


class MyKissHeadersFromStringTest(unittest.TestCase):
headers: Headers
Expand Down Expand Up @@ -169,6 +202,12 @@ def test_fixed_type_output(self):

self.assertEqual(str, type(headers.accept[-1].q))

def test_parse_with_extra_connect(self):
headers: Headers = parse_it(RAW_HEADERS_WITH_CONNECT)

self.assertTrue("Date" in headers)
self.assertTrue("Server" in headers)


if __name__ == "__main__":
unittest.main()
Loading