diff --git a/pyproject.toml b/pyproject.toml index 829d3cd..b4c3d7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "fastapi-cloud-cli" dynamic = ["version"] description = "Deploy and manage FastAPI Cloud apps from the command line 🚀" authors = [{ name = "Patrick Arminio", email = "patrick@fastapilabs.com" }] -requires-python = ">=3.10" +requires-python = ">=3.11" readme = "README.md" license = { text = "MIT" } classifiers = [ @@ -22,7 +22,6 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", diff --git a/src/fastapi_cloud_cli/commands/deploy/archive.py b/src/fastapi_cloud_cli/commands/deploy/archive.py index 795dcf0..eaa9b54 100644 --- a/src/fastapi_cloud_cli/commands/deploy/archive.py +++ b/src/fastapi_cloud_cli/commands/deploy/archive.py @@ -7,6 +7,8 @@ import rignore from pydantic import AfterValidator +import tomllib + logger = logging.getLogger(__name__) @@ -42,9 +44,32 @@ def validate_app_directory(v: str | None) -> str | None: AppDirectory = Annotated[str | None, AfterValidator(validate_app_directory)] +def _project_name_from_pyproject(path: Path) -> str: + if not path.exists(): + return "" + + try: + with path.open("rb") as f: + data = tomllib.load(f) + + project_name = data.get("project", {}).get("name") + if project_name: + return str(project_name) + + poetry_name = data.get("tool", {}).get("poetry", {}).get("name") + if poetry_name: + return str(poetry_name) + + except Exception: + return "" + + return "" def _get_app_name(path: Path) -> str: - # TODO: use pyproject.toml to get the app name + pyproject_path = path / "pyproject.toml" + name = _project_name_from_pyproject(pyproject_path) + if name: + return name return path.name diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 1dbf4a5..0df1d12 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -22,6 +22,8 @@ from fastapi_cloud_cli.utils.api import StreamLogError, TooManyRetriesError from tests.conftest import ConfiguredApp from tests.utils import Keys, build_logs_response, changing_dir, create_jwt_token +from fastapi_cloud_cli.commands.deploy.archive import _project_name_from_pyproject + runner = CliRunner() @@ -2494,3 +2496,70 @@ def test_invalid_large_file_threshold( assert result.exit_code == 2 assert "Invalid value for '--large-file-threshold'" in result.output + +def test_project_name_from_pyproject_project_section(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project] +name = "my-app" +""", + encoding="utf-8", + ) + + result = _project_name_from_pyproject(pyproject) + + assert result == "my-app" + + +def test_project_name_from_pyproject_poetry_section(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[tool.poetry] +name = "my-poetry-app" +""", + encoding="utf-8", + ) + + result = _project_name_from_pyproject(pyproject) + + assert result == "my-poetry-app" + + +def test_project_name_from_pyproject_missing_file(tmp_path: Path) -> None: + pyproject = tmp_path / "missing.toml" + + result = _project_name_from_pyproject(pyproject) + + assert result == "" + + +def test_project_name_from_pyproject_missing_name(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project] +version = "0.1.0" +""", + encoding="utf-8", + ) + + result = _project_name_from_pyproject(pyproject) + + assert result == "" + + +def test_project_name_from_pyproject_invalid_toml(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project +name = "invalid" +""", + encoding="utf-8", + ) + + result = _project_name_from_pyproject(pyproject) + + assert result == "" \ No newline at end of file