déployer un paquet sur pypi

Bonjour,
Je souhaite déployer un paquet sur pypi. (c’est la première fois que je m’y essaie).
J’ai construit le paquet en suivant l’exemple donné par sam et max , Créer un setup.py et mettre sa bibliothèque Python en ligne sur Pypi – Sam & Max
C’est un article certe ancien, mais à l’aide des commentaires je pense l’avoir correctement mis au gout du jour(python3). En tout cas, il est fonctionnel en local
Pour le déployer sur pypi j’ai commencé par essayer avec :

python3 setup.py register

mais au vu de la réponse, je comprends que cela est déprécié.
Je vais sur la doc de pypi accompagné de mon ami deepl (hablo ingles commo una vaca frances) et j’essaie avec twine qui semble être l’outil adéquate:
Cependant voici le retour:

(fabien_lib) ~/fabien_lib jt'écoute :python3 -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your username: pseudofab 
Enter your password: 
Uploading fabien_lib-0.0.1-py3-none-any.whl
100%|██████████████████████████████████████| 6.51k/6.51k [00:01<00:00, 4.41kB/s]
NOTE: Try --verbose to see response content.
HTTPError: 400 Bad Request from https://test.pypi.org/legacy/
The description failed to render in the default format of reStructuredText. See https://test.pypi.org/help/#description-content-type for more information.
(fabien_lib) ~/fabien_lib jt'écoute :

‘pseudofab’ et le code sont les identifiants que j’ai fourni lors de mon inscription pypi.
Ne comprenant pas où ça bloque, j’ai aussi essayé avec les identifiants de ma session sans plus de résultat.

Bref, une aide serait la bienvenue.
Merci
édit:
Je comprends qu’il s’agit du format Markdown qui n’est pas reconnu. En suivant les liens de la doc, il semble que je peux le convertir en format reStructuredText en utilisant readme_renderer . Je ferais des essais demain, je fatigue … Pas facile de lire des docs lorsqu’on ne parle pas anglais …

Bonjour,

Je n’ai jamais essayé mais à priori tu peux spécifier que c’est du Markdown comme ceci:

long_description = file: README.md
long_description_content_type = text/markdown

Selon le tuto Packaging Python Projects — Python Packaging User Guide

1 J'aime

Merci @dancergraham,
Il y a manifestement d’autres soucis:

(fabien_lib) ~/fabien_lib jt'écoute :python3 -m twine upload --repository testpypi dist/* --verbose
Using configuration from /home/fab/.pypirc
Uploading distributions to https://test.pypi.org/legacy/
  dist/fabien_lib-0.0.1-py3-none-any.whl (2.5 KB)
  dist/fabien_lib-0.0.1-py3.8.egg (3.2 KB)
  dist/fabien_lib-0.0.1.tar.gz (3.2 KB)
Enter your username: pseudofab
Enter your password: 
username: pseudofab
password: <hidden>
Uploading fabien_lib-0.0.1-py3-none-any.whl
100%|██████████████████████████████| 6.55k/6.55k [00:00<00:00, 6.75kB/s]
Content received from server:
<html>
 <head>
  <title>400 File already exists. See https://test.pypi.org/help/#file-name-reuse for more information.</title>
 </head>
 <body>
  <h1>400 File already exists. See https://test.pypi.org/help/#file-name-reuse for more information.</h1>
  The server could not comply with the request since it is either malformed or otherwise incorrect.<br/><br/>
File already exists. See https://test.pypi.org/help/#file-name-reuse for more information.


 </body>
</html>
HTTPError: 400 Bad Request from https://test.pypi.org/legacy/
File already exists. See https://test.pypi.org/help/#file-name-reuse for more information.
(fabien_lib) ~/fabien_lib jt'écoute :

voici le contenu actuel de setup.py:

(fabien_lib) ~/fabien_lib jt'écoute :cat setup.py 
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
from setuptools import setup, find_packages

# notez qu'on import la lib
# donc assurez-vous que l'importe n'a pas d'effet de bord
import fabien_lib
 
# Ceci n'est qu'un appel de fonction. Mais il est trèèèèèèèèèèès long
# et il comporte beaucoup de paramètres
setup(
        # le nom de votre bibliothèque, tel qu'il apparaitre sur pypi
        name='fabien_lib',
        # la version du code
        version=fabien_lib.__version__,
        # Liste les packages à insérer dans la distribution
        # plutôt que de le faire à la main, on utilise la foncton
        # find_packages() de setuptools qui va cherche tous les packages
        # python recursivement dans le dossier courant.
        # C'est pour cette raison que l'on a tout mis dans un seul dossier:
        # on peut ainsi utiliser cette fonction facilement
        packages=find_packages(),
        # votre pti nom
        author="fabien /Sam et Max",
        # Votre email, sachant qu'il sera publique visible, avec tous les risques
        # que ça implique.
        author_email="moi@e-nautia.com",
        # Une description courte
        description="Proclame la bonne parole de sieurs Sam et Max",
        # Une description longue, sera affichée pour présenter la lib
        # Généralement on dump le README ici
        long_description= open('README.md').read(),
        long_description_content_type='text/markdown', 
        
        # Vous pouvez rajouter une liste de dépendances pour votre lib
        # et même préciser une version. A l'installation, Python essayera de
        # les télécharger et les installer.
        #
        # Ex: ["gunicorn", "docutils >= 0.3", "lxml==0.5a7"]
        #
        # Dans notre cas on en a pas besoin, donc je le commente, mais je le
        # laisse pour que vous sachiez que ça existe car c'est très utile.
        # install_requires= ,
        # Active la prise en compte du fichier MANIFEST.in
        #Definition list ends without a blank line; unexpected unindent.
        include_package_data=True,
        # Une url qui pointe vers la page officielle de votre lib
        url='http://github.com/pseudofab/fabien_lib', #non crée
        # Il est d'usage de mettre quelques metadata à propos de sa lib
        # Pour que les robots puissent facilement la classer.
        # La liste des marqueurs autorisées est longue:
        # https://pypi.python.org/pypi?%3Aaction=list_classifiers.
        #
        # Il n'y a pas vraiment de règle pour le contenu. Chacun fait un peu
        # comme il le sent. Il y en a qui ne mettent rien.
        classifiers=[
            "Programming Language :: Python",
            "Development Status :: 1 - Planning",
            "License :: OSI Approved",
            "Natural Language :: French",
            "Operating System :: OS Independent",
            "Programming Language :: Python :: 3.8",
            "Topic :: Communications",
            ],
        # C'est un système de plugin, mais on s'en sert presque exclusivement
        # Pour créer des commandes, comme "django-admin".
        # Par exemple, si on veut créer la fabuleuse commande "proclame-sm", on
        # va faire pointer ce nom vers la fonction proclamer(). La commande ser
        # créé automatiquement. 
        # La syntaxe est "nom-de-commande-a-creer = package.module:fonction".
        entry_points = {
                'console_scripts': [
                    'proclame-sm = fabien_lib.core:proclamer',
                    ],
                },
        # A fournir uniquement si votre licence n'est pas listée dans "classifiers"
        # ce qui est notre cas
        license="WTFPL",
        # Il y a encore une chiée de paramètres possibles, mais avec ça vous
        # couvrez 90% des besoins
        )
(fabien_lib) ~/fabien_lib jt'écoute :

Il doit y avoir une façon de faire périmée, je vais étudier le tutoriel pour essayer de voir où se situe le problème …

il existe testPyPi pour faire tes tests et éviter d’uploader des choses fausses sur le vrai PyPi
https://packaging.python.org/guides/migrating-to-pypi-org/#using-testpypi

perso j’étais partie de cette base pour faire mon package: GitHub - audreyfeldroy/cookiecutter-pypackage: Cookiecutter template for a Python package.
et ca fonctionne, le package est mis à jour par travis mais c’est faisable à la main en regardant les commandes dans le travis.yml

@fabien a mon avis tu n’en est pas loin.

En effet twine est la bonne méthode.
Maintenant que tu as précisé le content-type de ton README, ça c’est bon.

Ta dernière erreur semble :

400 File already exists.

Ça veut dire que parmis tes tests, tu as déjà réussi à uploader fabien_lib==0.0.1 (ce qui est le cas : fabien-lib · TestPyPI), bravo :slight_smile:

Tu ne peux pas “écraser” une version, donc si tu veux uploader a nouveau, il faut passer en 0.0.2.

2 J'aime

Je suis happy, tout semble fonctionnel.
C’est un grand moment pour moi :grinning:
Merci beaucoup.

1 J'aime

Je reviens vers vous car je n’arrive pas empaqueter correctement, avec mon module (core.py), un fichier (texte.txt) que le module utiliserais:

~ jt'écoute :ls fabien_lib/
bin    dist        fabien_lib.egg-info  lib    MANIFEST.in  README.md  share
build  fabien_lib  include              lib64  pyvenv.cfg   setup.py
~ jt'écoute :ls fabien_lib/fabien_lib
core.py  __init__.py  __pycache__  texte.txt
~ jt'écoute :

Pour cela j’ai édité le fichier MANIFEST.in :

~/fabien_lib jt'écoute :cat MANIFEST.in 
include *.md
recursive-include *.txt
~/fabien_lib jt'écoute :

J’ai upgradé le paquet sur pypi sans souci mais le module ne trouve pas le chemin du texte.txt.

Python 3.8.5 (default, Jan 27 2021, 15:41:15) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from fabien_lib import proclamer
>>> proclamer()
Hello mon grand
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/fab/.local/lib/python3.8/site-packages/fabien_lib/core.py", line 24, in proclamer
    with open('texte.txt', 'r') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'texte.txt'

Je suis passé à coté de quoi? Comment dois je m’y prendre?

édit: Je précise que j’ai bien dans mon setup :

include_package_data=True,

Comme ça je ne voit pas.

Relis bien : Data Files Support — setuptools 54.1.0 documentation

Tu peux aussi tenter de couper le problème en deux (est-ce que le fichier est bien dans la distrib, mais pas trouvé à l’exécution ? Ou est-ce que le fichier n’a pas été mis dans la distrib ?). Pour ça tu peux utiliser tar -t pour voir si le fichier est bien dans l’archive :

$ python setup.py sdist
$ cd dist
$ tar -tzf *.tar.gz  | grep texte.txt

Tu peux aussi nous montrer tout le code (le plus propre serait de pousser ça sur une forge (gitlab, github, …), qu’on puisse le reproduire chez nous.

Pendant que je suis là, je voit que tu as mis ton venv dans le même dossier que ton code, c’est pas une très bonne idée, ça rend le ls assez illisible.

Tu as du fait un python3 -m venv . ou quelque chose comme ça, je te conseille de mettre tes venvs dans un dossier nommé .venv : python3 -m venv .venv. Comme ça au lieu d’avoir :

~ jt'écoute :ls fabien_lib/
bin    dist        fabien_lib.egg-info  lib    MANIFEST.in  README.md  share
build  fabien_lib  include              lib64  pyvenv.cfg   setup.py

Tu auras:

~ jt'écoute :ls fabien_lib/
dist   fabien_lib.egg-info  MANIFEST.in  README.md
build  fabien_lib           setup.py
~ jt'écoute :ls fabien_lib/.venv
bin  include  lib  lib64  pyvenv.cfg

C’est mieux rangé :slight_smile:

1 J'aime

merci @mdk.
Manifestement le fichier n’est pas dans l’archive.
Je vais étudier ça…

… la raison pour laquelle le fichier n’était pas dans l’archive c’est que dans mon MANIFEST.in j’avais oublié de préciser le nom de la paquet à partir du quel il fallait chercher:

recursive-include fabien_lib *.txt

Maintenant il est bien listé dans l’archive.
Cependant, le programme ne trouve toujours pas son chemin. :grimacing:
J’ai essayé de nombreuses choses mais en vain.
Je me perds dans la doc… Un exemple minimal de comment utiliser dans un paquet un fichier texte (ou une image, un son) serait bien plus explicite.
Le code source est sur pypi… (désolé je ne maitrise pas git, github, … , je m’y mettrais bientôt. En attendant, merci de votre indulgence)

Il te manque :

https://setuptools.readthedocs.io/en/latest/userguide/datafiles.html#accessing-data-files-at-runtime

print(importlib.resources.read_text("fabien_lib", "texte.txt"))

Il existe tout un tât de fonctions dans importlib, comme read_text, open_text, … :

https://docs.python.org/fr/3/library/importlib.html#importlib.resources.open_text

1 J'aime

Maintenant je sais comment faire . Je vais pouvoir essayer avec de vrais paquets. Cela va m’ouvrir sur de belles perspectives.
Je vais aussi apprendre à utiliser git que je trouve pas vraiment intuitif (du moins c’est l’impression que j’avais eu lors de ma première et unique approche) .
Je te remercie pour ta patience, ta bienveillance ,l’aide et les bons conseils que tu m’as prodigué :grinning:

Tu peux jouer avec oeis c’est mon projet d’entraînement, j’y met souvent mes élèves dessus : y’a aucun risque de casser qqch, c’est juste pour s’entraîner.

C’est facile d’y rajouter une série facile (tautologie, maix je veux dire par là n’essaye pas d’y ajouter une série perchée comme A000001 - OEIS), et ça entraîne au processus (fork, git clone, git add, git commit, git push, pull request, interaction humaines, …, …).

Question jouer, j’avoue que je ne trouve pas ce type de contenu très ludique. Probablement, parce que toutes ces suites sont des choses abstraites pour moi. (je ne suis pas un étudiant et je ne vois pas trop à quoi cela peut me servir ). Ceci dit, je garde ça sous le coude car effectivement ton projet, peut me servir à m’entrainer à utiliser git/github :wink:

Il sert à ça.

Oh si tu veux du ludique j’ai aussi fait hackinscience.org.

yes, je sais . J’ai réalisé 86% des exercices… Ludique en effet. Je conseille ton site lorsque j’en ai l’occasion. :wink:

Ah pas mal ! Bravo :slight_smile: Je voulais rédiger un nouvel exercice ce soir, is_anagram, mais je me fais déconcentrer de partout :frowning:

Tiens si cela t’inspire, j’avais inventé l’exercice ci-dessous pour un plus débutant que moi :
Soit la liste:

l = [{'n':'dupont','p':'stephane','s':2500},
     {'n':'dubois','p':'nicolas','s':7000},
     {'n':'ducon','p':'phillipe','s':1250},
     {'n':'ducon','p':'alice','s':4530},
     {'n':'dupont','p':'jackie','s':2200},
     {'n':'lapin','p':'jeannot'},
     {'n':'dubois','p':'fanny','s':5000},
     {'n':'dupont','p':'steven','s':1670},
	 {'n':'durant', 'p':'fabienne', 's':550},
	 {'n':'durant', 'p':'eric', 's':1300}]

Créer une fonction qui prend pour argument la liste et qui retourne un tuple avec:
→ le nom de la famille qui gagne le plus.
→ le nom de la famille qui gagne le moins.

1 J'aime