Files
Heyi Tang c66c9f929a Add one line in init for dev setup (#3211)
# Description

Add the line `__path__ = __import__('pkgutil').extend_path(__path__,
__name__)` to dev setup.

# All Promptflow Contribution checklist:
- [ ] **The pull request does not introduce [breaking changes].**
- [ ] **CHANGELOG is updated for new features, bug fixes or other
significant changes.**
- [ ] **I have read the [contribution guidelines](../CONTRIBUTING.md).**
- [ ] **Create an issue and link to the pull request to get dedicated
review from promptflow team. Learn more: [suggested
workflow](../CONTRIBUTING.md#suggested-workflow).**

## General Guidelines and Best Practices
- [ ] Title of the pull request is clear and informative.
- [ ] There are a small number of commits, each of which have an
informative message. This means that previously merged commits do not
appear in the history of the PR. For more information on cleaning up the
commits in your PR, [see this
page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).

### Testing Guidelines
- [ ] Pull request includes test coverage for the included changes.

Co-authored-by: Heyi Tang <heta@microsoft.com>
2024-05-11 14:15:16 +08:00

161 lines
5.9 KiB
Python

# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
import argparse
import os
import site
import typing
import sys
from dataclasses import dataclass
from pathlib import Path
from test_resources import REGISTERED_TEST_RESOURCES_FUNCTIONS
from utils import REPO_ROOT_DIR, change_cwd, print_blue, run_cmd
PROMPT_FLOW_PKGS = [
"promptflow-tracing",
"promptflow-core",
"promptflow-devkit",
"promptflow-azure",
"promptflow[azure]",
"promptflow-tools",
"promptflow-evals",
"promptflow-recording",
]
def set_up_git_hook_scripts(verbose: bool) -> None:
run_cmd(["pip", "install", "pre-commit"], verbose=False) # ensure pre-commit is installed
cmd = ["pre-commit", "install"]
print_blue("Running `pre-commit install` to set up the git hook scripts")
run_cmd(cmd, verbose=verbose)
def collect_and_install_from_pyproject() -> None:
# collect dependencies from pyproject.toml and install
deps = []
# cwd is already changed to the package folder in the context
# so we can safely open pyproject.toml here
with open("pyproject.toml", mode="r", encoding="utf-8") as f:
lines = f.readlines()
collect_flag = False
for line in lines:
line = line.strip()
if line.startswith("["):
if "[tool.poetry.group.dev.dependencies]" in line or "[tool.poetry.group.test.dependencies]" in line:
collect_flag = True
continue
else:
collect_flag = False
if collect_flag and line:
deps.append(line.split("=")[0].strip())
cmd = ["pip", "install"] + deps
run_cmd(cmd)
def inject_pth_file() -> None:
# content of the .pth file will be added to `sys.path`
# for packages installed from pyproject.toml, this file should already be there
# for `promptflow`, inject this, and we can avoid `conda env config vars set`
# reference: https://docs.python.org/3/library/site.html
site_packages_path = Path(site.getsitepackages()[0])
with open(site_packages_path / "promptflow.pth", mode="w", encoding="utf-8") as f:
f.write((REPO_ROOT_DIR / "src" / "promptflow").resolve().absolute().as_posix())
def install_pkg_editable(pkg: str, verbose: bool, is_vscode: bool = False) -> None:
if "[" in pkg:
folder_name, extras = pkg.split("[")
extras = f"[{extras}"
else:
folder_name = pkg
extras = ""
pkg_working_dir = REPO_ROOT_DIR / "src" / folder_name
with change_cwd(pkg_working_dir):
print_blue(f"- Setting up {pkg}")
# pip install -e . from pyproject.toml/setup.py
print_blue(f"- Installing {pkg} from source")
cmd = [sys.executable, "-m", "pip", "install", "--editable", f".{extras}"]
run_cmd(cmd, verbose=verbose)
# dev and test dependencies
print_blue(f"- Installing dev and test dependencies for {pkg}")
# promptflow folder has "dev_requirements.txt", directly install it
if folder_name == "promptflow":
cmd = ["pip", "install", "-r", "dev_requirements.txt"]
run_cmd(cmd, verbose=verbose)
inject_pth_file()
# promptflow-tools
# reference: https://github.com/microsoft/promptflow/blob/main/src/promptflow-tools/README.dev.md
elif pkg == "promptflow-tools":
cmd = ["pip", "install", "-r", "requirements.txt"]
run_cmd(cmd, verbose=verbose)
cmd = ["pip", "install", "pytest", "pytest-mock"]
run_cmd(cmd, verbose=verbose)
# promptflow-* will have "pyproject.toml", parse and install
# we can refine this leveraging `poetry export` later
elif os.path.exists("pyproject.toml"):
collect_and_install_from_pyproject()
# touch __init__.py for the package for VS Code
# NOTE that this is a workaround to enable VS Code to recognize the namespace package
# we should be able to remove this after we fully deprecate promptflow in local development
if is_vscode:
with open(pkg_working_dir / "promptflow" / "__init__.py", mode="w", encoding="utf-8") as f:
f.write("__path__ = __import__('pkgutil').extend_path(__path__, __name__)\n")
@dataclass
class Arguments:
packages: typing.List[str]
verbose: bool = False
vscode: bool = False
@staticmethod
def parse_args() -> "Arguments":
epilog = "Example usage: main.py --packages promptflow-tracing promptflow-core"
parser = argparse.ArgumentParser(
description="Welcome to promptflow dev setup!",
epilog=epilog,
)
parser.add_argument(
"--packages",
required=False,
nargs="+",
type=str,
default=PROMPT_FLOW_PKGS,
help="packages to install in editable mode",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="turn on verbose output",
)
parser.add_argument(
"--vscode",
action="store_true",
help="extra setup step for Visual Studio Code",
)
args = parser.parse_args()
return Arguments(
packages=args.packages,
verbose=args.verbose,
vscode=args.vscode,
)
def main(args: Arguments) -> None:
for pkg in args.packages:
install_pkg_editable(pkg, verbose=args.verbose, is_vscode=args.vscode)
# invoke test resource template creation function if available
if pkg in REGISTERED_TEST_RESOURCES_FUNCTIONS:
REGISTERED_TEST_RESOURCES_FUNCTIONS[pkg]()
set_up_git_hook_scripts(verbose=args.verbose)
if __name__ == "__main__":
main(args=Arguments.parse_args())