Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release #229

Merged
merged 10 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.8'

Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
git push --tags

- name: Checkout develop branch
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: 'develop'
fetch-depth: 0
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ jobs:
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -55,8 +55,9 @@ jobs:

- name: Upload coverage to Codecov
if: matrix.python-version == 3.8 && success()
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
flags: unittests
name: codecov-client-reportportal
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]
### Added
- `is_binary` method in `helpers` module, by @HardNorth
- `guess_content_type_from_bytes` method in `helpers` module, by @HardNorth

## [5.5.4]
### Added
- Issue [#225](https://github.com/reportportal/client-Python/issues/225): JSON decoding error logging, by @HardNorth
### Fixed
- Issue [#226](https://github.com/reportportal/client-Python/issues/226): Logging batch flush on client close, by @HardNorth
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Python versions](https://img.shields.io/pypi/pyversions/reportportal-client.svg)](https://pypi.org/project/reportportal-client)
[![Build Status](https://github.com/reportportal/client-Python/actions/workflows/tests.yml/badge.svg)](https://github.com/reportportal/client-Python/actions/workflows/tests.yml)
[![codecov.io](https://codecov.io/gh/reportportal/client-Python/branch/develop/graph/badge.svg)](https://codecov.io/gh/reportportal/client-Python)
[![Join Slack chat!](https://slack.epmrpp.reportportal.io/badge.svg)](https://slack.epmrpp.reportportal.io/)
[![Join Slack chat!](https://img.shields.io/badge/slack-join-brightgreen.svg)](https://slack.epmrpp.reportportal.io/)
[![stackoverflow](https://img.shields.io/badge/reportportal-stackoverflow-orange.svg?style=flat)](http://stackoverflow.com/questions/tagged/reportportal)
[![Build with Love](https://img.shields.io/badge/build%20with-❤%EF%B8%8F%E2%80%8D-lightgrey.svg)](http://reportportal.io?style=flat)

Expand Down
6 changes: 2 additions & 4 deletions reportportal_client/_internal/static/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,14 @@ class Implementation(Interface):
def __call__(cls, *args, **kwargs):
"""Disable instantiation for the interface classes."""
if cls.__name__ in AbstractBaseClass._abc_registry:
raise TypeError("No instantiation allowed for Interface-Class"
" '{}'. Please inherit.".format(cls.__name__))
raise TypeError("No instantiation allowed for Interface-Class '{}'. Please inherit.".format(cls.__name__))

result = super(AbstractBaseClass, cls).__call__(*args, **kwargs)
return result

def __new__(mcs, name, bases, namespace):
"""Register instance of the implementation class."""
class_ = super(AbstractBaseClass, mcs).__new__(mcs, name,
bases, namespace)
class_ = super(AbstractBaseClass, mcs).__new__(mcs, name, bases, namespace)
if namespace.get("__metaclass__") is AbstractBaseClass:
mcs._abc_registry.append(name)
return class_
75 changes: 75 additions & 0 deletions reportportal_client/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,78 @@ async def await_if_necessary(obj: Optional[Any]) -> Optional[Any]:
elif asyncio.iscoroutinefunction(obj):
return await obj()
return obj


def is_binary(iterable: Union[bytes, bytearray, str]) -> bool:
"""Check if given iterable is binary.

:param iterable: iterable to check
:return: True if iterable contains binary bytes, False otherwise
"""
if isinstance(iterable, str):
byte_iterable = iterable.encode('utf-8')
else:
byte_iterable = iterable

if 0x00 in byte_iterable:
return True
return False


def guess_content_type_from_bytes(data: Union[bytes, bytearray, List[int]]) -> str:
"""Guess content type from bytes.

:param data: bytes or bytearray
:return: content type
"""
my_data = data
if isinstance(data, list):
my_data = bytes(my_data)

if len(my_data) >= 128:
my_data = my_data[:128]

if not is_binary(my_data):
return 'text/plain'

# images
if my_data.startswith(b'\xff\xd8\xff'):
return 'image/jpeg'
if my_data.startswith(b'\x89PNG\r\n\x1a\n'):
return 'image/png'
if my_data.startswith(b'GIF8'):
return 'image/gif'
if my_data.startswith(b'BM'):
return 'image/bmp'
if my_data.startswith(b'\x00\x00\x01\x00'):
return 'image/vnd.microsoft.icon'
if my_data.startswith(b'RIFF') and b'WEBP' in my_data:
return 'image/webp'

# audio
if my_data.startswith(b'ID3'):
return 'audio/mpeg'
if my_data.startswith(b'RIFF') and b'WAVE' in my_data:
return 'audio/wav'

# video
if my_data.startswith(b'\x00\x00\x01\xba'):
return 'video/mpeg'
if my_data.startswith(b'RIFF') and b'AVI LIST' in my_data:
return 'video/avi'
if my_data.startswith(b'\x1aE\xdf\xa3'):
return 'video/webm'

# archives
if my_data.startswith(b'PK\x03\x04'):
if my_data.startswith(b'PK\x03\x04\x14\x00\x08'):
return 'application/java-archive'
return 'application/zip'
if my_data.startswith(b'PK\x05\x06'):
return 'application/zip'

# office
if my_data.startswith(b'%PDF'):
return 'application/pdf'

return 'application/octet-stream'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from setuptools import setup, find_packages

__version__ = '5.5.4'
__version__ = '5.5.5'

TYPE_STUBS = ['*.pyi']

Expand Down
Binary file added test_res/files/demo.zip
Binary file not shown.
Binary file added test_res/files/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions test_res/files/simple.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
HTTP/1.1 407 Proxy Authentication Required
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Content-Length: 0
Binary file added test_res/files/test.bin
Binary file not shown.
Binary file added test_res/files/test.jar
Binary file not shown.
Binary file added test_res/files/test.pdf
Binary file not shown.
Binary file added test_res/pug/lucky.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test_res/pug/unlucky.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from reportportal_client.helpers import (
gen_attributes,
get_launch_sys_attrs,
verify_value_length, ATTRIBUTE_LENGTH_LIMIT, TRUNCATE_REPLACEMENT
verify_value_length, ATTRIBUTE_LENGTH_LIMIT, TRUNCATE_REPLACEMENT, guess_content_type_from_bytes, is_binary
)


Expand Down Expand Up @@ -94,3 +94,43 @@ def test_verify_value_length(attributes, expected_attributes):
assert element.get('key') == expected.get('key')
assert element.get('value') == expected.get('value')
assert element.get('system') == expected.get('system')


@pytest.mark.parametrize(
'file, expected_is_binary',
[
('test_res/pug/lucky.jpg', True),
('test_res/pug/unlucky.jpg', True),
('test_res/files/image.png', True),
('test_res/files/demo.zip', True),
('test_res/files/test.jar', True),
('test_res/files/test.pdf', True),
('test_res/files/test.bin', True),
('test_res/files/simple.txt', False),
]
)
def test_binary_content_detection(file, expected_is_binary):
"""Test for validate binary content detection."""
with open(file, 'rb') as f:
content = f.read()
assert is_binary(content) == expected_is_binary


@pytest.mark.parametrize(
'file, expected_type',
[
('test_res/pug/lucky.jpg', 'image/jpeg'),
('test_res/pug/unlucky.jpg', 'image/jpeg'),
('test_res/files/image.png', 'image/png'),
('test_res/files/demo.zip', 'application/zip'),
('test_res/files/test.jar', 'application/java-archive'),
('test_res/files/test.pdf', 'application/pdf'),
('test_res/files/test.bin', 'application/octet-stream'),
('test_res/files/simple.txt', 'text/plain'),
]
)
def test_binary_content_type_detection(file, expected_type):
"""Test for validate binary content type detection."""
with open(file, 'rb') as f:
content = f.read()
assert guess_content_type_from_bytes(content) == expected_type