Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@cswiercz
Last active June 7, 2018 00:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cswiercz/c632d920565a2da519b73bd2b79d7920 to your computer and use it in GitHub Desktop.
Save cswiercz/c632d920565a2da519b73bd2b79d7920 to your computer and use it in GitHub Desktop.
A guide to creating a Sage package outside of Sage itself.

(Work in progress. Please suggest improvements.)

Creating External Sage Packages

Sometimes you want to write code that uses Sage and make it available to the world but you don't want to write your library within Sage. This is a quick guide to making sure that your package compiles and runs properly.

This guide assumes that your project is in the usual Python package format.

myproject/
  myproject/
    __init__.py
    (source files)
  README.md
  setup.py

Take a look at How To Package Your Python Code if you are new to writing Python packages.

Pure Python Package

If your package sources are written purely in Python, even if they use Sage functionality via from sage.x import y then you're already done! The only difference is to call your setup.py script with Sage:

$ sage setup.py install --user

This will install your package into Sage's standard location for third-party Python modules.

Using Cython

If your module sources are written in Cython but do not interface with Sage's Cython modules then you are also already done! Just make sure your setup.py script is written to compile your Cython extensions properly. See the Cython Compilation reference page for more details.

If on the other hand you have Cython modules in your package that depend on Sage Cython modules then some extra work is needed. That is, if module1.pyx contains,

# module1.pyx
from sage.x cimport y

For example, suppose your setup.py script looked something like this,

# setup.py
import os
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

# list mypackage's Cython extension modules. will not compile if they depend on
# Sage extension modules
ext_modules = [
    Extension(
        'mypackage.module1',
        sources=[os.path.join('mypackage','module1.pyx')]
    ),
    Extension(
        'mypackage.module2',
        sources=[
            os.path.join('mypackage','module2.pyx'),
            os.path.join('mypackage','utils.c')
        ],
    ),
]

setup(
  name = 'mypackage',
  packages = ['mypackage'],
  ext_modules = cythonize(ext_modules),
)

In order for these extensions to properly link to Sage's Cython modules you will need to include the appropriate Cython headers in the modules' include_dirs lists,

# setup.py
import os
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

#
# define ext_modules - the list of your project's Cython modules
#

# add the sage include directories to each extension module
from sage.env import sage_include_directories
for mod in ext_modules:
    mod.include_dirs.extend(sage_include_directories())

#
# call distutils.core.setup()
#

Our setup script is complete. You can now install your external package into Sage as you would a Cython package. The only difference is to call your setup.py script with Sage,

$ sage setup.py install --user

Running Tests

If your code uses Sage-style doctests,

$ sage setup.py install --user
$ sage -t --force-lib mypackage

If your code uses an alternate test suite then build your package in-place and run your test suite. For example, if you use Nose,

$ sage setup.py build_ext --inplace
$ nosetests

There are many design decisions to make when it comes to writing tests for Python software.

Current Examples

If you have any suggestions or improvements please leave a comment here.

@jdemeyer
Copy link

I think there are many things wrong with the Cython part. If this guide really gets taken seriously, this should be addressed (maybe I could do that). In short:

  1. build_ext does not work, you must use cythonize.
  2. Manually guessing the Sage include directories is no longer needed, use sage_include_directories from sage.env
  3. You don't need to change the Extension objects after creating them, you can give the include_dirs to distutils.

@jdemeyer
Copy link

If you do point to an external guide about packaging: this one https://python-packaging-user-guide.readthedocs.org/en/latest/ is much more up-to-date and really documents the current Python packaging best practices, not those of 4 years ago.

@vbraun
Copy link

vbraun commented Apr 15, 2016

  • I'd always use pip instead of calling setup.py by hand
  • What about doctests/unittests? >>> vs sage:, how to run tests, ...
  • What about documentation? Sage-style docstrings vs others, how to build documentation, ...

@cswiercz
Copy link
Author

@jdemeyer Thank you for the corrections. What I had written here is in part based on my own experience trying to get Abelfunctions working with Sage. Maybe I'm off my Cython game.

@cswiercz
Copy link
Author

@vbraun Those are both good questions.

  • Both Sage-style doctests and unit tests a la unittest are supported. (I'm not familiar with py.test or Nose so I cannot comment on supporting them.) In my own code I opted for the latter. Instructions can be provided for both.
  • Documentation is tricky. I don't know a good solution, especially if someone (like myself) wanted to use ReadTheDocs. At the very least, Sage-style docstrings would render in, say, the notebook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment