Skip to content

Extending setuptools-scm

setuptools-scm uses entry-point based hooks to extend its default capabilities.

Adding a new SCM

setuptools-scm provides two entrypoints for adding new SCMs:

setuptools_scm.parse_scm

A function used to parse the metadata of the current workdir using the name of the control directory/file of your SCM as the entrypoint's name. E.g. for the built-in entrypoint for Git the entrypoint is named .git and references setuptools_scm.git:parse

The return value MUST be a setuptools_scm.version.ScmVersion instance created by the function setuptools_scm.version.meta.

setuptools_scm.files_command
Either a string containing a shell command that prints all SCM managed files in its current working directory or a callable, that given a pathname will return that list.

Also uses then name of your SCM control directory as name of the entrypoint.

api reference for scm version objects

setuptools_scm.version.ScmVersion dataclass

represents a parsed version from scm

Source code in src/setuptools_scm/version.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
@dataclasses.dataclass
class ScmVersion:
    """represents a parsed version from scm"""

    tag: _v.Version | _v.NonNormalizedVersion | str
    """the related tag or preformatted version string"""
    config: _config.Configuration
    """the configuration used to parse the version"""
    distance: int = 0
    """the number of commits since the tag"""
    node: str | None = None
    """the shortened node id"""
    dirty: bool = False
    """whether the working copy had uncommitted changes"""
    preformatted: bool = False
    """whether the version string was preformatted"""
    branch: str | None = None
    """the branch name if any"""
    node_date: date | None = None
    """the date of the commit if available"""
    time: datetime = dataclasses.field(default_factory=_source_epoch_or_utc_now)
    """the current time or source epoch time
    only set for unit-testing version schemes
    for real usage it must be `now(utc)` or `SOURCE_EPOCH`
    """

    @property
    def exact(self) -> bool:
        """returns true checked out exactly on a tag and no local changes apply"""
        return self.distance == 0 and not self.dirty

    def __repr__(self) -> str:
        return (
            f"<ScmVersion {self.tag} dist={self.distance} "
            f"node={self.node} dirty={self.dirty} branch={self.branch}>"
        )

    def format_with(self, fmt: str, **kw: object) -> str:
        """format a given format string with attributes of this object"""
        return fmt.format(
            time=self.time,
            tag=self.tag,
            distance=self.distance,
            node=self.node,
            dirty=self.dirty,
            branch=self.branch,
            node_date=self.node_date,
            **kw,
        )

    def format_choice(self, clean_format: str, dirty_format: str, **kw: object) -> str:
        """given `clean_format` and `dirty_format`

        choose one based on `self.dirty` and format it using `self.format_with`"""

        return self.format_with(dirty_format if self.dirty else clean_format, **kw)

    def format_next_version(
        self,
        guess_next: Callable[Concatenate[ScmVersion, _P], str],
        fmt: str = "{guessed}.dev{distance}",
        *k: _P.args,
        **kw: _P.kwargs,
    ) -> str:
        guessed = guess_next(self, *k, **kw)
        return self.format_with(fmt, guessed=guessed)
branch class-attribute instance-attribute
branch: str | None = None

the branch name if any

config instance-attribute
config: Configuration

the configuration used to parse the version

dirty class-attribute instance-attribute
dirty: bool = False

whether the working copy had uncommitted changes

distance class-attribute instance-attribute
distance: int = 0

the number of commits since the tag

exact property
exact: bool

returns true checked out exactly on a tag and no local changes apply

node class-attribute instance-attribute
node: str | None = None

the shortened node id

node_date class-attribute instance-attribute
node_date: date | None = None

the date of the commit if available

preformatted class-attribute instance-attribute
preformatted: bool = False

whether the version string was preformatted

tag instance-attribute
tag: Version | NonNormalizedVersion | str

the related tag or preformatted version string

time class-attribute instance-attribute
time: datetime = field(default_factory=_source_epoch_or_utc_now)

the current time or source epoch time only set for unit-testing version schemes for real usage it must be now(utc) or SOURCE_EPOCH

format_choice
format_choice(clean_format: str, dirty_format: str, **kw: object) -> str

given clean_format and dirty_format

choose one based on self.dirty and format it using self.format_with

Source code in src/setuptools_scm/version.py
175
176
177
178
179
180
def format_choice(self, clean_format: str, dirty_format: str, **kw: object) -> str:
    """given `clean_format` and `dirty_format`

    choose one based on `self.dirty` and format it using `self.format_with`"""

    return self.format_with(dirty_format if self.dirty else clean_format, **kw)
format_with
format_with(fmt: str, **kw: object) -> str

format a given format string with attributes of this object

Source code in src/setuptools_scm/version.py
162
163
164
165
166
167
168
169
170
171
172
173
def format_with(self, fmt: str, **kw: object) -> str:
    """format a given format string with attributes of this object"""
    return fmt.format(
        time=self.time,
        tag=self.tag,
        distance=self.distance,
        node=self.node,
        dirty=self.dirty,
        branch=self.branch,
        node_date=self.node_date,
        **kw,
    )

setuptools_scm.version.meta

meta(tag: str | _VersionT, *, distance: int = 0, dirty: bool = False, node: str | None = None, preformatted: bool = False, branch: str | None = None, config: _config.Configuration, node_date: date | None = None) -> ScmVersion
Source code in src/setuptools_scm/version.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def meta(
    tag: str | _VersionT,
    *,
    distance: int = 0,
    dirty: bool = False,
    node: str | None = None,
    preformatted: bool = False,
    branch: str | None = None,
    config: _config.Configuration,
    node_date: date | None = None,
) -> ScmVersion:
    parsed_version = _parse_tag(tag, preformatted, config)
    log.info("version %s -> %s", tag, parsed_version)
    assert parsed_version is not None, f"Can't parse version {tag}"
    return ScmVersion(
        parsed_version,
        distance=distance,
        node=node,
        dirty=dirty,
        preformatted=preformatted,
        branch=branch,
        config=config,
        node_date=node_date,
    )

Version number construction

setuptools_scm.version_scheme

Configures how the version number is constructed given a ScmVersion instance and should return a string representing the version.

Available implementations

guess-next-dev (default)
Automatically guesses the next development version (default). Guesses the upcoming release by incrementing the pre-release segment if present, otherwise by incrementing the micro segment. Then appends :code:.devN. In case the tag ends with .dev0 the version is not bumped and custom .devN versions will trigger a error.
post-release (deprecated)

Generates post release versions (adds .postN) after review of the version number pep this is considered a bad idea as post releases are intended to be chosen not autogenerated.

the recommended replacement is no-guess-dev

python-simplified-semver

Basic semantic versioning.

Guesses the upcoming release by incrementing the minor segment and setting the micro segment to zero if the current branch contains the string feature, otherwise by incrementing the micro version. Then appending .devN.

This scheme is not compatible with pre-releases.

release-branch-semver
Semantic versioning for projects with release branches. The same as guess-next-dev (incrementing the pre-release or micro segment) however when on a release branch: a branch whose name (ignoring namespace) parses as a version that matches the most recent tag up to the minor segment. Otherwise if on a non-release branch, increments the minor segment and sets the micro segment to zero, then appends .devN
no-guess-dev
Does no next version guessing, just adds .post1.devN
only-version

Only use the version from the tag, as given.

This means version is no longer pseudo unique per commit

setuptools_scm.local_scheme

Configures how the local part of a version is rendered given a ScmVersion instance and should return a string representing the local version. Dates and times are in Coordinated Universal Time (UTC), because as part of the version, they should be location independent.

Available implementations

node-and-date (default)
adds the node on dev versions and the date on dirty workdir
node-and-timestamp
like node-and-date but with a timestamp of the form %Y%m%d%H%M%S instead
dirty-tag
adds +dirty if the current workdir has changes
no-local-version
omits local version, useful e.g. because pypi does not support it