remove pytest-depends, rerouting functions (#250)

pull/5155/head
Silen Naihin 2023-08-06 22:35:22 +01:00 committed by GitHub
parent aa37109707
commit 19848f362d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1275 additions and 182 deletions

@ -1 +1 @@
Subproject commit c7daa64eb72e7d4598de2c748727627898ae81e0
Subproject commit e2d7b3b8d2cd34c2ac628debbf026b994a04346d

View File

@ -14,7 +14,6 @@ from agbenchmark.reports.reports import (
generate_combined_suite_report,
generate_single_call_report,
session_finish,
setup_dummy_dependencies,
)
from agbenchmark.start_benchmark import CONFIG_PATH, get_regression_data
from agbenchmark.utils.data_types import SuiteConfig
@ -23,6 +22,8 @@ GLOBAL_TIMEOUT = (
1500 # The tests will stop after 25 minutes so we can send the reports.
)
pytest_plugins = ["agbenchmark.utils.dependencies"]
def resolve_workspace(workspace: str) -> str:
if workspace.startswith("${") and workspace.endswith("}"):
@ -159,15 +160,12 @@ def pytest_runtest_makereport(item: Any, call: Any) -> None:
flags = "--test" in sys.argv or "--maintain" in sys.argv or "--improve" in sys.argv
if call.when == "call":
test_name = ""
# if it's a same task suite, we combine the report.
# but not if it's a single --test
if is_suite and is_suite.same_task and not flags:
test_name = is_suite.prefix
generate_combined_suite_report(item, challenge_data, challenge_location)
else:
# single non suite test
test_name = challenge_data["name"]
generate_single_call_report(item, call, challenge_data)
# else: it's a same_task=false suite (tests aren't combined)
if call.when == "teardown":
@ -204,16 +202,6 @@ def scores(request: Any) -> None:
return request.node.cls.scores.get(test_class_name)
def pytest_generate_tests(metafunc: Any) -> None:
"""This is to generate the dummy dependencies each test class"""
test_class_instance = metafunc.cls()
if test_class_instance.setup_dependencies:
test_class = metafunc.cls
setup_dummy_dependencies(test_class_instance, test_class)
setattr(test_class, "setup_dependencies", [])
# this is adding the dependency marker and category markers automatically from the json
def pytest_collection_modifyitems(items: Any, config: Any) -> None:
data = get_regression_data()
@ -222,7 +210,6 @@ def pytest_collection_modifyitems(items: Any, config: Any) -> None:
# Assuming item.cls is your test class
test_class_instance = item.cls()
# if it's a dummy dependency setup test, we also skip
if "test_method" not in item.name:
continue
@ -231,28 +218,22 @@ def pytest_collection_modifyitems(items: Any, config: Any) -> None:
dependencies = test_class_instance.data.dependencies
# Filter dependencies if they exist in regression data if its an improvement test
if (
config.getoption("--improve")
or config.getoption("--category")
or test_class_instance.setup_dependencies # same_task suite
):
if config.getoption("--improve") or config.getoption(
"--category"
): # TODO: same task suite
dependencies = [dep for dep in dependencies if not data.get(dep, None)]
if (
if ( # TODO: separate task suite
config.getoption("--test")
or ( # separate task suite
not test_class_instance.setup_dependencies
and config.getoption("--suite")
)
or config.getoption("--no_dep")
or config.getoption("--maintain")
):
dependencies = []
categories = test_class_instance.data.category
# Add depends marker dynamically
item.add_marker(pytest.mark.depends(on=dependencies, name=name))
categories = test_class_instance.data.category
# Add category marker dynamically
for category in categories:
item.add_marker(getattr(pytest.mark, category))

View File

@ -5,7 +5,7 @@ import sys
import types
from collections import deque
from pathlib import Path
from typing import Any, Dict, Optional
from typing import Any, Callable, Dict, Optional
import pytest
@ -14,11 +14,52 @@ from agbenchmark.utils.challenge import Challenge
from agbenchmark.utils.data_types import ChallengeData, SuiteConfig
from agbenchmark.utils.utils import get_test_path
DATA_CATEGORY = {}
def setup_dummy_dependencies(
file_datum: list[dict[str, Any]],
challenge_class: Any,
challenge_data: ChallengeData,
) -> None:
"""Sets up the dependencies if it's a suite. Creates tests that pass
based on the main test run."""
def create_test_func(test_name: str) -> Callable[[Any, dict[str, Any]], None]:
# This function will return another function
# Define a dummy test function that does nothing
def setup_dependency_test(self: Any, scores: dict[str, Any]) -> None:
scores = self.get_dummy_scores(test_name, scores)
assert scores == 1
return setup_dependency_test
for datum in file_datum:
DATA_CATEGORY[datum["name"]] = challenge_data.category[0]
test_func = create_test_func(datum["name"])
# TODO: replace this once I figure out actual dependencies
test_func = pytest.mark.depends(on=[challenge_data.name], name=datum["name"])(
test_func
)
test_func = pytest.mark.parametrize(
"challenge_data",
[None],
indirect=True,
)(test_func)
# Add category markers
for category in challenge_data.category:
test_func = getattr(pytest.mark, category)(test_func)
test_func = pytest.mark.usefixtures("scores")(test_func)
setattr(challenge_class, f"test_{datum['name']}", test_func)
def create_single_test(
data: Dict[str, Any] | ChallengeData,
challenge_location: str,
suite_config: Optional[SuiteConfig] = None,
file_datum: Optional[list[dict[str, Any]]] = None,
) -> None:
challenge_data = None
artifacts_location = None
@ -26,23 +67,22 @@ def create_single_test(
challenge_data = data
data = data.get_data()
DATA_CATEGORY[data["name"]] = data["category"][0]
# Define test class dynamically
challenge_class = types.new_class(data["name"], (Challenge,))
clean_challenge_location = get_test_path(challenge_location)
setattr(challenge_class, "CHALLENGE_LOCATION", clean_challenge_location)
# if its a parallel run suite we just give it the data
if suite_config and suite_config.same_task:
# in the case of a suite
if isinstance(challenge_data, ChallengeData):
if file_datum: # same task suite
setup_dummy_dependencies(file_datum, challenge_class, challenge_data)
artifacts_location = str(Path(challenge_location).resolve())
if "--test" in sys.argv or "--maintain" in sys.argv or "--improve" in sys.argv:
artifacts_location = str(Path(challenge_location).resolve().parent.parent)
else:
setattr(
challenge_class,
"setup_dependencies",
[test_name for test_name in data["info"].keys()],
)
setattr(
challenge_class,
"_data_cache",
@ -83,15 +123,8 @@ def create_single_test(
setattr(module, data["name"], challenge_class)
def create_single_suite_challenge(
suite_config: SuiteConfig, data: Dict[str, Any], path: Path
) -> None:
test_data = suite_config.challenge_from_test_data(data)
create_single_test(
test_data,
str(path),
suite_config=suite_config,
)
def create_single_suite_challenge(challenge_data: ChallengeData, path: Path) -> None:
create_single_test(challenge_data, str(path))
def create_challenge(
@ -106,7 +139,8 @@ def create_challenge(
# if its a single test running we dont care about the suite
if "--test" in sys.argv or "--maintain" in sys.argv or "--improve" in sys.argv:
create_single_suite_challenge(suite_config, data, path)
challenge_data = suite_config.challenge_from_test_data(data)
create_single_suite_challenge(challenge_data, path)
return json_files
# Get all data.json files within the grandparent directory
@ -132,7 +166,7 @@ def create_challenge(
challenge_data = suite_config.challenge_from_datum(file_datum)
create_single_test(
challenge_data, str(grandparent_dir), suite_config=suite_config
challenge_data, str(grandparent_dir), file_datum=file_datum
)
else:
reverse = suite_config.reverse_order

View File

@ -2,9 +2,7 @@ import json
import os
import sys
from pathlib import Path
from typing import Any, Callable
import pytest
from typing import Any
from agbenchmark.agent_interface import MOCK_FLAG
from agbenchmark.reports.ReportManager import ReportManager
@ -194,41 +192,6 @@ def generate_single_call_report(
item.info_details = info_details
def setup_dummy_dependencies(test_class_instance: Any, test_class: Any) -> None:
"""Sets up the dependencies if it's a suite. Creates tests that pass
based on the main test run."""
def create_test_func(test_name: str) -> Callable[[Any, dict[str, Any]], None]:
# This function will return another function
# Define a dummy test function that does nothing
def setup_dependency_test(self: Any, scores: dict[str, Any]) -> None:
scores = self.get_dummy_scores(test_name, scores)
assert scores == 1
return setup_dependency_test
for test_name in test_class_instance.setup_dependencies:
setup_dependency_test = create_test_func(test_name)
# Add the dummy test function to the class that the current test is part of
# TODO: remove on=[test_class.__name__] and fix the actual dependencies problem
test_func = pytest.mark.depends(on=[test_class.__name__], name=test_name)(
setup_dependency_test
)
# Parametrize to tell makereport to skip it
test_func = pytest.mark.parametrize(
"challenge_data",
[None],
indirect=True,
)(test_func)
# Add category markers
for category in test_class_instance.data.category:
test_func = getattr(pytest.mark, category)(test_func)
test_func = pytest.mark.usefixtures("scores")(test_func)
setattr(test_class, f"test_{test_name}", test_func)
def finalize_reports(item: Any, challenge_data: dict[str, Any]) -> None:
run_time = dict(item.user_properties).get("run_time")

View File

@ -22,6 +22,7 @@ if os.environ.get("HELICONE_API_KEY"):
"benchmark_start_time", BENCHMARK_START_TIME
)
(
HOME_DIRECTORY,
CONFIG_PATH,

View File

@ -25,7 +25,6 @@ class Challenge(ABC):
_data_cache: Dict[str, ChallengeData] = {}
CHALLENGE_LOCATION: str = ""
ARTIFACTS_LOCATION: str = "" # this is for suites
setup_dependencies: List[str] = [] # this is for suites
scores: dict[str, Any] = {} # this is for suites
@property

View File

@ -0,0 +1,185 @@
"""
A module that provides the pytest hooks for this plugin.
The logic itself is in main.py.
"""
import warnings
from typing import Any, Callable, Optional
import pytest
from _pytest.config.argparsing import OptionGroup
from _pytest.nodes import Item
from .main import DependencyManager
# Each test suite run should have a single manager object. For regular runs, a simple singleton would suffice, but for
# our own tests this causes problems, as the nested pytest runs get the same instance. This can be worked around by
# running them all in subprocesses, but this slows the tests down massively. Instead, keep a stack of managers, so each
# test suite will have its own manager, even nested ones.
managers: list[DependencyManager] = []
DEPENDENCY_PROBLEM_ACTIONS: dict[str, Callable[[str], None] | None] = {
"run": None,
"skip": lambda m: pytest.skip(m),
"fail": lambda m: pytest.fail(m, False),
"warning": lambda m: warnings.warn(m),
}
def _add_ini_and_option(
parser: Any,
group: OptionGroup,
name: str,
help: str,
default: str | bool | int,
**kwargs: Any,
) -> None:
"""Add an option to both the ini file as well as the command line flags, with the latter overriding the former."""
parser.addini(
name,
help + " This overrides the similarly named option from the config.",
default=default,
)
group.addoption(f'--{name.replace("_", "-")}', help=help, default=None, **kwargs)
def _get_ini_or_option(
config: Any, name: str, choices: Optional[list[str]]
) -> str | None:
"""Get an option from either the ini file or the command line flags, the latter taking precedence."""
value = config.getini(name)
if value is not None and choices is not None and value not in choices:
raise ValueError(
f'Invalid ini value for {name}, choose from {", ".join(choices)}'
)
return config.getoption(name) or value
def pytest_addoption(parser: Any) -> None:
group = parser.getgroup("depends")
# Add a flag to list all names + the tests they resolve to
group.addoption(
"--list-dependency-names",
action="store_true",
default=False,
help=(
"List all non-nodeid dependency names + the tests they resolve to. "
"Will also list all nodeid dependency names when verbosity is high enough."
),
)
# Add a flag to list all (resolved) dependencies for all tests + unresolvable names
group.addoption(
"--list-processed-dependencies",
action="store_true",
default=False,
help="List all dependencies of all tests as a list of nodeids + the names that could not be resolved.",
)
# Add an ini option + flag to choose the action to take for failed dependencies
_add_ini_and_option(
parser,
group,
name="failed_dependency_action",
help=(
"The action to take when a test has dependencies that failed. "
'Use "run" to run the test anyway, "skip" to skip the test, and "fail" to fail the test.'
),
default="skip",
choices=DEPENDENCY_PROBLEM_ACTIONS.keys(),
)
# Add an ini option + flag to choose the action to take for unresolved dependencies
_add_ini_and_option(
parser,
group,
name="missing_dependency_action",
help=(
"The action to take when a test has dependencies that cannot be found within the current scope. "
'Use "run" to run the test anyway, "skip" to skip the test, and "fail" to fail the test.'
),
default="warning",
choices=DEPENDENCY_PROBLEM_ACTIONS.keys(),
)
def pytest_configure(config: Any) -> None:
manager = DependencyManager()
managers.append(manager)
# Setup the handling of problems with dependencies
manager.options["failed_dependency_action"] = _get_ini_or_option(
config,
"failed_dependency_action",
list(DEPENDENCY_PROBLEM_ACTIONS.keys()),
)
manager.options["missing_dependency_action"] = _get_ini_or_option(
config,
"missing_dependency_action",
list(DEPENDENCY_PROBLEM_ACTIONS.keys()),
)
# Register marker
config.addinivalue_line(
"markers",
"depends(name='name', on=['other_name']): marks depencies between tests.",
)
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(config: Any, items: list[Item]) -> None:
manager = managers[-1]
# Register the founds tests on the manager
manager.items = items
# Show the extra information if requested
if config.getoption("list_dependency_names"):
verbose = config.getoption("verbose") > 1
manager.print_name_map(verbose)
if config.getoption("list_processed_dependencies"):
color = config.getoption("color")
manager.print_processed_dependencies(color)
# Reorder the items so that tests run after their dependencies
items[:] = manager.sorted_items
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item: Item) -> Any:
manager = managers[-1]
# Run the step
outcome = yield
# Store the result on the manager
manager.register_result(item, outcome.get_result())
def pytest_runtest_call(item: Item) -> None:
manager = managers[-1]
# Handle missing dependencies
missing_dependency_action = DEPENDENCY_PROBLEM_ACTIONS[
manager.options["missing_dependency_action"]
]
missing = manager.get_missing(item)
if missing_dependency_action and missing:
missing_dependency_action(
f'{item.nodeid} depends on {", ".join(missing)}, which was not found'
)
# Check whether all dependencies succeeded
failed_dependency_action = DEPENDENCY_PROBLEM_ACTIONS[
manager.options["failed_dependency_action"]
]
failed = manager.get_failed(item)
if failed_dependency_action and failed:
failed_dependency_action(f'{item.nodeid} depends on {", ".join(failed)}')
def pytest_unconfigure() -> None:
managers.pop()

View File

@ -0,0 +1,10 @@
""" Constants for this module. """
# The name of the marker used
MARKER_NAME = "depends"
# The name of the keyword argument for the marker that contains custom name(s) for the tests
MARKER_KWARG_ID = "name"
# The name of the keyword argument for the marker that specifies the tests to depend on
MARKER_KWARG_DEPENDENCIES = "on"

View File

@ -0,0 +1,265 @@
import math
from pathlib import Path
from typing import Any, Dict, List, Tuple
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
from pyvis.network import Network
from agbenchmark.generate_test import DATA_CATEGORY
def bezier_curve(
src: np.ndarray, ctrl: List[float], dst: np.ndarray
) -> List[np.ndarray]:
"""
Generate Bézier curve points.
Args:
- src (np.ndarray): The source point.
- ctrl (List[float]): The control point.
- dst (np.ndarray): The destination point.
Returns:
- List[np.ndarray]: The Bézier curve points.
"""
curve = []
for t in np.linspace(0, 1, num=100):
curve_point = (
np.outer((1 - t) ** 2, src)
+ 2 * np.outer((1 - t) * t, ctrl)
+ np.outer(t**2, dst)
)
curve.append(curve_point[0])
return curve
def curved_edges(
G: nx.Graph, pos: Dict[Any, Tuple[float, float]], dist: float = 0.2
) -> None:
"""
Draw curved edges for nodes on the same level.
Args:
- G (Any): The graph object.
- pos (Dict[Any, Tuple[float, float]]): Dictionary with node positions.
- dist (float, optional): Distance for curvature. Defaults to 0.2.
Returns:
- None
"""
ax = plt.gca()
for u, v, data in G.edges(data=True):
src = np.array(pos[u])
dst = np.array(pos[v])
same_level = abs(src[1] - dst[1]) < 0.01
if same_level:
control = [(src[0] + dst[0]) / 2, src[1] + dist]
curve = bezier_curve(src, control, dst)
arrow = patches.FancyArrowPatch(
posA=curve[0], # type: ignore
posB=curve[-1], # type: ignore
connectionstyle=f"arc3,rad=0.2",
color="gray",
arrowstyle="-|>",
mutation_scale=15.0,
lw=1,
shrinkA=10,
shrinkB=10,
)
ax.add_patch(arrow)
else:
ax.annotate(
"",
xy=dst,
xytext=src,
arrowprops=dict(
arrowstyle="-|>", color="gray", lw=1, shrinkA=10, shrinkB=10
),
)
def tree_layout(graph: nx.DiGraph, root_node: Any) -> Dict[Any, Tuple[float, float]]:
"""Compute positions as a tree layout centered on the root with alternating vertical shifts."""
bfs_tree = nx.bfs_tree(graph, source=root_node)
levels = {
node: depth
for node, depth in nx.single_source_shortest_path_length(
bfs_tree, root_node
).items()
}
pos = {}
max_depth = max(levels.values())
level_positions = {i: 0 for i in range(max_depth + 1)} # type: ignore
# Count the number of nodes per level to compute the width
level_count: Any = {}
for node, level in levels.items():
level_count[level] = level_count.get(level, 0) + 1
vertical_offset = (
0.07 # The amount of vertical shift per node within the same level
)
# Assign positions
for node, level in sorted(levels.items(), key=lambda x: x[1]):
total_nodes_in_level = level_count[level]
horizontal_spacing = 1.0 / (total_nodes_in_level + 1)
pos_x = (
0.5
- (total_nodes_in_level - 1) * horizontal_spacing / 2
+ level_positions[level] * horizontal_spacing
)
# Alternately shift nodes up and down within the same level
pos_y = (
-level
+ (level_positions[level] % 2) * vertical_offset
- ((level_positions[level] + 1) % 2) * vertical_offset
)
pos[node] = (pos_x, pos_y)
level_positions[level] += 1
return pos
def graph_spring_layout(
dag: nx.DiGraph, labels: Dict[Any, str], tree: bool = True
) -> None:
num_nodes = len(dag.nodes())
# Setting up the figure and axis
fig, ax = plt.subplots()
ax.axis("off") # Turn off the axis
base = 3.0
if num_nodes > 10:
base /= 1 + math.log(num_nodes)
font_size = base * 10
font_size = max(10, base * 10)
node_size = max(300, base * 1000)
if tree:
root_node = [node for node, degree in dag.in_degree() if degree == 0][0]
pos = tree_layout(dag, root_node)
else:
# Adjust k for the spring layout based on node count
k_value = 3 / math.sqrt(num_nodes)
pos = nx.spring_layout(dag, k=k_value, iterations=50)
# Draw nodes and labels
nx.draw_networkx_nodes(dag, pos, node_color="skyblue", node_size=int(node_size))
nx.draw_networkx_labels(dag, pos, labels=labels, font_size=int(font_size))
# Draw curved edges
curved_edges(dag, pos) # type: ignore
plt.tight_layout()
plt.show()
def rgb_to_hex(rgb: Tuple[float, float, float]) -> str:
return "#{:02x}{:02x}{:02x}".format(
int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)
)
def get_category_colors(categories: Dict[Any, str]) -> Dict[str, str]:
unique_categories = set(categories.values())
colormap = plt.cm.get_cmap("tab10", len(unique_categories)) # type: ignore
return {
category: rgb_to_hex(colormap(i)[:3])
for i, category in enumerate(unique_categories)
}
def graph_interactive_network(
dag: nx.DiGraph, labels: Dict[Any, str], show: bool = False
) -> None:
nt = Network(notebook=True, width="100%", height="800px", directed=True)
category_colors = get_category_colors(DATA_CATEGORY)
# Add nodes and edges to the pyvis network
for node, label in labels.items():
node_id_str = node.nodeid
# Get the category for this label
category = DATA_CATEGORY.get(
label, "unknown"
) # Default to 'unknown' if label not found
# Get the color for this category
color = category_colors.get(category, "grey")
nt.add_node(node_id_str, label=label, color=color)
# Add edges to the pyvis network
for edge in dag.edges():
source_id_str = edge[0].nodeid
target_id_str = edge[1].nodeid
if not (source_id_str in nt.get_nodes() and target_id_str in nt.get_nodes()):
print(
f"Skipping edge {source_id_str} -> {target_id_str} due to missing nodes."
)
continue
nt.add_edge(source_id_str, target_id_str)
# Configure physics for hierarchical layout
hierarchical_options = {
"enabled": True,
"levelSeparation": 200, # Increased vertical spacing between levels
"nodeSpacing": 250, # Increased spacing between nodes on the same level
"treeSpacing": 250, # Increased spacing between different trees (for forest)
"blockShifting": True,
"edgeMinimization": True,
"parentCentralization": True,
"direction": "UD",
"sortMethod": "directed",
}
physics_options = {
"stabilization": {
"enabled": True,
"iterations": 1000, # Default is often around 100
},
"hierarchicalRepulsion": {
"centralGravity": 0.0,
"springLength": 200, # Increased edge length
"springConstant": 0.01,
"nodeDistance": 250, # Increased minimum distance between nodes
"damping": 0.09,
},
"solver": "hierarchicalRepulsion",
"timestep": 0.5,
}
nt.options = {
"nodes": {
"font": {
"size": 20, # Increased font size for labels
"color": "black", # Set a readable font color
},
"shapeProperties": {"useBorderWithImage": True},
},
"edges": {
"length": 250, # Increased edge length
},
"physics": physics_options,
"layout": {"hierarchical": hierarchical_options},
}
relative_path = "agbenchmark/challenges/dependencies.html"
file_path = str(Path(relative_path).resolve())
if show:
nt.show(file_path, notebook=False)
nt.write_html(file_path)

View File

@ -0,0 +1,239 @@
"""
A module to manage dependencies between pytest tests.
This module provides the methods implementing the main logic. These are used in the pytest hooks that are in
__init__.py.
"""
import collections
from typing import Any, Generator
import colorama
import networkx
from _pytest.nodes import Item
from .constants import MARKER_KWARG_DEPENDENCIES, MARKER_NAME
from .graphs import graph_interactive_network
from .util import clean_nodeid, get_absolute_nodeid, get_markers, get_name
class TestResult(object):
"""Keeps track of the results of a single test."""
STEPS = ["setup", "call", "teardown"]
GOOD_OUTCOMES = ["passed"]
def __init__(self, nodeid: str) -> None:
"""Create a new instance for a test with a given node id."""
self.nodeid = nodeid
self.results: dict[str, Any] = {}
def register_result(self, result: Any) -> None:
"""Register a result of this test."""
if result.when not in self.STEPS:
raise ValueError(
f"Received result for unknown step {result.when} of test {self.nodeid}"
)
if result.when in self.results:
raise AttributeError(
f"Received multiple results for step {result.when} of test {self.nodeid}"
)
self.results[result.when] = result.outcome
@property
def success(self) -> bool:
"""Whether the entire test was successful."""
return all(
self.results.get(step, None) in self.GOOD_OUTCOMES for step in self.STEPS
)
class TestDependencies(object):
"""Information about the resolved dependencies of a single test."""
def __init__(self, item: Item, manager: "DependencyManager") -> None:
"""Create a new instance for a given test."""
self.nodeid = clean_nodeid(item.nodeid)
self.dependencies = set()
self.unresolved = set()
markers = get_markers(item, MARKER_NAME)
dependencies = [
dep
for marker in markers
for dep in marker.kwargs.get(MARKER_KWARG_DEPENDENCIES, [])
]
for dependency in dependencies:
# If the name is not known, try to make it absolute (ie file::[class::]method)
if dependency not in manager.name_to_nodeids:
absolute_dependency = get_absolute_nodeid(dependency, self.nodeid)
if absolute_dependency in manager.name_to_nodeids:
dependency = absolute_dependency
# Add all items matching the name
if dependency in manager.name_to_nodeids:
for nodeid in manager.name_to_nodeids[dependency]:
self.dependencies.add(nodeid)
else:
self.unresolved.add(dependency)
class DependencyManager(object):
"""Keep track of tests, their names and their dependencies."""
def __init__(self) -> None:
"""Create a new DependencyManager."""
self.options: dict[str, Any] = {}
self._items: list[Item] | None = None
self._name_to_nodeids: Any = None
self._nodeid_to_item: Any = None
self._results: Any = None
@property
def items(self) -> list[Item]:
"""The collected tests that are managed by this instance."""
if self._items is None:
raise AttributeError("The items attribute has not been set yet")
return self._items
@items.setter
def items(self, items: list[Item]) -> None:
if self._items is not None:
raise AttributeError("The items attribute has already been set")
self._items = items
self._name_to_nodeids = collections.defaultdict(list)
self._nodeid_to_item = {}
self._results = {}
self._dependencies = {}
for item in items:
nodeid = clean_nodeid(item.nodeid)
# Add the mapping from nodeid to the test item
self._nodeid_to_item[nodeid] = item
# Add the mappings from all names to the node id
name = get_name(item)
self._name_to_nodeids[name].append(nodeid)
# Create the object that will contain the results of this test
self._results[nodeid] = TestResult(clean_nodeid(item.nodeid))
# Don't allow using unknown keys on the name_to_nodeids mapping
self._name_to_nodeids.default_factory = None
for item in items:
nodeid = clean_nodeid(item.nodeid)
# Process the dependencies of this test
# This uses the mappings created in the previous loop, and can thus not be merged into that loop
self._dependencies[nodeid] = TestDependencies(item, self)
@property
def name_to_nodeids(self) -> dict[str, list[str]]:
"""A mapping from names to matching node id(s)."""
assert self.items is not None
return self._name_to_nodeids
@property
def nodeid_to_item(self) -> dict[str, Item]:
"""A mapping from node ids to test items."""
assert self.items is not None
return self._nodeid_to_item
@property
def results(self) -> dict[str, TestResult]:
"""The results of the tests."""
assert self.items is not None
return self._results
@property
def dependencies(self) -> dict[str, TestDependencies]:
"""The dependencies of the tests."""
assert self.items is not None
return self._dependencies
def print_name_map(self, verbose: bool = False) -> None:
"""Print a human-readable version of the name -> test mapping."""
print("Available dependency names:")
for name, nodeids in sorted(self.name_to_nodeids.items(), key=lambda x: x[0]):
if len(nodeids) == 1:
if name == nodeids[0]:
# This is just the base name, only print this when verbose
if verbose:
print(f" {name}")
else:
# Name refers to a single node id, so use the short format
print(f" {name} -> {nodeids[0]}")
else:
# Name refers to multiple node ids, so use the long format
print(f" {name} ->")
for nodeid in sorted(nodeids):
print(f" {nodeid}")
def print_processed_dependencies(self, colors: bool = False) -> None:
"""Print a human-readable list of the processed dependencies."""
missing = "MISSING"
if colors:
missing = f"{colorama.Fore.RED}{missing}{colorama.Fore.RESET}"
colorama.init()
try:
print("Dependencies:")
for nodeid, info in sorted(self.dependencies.items(), key=lambda x: x[0]):
descriptions = []
for dependency in info.dependencies:
descriptions.append(dependency)
for dependency in info.unresolved:
descriptions.append(f"{dependency} ({missing})")
if descriptions:
print(f" {nodeid} depends on")
for description in sorted(descriptions):
print(f" {description}")
finally:
if colors:
colorama.deinit()
@property
def sorted_items(self, show_graph: bool = False) -> Generator:
"""Get a sorted list of tests where all tests are sorted after their dependencies."""
# Build a directed graph for sorting
dag = networkx.DiGraph()
# Insert all items as nodes, to prevent items that have no dependencies and are not dependencies themselves from
# being lost
dag.add_nodes_from(self.items)
# Insert edges for all the dependencies
for item in self.items:
nodeid = clean_nodeid(item.nodeid)
for dependency in self.dependencies[nodeid].dependencies:
dag.add_edge(self.nodeid_to_item[dependency], item)
labels = {}
for item in self.items:
node_name = get_name(item)
labels[item] = node_name
if show_graph:
# graph_spring_layout(dag, labels)
graph_interactive_network(dag, labels, show=False)
# Sort based on the dependencies
return networkx.topological_sort(dag)
def register_result(self, item: Item, result: Any) -> None:
"""Register a result of a test."""
nodeid = clean_nodeid(item.nodeid)
self.results[nodeid].register_result(result)
def get_failed(self, item: Item) -> Any:
"""Get a list of unfulfilled dependencies for a test."""
nodeid = clean_nodeid(item.nodeid)
failed = []
for dependency in self.dependencies[nodeid].dependencies:
result = self.results[dependency]
if not result.success:
failed.append(dependency)
return failed
def get_missing(self, item: Item) -> Any:
"""Get a list of missing dependencies for a test."""
nodeid = clean_nodeid(item.nodeid)
return self.dependencies[nodeid].unresolved

View File

@ -0,0 +1,85 @@
""" Utility functions to process the identifiers of tests. """
import re
from typing import Iterator
from _pytest.mark.structures import Mark
from _pytest.nodes import Item
from .constants import MARKER_KWARG_ID, MARKER_NAME
REGEX_PARAMETERS = re.compile(r"\[.+\]$")
def clean_nodeid(nodeid: str) -> str:
"""
Remove any superfluous ::() from a node id.
>>> clean_nodeid('test_file.py::TestClass::()::test')
'test_file.py::TestClass::test'
>>> clean_nodeid('test_file.py::TestClass::test')
'test_file.py::TestClass::test'
>>> clean_nodeid('test_file.py::test')
'test_file.py::test'
"""
return nodeid.replace("::()::", "::")
def strip_nodeid_parameters(nodeid: str) -> str:
"""
Strip parameters from a node id.
>>> strip_nodeid_parameters('test_file.py::TestClass::test[foo]')
'test_file.py::TestClass::test'
>>> strip_nodeid_parameters('test_file.py::TestClass::test')
'test_file.py::TestClass::test'
"""
return REGEX_PARAMETERS.sub("", nodeid)
def get_absolute_nodeid(nodeid: str, scope: str) -> str:
"""
Transform a possibly relative node id to an absolute one using the scope in which it is used.
>>> scope = 'test_file.py::TestClass::test'
>>> get_absolute_nodeid('test2', scope)
'test_file.py::TestClass::test2'
>>> get_absolute_nodeid('TestClass2::test2', scope)
'test_file.py::TestClass2::test2'
>>> get_absolute_nodeid('test_file2.py::TestClass2::test2', scope)
'test_file2.py::TestClass2::test2'
"""
parts = nodeid.split("::")
# Completely relative (test_name), so add the full current scope (either file::class or file)
if len(parts) == 1:
base_nodeid = scope.rsplit("::", 1)[0]
nodeid = f"{base_nodeid}::{nodeid}"
# Contains some scope already (Class::test_name), so only add the current file scope
elif "." not in parts[0]:
base_nodeid = scope.split("::", 1)[0]
nodeid = f"{base_nodeid}::{nodeid}"
return clean_nodeid(nodeid)
def get_name(item: Item) -> str:
"""
Get all names for a test.
This will use the following methods to determine the name of the test:
- If given, the custom name(s) passed to the keyword argument name on the marker
"""
name = ""
# Custom name
markers = get_markers(item, MARKER_NAME)
for marker in markers:
if MARKER_KWARG_ID in marker.kwargs:
name = marker.kwargs[MARKER_KWARG_ID]
return name
def get_markers(item: Item, name: str) -> Iterator[Mark]:
"""Get all markers with the given name for a given item."""
for marker in item.iter_markers():
if marker.name == name:
yield marker

511
poetry.lock generated
View File

@ -162,6 +162,34 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-
test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (<0.22)"]
[[package]]
name = "appnope"
version = "0.1.3"
description = "Disable App Nap on macOS >= 10.9"
optional = false
python-versions = "*"
files = [
{file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
]
[[package]]
name = "asttokens"
version = "2.2.1"
description = "Annotate AST trees with source code positions"
optional = false
python-versions = "*"
files = [
{file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"},
{file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"},
]
[package.dependencies]
six = "*"
[package.extras]
test = ["astroid", "pytest"]
[[package]]
name = "async-timeout"
version = "4.0.2"
@ -206,6 +234,17 @@ files = [
pyflakes = ">=1.1.0,<3"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[[package]]
name = "backcall"
version = "0.2.0"
description = "Specifications for callback functions passed in to an API"
optional = false
python-versions = "*"
files = [
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
]
[[package]]
name = "black"
version = "22.3.0"
@ -451,6 +490,17 @@ files = [
{file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
]
[[package]]
name = "decorator"
version = "5.1.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.5"
files = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
[[package]]
name = "exceptiongroup"
version = "1.1.2"
@ -465,6 +515,20 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "executing"
version = "1.2.0"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = "*"
files = [
{file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
{file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"},
]
[package.extras]
tests = ["asttokens", "littleutils", "pytest", "rich"]
[[package]]
name = "fastapi"
version = "0.100.1"
@ -502,45 +566,45 @@ pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "fonttools"
version = "4.41.1"
version = "4.42.0"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.8"
files = [
{file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7bbb290d13c6dd718ec2c3db46fe6c5f6811e7ea1e07f145fd8468176398224"},
{file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec453a45778524f925a8f20fd26a3326f398bfc55d534e37bab470c5e415caa1"},
{file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2071267deaa6d93cb16288613419679c77220543551cbe61da02c93d92df72f"},
{file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e3334d51f0e37e2c6056e67141b2adabc92613a968797e2571ca8a03bd64773"},
{file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cac73bbef7734e78c60949da11c4903ee5837168e58772371bd42a75872f4f82"},
{file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edee0900cf0eedb29d17c7876102d6e5a91ee333882b1f5abc83e85b934cadb5"},
{file = "fonttools-4.41.1-cp310-cp310-win32.whl", hash = "sha256:2a22b2c425c698dcd5d6b0ff0b566e8e9663172118db6fd5f1941f9b8063da9b"},
{file = "fonttools-4.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:547ab36a799dded58a46fa647266c24d0ed43a66028cd1cd4370b246ad426cac"},
{file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:849ec722bbf7d3501a0e879e57dec1fc54919d31bff3f690af30bb87970f9784"},
{file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38cdecd8f1fd4bf4daae7fed1b3170dfc1b523388d6664b2204b351820aa78a7"},
{file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ae64303ba670f8959fdaaa30ba0c2dabe75364fdec1caeee596c45d51ca3425"},
{file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14f3ccea4cc7dd1b277385adf3c3bf18f9860f87eab9c2fb650b0af16800f55"},
{file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:33191f062549e6bb1a4782c22a04ebd37009c09360e2d6686ac5083774d06d95"},
{file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:704bccd69b0abb6fab9f5e4d2b75896afa48b427caa2c7988792a2ffce35b441"},
{file = "fonttools-4.41.1-cp311-cp311-win32.whl", hash = "sha256:4edc795533421e98f60acee7d28fc8d941ff5ac10f44668c9c3635ad72ae9045"},
{file = "fonttools-4.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:aaaef294d8e411f0ecb778a0aefd11bb5884c9b8333cc1011bdaf3b58ca4bd75"},
{file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d1f9471134affc1e3b1b806db6e3e2ad3fa99439e332f1881a474c825101096"},
{file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59eba8b2e749a1de85760da22333f3d17c42b66e03758855a12a2a542723c6e7"},
{file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b3cc10dc9e0834b6665fd63ae0c6964c6bc3d7166e9bc84772e0edd09f9fa2"},
{file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2c2964bdc827ba6b8a91dc6de792620be4da3922c4cf0599f36a488c07e2b2"},
{file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7763316111df7b5165529f4183a334aa24c13cdb5375ffa1dc8ce309c8bf4e5c"},
{file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b2d1ee95be42b80d1f002d1ee0a51d7a435ea90d36f1a5ae331be9962ee5a3f1"},
{file = "fonttools-4.41.1-cp38-cp38-win32.whl", hash = "sha256:f48602c0b3fd79cd83a34c40af565fe6db7ac9085c8823b552e6e751e3a5b8be"},
{file = "fonttools-4.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0938ebbeccf7c80bb9a15e31645cf831572c3a33d5cc69abe436e7000c61b14"},
{file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e5c2b0a95a221838991e2f0e455dec1ca3a8cc9cd54febd68cc64d40fdb83669"},
{file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891cfc5a83b0307688f78b9bb446f03a7a1ad981690ac8362f50518bc6153975"},
{file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73ef0bb5d60eb02ba4d3a7d23ada32184bd86007cb2de3657cfcb1175325fc83"},
{file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f240d9adf0583ac8fc1646afe7f4ac039022b6f8fa4f1575a2cfa53675360b69"},
{file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bdd729744ae7ecd7f7311ad25d99da4999003dcfe43b436cf3c333d4e68de73d"},
{file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b927e5f466d99c03e6e20961946314b81d6e3490d95865ef88061144d9f62e38"},
{file = "fonttools-4.41.1-cp39-cp39-win32.whl", hash = "sha256:afce2aeb80be72b4da7dd114f10f04873ff512793d13ce0b19d12b2a4c44c0f0"},
{file = "fonttools-4.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:1df1b6f4c7c4bc8201eb47f3b268adbf2539943aa43c400f84556557e3e109c0"},
{file = "fonttools-4.41.1-py3-none-any.whl", hash = "sha256:952cb405f78734cf6466252fec42e206450d1a6715746013f64df9cbd4f896fa"},
{file = "fonttools-4.41.1.tar.gz", hash = "sha256:e16a9449f21a93909c5be2f5ed5246420f2316e94195dbfccb5238aaa38f9751"},
{file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"},
{file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"},
{file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"},
{file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"},
{file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"},
{file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"},
{file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"},
{file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"},
{file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"},
{file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"},
{file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"},
{file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"},
{file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"},
{file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"},
{file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"},
{file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"},
{file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"},
{file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"},
{file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"},
{file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"},
{file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"},
{file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"},
{file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"},
{file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"},
{file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"},
{file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"},
{file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"},
{file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"},
{file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"},
{file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"},
{file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"},
{file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"},
{file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"},
{file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"},
]
[package.extras]
@ -627,20 +691,6 @@ files = [
{file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
]
[[package]]
name = "future-fstrings"
version = "1.2.0"
description = "A backport of fstrings to python<3.6"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63"},
{file = "future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089"},
]
[package.extras]
rewrite = ["tokenize-rt (>=3)"]
[[package]]
name = "gitdb"
version = "4.0.10"
@ -850,6 +900,44 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipython"
version = "8.14.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.9"
files = [
{file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"},
{file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"},
]
[package.dependencies]
appnope = {version = "*", markers = "sys_platform == \"darwin\""}
backcall = "*"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
pickleshare = "*"
prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0"
pygments = ">=2.4.0"
stack-data = "*"
traitlets = ">=5"
[package.extras]
all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
black = ["black"]
doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
kernel = ["ipykernel"]
nbconvert = ["nbconvert"]
nbformat = ["nbformat"]
notebook = ["ipywidgets", "notebook"]
parallel = ["ipyparallel"]
qtconsole = ["qtconsole"]
test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
[[package]]
name = "isort"
version = "5.12.0"
@ -867,6 +955,58 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"
plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "jedi"
version = "0.19.0"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
files = [
{file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"},
{file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"},
]
[package.dependencies]
parso = ">=0.8.3,<0.9.0"
[package.extras]
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "jsonpickle"
version = "3.0.1"
description = "Python library for serializing any arbitrary object graph into JSON"
optional = false
python-versions = ">=3.7"
files = [
{file = "jsonpickle-3.0.1-py2.py3-none-any.whl", hash = "sha256:130d8b293ea0add3845de311aaba55e6d706d0bb17bc123bd2c8baf8a39ac77c"},
{file = "jsonpickle-3.0.1.tar.gz", hash = "sha256:032538804795e73b94ead410800ac387fdb6de98f8882ac957fcd247e3a85200"},
]
[package.extras]
docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"]
testing = ["ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8 (>=1.1.1)", "scikit-learn", "sqlalchemy"]
testing-libs = ["simplejson", "ujson"]
[[package]]
name = "kiwisolver"
version = "1.4.4"
@ -955,6 +1095,65 @@ files = [
{file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
]
[[package]]
name = "markupsafe"
version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
files = [
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
[[package]]
name = "matplotlib"
version = "3.7.2"
@ -1016,6 +1215,20 @@ pillow = ">=6.2.0"
pyparsing = ">=2.3.1,<3.1"
python-dateutil = ">=2.7"
[[package]]
name = "matplotlib-inline"
version = "0.1.6"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.5"
files = [
{file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
{file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
]
[package.dependencies]
traitlets = "*"
[[package]]
name = "mccabe"
version = "0.6.1"
@ -1187,36 +1400,36 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"]
[[package]]
name = "numpy"
version = "1.25.1"
version = "1.25.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "numpy-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa"},
{file = "numpy-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b"},
{file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf"},
{file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588"},
{file = "numpy-1.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19"},
{file = "numpy-1.25.1-cp310-cp310-win32.whl", hash = "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"},
{file = "numpy-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57"},
{file = "numpy-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e"},
{file = "numpy-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800"},
{file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09"},
{file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6"},
{file = "numpy-1.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d"},
{file = "numpy-1.25.1-cp311-cp311-win32.whl", hash = "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb"},
{file = "numpy-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171"},
{file = "numpy-1.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105"},
{file = "numpy-1.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f"},
{file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625"},
{file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd"},
{file = "numpy-1.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7"},
{file = "numpy-1.25.1-cp39-cp39-win32.whl", hash = "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c"},
{file = "numpy-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631"},
{file = "numpy-1.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009"},
{file = "numpy-1.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004"},
{file = "numpy-1.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe"},
{file = "numpy-1.25.1.tar.gz", hash = "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf"},
{file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"},
{file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"},
{file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"},
{file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"},
{file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"},
{file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"},
{file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"},
{file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"},
{file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"},
{file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"},
{file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"},
{file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"},
{file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"},
{file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"},
{file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"},
{file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"},
{file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"},
{file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"},
{file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"},
{file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"},
{file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"},
{file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"},
{file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"},
{file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"},
{file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"},
]
[[package]]
@ -1352,6 +1565,21 @@ sql-other = ["SQLAlchemy (>=1.4.16)"]
test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.6.3)"]
[[package]]
name = "parso"
version = "0.8.3"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
files = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
]
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
testing = ["docopt", "pytest (<6.0.0)"]
[[package]]
name = "pathspec"
version = "0.11.2"
@ -1377,6 +1605,17 @@ files = [
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "pickleshare"
version = "0.7.5"
description = "Tiny 'shelve'-like database with concurrency support"
optional = false
python-versions = "*"
files = [
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
[[package]]
name = "pillow"
version = "10.0.0"
@ -1487,6 +1726,20 @@ files = [
{file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"},
]
[[package]]
name = "prompt-toolkit"
version = "3.0.39"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"},
{file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "psutil"
version = "5.9.5"
@ -1524,6 +1777,20 @@ files = [
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
[[package]]
name = "pure-eval"
version = "0.2.2"
description = "Safely evaluate AST nodes without side effects"
optional = false
python-versions = "*"
files = [
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
]
[package.extras]
tests = ["pytest"]
[[package]]
name = "pyasn1"
version = "0.5.0"
@ -1623,6 +1890,20 @@ files = [
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
[[package]]
name = "pygments"
version = "2.16.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.7"
files = [
{file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"},
{file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
]
[package.extras]
plugins = ["importlib-metadata"]
[[package]]
name = "pyparsing"
version = "3.0.9"
@ -1659,23 +1940,6 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-depends"
version = "1.0.1"
description = "Tests that depend on other tests"
optional = false
python-versions = "*"
files = [
{file = "pytest-depends-1.0.1.tar.gz", hash = "sha256:90a28e2b87b75b18abd128c94015248544acac20e4392e9921e5a86f93319dfe"},
{file = "pytest_depends-1.0.1-py3-none-any.whl", hash = "sha256:a1df072bcc93d77aca3f0946903f5fed8af2d9b0056db1dfc9ed5ac164ab0642"},
]
[package.dependencies]
colorama = "*"
future-fstrings = "*"
networkx = "*"
pytest = ">=3"
[[package]]
name = "python-dateutil"
version = "2.8.2"
@ -1715,6 +1979,22 @@ files = [
{file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
]
[[package]]
name = "pyvis"
version = "0.3.2"
description = "A Python network graph visualization library"
optional = false
python-versions = ">3.6"
files = [
{file = "pyvis-0.3.2-py3-none-any.whl", hash = "sha256:5720c4ca8161dc5d9ab352015723abb7a8bb8fb443edeb07f7a322db34a97555"},
]
[package.dependencies]
ipython = ">=5.3.0"
jinja2 = ">=2.9.6"
jsonpickle = ">=1.4.1"
networkx = ">=1.11"
[[package]]
name = "requests"
version = "2.31.0"
@ -1801,6 +2081,25 @@ files = [
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
[[package]]
name = "stack-data"
version = "0.6.2"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
files = [
{file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"},
{file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"},
]
[package.dependencies]
asttokens = ">=2.1.0"
executing = ">=1.2.0"
pure-eval = "*"
[package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "starlette"
version = "0.27.0"
@ -1849,6 +2148,21 @@ notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "traitlets"
version = "5.9.0"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.7"
files = [
{file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
{file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]]
name = "types-requests"
version = "2.31.0.2"
@ -1912,6 +2226,17 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "wcwidth"
version = "0.2.6"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
]
[[package]]
name = "wsproto"
version = "1.2.0"
@ -2016,4 +2341,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "c49ef31fd15da62773661a27bbd829e5d5bd0d0f45294f721a72eded670762e8"
content-hash = "793a224969f4cdc094de21061917863e1d0fb3a960d57bd2c3947c3d5d5040eb"

View File

@ -13,7 +13,6 @@ pytest = "^7.3.2"
requests = "^2.31.0"
openai = "^0.27.8"
pydantic = "^1.10.9"
pytest-depends = "^1.0.1"
python-dotenv = "^1.0.0"
click = "^8.1.3"
types-requests = "^2.31.0.1"
@ -23,7 +22,10 @@ helicone = "^1.0.6"
matplotlib = "^3.7.2"
pandas = "^2.0.3"
gitpython = "^3.1.32"
networkx = "^3.1"
colorama = "^0.4.6"
agent-protocol = "^0.1.2"
pyvis = "^0.3.2"
[tool.poetry.group.dev.dependencies]
flake8 = "^3.9.2"
@ -55,6 +57,10 @@ markers = [
"safety",
"content_gen"
]
filterwarnings = [
"ignore::pytest.PytestAssertRewriteWarning",
"ignore::matplotlib.MatplotlibDeprecationWarning"
]
[tool.poetry.scripts]
agbenchmark = "agbenchmark.start_benchmark:cli"