Python API

The library entry point is PackageExtractor. It detects the package type, picks the right extractor and returns a PackageMetadata object.

from upmex import PackageExtractor

extractor = PackageExtractor()
metadata = extractor.extract("requests-2.32.3-py3-none-any.whl")

print(metadata.name)                        # requests
print(metadata.version)                     # 2.32.3
print(metadata.package_type.value)          # python_wheel
print(metadata.purl)                        # pkg:pypi/requests@2.32.3
print([lic.spdx_id for lic in metadata.licenses])   # ['Apache-2.0']

upmex exports PackageExtractor, PackageMetadata and LicenseInfo at the top level. Everything else is imported from its module.

PackageExtractor

PackageExtractor(config: Optional[dict] = None)

config is a plain dictionary. The key upmex reads directly is registry_mode:

extractor = PackageExtractor({"registry_mode": True})

Building a Config object and passing config.to_dict() is the usual way to get the file and environment settings in as well. See Configuration.

extract

extract(package_path: str) -> PackageMetadata

Reads the package and returns its metadata. Raises FileNotFoundError if the path does not exist. A file whose type is not recognised does not raise; it comes back with package_type set to PackageType.UNKNOWN and only file level fields populated.

Problems encountered while reading a package are reported on standard output rather than raised, so a damaged archive returns a mostly empty record instead of failing. Check metadata.name against NO_ASSERTION if you need to tell that case apart.

PackageMetadata

A dataclass. Only name is required; everything else has a default, so a field being empty means the package did not declare it.

Field Type Notes
name str For Maven and jar packages this is groupId:artifactId
version str \| None  
package_type PackageType An enum, use .value for the string
purl str \| None None when no valid Package URL can be built
description str \| None  
homepage str \| None  
repository str \| None May be the string NO-ASSERTION
copyright str \| None Copyright statements, joined with ;
authors list[dict[str, str]] Each has name and usually email
maintainers list[dict[str, str]] May also carry organization and role
licenses list[LicenseInfo]  
dependencies dict[str, list[str]] Keyed by scope, usually runtime and dev
keywords list[str]  
classifiers list[str]  
file_size int \| None Bytes
file_hash str \| None SHA-1 of the package file
file_hash_md5 str \| None MD5 of the package file
fuzzy_hash str \| None TLSH, ssdeep or a simple fallback, prefixed with the scheme
extraction_timestamp datetime When extraction ran
schema_version str Version of the output shape
raw_metadata dict The unprocessed source, for example a parsed manifest
provenance dict[str, str] Field name to the source that supplied it
enrichment list[EnrichmentData] One record per external source used
vulnerabilities dict Populated by the VulnerableCode integration

to_dict

to_dict() -> dict

Returns the grouped structure the CLI prints, with sections package, metadata, people, licensing, dependencies, file_info, extraction_info, provenance, enrichment and vulnerabilities. Use this rather than reading the dataclass fields if you want the same shape as the JSON output.

Note that to_dict() returns the LicenseInfo values already flattened, but enrichment[].data holds whatever the source returned. If you serialise the result, serialise it with a default= handler or check that section first.

LicenseInfo

from upmex.core.models import LicenseInfo
Field Type Notes
spdx_id str \| None The SPDX identifier, or the declared text when it could not be matched
name str \| None  
text str \| None Full licence text when it was captured
confidence float 0.0 to 1.0
confidence_level LicenseConfidenceLevel exact, high, medium, low or none
detection_method str \| None How it was found, for example osslili_tag or declared
file_path str \| None Where in the package it was found

A detection_method of declared means the package stated a licence that could not be matched to an SPDX identifier. The stated value is kept in spdx_id and name so it is not lost, but it is not a valid SPDX identifier and should not be treated as one.

EnrichmentData

from upmex.core.models import EnrichmentData

Records that an external source contributed to the result. One per source, in metadata.enrichment.

Field Type Notes
source str For example maven_central or clearlydefined
source_type str registry for the package’s own registry, api for a third party
timestamp datetime  
data dict What the source returned
applied_fields list[str] Which fields were filled from it

PackageType

from upmex.core.models import PackageType

The values are python_wheel, python_sdist, npm, maven, jar, gradle, cocoapods, conda, conan, perl, ruby_gem, rust_crate, go_module, nuget, rpm, deb, generic and unknown.

Note that maven and jar are separate. A jar carrying an embedded POM is maven; one without is jar. Both map to the maven Package URL type.

Detecting without extracting

from upmex.utils.package_detector import detect_package_type

package_type = detect_package_type("rails-7.1.5.gem")
print(package_type.value)   # ruby_gem

This only inspects enough of the file to classify it, so it is much cheaper than a full extraction when you are routing files.

A worked example

Reading a directory of packages and reporting anything without a recognised licence:

from pathlib import Path
from upmex import PackageExtractor
from upmex.core.models import NO_ASSERTION

extractor = PackageExtractor()

for path in sorted(Path("lib").iterdir()):
    if not path.is_file():
        continue

    metadata = extractor.extract(str(path))

    if metadata.name == NO_ASSERTION:
        print(f"{path.name}: could not be read")
        continue

    spdx_ids = [lic.spdx_id for lic in metadata.licenses if lic.spdx_id]
    if spdx_ids:
        print(f"{path.name}: {', '.join(sorted(set(spdx_ids)))}")
    else:
        print(f"{path.name}: no licence declared")

Reuse one PackageExtractor across files rather than constructing one per file. It holds the extractor instances, and in registry mode it also benefits from the lookup cache that lives for the life of the process.