Skip to content

Commit

Permalink
Fix installed Python command, tests, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
jkbrzt committed Aug 27, 2023
1 parent 5b0126e commit e372eff
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 43 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test:

install: install-py install-js

release: release-pypi release-npm
release: release-py release-js

install-py:
poetry --version || python3 -m pip install poetry
Expand All @@ -20,11 +20,11 @@ install-py:
install-js:
npm install

release-pypi:
release-py:
poetry build
poetry run twine upload --verbose --repository=crosshash dist/*

release-npm:
release-js:
npm publish

# Default task to get a list of tasks when `make' is run without args.
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# `crosshash`

[![Build status](https://github.com/httpie/crosshash/workflows/test/badge.svg)](https://github.com/httpie/crosshash/actions)
[![PyPi](https://badge.fury.io/py/crosshash.svg)](https://pypi.python.org/pypi/crosshash)
[![NPM](https://badge.fury.io/js/crosshash.svg)](https://www.npmjs.com/package/crosshash)

https://badge.fury.io/js/crosshash.svg
Stable, cross-platform JSON serialization and hashing for Python and JavaScript.

## Motivation
Expand All @@ -10,7 +13,6 @@ To make it possible to compare and hash JSON objects in a stable way across plat

## Installation


### Python

https://pypi.org/project/crosshash
Expand Down Expand Up @@ -66,7 +68,7 @@ crosshash({'A': MAX_SAFE_INTEGER + 1})

#### CLI

You can invoke `crosshash.py` directly or use `python -m crosshash`. The package also installs two identical scripts `crosshash` and `crosshash.py` (the latter is useful when you want to ensure you’re invoking the Python implementation).
You can invoke `crosshash.py` directly or use `python -m crosshash`. The package also installs an executable `crosshash-py`.

```bash
python3 -m crosshash --json '{"B": 2, "C": [1, 2, 3], "A": 1}'
Expand Down Expand Up @@ -102,7 +104,7 @@ crosshash({A: Number.MAX_SAFE_INTEGER + 1})

#### CLI

You can invoke `crosshash.js` directly. The package also installs two identical scripts `crosshash` and `crosshash.js` (the latter is useful when you want to ensure you’re invoking the JavaScript implementation).
You can invoke `crosshash.js` directly. The package also installs an executable `crosshash-js`.

```bash
./crosshash.js --json '{"B": 2, "C": [1, 2, 3], "A": 1}'
Expand All @@ -123,6 +125,8 @@ To ensure consistency, the [test suite](./tests) invokes the Python and JavaScri

## Development

It should be fairly straightforward to add support for other languages.

```bash
git clone [email protected]:httpie/crosshash.git
cd ./crosshash
Expand Down
4 changes: 4 additions & 0 deletions crosshash.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const isSafeNumber = (value) => {

const main = () => {
const usage = `
crosshash — stable JSON serialization and hashing for Python and JavaScript
https://github.com/httpie/crosshash
Usage:
node crosshash.js --json '{"foo": "bar"}'
node crosshash.js --hash '{"foo": "bar"}'
Expand Down
13 changes: 7 additions & 6 deletions crosshash.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#!/usr/bin/env python3
"""
Stable JSON serialization and hashing for Python and JavaScript.
`crosshash` — stable JSON serialization and hashing for Python and JavaScript
<https://github.com/httpie/crosshash>
CLI usage:
python3 -m crosshash --json '{"foo": "bar"}'
python3 -m crosshash --hash '{"foo": "bar"}'
"""
import hashlib
import json
from textwrap import dedent
from typing import TypeAlias


Expand Down Expand Up @@ -85,11 +91,6 @@ def clean_float(value: float):


def main():
"""
Usage:
python3 -m crosshash --json '{"foo": "bar"}'
python3 -m crosshash --hash '{"foo": "bar"}'
"""
import sys
if len(sys.argv) != 3 or sys.argv[1] not in ('--json', '--hash'):
print(__doc__)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "crosshash",
"version": "0.3.1",
"version": "0.3.2",
"description": "Stable, cross-platform JSON serialization and hashing for Python and JavaScript.",
"homepage": "https://github.com/httpie/crosshash",
"license": "MIT",
Expand All @@ -11,8 +11,7 @@
"crosshash.d.ts"
],
"bin": {
"crosshash": "./crosshash.js",
"crosshash.js": "./crosshash.js"
"crosshash-js": "crosshash.js"
},
"author": "",
"dependencies": {
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "crosshash"
version = "0.3.1"
version = "0.3.2"
authors = ["Jakub Roztocil <[email protected]>"]
description = "Stable, cross-platform JSON serialization and hashing for Python and JavaScript."
license = "MIT"
Expand All @@ -11,8 +11,7 @@ packages = [{include = "crosshash.py"}]
include = ["LICENSE", "README.md"]

[tool.poetry.scripts]
crosshash = 'crosshash:main'
'crosshash.py' = 'crosshash:main'
crosshash-py = 'crosshash:main'

[tool.poetry.dependencies]
python = "^3.11"
Expand Down
13 changes: 12 additions & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# noinspection PyProtectedMember
from crosshash import ERROR_UNSAFE_NUMBER, JSON
from .cases import generate_cases, CASES_OK, CASES_UNSAFE_NUMBERS
from .utils import assert_all_equal, assert_all_fail
from .utils import assert_all_equal, assert_all_fail, IMPLEMENTATIONS, Implementation, get_command_output


@pytest.mark.parametrize('data', CASES_UNSAFE_NUMBERS)
Expand Down Expand Up @@ -33,3 +33,14 @@ def test_crossjson_generated(data: JSON):
@pytest.mark.parametrize('data', generate_cases())
def test_crosshash_generated(data: JSON):
assert_all_equal(data=data, output_format='--hash')


@pytest.mark.parametrize('imp', IMPLEMENTATIONS)
def test_cli_usage(imp: Implementation):
assert 'httpie/crosshash' in imp.run(expect_success=False)


def test_cli_installed_command():
# TODO: the same for crosshash.js
assert 'httpie/crosshash' in get_command_output(cmd=['crosshash-py'], expect_success=False)
assert 'httpie/crosshash' in get_command_output(cmd=['python3', '-m', 'crosshash'], expect_success=False)
63 changes: 41 additions & 22 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,46 @@
ROOT = Path(__file__).parent.parent.absolute()


IMPLEMENTATIONS: list[Path] = [
ROOT / 'crosshash.py',
ROOT / 'crosshash.js',
class Implementation:
def __init__(self, executable: Path):
self.executable = executable

def __str__(self):
return self.executable.name

def get_output(self, data: JSON, output_format='--hash', expect_success=True):
input_json = json.dumps(data)
return self.run(output_format, input_json, expect_success=expect_success)

def get_hash(self, data: JSON, expect_success=True):
return self.get_output(data=data, output_format='--hash', expect_success=expect_success)

def get_json(self, data: JSON, expect_success=True):
return self.get_output(data=data, output_format='--json', expect_success=expect_success)

def run(self, *args, expect_success=True):
return get_command_output(cmd=[self.executable, *args], expect_success=expect_success)


IMPLEMENTATIONS: list[Implementation] = [
Implementation(ROOT / 'crosshash.py'),
Implementation(ROOT / 'crosshash.js'),
]


def get_command_output(*, cmd: list[str], expect_success=True):
print(cmd)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output, _ = process.communicate()
output = output.decode().strip()
if expect_success:
if process.returncode:
raise Exception(output)
else:
assert process.returncode
return output


def assert_all_equal(data: JSON, output_format: str):
"""
Assert that all implementations return the same output.
Expand All @@ -25,8 +59,7 @@ def assert_all_equal(data: JSON, output_format: str):
"""
outputs = {
exe: get_output(data=shuffle_keys(data), exe=exe, output_format=output_format)
for exe in IMPLEMENTATIONS
imp: imp.get_output(data=shuffle_keys(data), output_format=output_format) for imp in IMPLEMENTATIONS
}
if len(set(outputs.values())) > 1:
for exe_a, exe_b in combinations(IMPLEMENTATIONS, 2):
Expand All @@ -39,29 +72,15 @@ def assert_all_equal(data: JSON, output_format: str):


def assert_all_fail(data: JSON, output_format: str, error_message: str):
for exe in IMPLEMENTATIONS:
for imp in IMPLEMENTATIONS:
print()
print(exe.name)
output = get_output(data=data, exe=exe, output_format=output_format, expect_success=False)
print(imp)
output = imp.get_output(data=data, output_format=output_format, expect_success=False)
print(output)
assert error_message in output
print()


def get_output(*, exe: Path, output_format: str, data: JSON, expect_success=True):
input_json = json.dumps(data)
cmd = [str(exe), output_format, input_json]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output, _ = process.communicate()
output = output.decode().strip()
if expect_success:
if process.returncode:
raise Exception(output)
else:
assert process.returncode
return output


def shuffle_keys(val):
if isinstance(val, dict):
keys = list(val.keys())
Expand Down

0 comments on commit e372eff

Please sign in to comment.