Skip to content

Commit

Permalink
Merge pull request #67 from corytodd/yaml-support
Browse files Browse the repository at this point in the history
Introduce YAML support
  • Loading branch information
fzumstein authored Jun 20, 2023
2 parents c0aab2a + 197b4ac commit a5d4bbe
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 17 deletions.
25 changes: 16 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,22 @@ Command Line Client

Usage::

jdiff [-h] [-p] [-s SYNTAX] [-i INDENT] first second
jdiff [-h] [-p] [-s {compact,symmetric,explicit}] [-i INDENT] [-f {json,yaml}] first second

positional arguments:
first
second
positional arguments:
first
second

optional arguments:
-h, --help show this help message and exit
-p, --patch
-s {compact,symmetric,explicit}, --syntax {compact,symmetric,explicit}
Diff syntax controls how differences are rendered (default: compact)
-i INDENT, --indent INDENT
Number of spaces to indent. None is compact, no indentation. (default: None)
-f {json,yaml}, --format {json,yaml}
Specify file format for input and dump (default: json)

optional arguments:
-h, --help show this help message and exit
-p, --patch
-s SYNTAX, --syntax SYNTAX
-i INDENT, --indent INDENT

Examples:

Expand All @@ -87,3 +92,5 @@ Examples:
$ jdiff a.json b.json -i 2
$ jdiff a.json b.json -i 2 -s symmetric
$ jdiff a.yaml b.yaml -f yaml -s symmetric
27 changes: 27 additions & 0 deletions jsondiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import sys
import json
import yaml

from .symbols import *
from .symbols import Symbol
Expand Down Expand Up @@ -35,6 +36,20 @@ def __call__(self, obj, dest=None):
default_dumper = JsonDumper()


class YamlDumper(object):
"""Write object as YAML string"""

def __init__(self, **kwargs):
self.kwargs = kwargs

def __call__(self, obj, dest=None):
"""Format obj as a YAML string and optionally write to dest
:param obj: dict to dump
:param dest: file-like object
:return: str
"""
return yaml.dump(obj, dest, **self.kwargs)

class JsonLoader(object):
def __init__(self, **kwargs):
self.kwargs = kwargs
Expand All @@ -49,6 +64,16 @@ def __call__(self, src):
default_loader = JsonLoader()


class YamlLoader(object):
"""Load YAML data from file-like object or string"""

def __call__(self, src):
"""Parse and return YAML data
:param src: str|file-like source
:return: dict parsed data
"""
return yaml.safe_load(src)

class JsonDiffSyntax(object):
def emit_set_diff(self, a, b, s, added, removed):
raise NotImplementedError()
Expand Down Expand Up @@ -641,4 +666,6 @@ def similarity(a, b, cls=JsonDiffer, **kwargs):
"JsonDiffer",
"JsonDumper",
"JsonLoader",
"YamlDumper",
"YamlLoader"
]
27 changes: 19 additions & 8 deletions jsondiff/cli.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import argparse
import jsondiff
import json
import warnings
import sys


def main():
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument("first")
parser.add_argument("second")
parser.add_argument("-p", "--patch", action="store_true", default=False)
parser.add_argument("-s", "--syntax", action="store", type=str, default="compact")
parser.add_argument("-i", "--indent", action="store", type=int, default=None)
parser.add_argument("-s", "--syntax", choices=(jsondiff.builtin_syntaxes.keys()), default="compact",
help="Diff syntax controls how differences are rendered")
parser.add_argument("-i", "--indent", action="store", type=int, default=None,
help="Number of spaces to indent. None is compact, no indentation.")
parser.add_argument("-f", "--format", choices=("json", "yaml"), default="json",
help="Specify file format for input and dump")

args = parser.parse_args()

# pyyaml _can_ load json but is ~20 times slower and has known issues so use
# the json from stdlib when json is specified.
serializers = {
"json": (jsondiff.JsonLoader(), jsondiff.JsonDumper(indent=args.indent)),
"yaml": (jsondiff.YamlLoader(), jsondiff.YamlDumper(indent=args.indent)),
}
loader, dumper = serializers[args.format]

with open(args.first, "r") as f:
with open(args.second, "r") as g:
jf = json.load(f)
jg = json.load(g)
jf = loader(f)
jg = loader(g)
if args.patch:
x = jsondiff.patch(
jf,
Expand All @@ -34,7 +45,7 @@ def main():
syntax=args.syntax
)

json.dump(x, sys.stdout, indent=args.indent)
dumper(x, sys.stdout)


if __name__ == '__main__':
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ classifiers = [
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
]
dependencies = [
"pyyaml"
]

[project.optional-dependencies]
test = [
Expand Down
6 changes: 6 additions & 0 deletions tests/data/test_01.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"hello": "world",
"data": [
1,2,3
]
}
5 changes: 5 additions & 0 deletions tests/data/test_01.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
hello: world
data:
- 1
- 2
- 3
110 changes: 110 additions & 0 deletions tests/test_jsondiff.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import io
import logging
import os.path
import sys
import unittest
import pytest

import jsondiff
from jsondiff import diff, replace, add, discard, insert, delete, update, JsonDiffer

from .utils import generate_random_json, perturbate_json
Expand Down Expand Up @@ -208,3 +211,110 @@ def test_right_only_syntax(self):
diff(c, d, syntax="rightonly", marshal=True),
[4, 5, 6],
)

class TestLoaders(unittest.TestCase):

here = os.path.dirname(__file__)
data_dir = os.path.join(here, "data")

def test_json_string_loader(self):
json_str = '{"hello": "world", "data": [1,2,3]}'
expected = {"hello": "world", "data": [1, 2, 3]}
loader = jsondiff.JsonLoader()
actual = loader(json_str)
self.assertEqual(expected, actual)

def test_json_file_loader(self):
json_file = os.path.join(TestLoaders.data_dir, "test_01.json")
expected = {"hello": "world", "data": [1, 2, 3]}
loader = jsondiff.JsonLoader()
with open(json_file) as f:
actual = loader(f)
self.assertEqual(expected, actual)

def test_yaml_string_loader(self):
json_str = """---
hello: world
data:
- 1
- 2
- 3
"""
expected = {"hello": "world", "data": [1, 2, 3]}
loader = jsondiff.YamlLoader()
actual = loader(json_str)
self.assertEqual(expected, actual)

def test_yaml_file_loader(self):
yaml_file = os.path.join(TestLoaders.data_dir, "test_01.yaml")
expected = {"hello": "world", "data": [1, 2, 3]}
loader = jsondiff.YamlLoader()
with open(yaml_file) as f:
actual = loader(f)
self.assertEqual(expected, actual)


class TestDumpers(unittest.TestCase):

def test_json_dump_string(self):
data = {"hello": "world", "data": [1, 2, 3]}
expected = '{"hello": "world", "data": [1, 2, 3]}'
dumper = jsondiff.JsonDumper()
actual = dumper(data)
self.assertEqual(expected, actual)

def test_json_dump_string_indented(self):
data = {"hello": "world", "data": [1, 2, 3]}
expected = """{
"hello": "world",
"data": [
1,
2,
3
]
}"""
dumper = jsondiff.JsonDumper(indent=4)
actual = dumper(data)
self.assertEqual(expected, actual)

def test_json_dump_string_fp(self):
data = {"hello": "world", "data": [1, 2, 3]}
expected = """{
"hello": "world",
"data": [
1,
2,
3
]
}"""
dumper = jsondiff.JsonDumper(indent=4)
buffer = io.StringIO()
dumper(data, buffer)
self.assertEqual(expected, buffer.getvalue())

def test_yaml_dump_string(self):
data = {"hello": "world", "data": [1, 2, 3]}
expected = """data:
- 1
- 2
- 3
hello: world
"""
# Sort keys just to make things deterministic
dumper = jsondiff.YamlDumper(sort_keys=True)
actual = dumper(data)
self.assertEqual(expected, actual)

def test_yaml_dump_string_fp(self):
data = {"hello": "world", "data": [1, 2, 3]}
expected = """data:
- 1
- 2
- 3
hello: world
"""
# Sort keys just to make things deterministic
dumper = jsondiff.YamlDumper(sort_keys=True)
buffer = io.StringIO()
dumper(data, buffer)
self.assertEqual(expected, buffer.getvalue())

0 comments on commit a5d4bbe

Please sign in to comment.