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

rfctr: prepare for adding metadata.orig_elements field #2647

Merged
merged 3 commits into from
Mar 14, 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: 1 addition & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ repos:
hooks:
- id: ruff
args:
[
"--fix",
"--select=C4,COM,E,F,I,PLR0402,PT,SIM,UP015,UP018,UP032,UP034",
"--ignore=PT011,PT012,SIM117,COM812",
]
["--fix"]

- repo: https://github.com/pycqa/flake8
rev: 4.0.1
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 0.12.7-dev2
## 0.12.7-dev3

### Enhancements

Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ check-flake8-print:

.PHONY: check-ruff
check-ruff:
ruff . --select C4,COM,E,F,I,PLR0402,PT,SIM,UP015,UP018,UP032,UP034 --ignore COM812,PT011,PT012,SIM117
# -- ruff options are determined by pyproject.toml --
ruff .

.PHONY: check-autoflake
check-autoflake:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ select = [
]
ignore = [
"COM812", # -- over aggressively insists on trailing commas where not desireable --
"PT005", # -- flags mock fixtures with names intentionally matching private method name --
"PT011", # -- pytest.raises({exc}) too broad, use match param or more specific exception --
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change need to be replicated in in .pre-commit-config.yaml and Makefile? (note at the top)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, good call. I've removed those redundant explicit options definitions. Now they should be picked up from the pyproject.toml so it's the single source of truth on options.

"PT012", # -- pytest.raises() block should contain a single simple statement --
"SIM117", # -- merge `with` statements for context managers that have same scope --
Expand Down
164 changes: 163 additions & 1 deletion test_unstructured/unit_utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
"""Utilities that ease unit-testing."""

from __future__ import annotations

import datetime as dt
import difflib
import pathlib
from typing import List, Optional
from typing import Any, List, Optional
from unittest.mock import (
ANY,
MagicMock,
Mock,
PropertyMock,
call,
create_autospec,
mock_open,
patch,
)

from pytest import FixtureRequest, LogCaptureFixture # noqa: PT013

from unstructured.documents.elements import Element
from unstructured.staging.base import elements_from_json, elements_to_json

__all__ = (
"ANY",
"FixtureRequest",
"LogCaptureFixture",
"MagicMock",
"Mock",
"call",
"class_mock",
"function_mock",
"initializer_mock",
"instance_mock",
"method_mock",
"property_mock",
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that this needs to be changed, but isn't __all__ used to define what's imported when you use * to import, or is there another use?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is, but it also indicates what you are intentionally "exporting" and prevents ruff errors related to "xyz is imported but not used". That's why I have it here.


def assert_round_trips_through_JSON(elements: List[Element]) -> None:
"""Raises AssertionError if `elements -> JSON -> List[Element] -> JSON` are not equal.
Expand Down Expand Up @@ -54,3 +83,136 @@ def example_doc_path(file_name: str) -> str:
def parse_optional_datetime(datetime_str: Optional[str]) -> Optional[dt.datetime]:
"""Parse `datetime_str` to a datetime.datetime instance or None if `datetime_str` is None."""
return dt.datetime.fromisoformat(datetime_str) if datetime_str else None


# ------------------------------------------------------------------------------------------------
# MOCKING FIXTURES
# ------------------------------------------------------------------------------------------------
# These allow full-featured and type-safe mocks to be created simply by adding a unit-test
# fixture.
# ------------------------------------------------------------------------------------------------


def class_mock(
request: FixtureRequest, q_class_name: str, autospec: bool = True, **kwargs: Any
) -> Mock:
"""Return mock patching class with qualified name `q_class_name`.

The mock is autospec'ed based on the patched class unless the optional argument `autospec` is
set to False. Any other keyword arguments are passed through to Mock(). Patch is reversed after
calling test returns.
"""
_patch = patch(q_class_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def cls_attr_mock(
request: FixtureRequest,
cls: type,
attr_name: str,
name: str | None = None,
**kwargs: Any,
):
"""Return a mock for attribute `attr_name` on `cls`.

Patch is reversed after pytest uses it.
"""
name = request.fixturename if name is None else name
_patch = patch.object(cls, attr_name, name=name, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def function_mock(
request: FixtureRequest, q_function_name: str, autospec: bool = True, **kwargs: Any
):
"""Return mock patching function with qualified name `q_function_name`.

Patch is reversed after calling test returns.
"""
_patch = patch(q_function_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def initializer_mock(request: FixtureRequest, cls: type, autospec: bool = True, **kwargs: Any):
"""Return mock for __init__() method on `cls`.

The patch is reversed after pytest uses it.
"""
_patch = patch.object(cls, "__init__", autospec=autospec, return_value=None, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def instance_mock(
request: FixtureRequest,
cls: type,
name: str | None = None,
spec_set: bool = True,
**kwargs: Any,
):
"""Return a mock for an instance of `cls` that draws its spec from the class.

The mock does not allow new attributes to be set on the instance. If `name` is missing or
|None|, the name of the returned |Mock| instance is set to *request.fixturename*. Additional
keyword arguments are passed through to the Mock() call that creates the mock.
"""
name = name if name is not None else request.fixturename
return create_autospec(cls, _name=name, spec_set=spec_set, instance=True, **kwargs)


def loose_mock(request: FixtureRequest, name: str | None = None, **kwargs: Any):
"""Return a "loose" mock, meaning it has no spec to constrain calls on it.

Additional keyword arguments are passed through to Mock(). If called without a name, it is
assigned the name of the fixture.
"""
if name is None:
name = request.fixturename
return Mock(name=name, **kwargs)


def method_mock(
request: FixtureRequest,
cls: type,
method_name: str,
autospec: bool = True,
**kwargs: Any,
):
"""Return mock for method `method_name` on `cls`.

The patch is reversed after pytest uses it.
"""
_patch = patch.object(cls, method_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def open_mock(request: FixtureRequest, module_name: str, **kwargs: Any):
"""Return a mock for the builtin `open()` method in `module_name`."""
target = "%s.open" % module_name
_patch = patch(target, mock_open(), create=True, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def property_mock(request: FixtureRequest, cls: type, prop_name: str, **kwargs: Any) -> Mock:
"""A mock for property `prop_name` on class `cls`.

Patch is reversed at the end of the test run.
"""
_patch = patch.object(cls, prop_name, new_callable=PropertyMock, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()


def var_mock(request: FixtureRequest, q_var_name: str, **kwargs: Any):
"""Return a mock patching the variable with qualified name `q_var_name`.

Patch is reversed after calling test returns.
"""
_patch = patch(q_var_name, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
2 changes: 1 addition & 1 deletion unstructured/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.12.7-dev2" # pragma: no cover
__version__ = "0.12.7-dev3" # pragma: no cover
Loading
Loading