-
Python 프로젝트가 여러 개일 때 uv workspace로 묶어도 될까Programming 2026. 9. 17. 22:52728x90반응형

함께 관리할 구성과 독립시킬 구성을 표현한 AI 생성 이미지입니다. 함께 해결할 수 있는 의존성을 가진 패키지는 uv workspace로 묶고, 환경 요구가 충돌하면 독립 프로젝트 경로를 검토하세요.
Python 저장소 하나에 웹앱, 공통 라이브러리, 관리 도구가 함께 들어 있다면 설정 파일이 여러 개 생깁니다. 이때 workspace를 쓰면 정리가 될 것 같지만, 폴더를 한데 모으는 기능만은 아닙니다. 여러 패키지의 의존성을 함께 관리하겠다는 선택이 들어갑니다.
따라서 “같은 Git 저장소에 있으니 묶는다”보다 “같은 의존성 해석과 환경을 공유해도 되는가”를 먼저 물어야 합니다. 이 글은 2026년 9월 13일 uv 공식 문서 기준의 설계 설명입니다. 아래 예시는 기존 패키지에 추가할 관련 설정이며, 실제 저장소의 lockfile을 바꾸거나 패키지를 설치한 기록은 아닙니다.
패키지별 설정은 남고 lockfile은 공유합니다
workspace의 각 멤버는 자신의
pyproject.toml을 가집니다. 이름, 버전, 의존성 선언을 패키지별로 유지하되 workspace 전체는 하나의 lockfile을 공유합니다.uv lock은 전체 workspace의 조건을 함께 다룹니다. 멤버별 파일이 있다는 이유로 의존성 해석도 완전히 독립적이라고 생각하면 안 됩니다. uv workspace의 기본 구조이 방식은 서로 연결된 라이브러리와 앱을 함께 개발할 때 유용합니다. 공통 라이브러리를 수정한 뒤 이를 사용하는 앱에서도 바뀐 내용을 확인하기 좋습니다. 반면 전혀 다른 배포 주기와 환경을 가진 도구들을 디렉터리가 가깝다는 이유로 묶으면, 한쪽 변경이 다른 쪽의 의존성 조건까지 건드릴 수 있습니다.
여기서 lock과 sync도 구분합니다. lock은 의존성 조건을 해결해 잠금 파일을 만드는 과정이고, sync는 그 결과 중 필요한 패키지를 환경에 설치하는 과정입니다. 하나의 lockfile을 쓴다는 사실이 모든 실행에서 모든 멤버를 무조건 실행한다는 뜻은 아닙니다. 잠금과 환경 동기화의 차이
어떤 조합을 함께 풀지와 어떤 패키지를 대상으로 실행할지를 따로 정하면 workspace의 효과와 비용을 이해하기 쉽습니다.
가상 메모 앱과 공통 라이브러리를 묶어 봅니다
설명용 저장소에는 루트 앱
note-app과 공통 라이브러리note-core가 있다고 하겠습니다. 두 패키지는 이미 각자의 코드와 빌드 설정을 갖추고 있으며, 앱은python -m note_app으로 실행할 수 있다는 전제입니다.notes-project/ ├── pyproject.toml ├── src/note_app/__main__.py └── packages/ └── note-core/ ├── pyproject.toml └── src/note_core/__init__.py루트
pyproject.toml에서 관계를 나타내는 부분은 다음과 같습니다. 기존 패키지의 빌드 설정이나 다른 필드를 지우고 통째로 대체하는 파일이 아닙니다.[project] name = "note-app" version = "0.1.0" requires-python = ">=3.12" dependencies = ["note-core"] [tool.uv.workspace] members = ["packages/note-core"] [tool.uv.sources] note-core = { workspace = true }라이브러리 쪽에는 자신의 프로젝트 이름과 조건이 남습니다. 역시 관련 필드만 보여 주는 조각입니다.
[project] name = "note-core" version = "0.1.0" requires-python = ">=3.12" dependencies = []세 선언의 역할은 다릅니다.
members는 함께 관리할 디렉터리를 고릅니다. 앱의dependencies는 앱이 라이브러리를 필요로 한다는 관계를 선언합니다.workspace = true는 그 의존성을 공개 패키지 저장소가 아니라 이 workspace의 멤버에서 가져오도록 지정합니다. 개발용 의존성 소스와 workspace 멤버폴더를 멤버 목록에 추가했다고 앱의 의존성 선언까지 자동으로 대신하는 것은 아닙니다. 또한 배포 패키지 이름의 하이픈과 import할 모듈 이름의 밑줄을 구분해야 합니다. 예시의 프로젝트 이름은
note-core, 코드 디렉터리는note_core입니다.멤버를 선택해도 전체 조건의 충돌은 남습니다
설정을 검토한 시험 환경에서 초기 잠금 파일을 만들고 루트 앱의 환경을 준비하는 흐름은 다음처럼 나눌 수 있습니다.
uv lock은 파일을 만들거나 바꿀 수 있고,uv sync는 설치 환경을 바꿉니다. 실제 작업 폴더와 기존 변경을 확인한 뒤 실행할 명령입니다.uv lock uv sync --locked --package note-app uv run --locked --package note-app python -m note_app--package는 대상 멤버를 명시합니다. 공식 문서는uv run과uv sync가 기본적으로 workspace 루트를 대상으로 하며, 다른 멤버를 선택할 때 이 옵션을 사용할 수 있다고 설명합니다. 멤버 이름은 폴더 경로를 추측해 넣기보다 해당project.name과 맞춥니다. 실행 대상 선택하지만
--package note-app을 붙였다고 다른 멤버의 요구를 무시하는 별도 lockfile이 생기는 것은 아닙니다. 전체 해석이 불가능한 조건이라면 실행 대상을 바꾸는 것만으로 근본 충돌이 해결되지 않습니다.또
--locked는 잠금 파일이 현재 선언과 맞는지 확인하고, 갱신이 필요하면 자동 수정 대신 오류로 멈추게 하는 옵션입니다. 이미 설치된 환경을 절대로 건드리지 않는다는 뜻은 아닙니다. 환경 동기화와 잠금 파일 변경은 서로 다른 작업입니다. 자동 잠금·동기화와 옵션처음 준비한 뒤 검사만 하려는 단계에서는
uv lock --check로 선언과 잠금 파일의 관계를 확인할 수 있습니다. 새 버전이 나왔다는 이유만으로 기존 lockfile이 무조건 오래된 것으로 판정되는 것은 아니므로, 정합성 확인과 의존성 업그레이드도 구분합니다.Python 지원 범위는 교집합으로 좁아집니다
workspace 전체의 Python 요구는 멤버들의
requires-python교집합을 사용합니다. 합쳐 놓으면 각 패키지가 원하는 Python을 독립적으로 하나씩 선택해 준다고 기대해서는 안 됩니다. workspace의 Python 요구 조건예를 들어 앱은
>=3.12,<3.14, 라이브러리는>=3.11이라고 하겠습니다. 함께 사용할 수 있는 범위는>=3.12,<3.14입니다. 이 workspace에서 앱과 라이브러리를 함께 검사했다고 해서 라이브러리가 선언한 Python 3.11 지원까지 확인한 것은 아닙니다.더 분명한 반례도 있습니다. 오래된 도구는
>=3.10,<3.12만 지원하고 새 앱은>=3.12를 요구한다면 공통 범위가 없습니다. 선언 숫자만 넓혀 의존성 해석을 통과시키기 전에 실제 코드와 의존 패키지가 그 버전에서 동작하는지부터 확인해야 합니다.requires-python은 패키지가 지원한다고 선언한 Python 조건이며 의존성 선택에도 영향을 줍니다. 문제를 숨기려고 선언을 느슨하게 바꾸면 잠금 성공과 실제 지원 사이의 간격만 커질 수 있습니다. 프로젝트 Python 버전 요구이런 경우에는 독립 프로젝트로 유지하고 필요한 패키지만 경로 의존성으로 연결하는 방식을 검토할 수 있습니다. 한 저장소 안에서 여러 환경을 관리하는 선택도 가능합니다. Git 저장소 경계와 Python 환경 경계를 꼭 같게 맞출 필요는 없습니다.
같은 환경에서 import됐다고 선언이 완전한 것은 아닙니다
workspace의 공유 환경은 개발을 편하게 하지만 숨은 의존성을 가릴 수 있습니다. 공식 문서도 Python의 특성상 어떤 멤버가 다른 멤버에만 선언된 의존성을 import하는 일을 uv가 완전히 막지는 못한다고 설명합니다. 멤버 간 의존성 격리의 한계
가상으로 앱에만 외부 패키지
helper-lib를 선언했는데 공통 라이브러리 코드가 그것을 직접 import한다고 해 보겠습니다. 둘이 함께 있는 환경에서는 실행될 수 있어도, 라이브러리만 배포한 환경에서는 필요한 패키지가 설치되지 않을 수 있습니다. 이 이름은 설명용이며 실제 패키지를 설치하라는 지시가 아닙니다.그래서 공통 라이브러리를 따로 배포할 계획이라면 공유 환경의 테스트 외에 단독 설치 환경에서도 확인해야 합니다. 해당 라이브러리가 직접 쓰는 의존성을 자신의 선언에 넣었는지, 배포물에 필요한 모듈이 포함됐는지를 봅니다.
빌드 시스템도 이 확인에 들어갑니다. workspace 설정만 추가하고 패키징 준비를 빠뜨리면 import나 명령 진입점이 기대대로 설치되지 않을 수 있습니다. uv 문서는 빌드 시스템이 정의되지 않은 프로젝트의 설치 동작과 패키징 선택을 별도로 설명합니다. 빌드와 패키징 설정
묶은 뒤에는 앱 실행과 배포 경계를 모두 확인합니다
앞의 메모 앱 예시에서는 먼저 잠금이 성공하는지와 의도한 로컬
note-core를 사용하는지 확인합니다. 같은 이름의 외부 패키지를 가져오고 있지 않은지, 빌드와 import 대상이 맞는지를 살핍니다. 그다음 앱에서 라이브러리의 동작을 호출하는 테스트를 실행합니다.라이브러리를 독립적으로 배포한다면 별도 환경의 검사도 남깁니다. 특히 workspace 교집합 밖에 있는, 라이브러리만의 지원 Python 버전은 그 환경에서 따로 검증해야 합니다. 앱의 최신 환경에서 한 번 성공한 것으로 모든 지원 버전을 대표하지 않습니다.
검사 기록에는 멤버 목록, 각 Python 지원 범위, 잠금 파일 변경, 실행 대상, 단독 배포 검사 여부를 적으면 됩니다. 이번 예시는 이를 설계한 설명이며 실제 테스트 성공 결과는 없습니다. 검사를 수행하지 않은 범위는 미확인으로 남깁니다.
uv workspace가 적합한 상황은 함께 바꾸고 함께 해결할 수 있는 의존성이 있을 때입니다. 반대로 멤버마다 독립된 환경이 필요하거나 요구가 충돌한다면 따로 관리하는 편이 요구를 정확히 드러낼 수 있습니다. 설정 파일 수를 줄이는 것보다 실제 개발·검사·배포 경계에 맞는 구성을 선택하세요.
728x90반응형'Programming' 카테고리의 다른 글
npm 로그인은 되는데 배포가 막혔다면: 복구 코드 사용 뒤 확인할 것 (0) 2026.09.19 API 키가 들어간 PR, push protection을 통과해도 머지를 막을 수 있을까 (0) 2026.09.17 파이썬 파일 하나만 보내도 실행되게 만들기: uv 스크립트 의존성 (0) 2026.09.17 같은 한글 파일명인데 검색이 안 맞을 때: NFC와 NFD (0) 2026.09.17 브라우저에 저장한 메모가 사라질 수 있을까 (0) 2026.09.16