diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a50e7e3..eb7cc23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] - python-version: [3.8, 3.9, '3.10', '3.11', '3.12', pypy-3.10] + python-version: [3.9, '3.10', '3.11', '3.12', pypy-3.10] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/docs/changelog.rst b/docs/changelog.rst index 84433cc..d366087 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,22 @@ Changelog ========= +1.1.14 (Unreleased) +------------------- + +Changed +~~~~~~~ + +- Some arguments must be keyword arguments: + + - :meth:`ocdskit.mapping_sheet.mapping_sheet` + - :meth:`ocdskit.schema.add_validation_properties` + - :meth:`ocdskit.util.iterencode` + - :meth:`ocdskit.util.json_dump` + - :meth:`ocdskit.util.json_dumps` + +- Drop support for Python 3.8. + 1.1.13 (2024-05-08) ------------------- @@ -45,6 +61,7 @@ Added ~~~~~ - :meth:`ocdskit.util.is_linked_release` accepts a ``maximum_properties`` argument (default 3). +- Drop support for Python 3.7. 1.1.8 (2023-06-26) ------------------ @@ -91,7 +108,7 @@ New CLI options: Changed ~~~~~~~ -- Drop support for Python 3.6 (end-of-life 2021-12-23). +- Drop support for Python 3.6. Fixed ~~~~~ diff --git a/docs/conf.py b/docs/conf.py index da16b27..711fa6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,10 +9,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) + import os import sys diff --git a/ocdskit/__main__.py b/ocdskit/__main__.py index 90c1785..5333e95 100644 --- a/ocdskit/__main__.py +++ b/ocdskit/__main__.py @@ -43,7 +43,7 @@ def main(description='Open Contracting Data Standard CLI', modules=COMMAND_MODUL try: command = importlib.import_module(module).Command(subparsers) except ImportError as e: - logger.error('exception "%s" prevented loading of %s module', e, module) + logger.error('exception "%s" prevented loading of %s module', e, module) # noqa: TRY400 # UX else: subcommands[command.name] = command @@ -73,17 +73,14 @@ def main(description='Open Contracting Data Standard CLI', modules=COMMAND_MODUL parser.print_help() -def _showwarning(message, category, filename, lineno, file=None, line=None): +def _showwarning(message, category, filename, lineno, file=None, line=None): # noqa: ARG001 if file is None: file = sys.stderr print(message, file=file) def _raise_encoding_error(e, encoding): - if encoding and encoding.lower() == 'iso-8859-1': - suggestion = 'utf-8' - else: - suggestion = 'iso-8859-1' + suggestion = 'utf-8' if encoding and encoding.lower() == 'iso-8859-1' else 'iso-8859-1' raise CommandError(f'encoding error: {e}\nTry `--encoding {suggestion}`?') diff --git a/ocdskit/combine.py b/ocdskit/combine.py index 607aec9..f85bf84 100644 --- a/ocdskit/combine.py +++ b/ocdskit/combine.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import warnings -from typing import Union from ocdsextensionregistry import ProfileBuilder from ocdsmerge import Merger @@ -142,15 +143,16 @@ def combine_release_packages(packages, uri='', publisher=None, published_date='' def merge( data, uri: str = '', - publisher: Union[dict, None] = None, + publisher: dict | None = None, published_date: str = '', version: str = DEFAULT_VERSION, - schema: Union[dict, None] = None, + schema: dict | None = None, + *, return_versioned_release: bool = False, return_package: bool = False, use_linked_releases: bool = False, streaming: bool = False, - force_version: Union[str, None] = None, + force_version: str | None = None, ignore_version: bool = False, convert_exceptions_to_warnings: bool = False, ): diff --git a/ocdskit/commands/base.py b/ocdskit/commands/base.py index 94d0c56..5f9d88e 100644 --- a/ocdskit/commands/base.py +++ b/ocdskit/commands/base.py @@ -17,7 +17,7 @@ def read(self, buf_size): class BaseCommand(ABC): - kwargs = {} + kwargs = {} # noqa: RUF012 def __init__(self, subparsers): """ @@ -28,12 +28,12 @@ def __init__(self, subparsers): self.add_arguments() self.args = None - def add_base_arguments(self): + def add_base_arguments(self): # noqa: B027 # noop """ Adds default arguments to all commands. """ - def add_arguments(self): + def add_arguments(self): # noqa: B027 # noop """ Adds arguments specific to this command. """ @@ -63,7 +63,7 @@ def items(self, **kwargs): file = StandardInputReader(self.args.encoding) yield from ijson.items(file, self.prefix(), multiple_values=True, **kwargs) - def print(self, data, streaming=False): + def print(self, data, *, streaming=False): """ Prints JSON data. diff --git a/ocdskit/commands/compile.py b/ocdskit/commands/compile.py index 5eebb84..6cb15d3 100644 --- a/ocdskit/commands/compile.py +++ b/ocdskit/commands/compile.py @@ -11,8 +11,10 @@ class Command(OCDSCommand): name = 'compile' - help = 'reads release packages and individual releases from standard input, merges the releases by OCID, and ' \ - 'prints the compiled releases' + help = ( + 'reads release packages and individual releases from standard input, merges the releases by OCID, ' + 'and prints the compiled releases' + ) def add_arguments(self): self.add_argument('--schema', help='the URL or path of the patched release schema to use') diff --git a/ocdskit/commands/indent.py b/ocdskit/commands/indent.py index 7013c44..99533f9 100644 --- a/ocdskit/commands/indent.py +++ b/ocdskit/commands/indent.py @@ -40,4 +40,4 @@ def indent(self, path): json_dump(data, f, indent=self.args.indent, ensure_ascii=self.args.ascii) f.write('\n') except json.decoder.JSONDecodeError as e: - logger.error('%s is not valid JSON. (json.decoder.JSONDecodeError: %s)', path, e) + logger.error('%s is not valid JSON. (json.decoder.JSONDecodeError: %s)', path, e) # noqa: TRY400 # UX diff --git a/ocdskit/commands/mapping_sheet.py b/ocdskit/commands/mapping_sheet.py index 5cd48b9..c1226bb 100644 --- a/ocdskit/commands/mapping_sheet.py +++ b/ocdskit/commands/mapping_sheet.py @@ -15,7 +15,7 @@ class Command(BaseCommand): name = 'mapping-sheet' help = 'generates a spreadsheet with all field paths in a JSON Schema' - kwargs = { + kwargs = { # noqa: RUF012 'epilog': dedent( """ The --extension option must be declared after the file argument. It accepts multiple values, which can be diff --git a/ocdskit/commands/schema_report.py b/ocdskit/commands/schema_report.py index 7e32211..ab3cf9c 100644 --- a/ocdskit/commands/schema_report.py +++ b/ocdskit/commands/schema_report.py @@ -47,10 +47,7 @@ def recurse(data): recurse(item) elif isinstance(data, dict): if 'codelist' in data: - if 'openCodelist' in data: - open_codelist = data['openCodelist'] - else: - open_codelist = 'enum' not in data + open_codelist = data.get('openCodelist', 'enum' not in data) codelists[data['codelist']].add(open_codelist) for key, value in data.items(): diff --git a/ocdskit/commands/schema_strict.py b/ocdskit/commands/schema_strict.py index 8a5c36c..039b74b 100644 --- a/ocdskit/commands/schema_strict.py +++ b/ocdskit/commands/schema_strict.py @@ -9,8 +9,10 @@ class Command(BaseCommand): name = 'schema-strict' - help = 'adds "minItems" and "uniqueItems" if an array, "minProperties" if an object and "minLength" if a ' \ - 'string and "enum", "format" and "pattern" are not set' + help = ( + 'adds "minItems" and "uniqueItems" if an array, "minProperties" if an object and ' + '"minLength" if a string and "enum", "format" and "pattern" are not set' + ) def add_arguments(self): self.add_argument('file', help='the schema file') diff --git a/ocdskit/commands/set_closed_codelist_enums.py b/ocdskit/commands/set_closed_codelist_enums.py index a385364..6cceef6 100644 --- a/ocdskit/commands/set_closed_codelist_enums.py +++ b/ocdskit/commands/set_closed_codelist_enums.py @@ -42,10 +42,7 @@ def handle(self): self.collect_codelists(directory) self.update_json_schema(directory) - codelists_not_seen = [] - for codelist in self.codelists: - if codelist not in self.codelists_seen: - codelists_not_seen.append(codelist) + codelists_not_seen = [codelist for codelist in self.codelists if codelist not in self.codelists_seen] if codelists_not_seen: logger.error('unused codelists: %s', ' '.join(codelists_not_seen)) @@ -57,10 +54,7 @@ def collect_codelists(self, directory): with open(os.path.join(root, name)) as f: reader = csv.DictReader(f) row = next(reader) - if 'Code' in row: - codes = [row['Code'] for row in itertools.chain([row], reader)] - else: - codes = [] + codes = [row['Code'] for row in itertools.chain([row], reader)] if 'Code' in row else [] if codes: if name.startswith('+'): diff --git a/ocdskit/commands/upgrade.py b/ocdskit/commands/upgrade.py index d42f57c..2d226b7 100644 --- a/ocdskit/commands/upgrade.py +++ b/ocdskit/commands/upgrade.py @@ -16,10 +16,7 @@ def handle(self): versions = self.args.versions version_from, version_to = versions.split(':') - if version_from < version_to: - direction = 'up' - else: - direction = 'down' + direction = 'up' if version_from < version_to else 'down' try: upgrade_method = getattr(upgrade, f"upgrade_{versions.replace('.', '').replace(':', '_')}") diff --git a/ocdskit/mapping_sheet.py b/ocdskit/mapping_sheet.py index 4c2d980..c64f45e 100644 --- a/ocdskit/mapping_sheet.py +++ b/ocdskit/mapping_sheet.py @@ -10,7 +10,7 @@ INLINE_LINK_RE = re.compile(r'\[([^\]]+)\]\(([^)]+)\)') -def mapping_sheet(schema, order_by=None, infer_required=False, extension_field=None, inherit_extension=True, +def mapping_sheet(schema, *, order_by=None, infer_required=False, extension_field=None, inherit_extension=True, include_codelist=False, include_deprecated=True, include_definitions=False, base_uri=None): """ Returns information about all field paths in a JSON Schema, as columns and rows. @@ -112,7 +112,7 @@ def mapping_sheet(schema, order_by=None, infer_required=False, extension_field=N try: rows.sort(key=lambda row: row[order_by]) except KeyError as e: - raise MissingColumnError(f"the column '{order_by}' doesn't exist – did you make a typo?") from e + raise MissingColumnError(f"the column '{order_by}' doesn't exist - did you make a typo?") from e columns = ['section', 'path', 'title', 'description', 'type', 'range', 'values', 'links', 'deprecated', 'deprecationNotes'] diff --git a/ocdskit/packager.py b/ocdskit/packager.py index 54993cf..fb55175 100644 --- a/ocdskit/packager.py +++ b/ocdskit/packager.py @@ -1,12 +1,13 @@ +from __future__ import annotations + import itertools import os import warnings from abc import ABC, abstractmethod from collections import defaultdict from tempfile import NamedTemporaryFile -from typing import Union +from typing import TYPE_CHECKING -import ocdsmerge from ocdsmerge.exceptions import InconsistentTypeError from ocdskit.exceptions import InconsistentVersionError, MergeErrorWarning, MissingOcidKeyError @@ -21,6 +22,9 @@ jsonlib, ) +if TYPE_CHECKING: + import ocdsmerge + try: import sqlite3 @@ -54,7 +58,7 @@ class Packager: same version of OCDS. """ - def __init__(self, force_version: Union[str, None] = None): + def __init__(self, force_version: str | None = None): """ :param force_version: version to use instead of the version of the first release package or individual release """ @@ -72,7 +76,7 @@ def __enter__(self): def __exit__(self, type_, value, traceback): self.backend.close() - def add(self, data, ignore_version: bool = False): + def add(self, data, *, ignore_version: bool = False): """ Adds release packages and/or individual releases to be merged. @@ -116,6 +120,7 @@ def add(self, data, ignore_version: bool = False): def output_package( self, merger: ocdsmerge.merge.Merger, + *, return_versioned_release: bool = False, use_linked_releases: bool = False, streaming: bool = False, @@ -137,7 +142,7 @@ def output_package( convert_exceptions_to_warnings=convert_exceptions_to_warnings, ) - # If a user wants to stream data but can’t exhaust records right away, we can add an `autoclose=True` argument. + # If a user wants to stream data but can't exhaust records right away, we can add an `autoclose=True` argument. # If set to `False`, `__exit__` will do nothing, and the user will need to call `packager.backend.close()`. if not streaming: records = list(records) @@ -153,6 +158,7 @@ def output_package( def output_records( self, merger: ocdsmerge.merge.Merger, + *, return_versioned_release: bool = False, use_linked_releases: bool = False, convert_exceptions_to_warnings: bool = False, @@ -204,6 +210,7 @@ def output_records( def output_releases( self, merger: ocdsmerge.merge.Merger, + *, return_versioned_release: bool = False, convert_exceptions_to_warnings: bool = False, ): @@ -263,12 +270,12 @@ def get_releases_by_ocid(self): OCIDs are yielded in alphabetical order. The iterable is in any order. """ - def flush(self): + def flush(self): # noqa: B027 # noop """ Flushes the internal buffer of releases. This may be a no-op on some backends. """ - def close(self): + def close(self): # noqa: B027 # noop """ Tidies up any resources used by the backend. This may be a no-op on some backends. """ diff --git a/ocdskit/schema.py b/ocdskit/schema.py index 7ea0aef..b05d5d3 100644 --- a/ocdskit/schema.py +++ b/ocdskit/schema.py @@ -67,7 +67,7 @@ def definition_path(self): def __repr__(self): return repr(self.asdict()) - def asdict(self, sep=None, exclude=None): + def asdict(self, sep=None, exclude=()): """ Returns the field as a dict, with keys for: ``schema``, ``pointer``, ``path``, ``definition_pointer``, ``definition_path``, ``required``, ``deprecated``, ``multilingual``. @@ -75,22 +75,17 @@ def asdict(self, sep=None, exclude=None): :param list sep: the separator to use in string representations of paths, overriding ``self.sep`` :param list exclude: a list of keys to exclude from the dict """ - data = {} - - exclude = exclude or () sep = sep or self.sep - for key, value in self.__dict__.items(): - if key not in exclude and not key.startswith('_') and not key.endswith('_components'): - data[key] = value - for key in ('pointer', 'definition_pointer'): - if key not in exclude: - data[key] = getattr(self, key) - for key in ('path', 'definition_path'): - if key not in exclude: - data[key] = sep.join(getattr(self, f'{key}_components')) - - return data + return ( + { + k: v + for k, v in self.__dict__.items() + if k not in exclude and not k.startswith("_") and not k.endswith("_components") + } + | {k: getattr(self, k) for k in ("pointer", "definition_pointer") if k not in exclude} + | {k: sep.join(getattr(self, f"{k}_components")) for k in ("path", "definition_path") if k not in exclude} + ) # This code is similar to `add_versioned` in `make_versioned_release_schema.py` in the `standard` repository. @@ -121,7 +116,7 @@ def get_schema_fields(schema, pointer=None, path=None, definition_pointer=None, multilingual = set() hidden = set() - for key, value in schema.get('patternProperties', {}).items(): + for key in schema.get('patternProperties', {}): # The pattern might have an extra set of parentheses. for offset in (2, 1): end = -LANGUAGE_CODE_SUFFIX_LEN - offset @@ -136,21 +131,21 @@ def get_schema_fields(schema, pointer=None, path=None, definition_pointer=None, break for key, value in schema.get('properties', {}).items(): - new_pointer = pointer + ('properties', key) - new_path = path + (key,) + new_pointer = (*pointer, 'properties', key) + new_path = (*path, key) required = schema.get('required', []) yield from _get_schema_field(key, value, new_pointer, new_path, definition_pointer, definition_path, required, deprecated or _deprecated(value), multilingual) for key, value in schema.get('definitions', {}).items(): - new_pointer = pointer + ('definitions', key) + new_pointer = (*pointer, 'definitions', key) yield from get_schema_fields(value, pointer=new_pointer, path=(), definition_pointer=new_pointer, definition_path=(key,), deprecated=deprecated) for key, value in schema.get('patternProperties', {}).items(): if key not in hidden: - new_pointer = pointer + ('patternProperties', key) - new_path = path + (f'({key})',) + new_pointer = (*pointer, 'patternProperties', key) + new_path = (*path, f'({key})') yield Field(schema=value, pointer_components=new_pointer, path_components=new_path, definition_pointer_components=definition_pointer, definition_path_components=definition_path, required=False, deprecated=deprecated or _deprecated(value), multilingual=False) @@ -170,8 +165,8 @@ def _get_schema_field(name, schema, pointer, path, definition_pointer, definitio definition_path=definition_path, deprecated=deprecated) for key, value in schema.get('items', {}).get('properties', {}).items(): - new_pointer = pointer + ('items', 'properties', key) - new_path = path + (key,) + new_pointer = (*pointer, 'items', 'properties', key) + new_path = (*path, key) required = schema['items'].get('required', []) yield from _get_schema_field(key, value, new_pointer, new_path, definition_pointer, definition_path, required, deprecated or _deprecated(value), multilingual) @@ -183,7 +178,7 @@ def _deprecated(value): return value.get('deprecated') or hasattr(value, '__reference__') and value.__reference__.get('deprecated') or {} -def add_validation_properties(schema, unique_items=True, coordinates=False): +def add_validation_properties(schema, *, unique_items=True, coordinates=False): """ Adds "minItems" and "uniqueItems" if an array, adds "minProperties" if an object, and adds "minLength" if a string and if "enum", "format" and "pattern" aren't set. diff --git a/ocdskit/upgrade.py b/ocdskit/upgrade.py index dc55293..28a2940 100644 --- a/ocdskit/upgrade.py +++ b/ocdskit/upgrade.py @@ -139,10 +139,7 @@ def _get_parties(release): if 'parties' in release: for party in release['parties']: - if 'id' in party: - _id = party['id'] - else: - _id = _create_party_id(party) + _id = party['id'] if 'id' in party else _create_party_id(party) parties[_id] = party return parties @@ -195,13 +192,11 @@ def _create_party_id(party): parts = [] for parent, fields in organization_identification_1_0: if not parent: - for field in fields: - parts.append(_get_bytes(party, field)) + parts.extend(_get_bytes(party, field) for field in fields) elif parent in party: - for field in fields: - parts.append(_get_bytes(party[parent], field)) + parts.extend(_get_bytes(party[parent], field) for field in fields) - return md5(b'-'.join(parts)).hexdigest() + return md5(b'-'.join(parts)).hexdigest() # noqa: S324 # non-cryptographic def _get_bytes(obj, field): diff --git a/ocdskit/util.py b/ocdskit/util.py index 34bb6ee..da5ed73 100644 --- a/ocdskit/util.py +++ b/ocdskit/util.py @@ -61,7 +61,7 @@ def default(self, obj): return json.JSONEncoder.default(self, obj) -def iterencode(data, ensure_ascii=False, **kwargs): +def iterencode(data, *, ensure_ascii=False, **kwargs): """ Returns a generator that yields each string representation as available. """ @@ -70,7 +70,7 @@ def iterencode(data, ensure_ascii=False, **kwargs): return JSONEncoder(ensure_ascii=ensure_ascii, **kwargs).iterencode(data) -def json_dump(data, io, ensure_ascii=False, **kwargs): +def json_dump(data, io, *, ensure_ascii=False, **kwargs): """ Dumps JSON to a file-like object. """ @@ -79,7 +79,7 @@ def json_dump(data, io, ensure_ascii=False, **kwargs): json.dump(data, io, ensure_ascii=ensure_ascii, cls=JSONEncoder, **kwargs) -def json_dumps(data, ensure_ascii=False, indent=None, sort_keys=False, **kwargs): +def json_dumps(data, *, ensure_ascii=False, indent=None, sort_keys=False, **kwargs): """ Dumps JSON to a string, and returns it. """ @@ -127,8 +127,8 @@ def get_ocds_patch_tag(version): prefix = version.replace('.', '__') + '__' try: return next(tag for tag in reversed(get_tags()) if tag.startswith(prefix)) - except StopIteration: - raise UnknownVersionError(version) + except StopIteration as e: + raise UnknownVersionError(version) from e def is_package(data): @@ -250,11 +250,11 @@ def detect_format(path, root_path='', reader=open): metadata_count += 1 if not prefix and event not in ('end_array', 'end_map', 'map_key'): return _detect_format_result( - True, is_array, has_records, has_releases, has_ocid, has_tag, is_compiled, metadata_count + True, is_array, has_records, has_releases, has_ocid, has_tag, is_compiled, metadata_count # noqa: FBT003 ) return _detect_format_result( - False, is_array, has_records, has_releases, has_ocid, has_tag, is_compiled, metadata_count + False, is_array, has_records, has_releases, has_ocid, has_tag, is_compiled, metadata_count # noqa: FBT003 ) @@ -286,10 +286,7 @@ def _detect_format_result( elif metadata_count == 4: detected_format = Format.empty_package else: - if is_array: - infix = 'array' - else: - infix = 'object' + infix = 'array' if is_array else 'object' raise UnknownFormatError(f'top-level JSON value is a non-OCDS {infix}') return (detected_format, is_concatenated, is_array) diff --git a/pyproject.toml b/pyproject.toml index 938e86b..9f5f74d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ urls = {Homepage = "https://github.com/open-contracting/ocdskit"} classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/tests/__init__.py b/tests/__init__.py index 11b31d3..a5dbb7d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -21,7 +21,7 @@ def read(filename, mode='rt', **kwargs): return f.read() -def assert_equal(actual, expected, ordered=True): +def assert_equal(actual, expected, *, ordered=True): if ordered: assert actual == expected, ''.join(ndiff(expected.splitlines(1), actual.splitlines(1))) else: @@ -31,7 +31,7 @@ def assert_equal(actual, expected, ordered=True): def run_command(capsys, monkeypatch, main, args): - monkeypatch.setattr(sys, 'argv', ['ocdskit'] + args) + monkeypatch.setattr(sys, 'argv', ['ocdskit', *args]) main() return capsys.readouterr() @@ -39,8 +39,8 @@ def run_command(capsys, monkeypatch, main, args): # Similar to `run_command`, but with `pytest.raises` block. def assert_command_error(capsys, monkeypatch, main, args, expected='', error=SystemExit): + monkeypatch.setattr(sys, 'argv', ['ocdskit', *args]) with pytest.raises(error) as excinfo: - monkeypatch.setattr(sys, 'argv', ['ocdskit'] + args) main() actual = capsys.readouterr() @@ -52,7 +52,7 @@ def assert_command_error(capsys, monkeypatch, main, args, expected='', error=Sys return excinfo -def assert_command(capsys, monkeypatch, main, args, expected, ordered=True): +def assert_command(capsys, monkeypatch, main, args, expected, *, ordered=True): actual = run_command(capsys, monkeypatch, main, args) if os.path.isfile(path(expected)): @@ -66,7 +66,7 @@ def run_streaming(capsys, monkeypatch, main, args, stdin): stdin = b''.join(read(filename, 'rb') for filename in stdin) with patch('sys.stdin', TextIOWrapper(BytesIO(stdin))): - monkeypatch.setattr(sys, 'argv', ['ocdskit'] + args) + monkeypatch.setattr(sys, 'argv', ['ocdskit', *args]) main() return capsys.readouterr() @@ -77,10 +77,9 @@ def assert_streaming_error(capsys, monkeypatch, main, args, stdin, expected='', if not isinstance(stdin, bytes): stdin = b''.join(read(filename, 'rb') for filename in stdin) - with pytest.raises(error) as excinfo: - with patch('sys.stdin', TextIOWrapper(BytesIO(stdin))): - monkeypatch.setattr(sys, 'argv', ['ocdskit'] + args) - main() + monkeypatch.setattr(sys, 'argv', ['ocdskit', *args]) + with pytest.raises(error) as excinfo, patch('sys.stdin', TextIOWrapper(BytesIO(stdin))): + main() actual = capsys.readouterr() @@ -91,7 +90,7 @@ def assert_streaming_error(capsys, monkeypatch, main, args, stdin, expected='', return excinfo -def assert_streaming(capsys, monkeypatch, main, args, stdin, expected, ordered=True): +def assert_streaming(capsys, monkeypatch, main, args, stdin, expected, *, ordered=True): actual = run_streaming(capsys, monkeypatch, main, args, stdin) if not isinstance(expected, str): diff --git a/tests/commands/test_combine_record_packages.py b/tests/commands/test_combine_record_packages.py index ac2e99f..bfba2a9 100644 --- a/tests/commands/test_combine_record_packages.py +++ b/tests/commands/test_combine_record_packages.py @@ -50,7 +50,7 @@ def test_command_missing_field(capsys, monkeypatch): captured = capsys.readouterr() - assert captured.out == '{"uri":"","publisher":{"name":"Acme"},"publishedDate":"","version":"1.1","records":[]}\n' # noqa: E501 + assert captured.out == '{"uri":"","publisher":{"name":"Acme"},"publishedDate":"","version":"1.1","records":[]}\n' assert captured.err == 'item 0 has no "records" field (check that it is a record package)\n' diff --git a/tests/commands/test_combine_release_packages.py b/tests/commands/test_combine_release_packages.py index 7c28d62..5f1a40e 100644 --- a/tests/commands/test_combine_release_packages.py +++ b/tests/commands/test_combine_release_packages.py @@ -52,5 +52,5 @@ def test_command_missing_field(capsys, monkeypatch): captured = capsys.readouterr() - assert captured.out == '{"uri":"","publisher":{"name":"Acme"},"publishedDate":"","version":"1.1","releases":[]}\n' # noqa: E501 + assert captured.out == '{"uri":"","publisher":{"name":"Acme"},"publishedDate":"","version":"1.1","releases":[]}\n' assert captured.err == 'item 0 has no "releases" field (check that it is a release package)\n' diff --git a/tests/commands/test_compile.py b/tests/commands/test_compile.py index 78dccb9..51f6604 100644 --- a/tests/commands/test_compile.py +++ b/tests/commands/test_compile.py @@ -20,7 +20,7 @@ def _remove_package_metadata(filenames): # Test with packages and with releases. -def assert_compile_command(capsys, monkeypatch, main, args, stdin, expected, remove_package_metadata=False): +def assert_compile_command(capsys, monkeypatch, main, args, stdin, expected, *, remove_package_metadata=False): assert_streaming(capsys, monkeypatch, main, args, stdin, expected) args[args.index('compile') + 1:0] = ['--root-path', 'releases.item'] @@ -146,9 +146,11 @@ def test_command_version_mismatch(capsys, monkeypatch, caplog): assert len(caplog.records) == 1 assert caplog.records[0].levelname == 'CRITICAL' - assert caplog.records[0].message == "item 1: version error: this item uses version 1.0, but earlier items " \ - "used version 1.1\nTry first upgrading items to the same version:\n cat file [file ...] | ocdskit " \ - "upgrade 1.0:1.1 | ocdskit compile --package --versioned" + assert caplog.records[0].message == ( + "item 1: version error: this item uses version 1.0, but earlier items used version 1.1\n" + "Try first upgrading items to the same version:\n" + " cat file [file ...] | ocdskit upgrade 1.0:1.1 | ocdskit compile --package --versioned" + ) @pytest.mark.usefixtures('sqlite') @@ -181,5 +183,7 @@ def test_command_without_sqlite(capsys, monkeypatch, caplog): assert len(caplog.records) == 1 assert caplog.records[0].levelname == 'WARNING' - assert caplog.records[0].message == 'sqlite3 is unavailable, so the command will run in memory. If input files ' \ - 'are too large, the command might exceed available memory.' + assert caplog.records[0].message == ( + 'sqlite3 is unavailable, so the command will run in memory. ' + 'If input files are too large, the command might exceed available memory.' + ) diff --git a/tests/commands/test_detect_format.py b/tests/commands/test_detect_format.py index 370e7cc..1c25964 100644 --- a/tests/commands/test_detect_format.py +++ b/tests/commands/test_detect_format.py @@ -6,7 +6,7 @@ from tests import assert_command, assert_command_error, path, run_command -@pytest.mark.parametrize('filename,result', [ +@pytest.mark.parametrize(('filename', 'result'), [ ('record-package_minimal.json', 'record package'), ('release-package_minimal.json', 'release package'), ('record_minimal.json', 'record'), @@ -24,7 +24,7 @@ def test_command(filename, result, capsys, monkeypatch): assert_command(capsys, monkeypatch, main, ['detect-format', path(filename)], expected) -@pytest.mark.parametrize('filename,root_path,result', [ +@pytest.mark.parametrize(('filename', 'root_path', 'result'), [ ('record-package_minimal.json', 'records', 'a JSON array of records'), ('record-package_minimal.json', 'records.item', 'record'), ]) @@ -52,7 +52,7 @@ def test_command_recursive(capsys, monkeypatch, caplog, tmpdir): assert len(caplog.records) == 0 -@pytest.mark.parametrize('basename,result', [ +@pytest.mark.parametrize(('basename', 'result'), [ ('false', 'boolean'), ('null', 'null'), ('number', 'number'), diff --git a/tests/commands/test_echo.py b/tests/commands/test_echo.py index e1bad22..d95c3de 100644 --- a/tests/commands/test_echo.py +++ b/tests/commands/test_echo.py @@ -13,10 +13,9 @@ def test_help(capsys, monkeypatch, caplog): stdin = read('release-package_minimal.json', 'rb') - with pytest.raises(SystemExit) as excinfo: - with patch('sys.stdin', TextIOWrapper(BytesIO(stdin))): - monkeypatch.setattr(sys, 'argv', ['ocdskit', '--help']) - main() + monkeypatch.setattr(sys, 'argv', ['ocdskit', '--help']) + with pytest.raises(SystemExit) as excinfo, patch('sys.stdin', TextIOWrapper(BytesIO(stdin))): + main() assert capsys.readouterr().out.startswith('usage: ocdskit [-h] ') @@ -75,8 +74,8 @@ def test_command_array_input(capsys, monkeypatch): actual = run_streaming(capsys, monkeypatch, main, ['echo'], ['release-packages.json']) assert actual.out == ( - '{"uri":"http://example.com/id/1","publisher":{"name":"Acme"},"publishedDate":"2001-02-03T04:05:07Z","releases":[{"ocid":"ocds-213czf-1","id":"1","date":"2001-02-03T04:05:06Z","tag":["planning"],"initiationType":"tender"}],"version":"1.1"}\n' # noqa: E501 - '{"uri":"http://example.com/id/1","publisher":{"name":"Acme"},"publishedDate":"2001-02-03T04:05:07Z","releases":[{"ocid":"ocds-213czf-1","id":"1","date":"2001-02-03T04:05:06Z","tag":["planning"],"initiationType":"tender"}],"version":"1.1"}\n' # noqa: E501 + '{"uri":"http://example.com/id/1","publisher":{"name":"Acme"},"publishedDate":"2001-02-03T04:05:07Z","releases":[{"ocid":"ocds-213czf-1","id":"1","date":"2001-02-03T04:05:06Z","tag":["planning"],"initiationType":"tender"}],"version":"1.1"}\n' + '{"uri":"http://example.com/id/1","publisher":{"name":"Acme"},"publishedDate":"2001-02-03T04:05:07Z","releases":[{"ocid":"ocds-213czf-1","id":"1","date":"2001-02-03T04:05:06Z","tag":["planning"],"initiationType":"tender"}],"version":"1.1"}\n' ) diff --git a/tests/commands/test_mapping_sheet.py b/tests/commands/test_mapping_sheet.py index 481125f..96530e9 100644 --- a/tests/commands/test_mapping_sheet.py +++ b/tests/commands/test_mapping_sheet.py @@ -119,4 +119,4 @@ def test_command_order_by_nonexistent(capsys, monkeypatch, caplog): assert len(caplog.records) == 1 assert caplog.records[0].levelname == 'CRITICAL' - assert caplog.records[0].message == "the column 'nonexistent' doesn't exist – did you make a typo?" + assert caplog.records[0].message == "the column 'nonexistent' doesn't exist - did you make a typo?" diff --git a/tests/commands/test_schema_report.py b/tests/commands/test_schema_report.py index bd09f24..bac7b9c 100644 --- a/tests/commands/test_schema_report.py +++ b/tests/commands/test_schema_report.py @@ -6,13 +6,15 @@ def test_command(capsys, monkeypatch): actual = run_command(capsys, monkeypatch, main, ['schema-report', '--min-occurrences', '2', path('test-schema.json')]) - assert actual.out == 'codelist,openCodelist\n' \ - 'a.csv,False/True\n' \ - 'b.csv,False\n' \ - 'c.csv,False\n' \ - 'd.csv,False\n' \ - '\n' \ - " 2: {'codelist': 'a.csv', 'openCodelist': True, 'type': ['string', 'null']}\n" + assert actual.out == ( + 'codelist,openCodelist\n' + 'a.csv,False/True\n' + 'b.csv,False\n' + 'c.csv,False\n' + 'd.csv,False\n' + '\n' + " 2: {'codelist': 'a.csv', 'openCodelist': True, 'type': ['string', 'null']}\n" + ) def test_command_no_codelists(capsys, monkeypatch): diff --git a/tests/commands/test_schema_strict.py b/tests/commands/test_schema_strict.py index ba51a93..e9d2519 100644 --- a/tests/commands/test_schema_strict.py +++ b/tests/commands/test_schema_strict.py @@ -4,7 +4,7 @@ from ocdskit.__main__ import main from tests import path, run_command -expected = '''{ +expected = """{ "required": [ "array", "minItemsArray", @@ -83,7 +83,7 @@ } } } -''' +""" def test_command(capsys, monkeypatch, tmpdir): diff --git a/tests/commands/test_set_closed_codelist_enums.py b/tests/commands/test_set_closed_codelist_enums.py index 7f559b2..ac4e8a3 100644 --- a/tests/commands/test_set_closed_codelist_enums.py +++ b/tests/commands/test_set_closed_codelist_enums.py @@ -6,7 +6,7 @@ schema = read('test-schema.json') -schema_with_enum = '''{ +schema_with_enum = """{ "properties": { "closedStringNull": { "type": [ @@ -72,9 +72,9 @@ } } } -''' +""" -schema_with_modification = '''{ +schema_with_modification = """{ "properties": { "closedStringNull": { "type": [ @@ -140,7 +140,7 @@ } } } -''' +""" codelist = 'Code\nfoo\nbar\n' diff --git a/tests/commands/test_upgrade.py b/tests/commands/test_upgrade.py index 05e614c..87c2628 100644 --- a/tests/commands/test_upgrade.py +++ b/tests/commands/test_upgrade.py @@ -53,21 +53,15 @@ def test_command_release_tenderers_amendment(capsys, monkeypatch, caplog): ) -@pytest.mark.parametrize('pointer', ('parties', 'buyer', 'tender', 'tender/procuringEntity', 'tender/tenderers', +@pytest.mark.parametrize('pointer', ['parties', 'buyer', 'tender', 'tender/procuringEntity', 'tender/tenderers', 'awards', 'awards/0/suppliers', 'contracts', 'contracts/0/implementation', - 'contracts/0/implementation/transactions')) + 'contracts/0/implementation/transactions']) def test_command_release_field_is_null(pointer, capsys, monkeypatch, caplog): data = json.loads(read('release_minimal.json')) parts = pointer.split('/') - for i, part in enumerate(parts, 1): - if i < len(parts): - if parts[i] == '0': - value = [{}] - else: - value = {} - else: - value = None + for i, _ in enumerate(parts, 1): + value = ([{}] if parts[i] == '0' else {}) if i < len(parts) else None set_pointer(data, '/' + '/'.join(parts[:i]), value) stdin = json.dumps(data).encode('utf-8') diff --git a/tests/test_combine.py b/tests/test_combine.py index 3b737c5..d6a1a85 100644 --- a/tests/test_combine.py +++ b/tests/test_combine.py @@ -5,7 +5,11 @@ from ocdsmerge.exceptions import DuplicateIdValueWarning, InconsistentTypeError from ocdskit.combine import compile_release_packages, merge, package_records -from ocdskit.exceptions import InconsistentVersionError, MergeErrorWarning, UnknownVersionError +from ocdskit.exceptions import ( + InconsistentVersionError, + MergeErrorWarning, + UnknownVersionError, +) from tests import read inconsistent = [{ @@ -41,14 +45,14 @@ def test_merge_with_schema(): schema = builder.patched_release_schema() data = json.loads(read('release-package_additional-contact-points.json'))['releases'] - compiled_release = list(merge(data, schema=schema))[0] + compiled_release = next(iter(merge(data, schema=schema))) assert compiled_release == json.loads(read('compile_extensions.json')) def test_merge_without_schema(): data = json.loads(read('release-package_additional-contact-points.json'))['releases'] - compiled_release = list(merge(data))[0] + compiled_release = next(iter(merge(data))) assert compiled_release == json.loads(read('compile_no-extensions.json')) @@ -57,7 +61,7 @@ def test_merge_warning(): data = json.loads(read('release-package_warning.json'))['releases'] with pytest.warns(DuplicateIdValueWarning) as records: - compiled_release = list(merge(data))[0] + compiled_release = next(iter(merge(data))) assert compiled_release == json.loads(read('compile_warning.json')) @@ -108,7 +112,7 @@ def data(): list(merge(data(), return_package=return_package)) -@pytest.mark.parametrize('return_package,expected', [ +@pytest.mark.parametrize(("return_package", "expected"), [ ( True, [ diff --git a/tests/test_util.py b/tests/test_util.py index c08d06e..75b732d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -19,7 +19,7 @@ # Same fixture files as in test_detect_format.py, except for concatenated JSON files. -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', True), ('release-package_minimal.json', True), ('record_minimal.json', False), @@ -33,7 +33,7 @@ def test_is_package(filename, expected): assert is_package(json.loads(read(filename))) == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', True), ('release-package_minimal.json', False), ('record_minimal.json', False), @@ -47,7 +47,7 @@ def test_is_record_package(filename, expected): assert is_record_package(json.loads(read(filename))) == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', False), ('release-package_minimal.json', False), ('record_minimal.json', True), @@ -61,7 +61,7 @@ def test_is_record(filename, expected): assert is_record(json.loads(read(filename))) == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', False), ('release-package_minimal.json', True), ('record_minimal.json', False), @@ -75,7 +75,7 @@ def test_is_release_package(filename, expected): assert is_release_package(json.loads(read(filename))) == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', False), ('release-package_minimal.json', False), ('record_minimal.json', False), @@ -89,7 +89,7 @@ def test_is_release(filename, expected): assert is_release(json.loads(read(filename))) == expected -@pytest.mark.parametrize('data,expected', [ +@pytest.mark.parametrize(('data', 'expected'), [ ({'url': 'http://example.com', 'date': '2001-02-03T04:05:06Z', 'tag': ['compiled']}, True), ({'url': 'http://example.com', 'date': '2001-02-03T04:05:06Z', 'tag': ['tender']}, False), ({'url': 'http://example.com', 'date': '2001-02-03T04:05:06Z', 'tag': None}, False), @@ -99,7 +99,7 @@ def test_is_compiled_release(data, expected): assert is_compiled_release(data) == expected -@pytest.mark.parametrize('data,expected', [ +@pytest.mark.parametrize(('data', 'expected'), [ ({'url': 'http://example.com'}, True), ({'url': 'http://example.com', 'date': '2001-02-03T04:05:06Z'}, True), ({'url': 'http://example.com', 'date': '2001-02-03T04:05:06Z', 'tag': ['tender']}, True), @@ -109,7 +109,7 @@ def test_is_linked_release(data, expected): assert is_linked_release(data) == expected -@pytest.mark.parametrize('data,expected', [ +@pytest.mark.parametrize(('data', 'expected'), [ ({'records': [], 'version': '99.99'}, '99.99'), ({'records': []}, '1.0'), ({'releases': [], 'version': '99.99'}, '99.99'), @@ -124,7 +124,7 @@ def test_get_ocds_minor_version(data, expected): assert get_ocds_minor_version(data) == expected -@pytest.mark.parametrize('data,expected', [ +@pytest.mark.parametrize(('data', 'expected'), [ (iter([]), '[]'), ({'a': 1, 'b': 2}, '{"a":1,"b":2}'), ]) @@ -137,7 +137,7 @@ def test_json_dump(data, expected, tmpdir): assert p.read() == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('record-package_minimal.json', ('record package', False, False)), ('release-package_minimal.json', ('release package', False, False)), ('record_minimal.json', ('record', False, False)), @@ -156,7 +156,7 @@ def test_detect_format(filename, expected): assert result == expected -@pytest.mark.parametrize('filename,expected', [ +@pytest.mark.parametrize(('filename', 'expected'), [ ('ocds-sample-data.json.gz', ('release package', False, False)) ]) def test_detect_format_gz(filename, expected):