minchoc

Python versions PyPI - Version GitHub tag (with filter) License GitHub commits since latest release (by SemVer including pre-releases) CodeQL QA Tests Coverage Status Dependabot Documentation Status mypy uv pytest Ruff Downloads Stargazers pre-commit Prettier Follow @Tatsh Mastodon Follow

Minimal Chocolatey-compatible NuGet server in a Django app.

Installation

pip install minchoc

In settings.py, add 'minchoc' to INSTALLED_APPS. Set ALLOW_PACKAGE_DELETION to True if you want to enable this API.

INSTALLED_APPS = ['minchoc']
ALLOW_PACKAGE_DELETION = True

A DELETE call to /api/v2/package/<id>/<version> will be denied even with authentication unless ALLOW_PACKAGE_DELETION is set to True.

Add path('', include('minchoc.urls')) to your root urls.py. Example:

from django.urls import include, path
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('minchoc.urls')),
]

Run ./manage.py migrate or similar to install the database schema.

Notes

When a user is created, a NugetUser is also made. This will contain the API key for pushing. It can be viewed in admin.

Add your source to Chocolatey

As administrator:

choco source add -s 'https://your-host/url-prefix'
choco apikey add -s 'https://your-host/url-prefix' -k 'your-key'

On non-Windows platforms, you can use my pychoco package, which also supports the above commands.

Supported commands

  • choco install

  • choco push

  • choco search

Models

Model definitions.

class minchoc.models.Author(*args, **kwargs)

Author of the software, not the NuGet spec.

exception DoesNotExist
exception MultipleObjectsReturned
exception NotUpdated
class minchoc.models.Company(*args, **kwargs)

Company associated to NuGet packages.

exception DoesNotExist
exception MultipleObjectsReturned
exception NotUpdated
class minchoc.models.NugetUser(*args, **kwargs)

An owner of a NuGet spec.

exception DoesNotExist
exception MultipleObjectsReturned
exception NotUpdated
async static arequest_has_valid_token(request: HttpRequest) bool

Asynchronously check if the API key in the request is valid.

Parameters:
request : HttpRequest

The incoming HTTP request.

Returns:

True if the X-NuGet-ApiKey header is valid.

Return type:

bool

async static atoken_exists(token: str | None) bool

Asynchronously check if a token exists.

Parameters:
token : str | None

The API token to look up, or None.

Returns:

True if the token matches an existing user.

Return type:

bool

static request_has_valid_token(request: HttpRequest) bool

Check if the API key in the request is valid.

Parameters:
request : HttpRequest

The incoming HTTP request.

Returns:

True if the X-NuGet-ApiKey header is valid.

Return type:

bool

static token_exists(token: str | None) bool

Check if a token exists.

Parameters:
token : str | None

The API token to look up, or None.

Returns:

True if the token matches an existing user.

Return type:

bool

class minchoc.models.Package(*args, **kwargs)

An instance of a NuGet package.

exception DoesNotExist
exception MultipleObjectsReturned
exception NotUpdated

Views

Views.

class minchoc.views.APIV2PackageView(**kwargs)

API V2 package upload view.

async dispatch(request: HttpRequest, *args: Any, **kwargs: Any) HttpResponse

Check if a user is authorised before allowing the request to continue.

Parameters:
request : HttpRequest

The incoming request.

*args : Any

Positional arguments forwarded to the parent view.

**kwargs : Any

Keyword arguments forwarded to the parent view.

Returns:

Forbidden JSON if unauthorised; otherwise the parent dispatch result.

Return type:

HttpResponse

async post(request: HttpRequest) HttpResponse

Treat POST requests the same as PUT.

Parameters:
request : HttpRequest

The incoming request.

Returns:

Result of put().

Return type:

HttpResponse

async put(request: HttpRequest) HttpResponse

Upload a package. This must be a multipart upload with a single valid NuGet file.

Parameters:
request : HttpRequest

The upload request.

Returns:

201 on success, or JSON error with an appropriate status code.

Return type:

HttpResponse

async minchoc.views.fetch_package_file(request: HttpRequest, name: str, version: str) HttpResponse

Get the file for a package instance.

Sample URL: /api/package/name/123.0.0

This also handles deletions. Deletions will only be allowed with authentication and with settings.ALLOW_PACKAGE_DELETION set to True.

Parameters:
request : HttpRequest

The incoming GET or DELETE request.

name : str

NuGet package identifier.

version : str

Package version string.

Returns:

Zip payload, 204 on authorised delete, error JSON, 404, or 405.

Return type:

HttpResponse

async minchoc.views.find_packages_by_id(request: HttpRequest) HttpResponse

Take a GET request to find packages.

Sample URL: /FindPackagesById()?id=package-name

Supports $skiptoken parameter for pagination in the format: $skiptoken='PackageName','Version'.

Parameters:
request : HttpRequest

The incoming GET request.

Returns:

Atom feed XML, or 400 if required query parameters are missing.

Return type:

HttpResponse

minchoc.views.home(_request: HttpRequest) HttpResponse

Get the content for the static homepage.

Parameters:
_request : HttpRequest

The incoming request (unused).

Returns:

JSON response with an empty object.

Return type:

HttpResponse

minchoc.views.metadata(_request: HttpRequest) HttpResponse

Get content for static page at /$metadata and at /api/v2/$metadata.

Parameters:
_request : HttpRequest

The incoming request (unused).

Returns:

Service document XML for the NuGet v2 API.

Return type:

HttpResponse

async minchoc.views.packages(request: HttpRequest) HttpResponse

Take a GET request to find packages.

Query parameters $skip, $top and semVerLevel are ignored. This means pagination is currently not supported.

Sample URL: /Packages()?$orderby=id&$filter=(tolower(Id) eq 'package-name') and IsLatestVersion&$skip=0&$top=1

Parameters:
request : HttpRequest

The incoming GET request.

Returns:

Atom feed XML, or JSON error if the $filter expression is invalid.

Return type:

HttpResponse

async minchoc.views.packages_with_args(request: HttpRequest, name: str, version: str) HttpResponse

Alternate Packages() with arguments to find a single package instance.

Sample URL: /Packages(Id='name',Version='123.0.0')

Parameters:
request : HttpRequest

The incoming GET request.

name : str

NuGet package identifier.

version : str

Package version string.

Returns:

Atom entry XML if found, or 404 if the package does not exist.

Return type:

HttpResponse

Parsing

Parser.

minchoc.filteryacc.parser = <ply.yacc.LRParser object>

An extremely basic parser for parsing $filter strings. Returns a Q instance.

Utilities

Utility functions.

async minchoc.utils.make_entry(host: str, package: Package, ending: str = '\n') str

Create a package <entry> element for a package XML feed.

Parameters:
host : str

The protocol and hostname prefix for URLs, e.g. https://example.com.

package : Package

The Package instance to render.

ending : str

Trailing string appended after the closing </entry> tag.

Returns:

The rendered XML <entry> element.

Return type:

str

minchoc.utils.tag_text_or(tag: Element | None, default: str | None = None) str | None

Return text from a tag or the default value specified.

Parameters:
tag : Element | None

An XML element to extract text from, or None.

default : str | None

Value to return when tag is None or has no text content.

Returns:

The element’s text content, or default.

Return type:

str | None

Indices and tables