|
| 1 | +import sys |
| 2 | +import pathlib |
| 3 | +import argparse |
| 4 | +import unittest.mock |
| 5 | +import importlib.util |
| 6 | + |
| 7 | +from dffml.base import config |
| 8 | +from dffml.util.os import chdir |
| 9 | +from dffml.df.base import opimp_in, op |
| 10 | +from dffml.df.types import Input, DataFlow |
| 11 | +from dffml.service.dev import SetupPyKWArg |
| 12 | +from dffml.operation.output import GetMulti |
| 13 | +from dffml.df.memory import MemoryOrchestrator |
| 14 | +from dffml.util.asynctestcase import AsyncTestCase |
| 15 | + |
| 16 | +from shouldi.pypi import * |
| 17 | + |
| 18 | + |
| 19 | +def remove_package_versions(packages): |
| 20 | + no_versions = [] |
| 21 | + |
| 22 | + appended = False |
| 23 | + for package in packages: |
| 24 | + for char in [">", "<", "="]: |
| 25 | + if char in package: |
| 26 | + no_versions.append(package.split(char)[0].strip()) |
| 27 | + appended = True |
| 28 | + break |
| 29 | + if not appended: |
| 30 | + no_versions.append(package.strip()) |
| 31 | + appended = False |
| 32 | + |
| 33 | + return no_versions |
| 34 | + |
| 35 | + |
| 36 | +PACKAGE_DEPS_KWARGS = dict( |
| 37 | + inputs={"src": pypi_package_contents.op.outputs["directory"],}, |
| 38 | + outputs={"package": pypi_package_json.op.inputs["package"]}, |
| 39 | + expand=["package"], |
| 40 | +) |
| 41 | + |
| 42 | + |
| 43 | +@op(**PACKAGE_DEPS_KWARGS) |
| 44 | +async def package_deps_setup_py(src: str): |
| 45 | + setup_py_path = list(pathlib.Path(src).rglob("**/setup.py")) |
| 46 | + if not setup_py_path: |
| 47 | + return |
| 48 | + |
| 49 | + setup_py_path = setup_py_path[0] |
| 50 | + |
| 51 | + deps = SetupPyKWArg.get_kwargs(str(setup_py_path)).get( |
| 52 | + "install_requires", [] |
| 53 | + ) |
| 54 | + |
| 55 | + no_versions = {} |
| 56 | + |
| 57 | + print(src, remove_package_versions(deps)) |
| 58 | + |
| 59 | + return {"package": remove_package_versions(deps)} |
| 60 | + |
| 61 | + |
| 62 | +@op(**PACKAGE_DEPS_KWARGS) |
| 63 | +async def package_deps_setup_cfg(src: str): |
| 64 | + # TODO |
| 65 | + return {"package": []} |
| 66 | + |
| 67 | + |
| 68 | +@op(**PACKAGE_DEPS_KWARGS) |
| 69 | +async def package_deps_requirements_txt(src: str): |
| 70 | + # TODO |
| 71 | + return {"package": []} |
| 72 | + |
| 73 | + |
| 74 | +SUBFLOW = DataFlow.auto(*[opimp for opimp in opimp_in(sys.modules[__name__])]) |
| 75 | +SUBFLOW.seed.append( |
| 76 | + Input( |
| 77 | + value=[pypi_package_json.op.inputs["package"].name], |
| 78 | + definition=GetMulti.op.inputs["spec"], |
| 79 | + ) |
| 80 | +) |
| 81 | +# Do not allow package names in the subflow to re-trigger the whole subflow |
| 82 | +# again, since this will cause version numbers and directories to get crossed |
| 83 | +SUBFLOW.flow["pypi_package_json"].inputs["package"] = ["seed"] |
| 84 | +SUBFLOW.update_by_origin() |
| 85 | + |
| 86 | + |
| 87 | +def create_parent_flow(): |
| 88 | + """ |
| 89 | + This function exists so that shouldi_dataflow_as_operation doesn't end up |
| 90 | + in the subflow when we grab from sys.modules[__name__] |
| 91 | + """ |
| 92 | + |
| 93 | + @config |
| 94 | + class ShouldIDataFlowAsOperationConfig: |
| 95 | + dataflow: DataFlow |
| 96 | + |
| 97 | + @op( |
| 98 | + inputs={"package": pypi_package_json.op.inputs["package"]}, |
| 99 | + outputs={"package": pypi_package_json.op.inputs["package"]}, |
| 100 | + expand=["package"], |
| 101 | + config_cls=ShouldIDataFlowAsOperationConfig, |
| 102 | + ) |
| 103 | + async def shouldi_dataflow_as_operation(self, package: str): |
| 104 | + async with self.octx.parent(self.config.dataflow) as octx: |
| 105 | + async for ctx, result in octx.run( |
| 106 | + { |
| 107 | + package: [ |
| 108 | + Input( |
| 109 | + value=package, |
| 110 | + definition=self.parent.op.inputs["package"], |
| 111 | + ) |
| 112 | + ] |
| 113 | + } |
| 114 | + ): |
| 115 | + packages = result[self.parent.op.inputs["package"].name] |
| 116 | + # Remove input package from list |
| 117 | + packages = list(filter(lambda pkg: pkg != package, packages)) |
| 118 | + # TODO Deduplicate |
| 119 | + return {"package": packages} |
| 120 | + |
| 121 | + dataflow = DataFlow.auto(shouldi_dataflow_as_operation, GetMulti) |
| 122 | + dataflow.seed.append( |
| 123 | + Input( |
| 124 | + value=[pypi_package_json.op.inputs["package"].name], |
| 125 | + definition=GetMulti.op.inputs["spec"], |
| 126 | + ) |
| 127 | + ) |
| 128 | + dataflow.configs[ |
| 129 | + "shouldi_dataflow_as_operation" |
| 130 | + ] = ShouldIDataFlowAsOperationConfig(dataflow=SUBFLOW) |
| 131 | + dataflow.flow["shouldi_dataflow_as_operation"].inputs["package"].append( |
| 132 | + "seed" |
| 133 | + ) |
| 134 | + dataflow.update_by_origin() |
| 135 | + return dataflow |
| 136 | + |
| 137 | + |
| 138 | +DATAFLOW = create_parent_flow() |
| 139 | + |
| 140 | + |
| 141 | +class TestOperations(AsyncTestCase): |
| 142 | + async def test_run(self): |
| 143 | + check = {"shouldi": [], "dffml-config-yaml": []} |
| 144 | + async with MemoryOrchestrator.withconfig({}) as orchestrator: |
| 145 | + async with orchestrator(DATAFLOW) as octx: |
| 146 | + async for ctx, results in octx.run( |
| 147 | + { |
| 148 | + package_name: [ |
| 149 | + Input( |
| 150 | + value=package_name, |
| 151 | + definition=pypi_package_json.op.inputs[ |
| 152 | + "package" |
| 153 | + ], |
| 154 | + ), |
| 155 | + ] |
| 156 | + for package_name in check.keys() |
| 157 | + } |
| 158 | + ): |
| 159 | + ctx_str = (await ctx.handle()).as_string() |
| 160 | + with self.subTest(package=ctx_str): |
| 161 | + print(ctx_str, results) |
| 162 | + print(DATAFLOW.flow) |
| 163 | + continue |
| 164 | + self.assertEqual( |
| 165 | + check[ctx_str], |
| 166 | + results[ |
| 167 | + pypi_package_json.op.inputs["package"].name |
| 168 | + ], |
| 169 | + ) |
0 commit comments