Was sind die minimalen Voraussetzungen für ein python- package, das mit pip ausgeliefert werden soll?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Ich habe diese Frage schon in stackoverflow gestellt, dort scheint sich aber keiner die Mühe zu machen, Fragen von einem scheinbaren newbie zu beantworten. Daher stelle ich sie hier noch einmal in deutsch. Der StackOverflow- Link lautet
https://stackoverflow.com/questions/766 ... red-by-pip

Übersetzung:
Ich versuche gerade, Teile meines Codes in Pakete zu verpacken, die mit pip ausgeliefert werden sollen. Aber ich verstehe das folgende nicht: Es gibt das Verzeichnis my_package welches die Dateien __init__.py (leer), my_module.py (mit etwas Code), setup.py:

Code: Alles auswählen

from setuptools import setup, find_packages

setup(
    name='mypackage',
    version='0.1',
    description='My Package',
    author='Its me',
    author_email='my.email@address.moon',
    packages=find_packages(),
    install_requires=[
        'cx_Oracle>=7.2.2'
    ]
)
ich habe ein dist- file erzeugt dist\mypackage-0.1.tar.gz durch

Code: Alles auswählen

...my_package>python.exe setup.py sdist
Ich erzeugte eine virtuelle umgebung, aktivierte es und benutzte dann
...inside_venv>Scripts\pip.exe install <complete_path_to_dist\mypackage-0.1.tar.gz>
Dann habe ich ein Testmodul test.py erzeugt mit dem folgenden Inhalt:

Code: Alles auswählen

import cx_Oracle
print(cx_Oracle.version)
import mypackage
everything worked (I got the message "newer PIP- Version available...")
Das hat anscheinend auch alles funktioniert (ich bekam die Nachricht: neuere PIP- Version vorhanden)

Das Modul cx_Oracle wurde ohne Probleme erfolgreich installiert (die Version ist aktueller, als alle anderen auf meinem Rechner), aber es ist unmöglich, das Modul my_packages zu importieren.
Die Fehlermeldung lautet: ModuleNotFoundError: No module named 'mypackage'

Ich habe folgende Dinge ausprobiert:
  • Original hieß das package my_package und ich habe viel Zeit damit verbracht, alle Kombinationen aus "_" und "-" auszuprobieren, so dass ich mich letzendlich entschied, das package ohne Unterstrich zu benennen.
  • Ich habe PIP in der virtuellen Umgebung aktualisiert
  • Ich habe das Package außerhalb der virtuellen Umgebung installiert
Was immer ich probierte, die Antwort war ModuleNotFoundError: No module named 'mypackage'

(nebenbei bemerkt: Der Code innerhalb my_module.py funktioniert ohne Probleme, wenn ich ihn direkt aufrufe.

Irgendwelche Ideen?

Vielen Dank für Eure Mühe
Benutzeravatar
sparrow
User
Beiträge: 4202
Registriert: Freitag 17. April 2009, 10:28

Man installiert Pakete aber importiert Module.

Die Frage ist also, wie das Modul im sys.path heißt.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

DIese Antwort verstehe ich leider nicht.
  • cx_Oracle ist ein Paket und ich habe es importiert, oder liege ich da falsch?
  • sys.path verweist letztlich nur auf die Pfade, in denen das System nach Bibliotheken, Programmen etc. sucht. Ist es nicht so, dass PIP ein Paket so installiert, dass es "gefunden" wird? Es dürfte doch keinen eigenen Eintrag in sys.path haben, oder liege ich auch hier falsch?
  • wie würde ich denn herausbekommen, wie das Modul in sys.path heißt?
Interessant ist sys.path dennoch, denn die virtuelle Umgebung ist offenbar für python 2 erstellt worden. Das sollte zwar keinen Unterschied darstellen und im "richtigen" System ist es auch nicht so, aber beabsichtigt war das auch nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13143
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NoPy: Du bist im Ausgangsbeitrag recht grosszügig mit den Schreibweisen. `my_package`, `my_packages`, `mypackage`. Und dann ist es ja ein Unterschied wie das Python-Package heisst, und wie das PyPI-Package heisst. In der Regel ja gleich, aber das muss ja nicht.

Es liest sich im Ausgangsbeitrag auch fast so als wenn `setup.py` *in* dem Package wäre. Da gehört das natürlich nicht hin.

Bei mir funktioniert das was Du mehr oder weniger beschrieben hast problemlos. Ausgangssituation:

Code: Alles auswählen

$ tree
.
├── my_package
│   ├── __init__.py
│   └── my_module.py
└── setup.py
Quelltextarchiv erzeugen:

Code: Alles auswählen

$ python3.8 setup.py sdist
running sdist
running egg_info
creating my_package.egg-info
writing my_package.egg-info/PKG-INFO
writing dependency_links to my_package.egg-info/dependency_links.txt
writing requirements to my_package.egg-info/requires.txt
writing top-level names to my_package.egg-info/top_level.txt
writing manifest file 'my_package.egg-info/SOURCES.txt'
reading manifest file 'my_package.egg-info/SOURCES.txt'
writing manifest file 'my_package.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md

running check
creating my_package-0.1
creating my_package-0.1/my_package
creating my_package-0.1/my_package.egg-info
copying files to my_package-0.1...
copying setup.py -> my_package-0.1
copying my_package/__init__.py -> my_package-0.1/my_package
copying my_package/my_module.py -> my_package-0.1/my_package
copying my_package.egg-info/PKG-INFO -> my_package-0.1/my_package.egg-info
copying my_package.egg-info/SOURCES.txt -> my_package-0.1/my_package.egg-info
copying my_package.egg-info/dependency_links.txt -> my_package-0.1/my_package.egg-info
copying my_package.egg-info/requires.txt -> my_package-0.1/my_package.egg-info
copying my_package.egg-info/top_level.txt -> my_package-0.1/my_package.egg-info
Writing my_package-0.1/setup.cfg
creating dist
Creating tar archive
removing 'my_package-0.1' (and everything under it)

$ tree
.
├── dist
│   └── my_package-0.1.tar.gz
├── my_package
│   ├── __init__.py
│   └── my_module.py
├── my_package.egg-info
│   ├── dependency_links.txt
│   ├── PKG-INFO
│   ├── requires.txt
│   ├── SOURCES.txt
│   └── top_level.txt
└── setup.py

3 directories, 9 files
Woanders venv anlegen und aktivieren und das Quelltextarchiv installieren:

Code: Alles auswählen

$ python3.8 -m venv .venv
$ . .venv/bin/activate
$ pip install ../src/dist/my_package-0.1.tar.gz 
Processing /home/bj/tmp/forum/packaging/src/dist/my_package-0.1.tar.gz
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Collecting cx-Oracle>=7.2.2 (from my-package==0.1)
  Using cached cx_Oracle-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (891 kB)
Building wheels for collected packages: my-package
  Building wheel for my-package (pyproject.toml) ... done
  Created wheel for my-package: filename=my_package-0.1-py3-none-any.whl size=1429 sha256=22b5f0fa1708af476ae6002c2db4c95a24a6e6b5ed523abb14677ba680c77f33
  Stored in directory: /home/bj/.cache/pip/wheels/a3/19/09/3aef4e4410e5457a4169cc75add9a4fc91204bccb3215a1771
Successfully built my-package
Installing collected packages: cx-Oracle, my-package
Successfully installed cx-Oracle-8.3.0 my-package-0.1
Und testen:

Code: Alles auswählen

$ echo 'import my_package' > test.py
$ python test.py
Hello, World.
Meine `__init__.py` ist nicht leer, sondern enthält ein `print()` mit der Ausgabe dort.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

__blackjack__ hat geschrieben: Freitag 14. Juli 2023, 09:48 @NoPy: Du bist im Ausgangsbeitrag recht grosszügig mit den Schreibweisen. `my_package`, `my_packages`, `mypackage`. Und dann ist es ja ein Unterschied wie das Python-Package heisst, und wie das PyPI-Package heisst. In der Regel ja gleich, aber das muss ja nicht.
Das ist mir klar. Eigentlich wollte ich das paket eben my_package nennen, was dann ja - warum auch immer - von PIP in my-package umgewandelt wird, beim import aber wieder my_package heißen würde. Und da ich genau dort den Fehler vermutet habe, habe ich dann irgendwann - auch wenn das Verzeichnis my_package hieß - das modul in der setup.py kurzerhand mypackage genannt.
__blackjack__ hat geschrieben: Freitag 14. Juli 2023, 09:48 Es liest sich im Ausgangsbeitrag auch fast so als wenn `setup.py` *in* dem Package wäre. Da gehört das natürlich nicht hin.
richtig gelesen, ich dachte, das muss so...
Könnte der Grund für mein Problem sein, probiere ich gleich aus.
__blackjack__ hat geschrieben: Freitag 14. Juli 2023, 09:48 Bei mir funktioniert das was Du mehr oder weniger beschrieben hast problemlos. Ausgangssituation:

Code: Alles auswählen

$ tree
.
├── my_package
│   ├── __init__.py
│   └── my_module.py
└── setup.py
...
stelle ich her

Der Rest sieht für ich aus, wie ein Folgefehler. Er hat im vermeintlichen package my_package nach dem package gesucht, aber da war es ja dann nicht. Das cx_Oracle hingegen schon...
Vielen Dank für Deine Hilfe. Es sind oft die kleinen Dinge, die einen großen Hä- Effekt haben ...
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

NoPy hat geschrieben: Freitag 14. Juli 2023, 11:08
__blackjack__ hat geschrieben: Freitag 14. Juli 2023, 09:48 Es liest sich im Ausgangsbeitrag auch fast so als wenn `setup.py` *in* dem Package wäre. Da gehört das natürlich nicht hin.
richtig gelesen, ich dachte, das muss so...
Könnte der Grund für mein Problem sein, probiere ich gleich aus.
Habe ich ausprobiert, jetzt sieht es so aus (in Wirklichkeit soll das pgk_ora heißen, die entsprechende Zeile in der setup.py lautet jetzt auch so):

Code: Alles auswählen

.├── pkg_ora
│   ├── __init__.py
│   └── my_module.py
└── setup.py
Folgender Befehl:

Code: Alles auswählen

...packages>python.exe setup.py sdist
brachte folgendes Ergebnis

Code: Alles auswählen

running sdist
running egg_info
creating pkg_ora.egg-info
writing pkg_ora.egg-info\PKG-INFO
writing dependency_links to pkg_ora.egg-info\dependency_links.txt
writing requirements to pkg_ora.egg-info\requires.txt
writing top-level names to pkg_ora.egg-info\top_level.txt
writing manifest file 'pkg_ora.egg-info\SOURCES.txt'
reading manifest file 'pkg_ora.egg-info\SOURCES.txt'
writing manifest file 'pkg_ora.egg-info\SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md

running check
creating pkg_ora-0.1
creating <mehr, als beabsichtigt>
creating pkg_ora-0.1\pkg_ora
creating pkg_ora-0.1\pkg_ora.egg-info
copying files to pkg_ora-0.1...
copying setup.py -> pkg_ora-0.1
copying <mehr, als beabsichtigt>
copying pkg_ora\my_module.py -> pkg_ora-0.1\pkg_ora
copying pkg_ora\__init__.py -> pkg_ora-0.1\pkg_ora
copying pkg_ora\conf.py -> pkg_ora-0.1\pkg_ora
copying pkg_ora.egg-info\PKG-INFO -> pkg_ora-0.1\pkg_ora.egg-info
copying pkg_ora.egg-info\SOURCES.txt -> pkg_ora-0.1\pkg_ora.egg-info
copying pkg_ora.egg-info\dependency_links.txt -> pkg_ora-0.1\pkg_ora.egg-info
copying pkg_ora.egg-info\requires.txt -> pkg_ora-0.1\pkg_ora.egg-info
copying pkg_ora.egg-info\top_level.txt -> pkg_ora-0.1\pkg_ora.egg-info
Writing pkg_ora-0.1\setup.cfg
Creating tar archive
removing 'pkg_ora-0.1' (and everything under it)
er hat also noch alle anderen packages, die sich im packagesverzeichnis befinden, mit eingepackt. Habe mir auch die Textfiles mal angesehen, demnach ist das ebenso.
Das ist letztlich nicht meine Absicht, aber darum kann ich mich später kümmern (vermutlich genau, wie um die Frage, wie ich die setup.py in die Quellcodeverwaltung hereinbekomme)

Wenn ich jetzt in meiner virtuellen Umgebung

Code: Alles auswählen

pip install <PFAD>pkg_ora-0.1.tar.gz
pip list
dann ergibt sich folgendes Bild:

Code: Alles auswählen

Package    Version
---------- -------
cx-Oracle  8.3.0
pip        23.1.2
pkg-ora    0.1
setuptools 46.1.3
wheel      0.34.2
Das Dumme ist: ich kann weiterhin cx_Oracle importieren, aber pkg_ora nicht...
Was könnte ich noch versuchen?
Benutzeravatar
__blackjack__
User
Beiträge: 13143
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`find_packages()` macht halt was der Name sagt: es findet alle Packages in dem Ordner. :-) Also dort vielleicht explizit nur das Package angeben das Du da verpacken willst. Wobei ich das insgesamt auch besser isolieren würde vor dem Verpacken. Denn eine Readme fehlt ja auch, und da kann man ja auch nur *eine* im Verzeichnis haben.

Bezüglich `find_packages()` und ``setup.py``: Der Trend scheint ja sowieso von einer ausführbaren Datei weg zu gehen und hin zu einer statischen Beschreibung in einer ``pyproject.toml``.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

__blackjack__ hat geschrieben: Freitag 14. Juli 2023, 12:30 `find_packages()` macht halt was der Name sagt: es findet alle Packages in dem Ordner. :-) Also dort vielleicht explizit nur das Package angeben das Du da verpacken willst. Wobei ich das insgesamt auch besser isolieren würde vor dem Verpacken. Denn eine Readme fehlt ja auch, und da kann man ja auch nur *eine* im Verzeichnis haben.

Bezüglich `find_packages()` und ``setup.py``: Der Trend scheint ja sowieso von einer ausführbaren Datei weg zu gehen und hin zu einer statischen Beschreibung in einer ``pyproject.toml``.
Kann ich sicher alles noch nachbessern, aber woran könnte es liegen, dass ich das package/modul weiterhin nicht importieren kann?
nebenbei: die readme.* liegt also offenbar auch auf der Ebene der setup.py, oder? An welcher Stelle wird der Inhalt der readme.* verwendet? Ich habe - inzwischen einigermaßen erfolgreich - aus dem Paket mittels sphinx eine html- Dokumentation erstellt. aber diese greift ja auch nur unterhalb des Verzeichnisses pkg_ora zu, die readme.rst würde also dafür nicht verwendet werden.

Insgesamt ist mir das "saubere" Strukturieren des Paketes einschließlich pip, sphinx und hg (Quellcodeverwaltung) noch nicht zu meiner vollen Zufriedenheit gelungen. neben meinen eigentlichen .py- Dateien Nutzcode kommen __init__.py- Dateien mit Pseudo- Nutzcode hinzu, __pycache__ baut auch noch ein eigenes Unterverzeichnis, hg baut noch eins, sphinx baut mehrere dort hin und auch noch eine conf.py - finde ich alles nicht so chic, ist aber anscheinend sinnvoll. Wenn ich jetzt nur noch die PIP- Geschichte hinbekommen würde, dann könnte ich weitermachen. (ich gehe mal davon aus, dass ich mehere setup.py in ein verzeichnis legen und benutzen könnte, wenn ich sie setip_pkg_ora.py, setip_pkg_was_anderes.py und setup_was_ganz_anderes.py nennen würde.

Darf ich neugierig fragen, wie Deine Verzeichnisstrukturen insgesamt so aussehen, wenn Du ein python- Projekt anlegst? So eine Art "Best Practice" habe ich nirgends gefunden, sondern immer nur partielle Lösungsansätze, die ich versucht habe, miteinander "brauchbar" zu kombinieren. Python bzw. die benutzten Hilfspackages haben da immer irgendwelche impliziten Annahmen, die zu durchsteigen mir bislang einige Mühe gemacht haben.
Benutzeravatar
__blackjack__
User
Beiträge: 13143
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NoPy: Wo da jetzt das Problem beim Import liegt kann ich nicht sagen. Bei mir funktioniert es wie gesagt, aber ich habe ja auch wirklich nur ein Minimalbeispiel gemacht. Du hast da ja anscheinend noch mehr in dem/den Ordner(n).

Die Readme beschreibt das (PyPi-)Package. Und wird üblicherweise auch angezeigt wenn man das Repository irgendwo hosted, also beispielsweise bei Github. Da kommt das rein was der Benutzer über das Package/das Quelltextarchiv wissen muss/soll. Das ist auch das was normalerweise im Package-Index angezeigt wird.

Mehrere `setup.py` geht eher nicht. Das ist *der* Einstiegspunkt für das bauen/installieren. Unter dem Namen erwarten Werkzeuge wie ``pip`` das im Quelltextarchiv. Das würde ja auch bedeuten, dass da mehrere Packages direkt in einem Verzeichnis liegen die man nicht als ein Projekt zusammen installiert haben möchte.

Ich lege üblicherweise ein Verzeichnis für das Projekt an, das normalerweise wie das Package heisst. Da drin dann ``doc/`` für Dokumentation, ``package_name/`` für das Package (es gibt Leute die stecken das noch mal in ein ``src/``-Verzeichnis, eine ``readme.md``. Ein `Makefile` damit man nicht immer alles tippen muss um beispielsweise mal aufzuräumen oder die Doku neu zu bauen. Diverse Konfigurationsdateien für Pytest und ähnliche Werkzeuge wo man projektspezifische Einstellungen vornehmen will, eine .env falls das Projekt Umgebungsvariablen benutzt, was man halt so braucht. Falls es eine Webanwendung ist, braucht man dann ja oft noch Verzeichnisse/Dateien dafür, also Templates, statische Dateien wie CSS, Bilder, …

Es gibt Leute, die einen extra Ordner für Tests haben. Ich persönlich habe die in der Regel im normalen Package in einem Unterpackage. Dann kann man die auch mal dort laufen lassen wo es letztendlich installiert wurde, falls Probleme in *der* Umgebung auftreten, die lokal nicht aufgetreten sind.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Keine Ahnung, kaum macht man's richtig, schon geht's ...

Ich habe jetzt auch noch mal ein NULL- Package aufgesetzt --> ging
dann habe ich noch mal das "gewünschte" package angeschaut --> keinen Fehler gefunden, ausprobiert --> ging

Vielen Dank für Deine Hilfe, letztlich hat sie zum Ziel geführt. Ich habe zwar immer noch das Gefühl, auf rohen Eiern zu wandeln, weil "unter der Haube" mehr Dinge passieren, als aktuell wirklich zu beeinflussen ich im Stande zu sein scheine. Aber: Der Anfang ist gemacht.
Antworten