pyproject.toml 없는 외부 repo 패키지화 전략

setuptools 설정 작성, 비표준 디렉토리 매핑, PR 반영 흐름

Python 패키지 설치 도구(pip, Poetry)는 pyproject.toml 또는 setup.py가 없는 repo를 패키지로 인식하지 못한다. 이 글에서는 패키지화 조건의 일반 원칙을 먼저 정리하고, 수정 권한이 없거나 PR을 통해서만 반영 가능한 외부 repo에 pyproject.toml을 추가하고 Poetry로 설치하는 전체 흐름을 다룬다. 비표준 디렉토리 매핑, archive 상태 우회, local path 임시 설치, git URL 전환까지 포함한다.

Engineering
DevOps
Python
Poetry
Security
SSH
저자

Kwangmin Kim

공개

2025년 03월 09일

1 개요

Python 패키지 설치 도구가 외부 repo를 인식하려면 반드시 pyproject.toml 또는 setup.py가 있어야 한다. 이 파일이 없으면 pip와 Poetry 모두 설치를 거부한다. 수정 권한이 없는 외부 repo라면 PR을 통해 파일을 추가하고, 승인 대기 중에는 local path로 임시 설치하는 전략이 실용적이다.

1.1 이 글이 다루는 상황

외부 repo (pyproject.toml 없음)
        ↓  poetry add git+ssh://...
ERROR: Source does not appear to be a Python project
        ↓  해결 방법?
pyproject.toml 설계 → PR 반영 → git URL 설치

실무에서 이 상황은 다음과 같은 경우에 자주 발생한다:

  • 데이터 분석/연구 목적으로 만들어진 repo (패키징 고려 없이 작성)
  • 오래된 legacy repo (setup.py도 없는 경우)
  • archive 상태 repo (직접 push 불가)
  • 내가 관리하지 않는 조직 repo (PR을 통해서만 기여 가능)

2 Python 패키지화의 일반 원칙

2.1 패키지로 인식되기 위한 조건

pip, Poetry 등 Python 패키지 설치 도구는 아래 파일 중 하나가 있어야 패키지로 인식한다.

파일 방식 비고
pyproject.toml 현대적 표준 (PEP 517/518) Poetry, setuptools, Hatch 등 지원
setup.py 구식 방식 여전히 동작하지만 deprecated 추세
setup.cfg setup.py의 선언형 버전 단독으로는 불충분, setup.py 필요

이 파일들이 없으면:

Source does not appear to be a Python project:
no pyproject.toml or setup.py

2.2 표준 Python 패키지 디렉토리 구조

패키지 설치 도구가 기대하는 구조는 아래와 같다:

repo/
├── pyproject.toml       ← 필수
├── README.md
├── src/
│   └── my_package/      ← src layout (권장)
│       ├── __init__.py
│       └── module.py
└── tests/

또는 flat layout:

repo/
├── pyproject.toml
├── my_package/          ← flat layout
│   ├── __init__.py
│   └── module.py
└── tests/

2.3 비표준 디렉토리명 처리

디렉토리명이 source/, lib/, code/ 등 비표준인 경우 [tool.setuptools.package-dir]로 매핑한다:

[tool.setuptools.package-dir]
"import_name" = "실제_디렉토리명"

이 설정이 없으면 setuptools는 해당 디렉토리를 패키지로 인식하지 못한다.

2.4 배포 이름 vs import 이름

구분 예시 사용처
배포 이름 (distribution name) sg-data-standardization pip install, pyproject.tomlname
import 이름 (package name) data_standardization import data_standardization

두 이름이 달라도 되지만, pyproject.toml에서 명확히 매핑해야 한다.

3 실전 사례: data_standardization repo 패키지화

조직 내부 repo에 pyproject.toml이 없어 Poetry로 설치가 불가한 상황. repo가 archive 상태라 직접 push도 불가하다. PR을 통해 파일을 추가하면서, 승인 대기 중에는 local path로 임시 설치한다.

3.1 대상 repo 구조 파악

먼저 repo를 clone해서 실제 구조를 확인한다.

git clone git@seegene_org:SeegeneDevelopmentPlatform/data_standardization.git /tmp/ds-check
ls /tmp/ds-check/
README.md  current_analysis  data_hierarchy     output  standardization_rules
css        data              domain_dictionary  source  term_dictionary

source/ 디렉토리에 실제 Python 코드가 있다.

ls /tmp/ds-check/source/
__init__.py               readme.md            troubleshooter.py
abbreviation_manager.py   report_generator.py  update_physical_names.py
completeness_analyzer.py  rule_analyzer.py     vocabulary_analyzer.py
data_loader.py            token_analysis.py
output                    token_processor.py
노트

일반적인 Python 패키지 구조라면 src/패키지명/ 또는 패키지명/이 루트에 있어야 한다. 이 repo는 source/라는 비표준 디렉토리명을 사용하고 있어 [tool.setuptools.package-dir]로 매핑이 필요하다.

3.2 __init__.py 내용 확인

패키지 이름과 공개 API를 파악하기 위해 __init__.py를 확인한다.

# source/__init__.py
from .abbreviation_manager import AbbreviationManager
from .token_processor import TokenProcessor
from .rule_analyzer import RuleAnalyzer
from .data_loader import DataLoader
# ...

__all__ = ["AbbreviationManager", "TokenProcessor", ...]
__version__ = "1.0.0"

import data_standardization 시 이 클래스들이 노출된다.

4 pyproject.toml 설계

4.1 결정해야 할 항목

항목 결정 이유
빌드 백엔드 setuptools 기존 코드 변경 최소화, 범용성
배포 이름 sg-data-standardization 조직 prefix로 충돌 방지
import 이름 data_standardization 간결하고 직관적
패키지 경로 source/data_standardization 매핑 기존 디렉토리명 유지
Python 버전 >=3.11 프로젝트 최소 버전

4.2 최종 pyproject.toml 작성

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "sg-data-standardization"
version = "1.0.0"
description = "데이터베이스 테이블/컬럼 명명 규칙 검증 및 물리명 자동 생성 패키지"
readme = "source/readme.md"
requires-python = ">=3.11"
license = { text = "Proprietary" }
authors = [
    { name = "김광민" },
]

dependencies = [
    "pandas>=1.5.0",
    "openpyxl>=3.0.0",
    "tqdm>=4.64.0",
]

[project.optional-dependencies]
analysis = [
    "numpy>=1.20.0",
    "matplotlib>=3.5.0",
    "seaborn>=0.11.0",
]

# 핵심: source/ 디렉토리를 data_standardization 이름으로 노출
[tool.setuptools]
packages = ["data_standardization"]

[tool.setuptools.package-dir]
"data_standardization" = "source"

[tool.setuptools.package-data]
"data_standardization" = ["*.md", "*.txt"]

4.3 [tool.setuptools.package-dir] 동작 방식

repo 실제 구조:          Python에서 보이는 구조:
source/              →   data_standardization/
  __init__.py              __init__.py
  abbreviation_manager.py  abbreviation_manager.py
  ...                      ...
# 이렇게 import 가능
import data_standardization
from data_standardization import AbbreviationManager

5 PR을 통한 pyproject.toml 반영 흐름

5.1 archive 상태 repo의 경우

repo가 archive 상태이면 직접 push가 불가하다.

$ git push
ERROR: Repository is archived.

archive는 GitHub에서 repo를 읽기 전용으로 만드는 기능이다. 관리자에게 unarchive를 요청한 후 PR 흐름을 진행한다.

5.2 전체 PR 흐름

1. repo 관리자에게 unarchive 요청
        ↓
2. feature 브랜치 생성
   git checkout -b docs/packaging
        ↓
3. pyproject.toml 추가 및 commit
   git add pyproject.toml
   git commit -m "feat: add pyproject.toml for pip/poetry packaging"
        ↓
4. remote에 push
   git push origin docs/packaging
        ↓
5. PR 생성 → 코드 리뷰 → main 브랜치에 merge
        ↓
6. 소비자 프로젝트에서 poetry install로 git URL 설치 완료

6 PR 승인 전 임시 local path 설치

PR 승인을 기다리는 동안 개발을 멈출 필요 없다. local clone에 pyproject.toml을 직접 추가하고 local path 의존성으로 임시 설치한다.

# local clone에 pyproject.toml 추가 (untracked 파일로 존재)
cp pyproject.toml /home/azureuser/projects/data_standardization/

# agent 프로젝트에서 local path로 설치
cd /home/azureuser/projects/kmkim/agent
poetry add /home/azureuser/projects/data_standardization

pyproject.toml의 의존성 항목:

# PR 승인 전 (local path)
"sg-data-standardization @ file:///home/azureuser/projects/data_standardization"

설치 확인:

python -c "import data_standardization; print(dir(data_standardization))"
# ['AbbreviationManager', 'DataLoader', 'RuleAnalyzer', 'TokenProcessor', ...]

7 PR 승인 후 git URL로 전환

PR이 merge되어 remote에 pyproject.toml이 반영되면 file:// 경로를 git URL로 교체한다.

# PR 승인 후 (git URL)
"sg-data-standardization @ git+ssh://git@seegene_org/SeegeneDevelopmentPlatform/data_standardization.git@main"
poetry lock && poetry install

출력:

  - Updating sg-data-standardization
    (1.0.0 /home/.../data_standardization -> 1.0.0 f3354b9)

local path에서 remote commit(f3354b9)으로 전환 완료다.

8 git pull 시 충돌 주의

local clone에 untracked 파일(pyproject.toml)이 있는 상태에서 remote에 같은 파일이 추가되면 git pull이 실패한다.

error: The following untracked working tree files would be overwritten by merge:
        pyproject.toml
Please move or remove them before you merge.
Aborting

해결:

# local에 만든 pyproject.toml 제거 후 pull
rm pyproject.toml
git pull

이후 remote의 pyproject.toml이 자동으로 내려온다.

9 요약

항목 내용
패키지화 조건 pyproject.toml 또는 setup.py 필요
비표준 디렉토리 매핑 [tool.setuptools.package-dir]로 해결
배포 이름 vs import 이름 name 필드와 package-dir 키로 분리 관리
archive repo 우회 unarchive 요청 → PR → merge 흐름
개발 연속성 PR 승인 전 local path로 임시 설치
전환 방법 file://git+ssh:// 교체 후 poetry lock
git pull 충돌 untracked 파일 제거 후 pull

다음 블로그에서는 local path에서 git URL로 전환하는 전체 흐름poetry.lock에서 올바르게 설치됐는지 검증하는 방법을 다룬다.

Subscribe

Enjoy this blog? Get notified of new posts by email: