2024년 11월 24일 일요일

파이썬 상대 경로 임포트

ImportError: attempted relative import with no known parent package
임포트를 상대 경로로 지정하면 편할 것 같아 써보니 이런 에러가 난다.
검색해보면 -m 옵션을 쓰면 된다는데, 정리할겸 테스트를 해봤다. 
다음과 같이 준비를 한다.
$ mkdir -p test/src/foo
$ cd test/src
$ touch foo/{__init__,bar}.py
$ cat << EOF > main.py
from foo import bar

def main():
    print('ok')

if __name__ == '__main__':
    main()
EOF

디렉토리 구조는 이렇게 된다. main.py 이외는 빈 파일이다.
test
└── src
    ├── foo
    │    ├── __init__.py
    │    └── bar.py
    └── main.py

1. 먼저 src/에서 실행해 본다.
$ python main.py     - ok
$ python -m main     - ok
잘 동작한다.
이 번에 하나 위에서 실행해 본다.
$ cd ..
$ python src/main.py # ok
$ python -m src.main # ModuleNotFoundError: No module named 'foo'
foo는 이 디렉토리엔 없다. 하나 아래인 src/에 있다.
앞의 3개는 main.py가 있는 곳에서 임포팅을 시도하는 것 같은데,
마지막 것은 현제 디렉토리에서 하고 있는 듯 하다.

2. 이 번엔 foo 앞에 .을 붙여 본다. (상대 경로 지정)
sed -i 's/foo/.&/' src/main.py

src/에서 실행해 보면,
$ cd src
$ python main.py     # ImportError: attempted relative import with no known parent package
$ python -m main     # ImportError: attempted relative import with no known parent package
문제의 임포트 에러가 나왔다.
하나 위에서 실행해 본다.
$ cd ..
$ python src/main.py # ImportError: attempted relative import with no known parent package
$ python -m src.main # ok
1에서 성공했던 앞의 세 명령은 실패했고, 실패했던 마지막 명령은 성공했다.

3. 하는 김에 src도 붙여 본다.
$ sed -i 's/\.foo/src&/' src/main.py
$ cd src
$ python main.py     # ModuleNotFoundError: No module named 'src'
$ python -m main     # ModuleNotFoundError: No module named 'src'
$ cd ..
$ python src/main.py # ModuleNotFoundError: No module named 'src'
$ python -m src.main # ok
앞의 두 명령은 src가 없으니 그렇다쳐도 3번째 명령도 src가 없다고 한다.

지금까지만 보면 python으로 실행하면 임포트 기준이 대상 파일이 있는 디렉토리고
python -m 으로 실행하면 명령을 실행하는 디렉토리인 것 같다.

4. 하나 더 올라가보면
$ cd ..
$ python -m test.src.main # ModuleNotFoundError: No module named 'src'
$ sed -i 's/src.foo/.foo/' test/src/main.py 
$ python -m test.src.main # ok
test/ 위로 올라오니 src가 없다고 에러가 났다.
src없이 상대 경로로 바꾸니 정상 동작을 한다.
상대 경로의 좋은점이다.

지금까지의 테스트를 보면
실행(?) 파일이 있는 디렉토리를 기준으로 임포트 경로를 잡으면 어디에서 실행시키든 문제가 없다.
상대 경로를 쓸거면 -m 옵션으로 실행하고 실행 디렉토리가 임포트의 기준(top-level)이 된다.

5. 하나를 더 보면
$ cd test
$ mkdir lib
$ touch lib/__init__.py
$ sed -i '1i from .. import lib' src/main.py
$ python -m src.main # ImportError: attempted relative import beyond top-level package
src와 같은 레벨에 있는 lib을 상대경로로 지정하면 top-level을 넘을 수 없다고 나온다.
$ cd ..
$ python -m test.src.main # ok
이 경우 src와 lib를 포함하도록 하나 더 위에서 실행하면 된다.

추가로,
1의 상태에서 다음과 같이 추가한다.
$ cat << EOF > src/foo/bar.py
from .baz import foobar
EOF
$ cat << EOF > src/foo/baz.py
foobar = 'foobar'
EOF
$ python src/main.py
bar.py는 상대 경로를 쓰고 있는데 아무런 문제가 없다.
오히려 .를 없애면 baz를 찾을 수 없다는 에러가 나온다. (foo.baz는 괜찮다)

애초부터 최초의 임포트에러는 top-level까지 올라왔을 때만 발생하는 듯 하다.

2024년 9월 17일 화요일

pi-hole 포트 충돌

osmc에 pihole을 사용 중인데,
apt dist-upgrade한 뒤 (rbp4-mediacenter-osmc 21.1.0-4),  pihole이 동작을 안한다.
확인 해보니 53포트가 충돌나서인데 처음 설치시 했던 설정이 먹히지 않는다.

$ sudo find /etc/systemd | grep connman
/etc/systemd/system/multi-user.target.wants/connman.service

$ cat /etc/systemd/system/multi-user.target.wants/connman.service
[Unit]
Description=Connman connection service
After=dbus.service network-pre.target wpa_supplicant.service
Wants=network.target remote-fs-pre.target
Before=remote-fs-pre.target network.target

[Service]
Type=dbus
BusName=net.connman
EnvironmentFile=-/etc/osmc/prefs.d/connman
Restart=always
RestartSec=5
ExecStart = /usr/bin/start-network
ExecStopPost = /bin/sleep 2
StandardOutput=null

[Install]
WantedBy=multi-user.target

업데이트해서인지 EnvironmentFile이 다르다.

새로운 EnvironmentFile에도 noproxy로 설정을 바꾼다.
 
$ sudo sed -i 's/dnsproxy=yes/dnsproxy=no/' /etc/osmc/prefs.d/connman
$ sudo systemctl restart connman
$ ps -ef | grep connman
root      3787     1  0 11:32 ?        00:00:00 /usr/sbin/connmand -n --nodnsproxy --config=/etc/connman.conf

이제 정상으로 돌아왔다.

2024년 4월 27일 토요일

obsidian + syncthing

obsidian (개인적이고 유연한 글쓰기 앱)을 알게되어 설치해 봤다.
기기 간의 동기화는 유료여서 동기화에는 syncthing을 사용한다.

1. 기기 및 공유 디렉토리
우분투 22.04
  ~/sync/obsidian
안드로이드 폰
  내부저장소/sync/obsidian
* 두 기기의 obsidian 디렉토리를 동기화 시킨다.
* wi-fi로 연결된 같은 네트워크

2. ubuntu 설정
  2.1. obsidian
    2.1.1. 설치
$ wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.5.12/obsidian_1.5.12_amd64.deb
$ sudo dpkg -i obsidian_1.5.12_amd64.deb
$ mkdir ~/sync/obsidian
$ obsidian

     2.1.2. 보관소 생성
English -> 한국어
새 보관소 생성 >
  위치: 탐색 > 홈 > sync > 열기
  보관소 이름: obsidian
생성

  2.2. syncthing
우분투 설치 가이드에 따라 apt로 설치하고 systemd 서비스로 등록해도 된다.
* 여기서는 도커 설치 가이드에 따라 도커로 설치. (추천에 따라 host 네트워크 사용)
  2.2.1 설치
$ cd ~/sync
$ mkdir -p ./syncthing/obsidian
$ cat docker-compose.yml
services:
  syncthing:
    image: syncthing/syncthing:1.27.6
    container_name: syncthing
    hostname: syncthing
    environment:
      - PUID=1000  # id -u 값 설정
      - PGID=1000  # id -g 값 설정 
volumes: - ./syncthing:/var/syncthing - ./obsidian:/var/syncthing/obsidian network_mode: host restart: unless-stopped $ docker compose up -d syncthing

  2.2.2 설정
브라우저로 http://localhost:8384/에 연결
GUI 인증 > 설정 > GUI > GUI 인증 사용자, GUI 인증 비밀번호 입력 > 저장

3. 안드로이드 폰 설정
  3.1. obsidian
    3.1.1. 설치
      Play 스토어 > 검색 > obsidian > 설치

     3.1.2. 보관소 생성
Create new vault >
  Vault location: Choose > 내부저장소 > sync(생성) > 선택
  Vault name: obsidian
Create 탐색기?(왼쪽 위) > 설정(톱니바쿼) > Options > General > Language > English -> 한국어 > Relaunch

  3.2. syncthing
    3.2.1. 설치
      Play 스토어 > 검색 > syncthing > 설치

4. 동기화 설정
1) 우분투 > http://localhost:8384/ > 동작 > 기기 식별자 보기 > 큐알코드 표시 상태
2) 폰 > syncthing > 기기 > + > 큐알코드 > 우분투의 큐알코드 촬영, 이름: 우분투 > 확인 (체크마크)
3) 우분투 > http://localhost:8384/ > 큐알코드 종료 > 새 기기 > +기기 추가 > 저장
4) 폰 > syncthing > 폴더 > + >
  폴더명: obsidian,
  디렉토리: 내부저장소/sync/obsidian > 선택
  우분투: 활성화
확인 (체크마크)
5) 우분투 > http://localhost:8384/ > 새 폴더 > v 추가 > 폴더명: obsidian, 폴더 경로: ~/obsidian > 저장

5. 동기화 확인
우분투나 폰의 obsidian 에서 파일을 만들거나 수정하면 15초 ~ 20초 정도 후에 상대 기기에서 반영 확인.
폰의 Wi-fi를 끊고 모바일 네트워크로도 동기화 되는 것 확인.

6. 감상
메모를 보는데 네트워크를 사용하지 않는다니 좋다.
폰에서는 syncthing를 꺼뒀다가(☰ > X 나가기) 필요할 때만 켜서 동기화하면 될 듯 하다

우분투에 설치한 obsidian에는 Vim 키 설정(옵션 > 편집기 > Vim 키 설정)이라는게 있다.
vimwiki는 잘 쓰고 있는데 vimwiki와 연동하거나 외부에서 메모하는데 써봐야겠다.

우분투의 syncthing를 docker로 설치하니 설정이 깔끔하지 않은 듯 하다.

syncthing으로 공유 폴더를 설정시 서로 설정해서 그런지 같은 설정이 2개가 되버렸다.
같은 것이니 하나를 지우면 되긴 하는데 설정 방법이 틀린걸까?