How to find the namespace of a package with pip - python

I'm trying to write a custom package for python. It has the following folder structure:
E:\GITHUB\FUNCTIONSYNTHESIZER
│ .gitignore
│ LICENSE
│ README.md
│ setup.py
│
├───docs
│ docs.md
│ Example-6-Points.png
│
└────src
│ function_synth.py
│ __init__.py
│
└───__pycache__
Pip allows an install via the Folderpath of the setup.py. The setup.py follows the guidelines of Python.org an most importantly works in another package with a different name:
from distutils.core import setup
def README():
return open("README.md").read()
setup(name='function_synthesizer',
version = '0.0.2',
description = 'Generates a polynomial interpolation from a set up points',
author = 'David J. Kowalk',
author_email = '...',
url = 'https://github.com/davidkowalk/FunctionSynthesizer',
license = "MIT",
keywords = "math interpolation polynomial calculus",
long_description = README(),
long_description_content_type='text/markdown',
classifiers=[
Calssifiers here
],
package_dir = {'':'src'},
python_requires = ">=3.6",
install_requires=['numpy'],
project_urls={
'Bug Reports': '...',
'Funding': '...',
'Source': '...',
}
)
The __init__.py imports the different functions of function_synth.
from function_synth import solve
from function_synth import to_str
from function_synth import calculate
The package gets installed by pip under the name "function_synthesizer" as defined by the setup.py but when I run pip show function_synthesizer I get
Name: function-synthesizer
Version: ...
This name violates the package name syntax and most importantly isn't defined anywhere. I cannot find any reason for that dash to be there, and it seems to replace the underscore defined by the name argument for some reason. But even when I remove the underscore and the resulting dash python is unable to import the packages under the following names:
function_synthesizer
function-synthesizer (syntax error at the dash)
functionsynthesizer (after removing the dash)
function_synth (file name)
Is there anyway of fetching the namespace from pip and how can I define that namespace in the setup.py?

Issue was fixed by adding a packages attribute:
packages = ["function_synthesizer"],
package_dir = {'function_synthesizer':'src'},
The __init__.py imports the functions then via
from function_synthesizer.function_synth import function_name

Related

PyPI - allow importing a module using a different name

I recently created a simple python package / library and uploaded it to (PyPI). That's all fine - I can upload my project to pypi and download it again using pip, but I can't import it because it contains a full stop .
My package is called dcode.py (which obviously causes issues with importing), I'd like to be able to import it by just typing import dcode (without the .py). I've looked around for a solution but can't seem to find one - I'd like to be able to import it under an alias (dcode) instead of dcode.py (which obviously doesn't work).
Is there a way to do this? I'd assume there must be (for example, both the packages discord.py and py-cord can both just be imported using import discord. Does anyone know how to do this?
Just in case you need it, my pyproject.toml file looks like this:
[project]
name = "dcode.py"
version = "0.0.2"
authors = [
{ name = "Dylan Rogers", email = "opendylan#proton.me" },
]
description = "dcode - the Python package for everything"
readme = "README.md"
requires-python = ">=2.0"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
"Homepage" = "https://github.com/dylanopen/dcode.py"
"Bug Tracker" = "https://github.com/dylanopen/dcode.py/issues"
And my entire project is on github.
Here is the PyPI project
I've tried importing dcode.py:
import dcode.py
But I just get this error:
File "/run/media/dylan/Programming/dcode.py-test/test.py", line 1, in <module>
import dcode_py
ModuleNotFoundError: No module named 'dcode_py'
If it helps, I'm using Manjaro Linux (no idea why it would help though, I just need to know how to allow users to import dcode.py by typing import dcode, like they import discord.py as discord)
Thank you so much for any help!
This is the recommended directory structure your project should have:
dcode.py/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── dcode/
│ ├── __init__.py
│ └── io/
│ ├── __init__.py
│ └── file.py
└── tests/
Here dcode (as defined by dcode.py/src/dcode) is the name of the only top-level importable package. And it also contains a sub-package dcode.io (as defined by dcode.py/src/dcode/io).
The name of the actual distribution project as seen on PyPI and that gets pip-installed is defined in the package metadata, in the pyproject.toml file:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "dcode.py"
version = "0.0.1"
authors = [
{ name = "Dylan Rogers", email = "opendylan#proton.me" },
]
description = "dcode - the Python package for everything"
readme = "README.md"
requires-python = ">=3.8"
[project.urls]
"Homepage" = "https://github.com/dylanopen/dcode.py"
"Bug Tracker" = "https://github.com/dylanopen/dcode.py/issues"
[tool.setuptools.packages.find]
where = ["src"]
Do not push the dist directory containing the built sdist and wheel distribution packages artifacts into the source code repository.
Do not push any *.egg-info directory into the source code repository.
Place all the actual Python code inside a src directory.
The name of the project can contain a dot such as dcode.py, although I would recommend against it.
The name of the actual top-level import packages and modules can be completely different than the name of the distribution project.
If you declare a classifier for the license in the package metadata in pyproject.toml, then it should match the actual license file in the source code repository (you had a GNU GPL v3.0 license file but a MIT license classifier).
References:
https://packaging.python.org/en/latest/tutorials/packaging-projects/
https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
https://sinoroc.gitlab.io/kb/python/packaging.html

Python TestPypi no module named XXX when importing functions from other folders in main.py

I am trying to use TestPypi to package my project. In the main scripts main.py, I use some functions in other folders like common. The project folder is like following:
proj_name/
├── LICENSE
├── pyproject.toml
├── README.md
├── proj_name/
│ └── main.py
├── commons/
│ └── common.py
└── tests/
I tried from commons import common as common and import commons.common as common in main.py but I cannot succeed because after I package my project and pip install it, I will get the result ModuleNotFoundError: No module named 'commons'.
My pyproject.toml is like this:
[tool.poetry]
name = "XXXX"
version = "XXX"
description = ""
authors = ["XXXX"]
readme = "README.md"
[tool.poetry.scripts]
proj_name = "proj_name.main:app"
[tool.poetry.dependencies]
python = "^3.9"
typer = {extras = ["all"], version = "^0.6.1"}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
May I ask how I can organise the code so that the common module can be imported?
Thanks.

Correct way to enable import of all submodule in python package

I'm making a python package using setuptools, and I'm having trouble making all nested folders in my source code available for import after installing the package. The directory I'm working in has a structcure like illustrated below.
├── setup.py
└── src
└── foo
├── a
│ ├── aa
│ │ └── aafile.py
│ └── afile.py
├── b
│ └── bfile.py
└── __init__.py
Currently, I can't import submodules, such as from foo.a import aa or from foo.a.aa import some_method, unless I explicitly pass the names of the submodules to setuptools. That is, setup.py needs to contain something like
from setuptools import setup
setup(
version="0.0.1",
name="foo",
py_modules=["foo", "foo.a", "foo.a.a", "foo.b"],
package_dir={"": "src"},
packages=["foo", "foo.a", "foo.a.a", "foo.b"],
include_package_data=True,
# more arguments go here
)
This makes organizing the code pretty cumbersome. Is there a simple way to just allows users of the package to install any submodule contained in src/foo?
You'll want setuptools.find_packages() – though all in all, you might want to consider tossing setup.py altogether in favor of a PEP 517 style build with no arbitrary Python but just pyproject.toml (and possibly setup.cfg).
from setuptools import setup, find_packages
setup(
version="0.0.1",
name="foo",
package_dir={"": "src"},
packages=find_packages(where='src'),
include_package_data=True,
)
Every package/subpackage must contain a (at least empty) __init__.py file to be considered so.
If you want to the whole package&subpackages tree to be imported with just one import foo consider filling your __init__.py files with the import of the relative subpackages.
# src/foo/__init__.py
import foo.a
import foo.b
# src/foo/a/__init__.py
import foo.a.aa
# src/foo/b/__init__.py
import foo.b.bb
Otherwise leave the __init__.py files empty and the user will need to manually load the subpackag/submodule he wants.

Requirement for __init__.py just to satisfy pylint and mypy

I have a project with the following (partial) directory structure
.
├── mypy.ini
├── src
│ ├── preppy
│ │ ├── cli.py
│ │ ├── __main__.py
│ │ ├── model.py
│ │ └── tools.py
├── pyproject.toml
└── tests
In cli.py, I have the following code (lines 13 and 14 in the file):
from .model import Problem
from .tools import get_abs_path, transcode
I also have similarly styled relative imports in model.py and __main__.py
All similar imports throw errors in both pylint (2.5.3) and mypy (0.761) when the tools are automatically run in my IDE (Code - OSS), e.g.:
Attempted relative import beyond top-level package pylint(relative-beyond-top-level) [13,1]
Cannot find implementation or library stub for module named '.model' mypy(error) [13,1]
Attempted relative import beyond top-level package pylint(relative-beyond-top-level) [14,1]
Cannot find implementation or library stub for module named '.tools' mypy(error) [14,1]
See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports mypy(note) [13,1]
When I add a blank __init__.py file to the folder, the errors disappear.
I don't need this __init__.py file for the package to work.
I thought that post-PEP 420, it shouldn't be required, especially if it's just there to satisfy linters.
Is there something else I'm doing wrong, or should I just add the __init__.py and get over it :) ?
Config for pylint is in pyproject.toml:
[tool.pylint.'MESSAGES CONTROL']
# Pylint and black disagree on hanging indentation.
disable = "C0330"
[tool.pylint.MISCELLANEOUS]
# Note: By default, "TODO" is flagged, this is disabled by omitting it
# from the list below.
notes = "FIXME,XXX"
Config for mypy is in mypy.ini:
[mypy]
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
disallow_untyped_decorators = True
mypy_path = src
namespace_packages = True
[mypy-openpyxl]
ignore_missing_imports = True
[mypy-pulp]
ignore_missing_imports = True
[mypy-pytest]
ignore_missing_imports = True
I'm running python 3.8.0.
PEP 420 does not allow to "create a package by omitting __init__.py", it enforces to "create a namespace package by omitting __init__.py". This means:
If you want a package, add __init__.py.
If you want a namespace package, omit __init__.py.
While using a namespace package like a regular package usually works, it may unexpectedly fail when package names clash. In most cases, a namespace package is not desirable.
For mypy, an alternative to the accepted answer is to use mypy option --namespace-packages so that namespace packages are taken into account by mypy

New to creating a Python Package

I tried making one to post on my GitHub and help users download dependencies for the program, but in the end, all it does is generate duplicate files. I was hoping to have (in the end) a package where the user could enter:
>>> import my_package
>>> my_package.main
but that isn't working. I've looked at several different websites and different templates, but seem to be getting nowhere with this.
Directory structure
Kodimer_Project
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── requirements.txt
├── setup.py
└── wav2bin
├── __version__.py
├── imgs
│   ├── App_Icon.gif
│   ├── App_Icon.ico
│   └── __init__.py
└── src
├── DrawGraph.py
├── GraphicInterface.py
├── SplashScreen.py
├── __init__.py
├── __main__.py
└── helper_functions.py
setup.py
From: https://github.com/kennethreitz/setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Note: To use the 'upload' functionality of this file, you must:
# $ pip install twine
import io
import os
import sys
from shutil import rmtree
from setuptools import find_packages, setup, Command
# Package meta-data.
NAME = 'wav2bin'
DESCRIPTION = 'GUI graphing tool used concurrently with lab.'
URL = 'https://github.com/jvanderen1/Kodimer_Project'
EMAIL = 'jvanderen1#gmail.com'
AUTHOR = 'Joshua Van Deren'
# What packages are required for this module to be executed?
REQUIRED = [
'matplotlib',
'numpy',
'scipy'
]
# The rest you shouldn't have to touch too much :)
# ------------------------------------------------
# Except, perhaps the License and Trove Classifiers!
# If you do change the License, remember to change the Trove Classifier for that!
here = os.path.abspath(os.path.dirname(__file__))
# Import the README and use it as the long-description.
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
LONG_DESCRIPTION = '\n' + f.read()
# Load the package's __version__.py module as a dictionary.
about = {}
with open(os.path.join(here, NAME, '__version__.py')) as f:
exec(f.read(), about)
class UploadCommand(Command):
"""Support setup.py upload."""
description = 'Build and publish the package.'
user_options = []
#staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass
self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
sys.exit()
# Where the magic happens:
setup(
name=NAME,
version=about['__version__'],
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
author=AUTHOR,
author_email=EMAIL,
url=URL,
package_dir={'': 'wav2bin'},
packages=find_packages(exclude=('tests', 'docs')),
# If your package is a single module, use this instead of 'packages':
# py_modules=['mypackage'],
entry_points={
'gui_scripts': ['wav2bin = wav2bin.__main__:main'],
},
install_requires=REQUIRED,
python_requires='>=3',
include_package_data=True,
license='MIT',
classifiers=[
# Trove classifiers
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3 :: Only',
'Natural Language :: English',
'Topic :: Scientific/Engineering :: Human Machine Interfaces',
'Topic :: Software Development :: User Interfaces'
],
# $ setup.py publish support.
cmdclass={
'upload': UploadCommand,
},
)
wav2bin/src/__main__.py
Snippet of code:
if __name__ == '__main__':
main()
After you have some experience with various packaging modules what you would usually do is decide how professional you want your packaging to be? Do you want to host it on pypi? Distribute it from github? Pass it along to friends?
That's how you pick your packaging method, but first you should probably get some experience with the existing packaging modules, the two most popular ones are:
setuptools which is what I usually go with and to which I linked a good tutorial
distutils an older api to distribute packages but it's still around and is also very good to know
If then you decide this is an overkill and you want a less professional approach you can always do it manually.
Either installation to the python package folder, which for pip usually means something like entering the packages root folder and entering either
pip install .
If you are certain of your package, or
pip install -e .
For installing in edit mode if you still wish to keep the package malleable
or having the package in your python path in some other manner before import is mandatory.

Categories