Source code for ocdsmerge.merge

from typing import Any, Dict, List, Optional, Type

from ocdsmerge.flatten import Flattened, RuleOverrides, flatten, unflatten
from ocdsmerge.rules import MergeRules, Schema, get_merge_rules
from ocdsmerge.util import sorted_releases


[docs] class Merger:
[docs] def __init__( self, schema: Schema = None, merge_rules: Optional[MergeRules] = None, rule_overrides: Optional[RuleOverrides] = None, ): """ Initializes a reusable ``Merger`` instance for creating merged releases. :param schema: the release schema (if not provided, will default to the latest version of OCDS) :param merge_rules: the merge rules (if not provided, will determine the rules from the ``schema``) :param rule_overrides: any rule overrides, in which keys are field paths as tuples, and values are either ``ocdsmerge.APPEND`` or ``ocdsmerge.MERGE_BY_POSITION`` :type schema: dict or str """ if merge_rules is None: merge_rules = get_merge_rules(schema) if rule_overrides is None: rule_overrides = {} self.merge_rules = merge_rules self.rule_overrides = rule_overrides
[docs] def create_compiled_release(self, releases: List[Dict[str, Any]]) -> Dict[str, Any]: """ Merges a list of releases into a compiled release. """ return self._create_merged_release(CompiledRelease, releases)
[docs] def create_versioned_release(self, releases: List[Dict[str, Any]]) -> Dict[str, Any]: """ Merges a list of releases into a versioned release. """ return self._create_merged_release(VersionedRelease, releases)
def _create_merged_release(self, cls: Type["MergedRelease"], releases: List[Dict[str, Any]]) -> Dict[str, Any]: merged_release = cls(merge_rules=self.merge_rules, rule_overrides=self.rule_overrides) merged_release.extend(releases) return merged_release.asdict()
[docs] class MergedRelease: """ Whether the class is for merging versioned releases. """ versioned: Optional[bool] = None
[docs] def __init__( self, data: Optional[Dict[str, Any]] = None, schema: Schema = None, merge_rules: Optional[MergeRules] = None, rule_overrides: Optional[RuleOverrides] = None, ): """ Initializes a merged release. :param data: the latest copy of the merged release, if any :param schema: the release schema (if not provided, will default to the latest version of OCDS) :param merge_rules: the merge rules (if not provided, will determine the rules from the ``schema``) :param rule_overrides: any rule overrides, in which keys are field paths as tuples, and values are either ``ocdsmerge.APPEND`` or ``ocdsmerge.MERGE_BY_POSITION`` :type schema: dict or str """ if merge_rules is None: merge_rules = get_merge_rules(schema) if rule_overrides is None: rule_overrides = {} self.merge_rules = merge_rules self.rule_overrides = rule_overrides if data is None: self.data = {} else: self.data = flatten(data, self.merge_rules, self.rule_overrides, flattened={}, versioned=self.versioned)
[docs] def asdict(self) -> Dict[str, Any]: """ Returns the merged release as a dictionary. """ return unflatten(self.data)
[docs] def extend(self, releases: List[Dict[str, Any]]) -> None: """ Sorts and merges many releases into the merged release. """ for release in sorted_releases(releases): self.append(release)
[docs] def append(self, release: Dict[str, Any]) -> None: """ Merges one release into the merged release. """ release = release.copy() # Store the values of fields that set "omitWhenMerged": true. ocid = release.get('ocid') release_id = release.get('id') date = release.get('date') # Prior to OCDS 1.1.4, `tag` didn't set "omitWhenMerged": true. tag = release.pop('tag', None) flat = flatten(release, self.merge_rules, self.rule_overrides, flattened={}) self.flat_append(flat, ocid, release_id, date, tag)
[docs] def flat_append( self, flat: Flattened, ocid: Optional[str], release_id: Optional[str], date: Optional[str], tag: Optional[str] ) -> None: raise NotImplementedError('subclasses must implement flat_append()')
[docs] class CompiledRelease(MergedRelease): versioned = False
[docs] def __init__(self, data: Optional[Dict[str, Any]] = None, **kwargs): super().__init__(data, **kwargs) self.data[('tag',)] = ['compiled']
[docs] def flat_append( self, flat: Flattened, ocid: Optional[str], release_id: Optional[str], date: Optional[str], tag: Optional[str] ) -> None: # Add an `id` and `date`. self.data[('id',)] = f'{ocid}-{date}' self.data[('date',)] = date # In OCDS 1.0, `ocid` incorrectly sets "mergeStrategy": "ocdsOmit". self.data[('ocid',)] = ocid self.data.update(flat)
[docs] class VersionedRelease(MergedRelease): versioned = True
[docs] def flat_append( self, flat: Flattened, ocid: Optional[str], release_id: Optional[str], date: Optional[str], tag: Optional[str] ) -> None: # Don't version the OCID. flat.pop(('ocid',), None) self.data[('ocid',)] = ocid for key, value in flat.items(): # If key is not versioned, continue. If the value is unchanged, don't add it to the history. if key in self.data and (type(self.data[key]) is not list or value == self.data[key][-1]['value']): continue self.data.setdefault(key, []).append({ 'releaseID': release_id, 'releaseDate': date, 'releaseTag': tag, 'value': value, })