Metadata-Version: 2.5
Name: lupaxa-domain-expiry
Version: 0.1.4
Summary: Look up domain name expiration dates over WHOIS.
Project-URL: Homepage, https://github.com/lupaxa-sre-toolbox/domain-expiry
Project-URL: Repository, https://github.com/lupaxa-sre-toolbox/domain-expiry
Project-URL: Issues, https://github.com/lupaxa-sre-toolbox/domain-expiry/issues
Author: The Lupaxa Project
License: MIT License
        
        Copyright (c) The Lupaxa Project
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENCE
Keywords: cli,domain,expiration,expiry,sre,whois
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet
Classifier: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.13
Requires-Dist: prettytable>=3.12
Requires-Dist: python-whois>=0.9
Requires-Dist: tld>=0.13
Provides-Extra: dev
Requires-Dist: bump-my-version>=1.2.0; extra == 'dev'
Requires-Dist: hatch>=1.12.0; extra == 'dev'
Requires-Dist: mypy>=1.12.0; extra == 'dev'
Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6.0; extra == 'dev'
Provides-Extra: test
Requires-Dist: pytest-cov>=5.0; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://github.com/lupaxa-sre-toolbox">
    <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/sre-toolbox/readme-logo.png" alt="SRE Toolbox" />
  </a>
</p>

<h1 align="center">Domain Expiry</h1>

Look up domain name expiration dates over WHOIS.

Requires Python 3.13 or newer and outbound access to the registry WHOIS
servers. `python-whois`, `prettytable`, and `tld` are installed with the
package.

## Install

```bash
pip install lupaxa-domain-expiry
domain-expiry --help
```

## CLI

```bash
domain-expiry example.com example.org
domain-expiry example.com,example.org
domain-expiry --file domains.txt --warn-days 14
domain-expiry --file - --timeout 5
domain-expiry --sort days --order descending example.com example.org
python -m lupaxa.domain_expiry --version
```

Pass several names as separate arguments, or separate them with commas.
`--file` reads one name per line. Blank lines and `#` comments are ignored.
`-` reads stdin. Repeat `--file` to combine lists. Names on the command line
are checked first, then names from each file. With no domains, the command
prints help and exits `2`.

| Flag                | Default   | Description                                          |
| :------------------ | :-------- | :--------------------------------------------------- |
| `DOMAIN`            | optional  | Domain name; repeat for more than one                |
| `--file`, `-f`      | none      | Text file of names, one per line; `-` reads stdin    |
| `--timeout`, `-t`   | `10`      | WHOIS socket timeout in seconds                      |
| `--warn-days`, `-w` | `30`      | Exit `1` when a domain expires within this many days |
| `--sort`, `-s`      | none      | Sort by `domain`, `date`, or `days`                  |
| `--order`           | ascending | `ascending` or `descending`; requires `--sort`       |
| `--version`         | —         | Print the package version and exit                   |

`--timeout` must be greater than `0`. `--warn-days` must be `0` or greater,
and the comparison is inclusive: `30` days left exits `1` when `--warn-days`
is `30`. `--warn-days 0` exits `1` for names that expire today or are already
expired. `--order` accepts `ascending`, `descending`, `asc`, or `desc`.
`--order` without `--sort` exits `2`.

`--sort` orders the table by domain, date, or days. Domain order is
alphabetical and ignores case. Date order uses the expiration date, including
names that show `Expired`. Day order uses the day count. Rows with no date or
day count stay at the end. Without `--sort`, rows stay in the order the names
were given.

## Output

Every name is a row, including invalid names and timeouts.

```text
+--------------+---------------------+------+
| Domain       | Date                | Days |
+--------------+---------------------+------+
| example.com  | 15th March 2027     |  174 |
| example.com  | 22nd September 2026 |    0 |
| example.com  | Expired             |  -12 |
| missing.test | Available           |    — |
| example.net  | unpublished         |    — |
| slow.test    | timed out           |    — |
| nope.invalid | Invalid Domain      |    — |
+--------------+---------------------+------+
```

A published date is the day, with `st`, `nd`, `rd`, or `th`, then the full
month name and the year, as in `11th November 2026`. `Expired` means that
date is already past. `Days` is the number of UTC calendar days remaining.
`0` means the name expires today. A negative number is how many days ago it
expired.

`Available` means the registry has no registration. `unpublished` means the
lookup succeeded but WHOIS included no date, which is common when a registry
redacts the record. `Invalid Domain` means the name was not queried: it is
empty, not a hostname, or its suffix is not a known top-level domain. A query
failure, including a connection timeout, puts the error in the date column.

International names are converted to ASCII before the query, and that form is
what the domain column prints.

## Exit Codes

| Code | When                                                                               |
| :--- | :--------------------------------------------------------------------------------- |
| `0`  | Help, version, or every domain has a published date further out than `--warn-days` |
| `1`  | A domain is due, expired, available, unpublished, or invalid                       |
| `2`  | Usage failed, a file could not be read, no domains were given, or a query failed   |

When one domain needs attention and another query fails, the command exits
`1`.

## Library

```python
from lupaxa.domain_expiry import lookup

result = lookup("example.com", timeout=10.0)
if result.status == "expires":
    print(result.expiration, result.days_remaining)
else:
    print(result.status, result.error)
```

`lookup` returns one `ExpiryResult` for a registry miss, an unpublished date,
an invalid name, or a query failure, including a connection timeout. `status`
is `expires`, `available`, `unpublished`, `invalid`, or `error`.
`days_remaining` is set only for `expires`. It is the difference of the UTC
calendar dates, so a date later today is `0`. `error` is set only for
`error`, and is a single line.

An empty name, a non-hostname, or an unknown top-level domain is `invalid`
and is not sent to WHOIS. A non-positive timeout raises `ValueError`. A
registry "no match" style failure is `available`, not `error`. Tests can pass
`whois_query` to replace the network call. Naive datetimes are treated as UTC,
and the earliest expiration date is kept.

`query_whois` runs the WHOIS query and restores the process socket timeout.
`DEFAULT_TIMEOUT` is `10.0` seconds. `DEFAULT_WARN_DAYS` is `30`.
`get_version()` returns the package version string.

## Development

```bash
make init
make python-install-dev
make python-check
```

<a href="https://github.com/the-lupaxa-project">
    <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
</a>
