diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index dfb062f..c982577 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ ## Pull Request ### My patch is about -- [ ] :bug: Bugfix +- [ ] :bug: Bugfix - [ ] :arrow_up: Improvement - [ ] :pencil: Documentation - [ ] :heavy_check_mark: Tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 08757aa..7f84847 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,11 @@ name: 🎨 Linters -on: [push, pull_request] +on: + push: + branches: + - master + - main + pull_request: jobs: lint: @@ -20,11 +25,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install -U pip setuptools + pip install -U pip build wheel pip install -r dev-requirements.txt - name: Install the package run: | - python setup.py install + pip install . - name: Type checking (Mypy) run: | mypy kiss_headers @@ -33,4 +38,4 @@ jobs: isort --check kiss_headers - name: Code format (Black) run: | - black --check --diff --target-version=py36 kiss_headers + black --check --diff --target-version=py37 kiss_headers diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index d5f3859..bf394f5 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -21,11 +21,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install build wheel twine - name: Build and publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9475546..deff995 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,6 +1,11 @@ name: Tests -on: [push, pull_request] +on: + push: + branches: + - main + - master + pull_request: jobs: tests: @@ -9,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11"] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"] os: [ubuntu-latest] steps: @@ -20,11 +25,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install -U pip setuptools + pip install -U pip build wheel pip install -r dev-requirements.txt - name: Install the package run: | - python setup.py install + pip install . - name: Run tests run: | pytest diff --git a/README.md b/README.md index 63a0aca..e7e19b5 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ response = get( * Fully type-annotated. * Provide great auto-completion in Python interpreter or any capable IDE. * No dependencies. Never will be. +* Support JSON in header value. * 90% test coverage. Plus all the features that you would expect from handling headers... @@ -67,7 +68,7 @@ Plus all the features that you would expect from handling headers... ### ✨ Installation -Whatever you like, use `pipenv` or `pip`, it simply works. Requires Python 3.6+ installed. +Whatever you like, use `pipenv` or `pip`, it simply works. Requires Python 3.7+ installed. ```sh pip install kiss-headers --upgrade ``` diff --git a/kiss_headers/__init__.py b/kiss_headers/__init__.py index 89ef672..6876eea 100644 --- a/kiss_headers/__init__.py +++ b/kiss_headers/__init__.py @@ -36,8 +36,8 @@ :license: MIT, see LICENSE for more details. """ -from kiss_headers.api import dumps, explain, get_polymorphic, parse_it -from kiss_headers.builder import ( +from .api import dumps, explain, get_polymorphic, parse_it +from .builder import ( Accept, AcceptEncoding, AcceptLanguage, @@ -87,9 +87,9 @@ XFrameOptions, XXssProtection, ) -from kiss_headers.models import Attributes, Header, Headers, lock_output_type -from kiss_headers.serializer import decode, encode -from kiss_headers.version import VERSION, __version__ +from .models import Attributes, Header, Headers, lock_output_type +from .serializer import decode, encode +from .version import VERSION, __version__ __all__ = ( "dumps", diff --git a/kiss_headers/api.py b/kiss_headers/api.py index 6730e3c..1e4f18e 100644 --- a/kiss_headers/api.py +++ b/kiss_headers/api.py @@ -4,21 +4,23 @@ from json import dumps as json_dumps, loads as json_loads from typing import Any, Iterable, List, Mapping, Optional, Tuple, Type, TypeVar, Union -from kiss_headers.models import Header, Headers -from kiss_headers.serializer import decode, encode -from kiss_headers.structures import CaseInsensitiveDict -from kiss_headers.utils import ( +from .builder import CustomHeader +from .models import Header, Headers +from .serializer import decode, encode +from .structures import CaseInsensitiveDict +from .utils import ( class_to_header_name, decode_partials, extract_class_name, extract_encoded_headers, header_content_split, header_name_to_class, + is_content_json_object, is_legal_header_name, normalize_str, ) -T = TypeVar("T") +T = TypeVar("T", bound=CustomHeader, covariant=True) def parse_it(raw_headers: Any) -> Headers: @@ -88,12 +90,17 @@ def parse_it(raw_headers: Any) -> Headers: list_of_headers: List[Header] = [] for head, content in revised_headers: - # We should ignore when a illegal name is considered as an header. We avoid ValueError (in __init__ of Header) if is_legal_header_name(head) is False: continue - entries: List[str] = header_content_split(content, ",") + is_json_obj: bool = is_content_json_object(content) + entries: List[str] + + if is_json_obj is False: + entries = header_content_split(content, ",") + else: + entries = [content] # Multiple entries are detected in one content at the only exception that its not IMAP header "Subject". if len(entries) > 1 and normalize_str(head) != "subject": diff --git a/kiss_headers/builder.py b/kiss_headers/builder.py index 01c910a..eab30b7 100644 --- a/kiss_headers/builder.py +++ b/kiss_headers/builder.py @@ -5,8 +5,8 @@ from typing import Dict, List, Optional, Tuple, Union from urllib.parse import quote as url_quote, unquote as url_unquote -from kiss_headers.models import Header -from kiss_headers.utils import ( +from .models import Header +from .utils import ( class_to_header_name, header_content_split, prettify_header_name, diff --git a/kiss_headers/models.py b/kiss_headers/models.py index 68e323c..a909f52 100644 --- a/kiss_headers/models.py +++ b/kiss_headers/models.py @@ -1,13 +1,14 @@ from copy import deepcopy -from json import dumps +from json import JSONDecodeError, dumps, loads from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union -from kiss_headers.structures import AttributeBag, CaseInsensitiveDict -from kiss_headers.utils import ( +from .structures import AttributeBag, CaseInsensitiveDict +from .utils import ( escape_double_quote, extract_comments, header_content_split, header_name_to_class, + is_content_json_object, is_legal_header_name, normalize_list, normalize_str, @@ -54,7 +55,29 @@ def __init__(self, name: str, content: str): self._pretty_name: str = prettify_header_name(self._name) self._content: str = content - self._members: List[str] = header_content_split(self._content, ";") + self._members: List[str] + + if is_content_json_object(self._content): + try: + payload = loads(self._content) + except JSONDecodeError as e: + raise ValueError( + f"Header '{self._name}' contain an invalid JSON value." + ) from e + + if isinstance(payload, list): + self._members = payload + elif isinstance(payload, dict): + self._members = [] + for k, v in payload.items(): + if v is not None: + self._members.append(f"{str(k)}={str(v)}") + else: + self._members.append(str(k)) + else: + raise ValueError(f"Header '{self._name}' is malformed.") + else: + self._members = header_content_split(self._content, ";") self._attrs: Attributes = Attributes(self._members) @@ -264,7 +287,6 @@ def __add__(self, other: Union[str, "Header"]) -> Union["Header", "Headers"]: ) if isinstance(other, Header): - headers = Headers() headers += self headers += other @@ -644,6 +666,8 @@ class Headers(object): from_: Union[Header, List[Header]] + report_to: Union[Header, List[Header]] + def __init__(self, *headers: Union[List[Header], Header]): """ :param headers: Initial list of header. Can be empty. @@ -801,11 +825,9 @@ def __setitem__(self, key: str, value: str) -> None: # Permit to detect multiple entries. if normalize_str(key) != "subject": - entries: List[str] = header_content_split(value, ",") if len(entries) > 1: - for entry in entries: self._headers.append(Header(key, entry)) @@ -878,7 +900,6 @@ def __repr__(self) -> str: result: List[str] = [] for header_name in self.keys(): - r = self.get(header_name) if not r: @@ -1193,7 +1214,6 @@ def __init__(self, members: List[str]): self._bag: AttributeBag = CaseInsensitiveDict() for member, index in zip(members, range(0, len(members))): - if member == "": continue @@ -1255,11 +1275,9 @@ def __eq__(self, other: object) -> bool: list_check: List[Tuple[int, str, Optional[str]]] = [] for index_a, key_a, value_a in list_repr_a: - key_a = normalize_str(key_a) for index_b, key_b, value_b in list_repr_b: - key_b = normalize_str(key_b) if ( @@ -1267,7 +1285,6 @@ def __eq__(self, other: object) -> bool: and value_a == value_b and (index_a, key_a, key_b) not in list_check ): - list_check.append((index_a, key_a, key_b)) return len(list_check) == len(list_repr_a) @@ -1358,7 +1375,6 @@ def remove( freed_indexes: List[int] = [] if index is not None: - if with_value is not None: raise ValueError( "Cannot set both index and with_value in the remove method." @@ -1373,9 +1389,7 @@ def remove( freed_indexes.append(self._bag[key][1].pop(pos)) if index is None: - if with_value is not None: - for index_, value_ in zip(self._bag[key][1], self._bag[key][0]): if with_value is True and value_ is not None: freed_indexes.append(index_) @@ -1397,7 +1411,6 @@ def remove( del self._bag[key] for attr in self._bag: - values, indexes = self._bag[attr] max_freed_index: int = max(freed_indexes) diff --git a/kiss_headers/serializer.py b/kiss_headers/serializer.py index 022b889..e554907 100644 --- a/kiss_headers/serializer.py +++ b/kiss_headers/serializer.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from kiss_headers.models import Header, Headers +from .models import Header, Headers def encode(headers: Headers) -> Dict[str, List[Dict]]: @@ -10,14 +10,12 @@ def encode(headers: Headers) -> Dict[str, List[Dict]]: result: Dict[str, List[Dict]] = dict() for header in headers: - if header.name not in result: result[header.name] = list() encoded_header: Dict[str, Union[Optional[str], List[str]]] = dict() for attribute, value in header: - if attribute not in encoded_header: encoded_header[attribute] = value continue diff --git a/kiss_headers/utils.py b/kiss_headers/utils.py index c76f4f7..5647270 100644 --- a/kiss_headers/utils.py +++ b/kiss_headers/utils.py @@ -103,7 +103,6 @@ def header_content_split(string: str, delimiter: str) -> List[str]: result: List[str] = [""] for letter, index in zip(string, range(0, len(string))): - if letter == '"': in_double_quote = not in_double_quote @@ -126,7 +125,6 @@ def header_content_split(string: str, delimiter: str) -> List[str]: } if not in_double_quote: - if not in_value and letter == "=": in_value = True elif letter == ";" and in_value: @@ -138,7 +136,6 @@ def header_content_split(string: str, delimiter: str) -> List[str]: if letter == delimiter and ( (in_value or in_double_quote or in_parenthesis or is_on_a_day) is False ): - result[-1] = result[-1].lstrip().rstrip() result.append("") @@ -453,3 +450,14 @@ def escape_double_quote(content: str) -> str: 'UTF\\"-8' """ return unescape_double_quote(content).replace('"', r"\"") + + +def is_content_json_object(content: str) -> bool: + """ + Sometime, you may receive a header that hold a JSON list or object. + This function detect it. + """ + content = content.strip() + return (content.startswith("{") and content.endswith("}")) or ( + content.startswith("[") and content.endswith("]") + ) diff --git a/kiss_headers/version.py b/kiss_headers/version.py index 432201b..06f9e53 100644 --- a/kiss_headers/version.py +++ b/kiss_headers/version.py @@ -2,5 +2,5 @@ Expose version """ -__version__ = "2.3.1" +__version__ = "2.4.0" VERSION = __version__.split(".") diff --git a/mkdocs.yml b/mkdocs.yml index 580574c..6292859 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,6 @@ site_name: Kiss-Headers site_description: Python package for object oriented headers, HTTP/1.1 style. Parse headers to objects. -site_url: https://kiss-headers.tech/ +site_url: https://ousret.github.io/kiss-headers theme: name: 'material' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5ac57ee --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,84 @@ +[build-system] +requires = ["hatchling>=1.6.0,<2"] +build-backend = "hatchling.build" + +[project] +name = "kiss-headers" +description = "Object-oriented HTTP and IMAP (structured) headers." +readme = "README.md" +license-files = { paths = ["LICENSE"] } +license = "MIT" +keywords = ["headers", "http", "mail", "text", "imap", "header", "https", "imap4"] +authors = [ + {name = "Ahmed R. TAHRI", email="ahmed.tahri@cloudnursery.dev"}, +] +maintainers = [ + {name = "Ahmed R. TAHRI", email="ahmed.tahri@cloudnursery.dev"}, +] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "Topic :: Communications :: Email", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", + "Environment :: Web Environment", + "Topic :: Software Development :: Libraries :: Python Modules", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Utilities", + "Programming Language :: Python :: Implementation :: PyPy", +] +requires-python = ">=3.7" +dynamic = ["version"] + +[project.urls] +"Documentation" = "https://ousret.github.io/kiss-headers" +"Code" = "https://github.com/ousret/kiss-headers" +"Issue tracker" = "https://github.com/ousret/kiss-headers/issues" + +[tool.hatch.version] +path = "kiss_headers/version.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/docs", + "/kiss_headers", + "/tests", + "/dev-requirements.txt", + "/README.md", + "/LICENSE", + "/setup.cfg", +] + +[tool.hatch.build.targets.wheel] +packages = [ + "kiss_headers/", +] + +[tool.isort] +profile = "black" +src_paths = ["kiss_headers", "tests"] +honor_noqa = true +combine_as_imports = true +force_grid_wrap = 0 +include_trailing_comma = true +known_first_party = "kiss_headers,tests" +line_length = 88 +multi_line_output = 3 + +[tool.pytest.ini_options] +addopts = "--cov=kiss_headers --doctest-modules --cov-report=term-missing -rxXs" +doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" +minversion = "6.2" +testpaths = ["tests", "kiss_headers"] + +[tool.mypy] +disallow_untyped_defs = true diff --git a/scripts/check b/scripts/check index 085c75e..51e2185 100755 --- a/scripts/check +++ b/scripts/check @@ -8,7 +8,7 @@ export SOURCE_FILES="kiss_headers tests" set -x -${PREFIX}black --check --diff --target-version=py36 $SOURCE_FILES +${PREFIX}black --check --diff --target-version=py37 $SOURCE_FILES ${PREFIX}mypy kiss_headers -${PREFIX}isort --check --diff --project=kiss_headers --recursive $SOURCE_FILES +${PREFIX}isort --check --diff --project=kiss_headers $SOURCE_FILES ${PREFIX}pytest diff --git a/scripts/install b/scripts/install index 3664869..ceadfd1 100755 --- a/scripts/install +++ b/scripts/install @@ -11,7 +11,7 @@ else python -m venv venv fi -${PREFIX}python -m pip install -U pip setuptools -${PREFIX}python -m pip install -r requirements.txt +${PREFIX}python -m pip install -U pip build wheel +${PREFIX}python -m pip install -r dev-requirements.txt set +x diff --git a/scripts/lint b/scripts/lint index 4fef19d..516e7ec 100755 --- a/scripts/lint +++ b/scripts/lint @@ -7,5 +7,5 @@ fi export SOURCE_FILES="kiss_headers tests" set -x -${PREFIX}black --diff --target-version=py36 $SOURCE_FILES -${PREFIX}isort --project=kiss_headers --recursive --apply $SOURCE_FILES +${PREFIX}black --target-version=py37 $SOURCE_FILES +${PREFIX}isort --project=kiss_headers $SOURCE_FILES diff --git a/setup.cfg b/setup.cfg index 6046732..131a4f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,3 @@ [flake8] ignore = W503, E203, B305, E501, F401, E128, E402, E731, F821, Q000 max-line-length = 88 - -[mypy] -disallow_untyped_defs = True - -[tool:isort] -combine_as_imports = True -force_grid_wrap = 0 -include_trailing_comma = True -known_first_party = kiss_headers,tests -line_length = 88 -multi_line_output = 3 - -[tool:pytest] -addopts = --cov=kiss_headers --doctest-modules --cov-report=term-missing -rxXs -doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS diff --git a/setup.py b/setup.py deleted file mode 100644 index e641df1..0000000 --- a/setup.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import io -import os - -from setuptools import find_packages, setup -from re import search - - -def get_version(): - with open("kiss_headers/version.py") as version_file: - return search( - r"""__version__\s+=\s+(['"])(?P.+?)\1""", version_file.read() - ).group("version") - - -# Package meta-data. -NAME = "kiss-headers" -DESCRIPTION = "Python package for object oriented headers, HTTP/1.1 style. Parser and serializer for http headers." -URL = "https://github.com/ousret/kiss-headers" -EMAIL = "ahmed.tahri@cloudnursery.dev" -AUTHOR = "Ahmed TAHRI @Ousret" -REQUIRES_PYTHON = ">=3.6" -VERSION = get_version() - -EXTRAS = {} - -here = os.path.abspath(os.path.dirname(__file__)) - -try: - with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: - long_description = "\n" + f.read() -except FileNotFoundError: - long_description = DESCRIPTION - -setup( - name=NAME, - version=VERSION, - description=DESCRIPTION, - long_description=long_description, - long_description_content_type="text/markdown", - author=AUTHOR, - author_email=EMAIL, - python_requires=REQUIRES_PYTHON, - url=URL, - project_urls={ - "Documentation": "https://ousret.github.io/kiss-headers", - "Source": "https://github.com/Ousret/kiss-headers", - "Issue tracker": "https://github.com/Ousret/kiss-headers/issues", - }, - keywords=["headers", "http", "mail", "text", "imap", "header", "https", "imap4"], - packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), - package_data={"kiss_headers": ["py.typed"]}, - install_requires=[], # We shall not require anything. This will remain the same. - extras_require=EXTRAS, - include_package_data=True, - license="MIT", - classifiers=[ - "License :: OSI Approved :: MIT License", - "Intended Audience :: Developers", - "Development Status :: 5 - Production/Stable", - "Topic :: Communications :: Email", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", - "Environment :: Web Environment", - "Topic :: Software Development :: Libraries :: Python Modules", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Utilities", - "Programming Language :: Python :: Implementation :: PyPy", - ], -) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_attributes.py b/tests/test_attributes.py index 5b0f0f0..3d214ef 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -24,7 +24,6 @@ def test_eq(self): self.assertNotEqual(attr_a, attr_e) def test_esc_double_quote(self): - with self.subTest( "Ensure that the double quote character is handled correctly." ): diff --git a/tests/test_builder.py b/tests/test_builder.py index 72cf34d..4f33035 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -18,19 +18,16 @@ class MyBuilderTestCase(unittest.TestCase): def test_custom_header_expect(self): - with self.assertRaises(NotImplementedError): k = CustomHeader("Should absolutely not work !") def test_content_type(self): - self.assertEqual( repr(ContentType("application/json", charset="utf-8")), 'Content-Type: application/json; charset="UTF-8"', ) def test_set_cookie(self): - dt = datetime.now() self.assertEqual( @@ -41,18 +38,15 @@ def test_set_cookie(self): ) def test_content_length(self): - self.assertEqual(repr(ContentLength(1881)), "Content-Length: 1881") def test_content_disposition(self): - self.assertEqual( repr(ContentDisposition("attachment", filename="test-file.json")), 'Content-Disposition: attachment; filename="test-file.json"', ) def test_value_error_builder(self): - with self.assertRaises(ValueError): CrossOriginResourcePolicy("policy-not-known-yet") @@ -66,7 +60,6 @@ def test_value_error_builder(self): Allow("DOES-NOT-EXIST-HTTP-VERB") def test_verify_always_gmt(self): - self.assertTrue(repr(Date(datetime.now())).endswith("GMT")) diff --git a/tests/test_builder_create.py b/tests/test_builder_create.py index 2037e97..601efde 100644 --- a/tests/test_builder_create.py +++ b/tests/test_builder_create.py @@ -62,7 +62,6 @@ class MyBuilderCreationTestCase(unittest.TestCase): def test_replicate_raw_from_objects_request(self): - headers = ( Host("developer.mozilla.org") + UserAgent( @@ -88,7 +87,6 @@ def test_replicate_raw_from_objects_request(self): self.assertEqual(parse_it(RAW_HEADERS_MOZILLA), headers) def test_replicate_raw_from_objects(self): - headers = ( Header("Accept-Ch", "DPR") + Header("Accept-Ch-Lifetime", "2592000") diff --git a/tests/test_explain.py b/tests/test_explain.py index 8be7423..03405b7 100644 --- a/tests/test_explain.py +++ b/tests/test_explain.py @@ -20,7 +20,6 @@ class MyExplainTestCase(unittest.TestCase): def test_explain_from_objects(self): - headers = ( Header("Accept-Ch", "DPR") + Header("Accept-Ch-Lifetime", "2592000") diff --git a/tests/test_header_operation.py b/tests/test_header_operation.py index 166f340..f57cc1c 100644 --- a/tests/test_header_operation.py +++ b/tests/test_header_operation.py @@ -27,7 +27,6 @@ def test_isub_adjective(self): self.assertEqual('charset="utf-8"', str(content_type)) def test_iadd_adjective(self): - content_type = Header("Content-Type", 'charset="utf-8"') self.assertNotIn("text/html", content_type) @@ -50,7 +49,6 @@ def test_subtract_adjective(self): self.assertEqual('charset="utf-8"', str(content_type)) def test_add_adjective(self): - content_type = Header("Content-Type", 'charset="utf-8"') self.assertNotIn("text/html", content_type) @@ -105,7 +103,6 @@ def test_complex_second_attr_removal(self): self.assertEqual('text/html; charset="utf-8"', str(content_type)) def test_simple_attr_add(self): - content_type = Header("Content-Type", 'text/html; charset="utf-8"') self.assertNotIn("format", content_type) @@ -119,7 +116,6 @@ def test_simple_attr_add(self): self.assertEqual('text/html; charset="utf-8"; format="flowed"', content_type) def test_contain_space_delimiter(self): - authorization = Header("Authorization", "Bearer mysupersecrettoken") self.assertIn("Bearer", authorization) @@ -142,7 +138,6 @@ def test_illegal_delitem_operation(self): del content_type["text/html"] def test_attrs_access_case_insensitive(self): - content_type = Header("Content-Type", 'text/html; charset="utf-8"') with self.subTest("Verify that attrs can be accessed no matter case"): diff --git a/tests/test_header_order.py b/tests/test_header_order.py index 4c9bfdc..0655ba3 100644 --- a/tests/test_header_order.py +++ b/tests/test_header_order.py @@ -17,7 +17,6 @@ def test_insertion_in_ordered_header(self): self.assertEqual(["a", "b", "ppp", "h", "h", "z"], header.attrs) def test_pop_in_ordered_header(self): - header = Header("Content-Type", "a; b=k; h; h; z=0") key, value = header.pop(2) diff --git a/tests/test_headers.py b/tests/test_headers.py index bd7ab39..43f9363 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -4,8 +4,17 @@ class MyKissHeaderTest(unittest.TestCase): - def test_invalid_eq(self): + def test_json_header(self): + header = Header( + "Report-To", + '{"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v3?s=zhnRFonTa%2FAOS8x%2BTZXZBDr5B0k7E7rziICyCRD5SVCtz106%2FlbzKGHKLwXpAC1XUZ5Nb5SQBuoI336MkB%2BzhxjictL4oc6wjku7aFfGBCi5rMi5Z5LPYVMDPUnhFDymbNiE7wc9"}],"group":"cf-nel","max_age":604800}', + ) + + assert "endpoints" in header + assert "group" in header + assert header.group == "cf-nel" + def test_invalid_eq(self): header = Header( "Message-ID", "<455DADE4FB733C4C8F62EB4CEB36D8DE05037EA94F@johndoe>" ) @@ -14,7 +23,6 @@ def test_invalid_eq(self): k = header == 1 def test_simple_eq(self): - self.assertEqual( Header( "Message-ID", "<455DADE4FB733C4C8F62EB4CEB36D8DE05037EA94F@johndoe>" @@ -49,7 +57,6 @@ def test_simple_eq(self): ) def test_attribute_access_exist(self): - self.assertIn( "charset", Header( @@ -84,7 +91,6 @@ def test_attribute_not_in(self): ) def test_access_attribute(self): - self.assertEqual( Header( "Content-Type", diff --git a/tests/test_headers_from_string.py b/tests/test_headers_from_string.py index 3aa57f1..9fa33ec 100644 --- a/tests/test_headers_from_string.py +++ b/tests/test_headers_from_string.py @@ -41,27 +41,23 @@ class MyKissHeadersFromStringTest(unittest.TestCase): - headers: Headers def setUp(self) -> None: MyKissHeadersFromStringTest.headers = parse_it(RAW_HEADERS) def test_decode_partials(self): - self.assertEqual( [("Subject", "pöstal")], decode_partials([("Subject", "=?iso-8859-1?q?p=F6stal?=")]), ) def test_bytes_headers(self): - self.assertEqual( MyKissHeadersFromStringTest.headers, parse_it(RAW_HEADERS.encode("utf-8")) ) def test_two_headers_eq(self): - self.assertEqual(MyKissHeadersFromStringTest.headers, parse_it(RAW_HEADERS)) self.assertNotEqual( @@ -69,7 +65,6 @@ def test_two_headers_eq(self): ) def test_headers_get_has(self): - self.assertIsNone(MyKissHeadersFromStringTest.headers.get("received")) self.assertFalse(MyKissHeadersFromStringTest.headers.has("received")) @@ -78,7 +73,6 @@ def test_headers_get_has(self): ) def test_repr_dict(self): - dict_ = MyKissHeadersFromStringTest.headers.to_dict() self.assertIn("set-cookie", dict_) @@ -98,7 +92,6 @@ def test_repr_dict(self): ) def test_repr_str(self): - self.assertEqual(RAW_HEADERS, repr(MyKissHeadersFromStringTest.headers)) self.assertEqual(RAW_HEADERS, str(MyKissHeadersFromStringTest.headers)) @@ -113,7 +106,6 @@ def test_repr_str(self): ) def test_control_basis_exist(self): - self.assertEqual("DPR", MyKissHeadersFromStringTest.headers.accept_ch) self.assertEqual(3, len(MyKissHeadersFromStringTest.headers.set_cookie)) @@ -134,7 +126,6 @@ def test_control_basis_exist(self): ) def test_control_first_line_not_header(self): - headers = parse_it(RAW_HEADERS_MOZILLA) self.assertEqual(17, len(headers)) diff --git a/tests/test_polymorphic.py b/tests/test_polymorphic.py index ed50108..acbcd26 100644 --- a/tests/test_polymorphic.py +++ b/tests/test_polymorphic.py @@ -5,7 +5,6 @@ class MyPolymorphicTestCase(unittest.TestCase): def test_get_polymorphic(self): - headers = parse_it( """accept-ch: DPR accept-ch-lifetime: 2592000 diff --git a/tests/test_serializer.py b/tests/test_serializer.py index ee8ab98..013ab34 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -43,12 +43,10 @@ def test_encode_decode(self): ) def test_decode_failure(self): - with self.assertRaises(ValueError): decode({"Not Decodable": "X"}) # type: ignore def test_json_with_indent(self): - headers: Headers = parse_it(RAW_HEADERS) json_headers: str = dumps(headers, indent=4) diff --git a/tests/test_with_http_request.py b/tests/test_with_http_request.py index daab32f..1b3ab66 100644 --- a/tests/test_with_http_request.py +++ b/tests/test_with_http_request.py @@ -7,15 +7,12 @@ class MyHttpTestKissHeaders(unittest.TestCase): - HTTPBIN_GET: Optional[Response] = None def setUp(self) -> None: - MyHttpTestKissHeaders.HTTPBIN_GET = get("https://httpbin.org/get") def test_httpbin_raw_headers(self): - headers = parse_it( """Host: developer.mozilla.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0 @@ -47,7 +44,6 @@ def test_httpbin_raw_headers(self): self.assertEqual("0", headers.cache_control.max_age) def test_parse_response(self): - headers = parse_it(MyHttpTestKissHeaders.HTTPBIN_GET) self.assertEqual(headers.content_type, "application/json") @@ -60,7 +56,6 @@ def test_parse_response(self): headers.user_agent def test_httpbin_get(self): - response_headers = MyHttpTestKissHeaders.HTTPBIN_GET.headers headers = parse_it(response_headers) @@ -74,7 +69,6 @@ def test_httpbin_get(self): headers.user_agent def test_httpbin_freeform(self): - response_headers = get( "https://httpbin.org/response-headers", params={ @@ -98,7 +92,6 @@ def test_httpbin_freeform(self): self.assertIn("application/kiss", headers.freeform) def test_httpbin_with_our_headers(self): - response = get( "https://httpbin.org/bearer", headers=Headers(Authorization("Bearer", "qwerty")),