diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index d10eeaa..166c030 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -55,7 +55,7 @@ pipeline { axes { axis { name 'VERSION' - values '2.7', '3.6', '3.7', '3.8' + values '3.6', '3.7', '3.8', '3.9', '3.10' } } stages { @@ -88,7 +88,7 @@ pipeline { } def python(Map v = [:], body) { - def dockerImage = v.version.equals('2.7') ? 'python:3.7' : "python:${v.version}" + def dockerImage = "python:${v.version}" // If dockerhub got issues then let's retry twice retry(2) { sleep 5 diff --git a/ecs_logging/_stdlib.py b/ecs_logging/_stdlib.py index 5905e86..c1254af 100644 --- a/ecs_logging/_stdlib.py +++ b/ecs_logging/_stdlib.py @@ -111,14 +111,9 @@ def __init__( if validate is not None: # validate was introduced in py3.8 so we need to only provide it if the user provided it _kwargs["validate"] = validate - if sys.version_info < (3, 0): # Different args in py2.7 - super(StdlibFormatter, self).__init__( # type: ignore[call-arg] - fmt=fmt, datefmt=datefmt - ) - else: - super(StdlibFormatter, self).__init__( # type: ignore[call-arg] - fmt=fmt, datefmt=datefmt, style=style, **_kwargs - ) + super().__init__( # type: ignore[call-arg] + fmt=fmt, datefmt=datefmt, style=style, **_kwargs + ) if stack_trace_limit is not None: if not isinstance(stack_trace_limit, int): diff --git a/ecs_logging/_utils.py b/ecs_logging/_utils.py index 244986f..be55e87 100644 --- a/ecs_logging/_utils.py +++ b/ecs_logging/_utils.py @@ -62,17 +62,15 @@ def flatten_dict(value): for key, val in value.items(): if not isinstance(val, collections_abc.Mapping): if key in top_level: - raise ValueError( - "Duplicate entry for '%s' with different nesting" % key - ) + raise ValueError(f"Duplicate entry for '{key}' with different nesting") top_level[key] = val else: val = flatten_dict(val) for vkey, vval in val.items(): - vkey = "%s.%s" % (key, vkey) + vkey = f"{key}.{vkey}" if vkey in top_level: raise ValueError( - "Duplicate entry for '%s' with different nesting" % vkey + f"Duplicate entry for '{vkey}' with different nesting" ) top_level[vkey] = vval @@ -163,16 +161,9 @@ def json_dumps(value): # Need to call json.dumps() on values just in # case the given values aren't strings (even though # they should be according to the spec) - ordered_json = ",".join( - '"%s":%s' - % ( - k, - json_dumps(v), - ) - for k, v in ordered_fields - ) + ordered_json = ",".join(f'"{k}":{json_dumps(v)}' for k, v in ordered_fields) if value: - return "{%s,%s" % ( + return "{{{},{}".format( ordered_json, json_dumps(value)[1:], ) diff --git a/noxfile.py b/noxfile.py index e5e30a1..a0a7082 100644 --- a/noxfile.py +++ b/noxfile.py @@ -32,11 +32,11 @@ def tests_impl(session): "--junitxml=junit-test.xml", "--cov=ecs_logging", *(session.posargs or ("tests/",)), - env={"PYTHONWARNINGS": "always::DeprecationWarning"} + env={"PYTHONWARNINGS": "always::DeprecationWarning"}, ) -@nox.session(python=["2.7", "3.6", "3.7", "3.8"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) def test(session): tests_impl(session) @@ -44,7 +44,7 @@ def test(session): @nox.session() def blacken(session): session.install("black") - session.run("black", "--target-version=py27", *SOURCE_FILES) + session.run("black", "--target-version=py36", *SOURCE_FILES) lint(session) @@ -52,7 +52,7 @@ def blacken(session): @nox.session def lint(session): session.install("flake8", "black", "mypy") - session.run("black", "--check", "--target-version=py27", *SOURCE_FILES) + session.run("black", "--check", "--target-version=py36", *SOURCE_FILES) session.run("flake8", "--ignore=E501,W503", *SOURCE_FILES) session.run( "mypy", diff --git a/pyproject.toml b/pyproject.toml index 4dd2450..5c97f24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -26,7 +25,7 @@ classifiers = [ requires = [ "backports.functools-lru-cache; python_version < '3.3'" ] -requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +requires-python = ">=3.6" [tool.flit.metadata.requires-extra] develop = [ diff --git a/tests/compat.py b/tests/compat.py deleted file mode 100644 index 73e03e7..0000000 --- a/tests/compat.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. licenses this file to you under -# the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import sys - -if sys.version_info >= (3,): - from io import StringIO -else: - from io import BytesIO as StringIO - -__all__ = ["StringIO"] diff --git a/tests/conftest.py b/tests/conftest.py index 45d6873..271668b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,15 +30,9 @@ class ValidationError(Exception): pass -if sys.version_info[0] >= 3: - basestring = str - - @pytest.fixture def spec_validator(): - with open( - os.path.join(os.path.dirname(__file__), "resources", "spec.json"), "r" - ) as fh: + with open(os.path.join(os.path.dirname(__file__), "resources", "spec.json")) as fh: spec = json.load(fh) def validator(data_json): @@ -63,13 +57,11 @@ def validator(data_json): if subval: found = True if not found: - raise ValidationError("Missing required key {}".format(k)) + raise ValidationError(f"Missing required key {k}") if k in data: - if v["type"] == "string" and not ( - isinstance(data[k], str) or isinstance(data[k], basestring) - ): + if v["type"] == "string" and not isinstance(data[k], str): raise ValidationError( - "Value {0} for key {1} should be string, is {2}".format( + "Value {} for key {} should be string, is {}".format( data[k], k, type(data[k]) ) ) @@ -78,12 +70,12 @@ def validator(data_json): datetime.datetime.strptime(data[k], "%Y-%m-%dT%H:%M:%S.%fZ") except ValueError: raise ValidationError( - "Value {0} for key {1} doesn't parse as an ISO datetime".format( + "Value {} for key {} doesn't parse as an ISO datetime".format( data[k], k ) ) if v.get("index") and list(data.keys())[v.get("index")] != k: - raise ValidationError("Key {0} is not at index {1}".format(k, index)) + raise ValidationError(f"Key {k} is not at index {index}") return data_json @@ -92,12 +84,8 @@ def validator(data_json): @pytest.fixture def apm(): - if sys.version_info < (3, 6): - pytest.skip("elasticapm only supports python 3.6+") - if sys.version_info[0] >= 3: - record_factory = logging.getLogRecordFactory() + record_factory = logging.getLogRecordFactory() apm = elasticapm.Client({"SERVICE_NAME": "apm-service", "DISABLE_SEND": True}) yield apm apm.close() - if sys.version_info[0] >= 3: - logging.setLogRecordFactory(record_factory) + logging.setLogRecordFactory(record_factory) diff --git a/tests/test_apm.py b/tests/test_apm.py index 2d8c8b4..493954d 100644 --- a/tests/test_apm.py +++ b/tests/test_apm.py @@ -24,7 +24,7 @@ import logging import structlog import pytest -from .compat import StringIO +from io import StringIO def test_elasticapm_structlog_log_correlation_ecs_fields(spec_validator, apm): diff --git a/tests/test_stdlib_formatter.py b/tests/test_stdlib_formatter.py index 2aca1b0..18cf555 100644 --- a/tests/test_stdlib_formatter.py +++ b/tests/test_stdlib_formatter.py @@ -17,23 +17,19 @@ import logging import logging.config -import mock +from unittest import mock import pytest import json import time import random import sys import ecs_logging -from .compat import StringIO - -requires_py3 = pytest.mark.skipif( - sys.version_info[0] < 3, reason="Test requires Python 3.x+" -) +from io import StringIO @pytest.fixture(scope="function") def logger(): - return logging.getLogger("test-logger-%f-%f" % (time.time(), random.random())) + return logging.getLogger(f"test-logger-{time.time():f}-{random.random():f}") def make_record(): @@ -78,7 +74,7 @@ def test_extra_global_is_merged(spec_validator): def test_can_be_overridden(spec_validator): class CustomFormatter(ecs_logging.StdlibFormatter): def format_to_ecs(self, record): - ecs_dict = super(CustomFormatter, self).format_to_ecs(record) + ecs_dict = super().format_to_ecs(record) ecs_dict["custom"] = "field" return ecs_dict @@ -311,7 +307,6 @@ def test_exclude_fields_type_and_values(): assert str(e.value) == "'exclude_fields' must be a sequence of strings" -@requires_py3 def test_stack_info(logger): stream = StringIO() handler = logging.StreamHandler(stream) @@ -327,7 +322,6 @@ def test_stack_info(logger): assert "test_stack_info" in error_stack_trace and __file__ in error_stack_trace -@requires_py3 @pytest.mark.parametrize("exclude_fields", [["error"], ["error.stack_trace"]]) def test_stack_info_excluded(logger, exclude_fields): stream = StringIO() diff --git a/tests/test_structlog_formatter.py b/tests/test_structlog_formatter.py index 26ebcac..75b6f22 100644 --- a/tests/test_structlog_formatter.py +++ b/tests/test_structlog_formatter.py @@ -17,8 +17,8 @@ import ecs_logging import structlog -import mock -from .compat import StringIO +from unittest import mock +from io import StringIO import pytest