diff --git a/.gitignore b/.gitignore index 581041a..cacf814 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .bin +bin build .idea/ \ No newline at end of file diff --git a/README.md b/README.md index cc844b9..ac66fcc 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ The buildpack will do the following: environment variable `POETRY_VIRTUAL_ENVS_PATH`. - Prepends the layer `poetry-venv` onto `PYTHONPATH`. - Prepends the `bin` directory of the `poetry-venv` layer to the `PATH` environment variable. + - Installs only dependencies defined in the main group, + - Installs dependencies from the main group with dependencies listed via the + environment variable `BP_POETRY_INSTALL_WITH`, following `--with` option behavior of the [`poetry install` command](https://python-poetry.org/docs/cli/#install) + - eg, `BP_POETRY_INSTALL_WITH=dev` will install dependencies from main and dev groups + - eg, `BP_POETRY_INSTALL_WITH=dev,docs` will install dependencies from main, dev and docs groups * At run time: - Does nothing diff --git a/install_process.go b/install_process.go index c3cac9d..8497fb5 100644 --- a/install_process.go +++ b/install_process.go @@ -36,6 +36,11 @@ func NewPoetryInstallProcess(executable Executable, logger scribe.Emitter) Poetr // a virtual env in the targetPath. func (p PoetryInstallProcess) Execute(workingDir, targetPath, cachePath string) (string, error) { args := []string{"install"} + if group, exists := os.LookupEnv("BP_POETRY_INSTALL_WITH"); exists && len(group) > 0 { + args = append(args, "--with", group) + } else { + args = append(args, "--with", "main") + } env := append( os.Environ(), diff --git a/install_process_test.go b/install_process_test.go index 7a3a966..2c82203 100644 --- a/install_process_test.go +++ b/install_process_test.go @@ -69,7 +69,90 @@ func testInstallProcess(t *testing.T, context spec.G, it spec.S) { }) context("Execute", func() { - it("runs installation", func() { + it("runs installation for main dependencies group when BP_POETRY_INSTALL_WITH is not set", func() { + dependencyGroups := os.Getenv("BP_POETRY_INSTALL_WITH") + Expect(dependencyGroups).To(BeEmpty()) + + venvDir, err := poetryInstallProcess.Execute(workingDir, packagesLayerPath, cacheLayerPath) + Expect(err).NotTo(HaveOccurred()) + + Expect(executable.ExecuteCall.CallCount).To(Equal(2)) + Expect(executableInvocations).To(HaveLen(2)) + + Expect(executableInvocations[0]).To(MatchFields(IgnoreExtras, Fields{ + "Args": Equal([]string{ + "install", "--with", "main", + }), + "Dir": Equal(workingDir), + "Env": ContainElements([]string{ + fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), + fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + }), + })) + + Expect(executableInvocations[1]).To(MatchFields(IgnoreExtras, Fields{ + "Args": Equal([]string{ + "env", "info", "--path", + }), + "Dir": Equal(workingDir), + "Env": ContainElements([]string{ + fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), + fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + }), + })) + + Expect(venvDir).To(Equal("/some/path/to/some/venv")) + Expect(buffer.String()).To(ContainLines( + fmt.Sprintf(" Running 'POETRY_CACHE_DIR=%s POETRY_VIRTUALENVS_PATH=%s poetry install --with main'", cacheLayerPath, packagesLayerPath), + " //some/path/xyz/../to/some/venv//", + " stderr output", + )) + }) + + it("runs installation for main dependencies group when BP_POETRY_INSTALL_WITH is empty", func() { + t.Setenv("BP_POETRY_INSTALL_WITH", "") + dependencyGroups := os.Getenv("BP_POETRY_INSTALL_WITH") + Expect(dependencyGroups).To(BeEmpty()) + + venvDir, err := poetryInstallProcess.Execute(workingDir, packagesLayerPath, cacheLayerPath) + Expect(err).NotTo(HaveOccurred()) + + Expect(executable.ExecuteCall.CallCount).To(Equal(2)) + Expect(executableInvocations).To(HaveLen(2)) + + Expect(executableInvocations[0]).To(MatchFields(IgnoreExtras, Fields{ + "Args": Equal([]string{ + "install", "--with", "main", + }), + "Dir": Equal(workingDir), + "Env": ContainElements([]string{ + fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), + fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + }), + })) + + Expect(executableInvocations[1]).To(MatchFields(IgnoreExtras, Fields{ + "Args": Equal([]string{ + "env", "info", "--path", + }), + "Dir": Equal(workingDir), + "Env": ContainElements([]string{ + fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), + fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + }), + })) + + Expect(venvDir).To(Equal("/some/path/to/some/venv")) + Expect(buffer.String()).To(ContainLines( + fmt.Sprintf(" Running 'POETRY_CACHE_DIR=%s POETRY_VIRTUALENVS_PATH=%s poetry install --with main'", cacheLayerPath, packagesLayerPath), + " //some/path/xyz/../to/some/venv//", + " stderr output", + )) + }) + + it("runs installation with dev dependencies group when BP_POETRY_INSTALL_WITH=dev", func() { + devGroup := "dev" + t.Setenv("BP_POETRY_INSTALL_WITH", devGroup) venvDir, err := poetryInstallProcess.Execute(workingDir, packagesLayerPath, cacheLayerPath) Expect(err).NotTo(HaveOccurred()) @@ -78,12 +161,13 @@ func testInstallProcess(t *testing.T, context spec.G, it spec.S) { Expect(executableInvocations[0]).To(MatchFields(IgnoreExtras, Fields{ "Args": Equal([]string{ - "install", + "install", "--with", devGroup, }), "Dir": Equal(workingDir), "Env": ContainElements([]string{ fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + fmt.Sprintf("BP_POETRY_INSTALL_WITH=%s", devGroup), }), })) @@ -95,12 +179,13 @@ func testInstallProcess(t *testing.T, context spec.G, it spec.S) { "Env": ContainElements([]string{ fmt.Sprintf("POETRY_VIRTUALENVS_PATH=%s", packagesLayerPath), fmt.Sprintf("POETRY_CACHE_DIR=%s", cacheLayerPath), + fmt.Sprintf("BP_POETRY_INSTALL_WITH=%s", devGroup), }), })) Expect(venvDir).To(Equal("/some/path/to/some/venv")) Expect(buffer.String()).To(ContainLines( - fmt.Sprintf(" Running 'POETRY_CACHE_DIR=%s POETRY_VIRTUALENVS_PATH=%s poetry install'", cacheLayerPath, packagesLayerPath), + fmt.Sprintf(" Running 'POETRY_CACHE_DIR=%s POETRY_VIRTUALENVS_PATH=%s poetry install --with dev'", cacheLayerPath, packagesLayerPath), " //some/path/xyz/../to/some/venv//", " stderr output", )) diff --git a/integration/build_with_dependency_group_test.go b/integration/build_with_dependency_group_test.go new file mode 100644 index 0000000..a3e6103 --- /dev/null +++ b/integration/build_with_dependency_group_test.go @@ -0,0 +1,187 @@ +package integration_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" + . "github.com/paketo-buildpacks/occam/matchers" +) + +func testWithDependencyGroup(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + Eventually = NewWithT(t).Eventually + + pack occam.Pack + docker occam.Docker + ) + + it.Before(func() { + pack = occam.NewPack() + docker = occam.NewDocker() + }) + + context("when the buildpack is run with pack build", func() { + var ( + image occam.Image + container occam.Container + name string + source string + ) + + it.Before(func() { + var err error + name, err = occam.RandomName() + Expect(err).NotTo(HaveOccurred()) + + source, err = occam.Source(filepath.Join("testdata", "app_with_dependency_group")) + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed()) + Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed()) + Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed()) + Expect(os.RemoveAll(source)).To(Succeed()) + }) + + it("builds and runs successfully with dev dependency group", func() { + var err error + var logs fmt.Stringer + buildPackIdUnderscore := strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_") + + image, logs, err = pack.WithNoColor().Build. + WithPullPolicy("never"). + WithBuildpacks( + settings.Buildpacks.CPython.Online, + settings.Buildpacks.Pip.Online, + settings.Buildpacks.Poetry.Online, + settings.Buildpacks.PoetryInstall.Online, + settings.Buildpacks.BuildPlan.Online, + ). + WithEnv(map[string]string{"BP_POETRY_INSTALL_WITH": "dev"}). + Execute(name, source) + Expect(err).ToNot(HaveOccurred(), logs.String) + + Expect(logs).To(ContainLines( + MatchRegexp(fmt.Sprintf(`%s \d+\.\d+\.\d+`, buildpackInfo.Buildpack.Name)), + " Executing build process", + MatchRegexp(fmt.Sprintf( + " Running 'POETRY_CACHE_DIR=/layers/%s/cache POETRY_VIRTUALENVS_PATH=/layers/%s/poetry-venv poetry install --with dev'", + buildPackIdUnderscore, + buildPackIdUnderscore, + )), + )) + + aDependencyFromMainGroup := "Installing flask" + aDependencyFromDevGroup := "Installing pytest" + Expect(logs).To(ContainSubstring(aDependencyFromMainGroup)) + Expect(logs).To(ContainSubstring(aDependencyFromDevGroup)) + + Expect(logs).To(ContainLines(MatchRegexp(` Completed in \d+\.\d+`))) + + Expect(logs).To(ContainLines( + " Configuring build environment", + MatchRegexp(fmt.Sprintf(` PATH -> "/layers/%s/poetry-venv/default-app-.*-py\d+\.\d+/bin:\$PATH"`, buildPackIdUnderscore)), + MatchRegexp(fmt.Sprintf(` POETRY_VIRTUALENVS_PATH -> "/layers/%s/poetry-venv"`, buildPackIdUnderscore)), + MatchRegexp(fmt.Sprintf(` PYTHONPATH -> "/layers/%s/poetry-venv/default-app-.*-py\d+\.\d+/lib/python\d+\.\d+/site-packages:\$PYTHONPATH"`, buildPackIdUnderscore)), + )) + Expect(logs).To(ContainLines( + " Configuring launch environment", + MatchRegexp(fmt.Sprintf(` PATH -> "/layers/%s/poetry-venv/default-app-.*-py\d+\.\d+/bin:\$PATH"`, buildPackIdUnderscore)), + MatchRegexp(fmt.Sprintf(` POETRY_VIRTUALENVS_PATH -> "/layers/%s/poetry-venv"`, buildPackIdUnderscore)), + MatchRegexp(fmt.Sprintf(` PYTHONPATH -> "/layers/%s/poetry-venv/default-app-.*-py\d+\.\d+/lib/python\d+\.\d+/site-packages:\$PYTHONPATH"`, buildPackIdUnderscore)), + )) + + container, err = docker.Container.Run. + WithCommand("gunicorn server:app"). + WithEnv(map[string]string{"PORT": "8080"}). + WithPublish("8080"). + Execute(image.ID) + Expect(err).ToNot(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + Eventually(container).Should(Serve(ContainSubstring("Hello, World!")).OnPort(8080)) + }) + + context("validating SBOM", func() { + var ( + sbomDir string + ) + + it.Before(func() { + var err error + sbomDir, err = os.MkdirTemp("", "sbom") + Expect(err).NotTo(HaveOccurred()) + Expect(os.Chmod(sbomDir, os.ModePerm)).To(Succeed()) + }) + + it.After(func() { + Expect(os.RemoveAll(sbomDir)).To(Succeed()) + }) + + it("writes SBOM files to the layer and label metadata", func() { + var err error + var logs fmt.Stringer + buildPackIdUnderscore := strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_") + + image, logs, err = pack.WithNoColor().Build. + WithPullPolicy("never"). + WithBuildpacks( + settings.Buildpacks.CPython.Online, + settings.Buildpacks.Pip.Online, + settings.Buildpacks.Poetry.Online, + settings.Buildpacks.PoetryInstall.Online, + settings.Buildpacks.BuildPlan.Online, + ). + WithEnv(map[string]string{ + "BP_LOG_LEVEL": "DEBUG", + "BP_POETRY_INSTALL_WITH": "dev", + }). + WithSBOMOutputDir(sbomDir). + Execute(name, source) + Expect(err).ToNot(HaveOccurred(), logs.String) + + container, err = docker.Container.Run. + WithCommand("gunicorn server:app"). + WithEnv(map[string]string{"PORT": "8080"}). + WithPublish("8080"). + Execute(image.ID) + Expect(err).ToNot(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + Eventually(container).Should(Serve(ContainSubstring("Hello, World!")).OnPort(8080)) + + Expect(logs).To(ContainLines( + fmt.Sprintf(" Generating SBOM for /layers/%s/poetry-venv", buildPackIdUnderscore), + MatchRegexp(` Completed in \d+(\.?\d+)*`), + )) + Expect(logs).To(ContainLines( + " Writing SBOM in the following format(s):", + " application/vnd.cyclonedx+json", + " application/spdx+json", + " application/vnd.syft+json", + )) + + // check that all required SBOM files are present + Expect(filepath.Join(sbomDir, "sbom", "launch", buildPackIdUnderscore, "poetry-venv", "sbom.cdx.json")).To(BeARegularFile()) + Expect(filepath.Join(sbomDir, "sbom", "launch", buildPackIdUnderscore, "poetry-venv", "sbom.spdx.json")).To(BeARegularFile()) + Expect(filepath.Join(sbomDir, "sbom", "launch", buildPackIdUnderscore, "poetry-venv", "sbom.syft.json")).To(BeARegularFile()) + + // check an SBOM file to make sure it has an entry for a poetry dependency + contents, err := os.ReadFile(filepath.Join(sbomDir, "sbom", "launch", buildPackIdUnderscore, "poetry-venv", "sbom.cdx.json")) + Expect(err).NotTo(HaveOccurred()) + Expect(string(contents)).To(ContainSubstring(`"name": "flask"`)) + // optional dev dependencies should be included + Expect(string(contents)).To(ContainSubstring(`"name": "pytest"`)) + }) + }) + }) +} diff --git a/integration/default_test.go b/integration/default_test.go index 20c5777..21bdc39 100644 --- a/integration/default_test.go +++ b/integration/default_test.go @@ -72,11 +72,17 @@ func testDefault(t *testing.T, context spec.G, it spec.S) { MatchRegexp(fmt.Sprintf(`%s \d+\.\d+\.\d+`, buildpackInfo.Buildpack.Name)), " Executing build process", MatchRegexp(fmt.Sprintf( - " Running 'POETRY_CACHE_DIR=/layers/%s/cache POETRY_VIRTUALENVS_PATH=/layers/%s/poetry-venv poetry install'", + " Running 'POETRY_CACHE_DIR=/layers/%s/cache POETRY_VIRTUALENVS_PATH=/layers/%s/poetry-venv poetry install --with main'", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), )), )) + + aDependencyFromMainGroup := "Installing flask" + aDependencyFromDevGroup := "Installing pytest" + Expect(logs).To(ContainSubstring(aDependencyFromMainGroup)) + Expect(logs).NotTo(ContainSubstring(aDependencyFromDevGroup)) + Expect(logs).To(ContainLines(MatchRegexp(` Completed in \d+\.\d+`))) Expect(logs).To(ContainLines( " Configuring build environment", @@ -166,6 +172,8 @@ func testDefault(t *testing.T, context spec.G, it spec.S) { contents, err := os.ReadFile(filepath.Join(sbomDir, "sbom", "launch", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), "poetry-venv", "sbom.cdx.json")) Expect(err).NotTo(HaveOccurred()) Expect(string(contents)).To(ContainSubstring(`"name": "flask"`)) + // optional dev dependencies should not be included + Expect(string(contents)).NotTo(ContainSubstring(`"name": "pytest"`)) }) }) }) diff --git a/integration/init_test.go b/integration/init_test.go index 475a757..0e5f45d 100644 --- a/integration/init_test.go +++ b/integration/init_test.go @@ -104,5 +104,6 @@ func TestIntegration(t *testing.T) { suite := spec.New("Integration", spec.Report(report.Terminal{})) suite("Default", testDefault, spec.Parallel()) + suite("WithDependencyGroup", testWithDependencyGroup) suite.Run(t) } diff --git a/integration/testdata/app_with_dependency_group/plan.toml b/integration/testdata/app_with_dependency_group/plan.toml new file mode 100644 index 0000000..d8ff3bd --- /dev/null +++ b/integration/testdata/app_with_dependency_group/plan.toml @@ -0,0 +1,11 @@ +[[requires]] + name = "poetry-venv" + + [requires.metadata] + launch = true + +[[requires]] + name = "cpython" + + [requires.metadata] + launch = true diff --git a/integration/testdata/app_with_dependency_group/poetry.lock b/integration/testdata/app_with_dependency_group/poetry.lock new file mode 100644 index 0000000..c399c1c --- /dev/null +++ b/integration/testdata/app_with_dependency_group/poetry.lock @@ -0,0 +1,286 @@ +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flask" +version = "2.3.2" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, + {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.3" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] + +[package.dependencies] +setuptools = ">=3.0" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[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 = "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 = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +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 = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "96b70c303c02442f03d10140b8500e6f38d7732098b7431b3fb52d42cf146225" diff --git a/integration/testdata/app_with_dependency_group/pyproject.toml b/integration/testdata/app_with_dependency_group/pyproject.toml new file mode 100644 index 0000000..02a47dd --- /dev/null +++ b/integration/testdata/app_with_dependency_group/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "default_app" +version = "0.1.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.10" +Flask = "^2.0.3" +gunicorn = "^20.1.0" + +[tool.poetry.dev-dependencies] +pytest = "^7.4.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/integration/testdata/app_with_dependency_group/server.py b/integration/testdata/app_with_dependency_group/server.py new file mode 100644 index 0000000..e5ba562 --- /dev/null +++ b/integration/testdata/app_with_dependency_group/server.py @@ -0,0 +1,9 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello_world(): + return 'Hello, World!' + +if __name__ == "__main__": + app.run()