Skip to content

change to ruff #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 16, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

.py text eol=lf
.rst text eol=lf
.txt text eol=lf
.yaml text eol=lf
.toml text eol=lf
.license text eol=lf
.md text eol=lf
43 changes: 11 additions & 32 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

repos:
- repo: https://github.com/python/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/fsfe/reuse-tool
rev: v1.1.2
hooks:
- id: reuse
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/pylint
rev: v2.17.4
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
hooks:
- id: pylint
name: pylint (library code)
types: [python]
args:
- --disable=consider-using-f-string
exclude: "^(docs/|examples/|tests/|setup.py$)"
- id: pylint
name: pylint (example code)
description: Run pylint rules on "examples/*.py" files
types: [python]
files: "^examples/"
args:
- --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code
- id: pylint
name: pylint (test code)
description: Run pylint rules on "tests/*.py" files
types: [python]
files: "^tests/"
args:
- --disable=missing-docstring,consider-using-f-string,duplicate-code
- id: ruff-format
- id: ruff
args: ["--fix"]
- repo: https://github.com/fsfe/reuse-tool
rev: v3.0.1
hooks:
- id: reuse
399 changes: 0 additions & 399 deletions .pylintrc

This file was deleted.

6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
@@ -13,9 +13,9 @@ Introduction
:target: https://github.com/adafruit/Adafruit_CircuitPython_datetime/actions
:alt: Build Status

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: Code Style: Black
.. image:: https://img.shields.io/endpoint?url=https://github.com/raw/astral-sh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Code Style: Ruff

Basic date and time types. Implements a subset of the `CPython datetime module <https://docs.python.org/3/library/datetime.html>`_.

181 changes: 51 additions & 130 deletions adafruit_datetime.py

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -4,5 +4,8 @@
.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py)
.. use this format as the module name: "adafruit_foo.foo"
API Reference
#############

.. automodule:: adafruit_datetime
:members:
8 changes: 2 additions & 6 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-

# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import datetime
import os
import sys
import datetime

sys.path.insert(0, os.path.abspath(".."))

@@ -51,9 +49,7 @@
creation_year = "2021"
current_year = str(datetime.datetime.now().year)
year_duration = (
current_year
if current_year == creation_year
else creation_year + " - " + current_year
current_year if current_year == creation_year else creation_year + " - " + current_year
)
copyright = year_duration + " Brent Rubell"
author = "Brent Rubell"
2 changes: 1 addition & 1 deletion examples/datetime_simpletest.py
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@

# Example of working with a `datetime` object
# from https://docs.python.org/3/library/datetime.html#examples-of-usage-datetime
from adafruit_datetime import datetime, date, time
from adafruit_datetime import date, datetime, time

# Using datetime.combine()
d = date(2005, 7, 14)
111 changes: 111 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT

target-version = "py38"
line-length = 100

[lint]
preview = true
select = ["I", "PL", "UP"]

extend-select = [
"D419", # empty-docstring
"E501", # line-too-long
"W291", # trailing-whitespace
"PLC0414", # useless-import-alias
"PLC2401", # non-ascii-name
"PLC2801", # unnecessary-dunder-call
"PLC3002", # unnecessary-direct-lambda-call
"E999", # syntax-error
"PLE0101", # return-in-init
"F706", # return-outside-function
"F704", # yield-outside-function
"PLE0116", # continue-in-finally
"PLE0117", # nonlocal-without-binding
"PLE0241", # duplicate-bases
"PLE0302", # unexpected-special-method-signature
"PLE0604", # invalid-all-object
"PLE0605", # invalid-all-format
"PLE0643", # potential-index-error
"PLE0704", # misplaced-bare-raise
"PLE1141", # dict-iter-missing-items
"PLE1142", # await-outside-async
"PLE1205", # logging-too-many-args
"PLE1206", # logging-too-few-args
"PLE1307", # bad-string-format-type
"PLE1310", # bad-str-strip-call
"PLE1507", # invalid-envvar-value
"PLE2502", # bidirectional-unicode
"PLE2510", # invalid-character-backspace
"PLE2512", # invalid-character-sub
"PLE2513", # invalid-character-esc
"PLE2514", # invalid-character-nul
"PLE2515", # invalid-character-zero-width-space
"PLR0124", # comparison-with-itself
"PLR0202", # no-classmethod-decorator
"PLR0203", # no-staticmethod-decorator
"UP004", # useless-object-inheritance
"PLR0206", # property-with-parameters
"PLR0904", # too-many-public-methods
"PLR0911", # too-many-return-statements
"PLR0912", # too-many-branches
"PLR0913", # too-many-arguments
"PLR0914", # too-many-locals
"PLR0915", # too-many-statements
"PLR0916", # too-many-boolean-expressions
"PLR1702", # too-many-nested-blocks
"PLR1704", # redefined-argument-from-local
"PLR1711", # useless-return
"C416", # unnecessary-comprehension
"PLR1733", # unnecessary-dict-index-lookup
"PLR1736", # unnecessary-list-index-lookup

# ruff reports this rule is unstable
#"PLR6301", # no-self-use

"PLW0108", # unnecessary-lambda
"PLW0120", # useless-else-on-loop
"PLW0127", # self-assigning-variable
"PLW0129", # assert-on-string-literal
"B033", # duplicate-value
"PLW0131", # named-expr-without-context
"PLW0245", # super-without-brackets
"PLW0406", # import-self
"PLW0602", # global-variable-not-assigned
"PLW0603", # global-statement
"PLW0604", # global-at-module-level

# fails on the try: import typing used by libraries
#"F401", # unused-import

"F841", # unused-variable
"E722", # bare-except
"PLW0711", # binary-op-exception
"PLW1501", # bad-open-mode
"PLW1508", # invalid-envvar-default
"PLW1509", # subprocess-popen-preexec-fn
"PLW2101", # useless-with-lock
"PLW3301", # nested-min-max
]

ignore = [
"PLR2004", # magic-value-comparison
"UP030", # format literals
"PLW1514", # unspecified-encoding
"PLR0913", # too-many-arguments
"PLR0915", # too-many-statements
"PLR0917", # too-many-positional-arguments
"PLR0904", # too-many-public-methods
"PLR0912", # too-many-branches
"PLR0916", # too-many-boolean-expressions
"PLR6301", # could-be-static no-self-use
"PLC0415", # import outside toplevel
"PLC2701", # private import
"PLW2901", # loop var overwrite
"PLW1641", # obj not implement hash function
"PLR0914", # too many locals
]

[format]
line-ending = "lf"
22 changes: 6 additions & 16 deletions tests/test_date.py
Original file line number Diff line number Diff line change
@@ -12,10 +12,10 @@
# pylint:disable=invalid-name, no-member, wrong-import-position, undefined-variable, no-self-use, cell-var-from-loop, too-many-public-methods, fixme, import-outside-toplevel, unused-argument, too-few-public-methods
import sys
import unittest
from datetime import MAXYEAR, MINYEAR

# CPython standard implementation
from datetime import date as cpython_date
from datetime import MINYEAR, MAXYEAR

# CircuitPython subset implementation
sys.path.append("..")
@@ -130,9 +130,7 @@ def test_today(self):

# It worked or it didn't. If it didn't, assume it's reason #2, and
# let the test pass if they're within half a second of each other.
self.assertTrue(
today == todayagain or abs(todayagain - today) < timedelta(seconds=0.5)
)
self.assertTrue(today == todayagain or abs(todayagain - today) < timedelta(seconds=0.5))

def test_weekday(self):
for i in range(7):
@@ -155,9 +153,7 @@ def test_weekday(self):
cpython_date(1956, 1, 2 + i).isoweekday(),
)

@unittest.skip(
"Skip for CircuitPython - isocalendar() not implemented for date objects."
)
@unittest.skip("Skip for CircuitPython - isocalendar() not implemented for date objects.")
def test_isocalendar(self):
# Check examples from
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
@@ -187,9 +183,7 @@ def test_ctime(self):
t = cpy_date(2002, 3, 2)
self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")

@unittest.skip(
"Skip for CircuitPython - strftime() not implemented for date objects."
)
@unittest.skip("Skip for CircuitPython - strftime() not implemented for date objects.")
def test_strftime(self):
t = cpy_date(2005, 3, 2)
self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
@@ -252,9 +246,7 @@ def strftime(self, format_spec):
self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
self.assertEqual(b.__format__(fmt), 'B')"""

@unittest.skip(
"Skip for CircuitPython - min/max/resolution not implemented for date objects."
)
@unittest.skip("Skip for CircuitPython - min/max/resolution not implemented for date objects.")
def test_resolution_info(self):
# XXX: Should min and max respect subclassing?
if issubclass(cpy_date, datetime):
@@ -405,9 +397,7 @@ def __ge__(self, other):
self.assertEqual(our < their, True)
self.assertEqual(their < our, False)

@unittest.skip(
"Skip for CircuitPython - min/max date attributes not implemented yet."
)
@unittest.skip("Skip for CircuitPython - min/max date attributes not implemented yet.")
def test_bool(self):
# All dates are considered true.
self.assertTrue(cpy_date.min)
83 changes: 24 additions & 59 deletions tests/test_datetime.py
Original file line number Diff line number Diff line change
@@ -14,20 +14,17 @@

# CircuitPython subset implementation
sys.path.append("..")
from adafruit_datetime import datetime as cpy_datetime
from adafruit_datetime import timedelta
from adafruit_datetime import tzinfo
from adafruit_datetime import date
from adafruit_datetime import time
from adafruit_datetime import timezone

import unittest
from test import support
from test_date import TestDate
from datetime import MAXYEAR, MINYEAR

# CPython standard implementation
from datetime import datetime as cpython_datetime
from datetime import MINYEAR, MAXYEAR
from test import support

from test_date import TestDate

from adafruit_datetime import date, time, timedelta, timezone, tzinfo
from adafruit_datetime import datetime as cpy_datetime


# TZinfo test
@@ -158,12 +155,8 @@ def test_isoformat(self):
self.assertEqual(t.isoformat(timespec="hours"), "0001-02-03T04")
self.assertEqual(t.isoformat(timespec="minutes"), "0001-02-03T04:05")
self.assertEqual(t.isoformat(timespec="seconds"), "0001-02-03T04:05:01")
self.assertEqual(
t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.000"
)
self.assertEqual(
t.isoformat(timespec="microseconds"), "0001-02-03T04:05:01.000123"
)
self.assertEqual(t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.000")
self.assertEqual(t.isoformat(timespec="microseconds"), "0001-02-03T04:05:01.000123")
self.assertEqual(t.isoformat(timespec="auto"), "0001-02-03T04:05:01.000123")
self.assertEqual(t.isoformat(sep=" ", timespec="minutes"), "0001-02-03 04:05")
self.assertRaises(ValueError, t.isoformat, timespec="foo")
@@ -173,23 +166,15 @@ def test_isoformat(self):
self.assertEqual(str(t), "0001-02-03 04:05:01.000123")

t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
self.assertEqual(
t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.999+00:00"
)
self.assertEqual(t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.999+00:00")

t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
self.assertEqual(
t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.999"
)
self.assertEqual(t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.999")

t = self.theclass(1, 2, 3, 4, 5, 1)
self.assertEqual(t.isoformat(timespec="auto"), "0001-02-03T04:05:01")
self.assertEqual(
t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.000"
)
self.assertEqual(
t.isoformat(timespec="microseconds"), "0001-02-03T04:05:01.000000"
)
self.assertEqual(t.isoformat(timespec="milliseconds"), "0001-02-03T04:05:01.000")
self.assertEqual(t.isoformat(timespec="microseconds"), "0001-02-03T04:05:01.000000")

t = self.theclass(2, 3, 2)
self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
@@ -284,9 +269,7 @@ def test_more_ctime(self):

# So test a case where that difference doesn't matter.
t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
self.assertEqual(
t.ctime(), cpython_time.ctime(cpython_time.mktime(t.timetuple()))
)
self.assertEqual(t.ctime(), cpython_time.ctime(cpython_time.mktime(t.timetuple())))

def test_tz_independent_comparing(self):
dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
@@ -435,16 +418,12 @@ def test_computations(self):
a + (week + day + hour + millisec),
self.theclass(2002, 3, 10, 18, 6, 0, 1000),
)
self.assertEqual(
a + (week + day + hour + millisec), (((a + week) + day) + hour) + millisec
)
self.assertEqual(a + (week + day + hour + millisec), (((a + week) + day) + hour) + millisec)
self.assertEqual(
a - (week + day + hour + millisec),
self.theclass(2002, 2, 22, 16, 5, 59, 999000),
)
self.assertEqual(
a - (week + day + hour + millisec), (((a - week) - day) - hour) - millisec
)
self.assertEqual(a - (week + day + hour + millisec), (((a - week) - day) - hour) - millisec)
# Add/sub ints or floats should be illegal
for i in 1, 1.0:
self.assertRaises(TypeError, lambda: a + i)
@@ -532,12 +511,8 @@ def test_timestamp_naive(self):
# Missing hour
t0 = self.theclass(2012, 3, 11, 2, 30)
t1 = t0.replace(fold=1)
self.assertEqual(
self.theclass.fromtimestamp(t1.timestamp()), t0 - timedelta(hours=1)
)
self.assertEqual(
self.theclass.fromtimestamp(t0.timestamp()), t1 + timedelta(hours=1)
)
self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()), t0 - timedelta(hours=1))
self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()), t1 + timedelta(hours=1))
# Ambiguous hour defaults to DST
t = self.theclass(2012, 11, 4, 1, 30)
self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
@@ -556,9 +531,7 @@ def test_timestamp_aware(self):
self.assertEqual(t.timestamp(), 0.0)
t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
self.assertEqual(t.timestamp(), 3600 + 2 * 60 + 3 + 4 * 1e-6)
t = self.theclass(
1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone(timedelta(hours=-5), "EST")
)
t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone(timedelta(hours=-5), "EST"))
self.assertEqual(t.timestamp(), 18000 + 3600 + 2 * 60 + 3 + 4 * 1e-6)

@unittest.skip("Not implemented - gmtime")
@@ -609,9 +582,7 @@ def test_timestamp_limits(self):
min_ts = min_dt.timestamp()
try:
# date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
self.assertEqual(
self.theclass.fromtimestamp(min_ts, tz=timezone.utc), min_dt
)
self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc), min_dt)
except (OverflowError, OSError) as exc:
# the date 0001-01-01 doesn't fit into 32-bit time_t,
# or platform doesn't support such very old date
@@ -718,7 +689,7 @@ def test_strptime(self):
sign = "+"
seconds = tzseconds
hours, minutes = divmod(seconds // 60, 60)
dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
dtstr = f"{sign}{hours:02d}{minutes:02d} {tzname}"
dt = strptime(dtstr, "%z %Z")
self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
self.assertEqual(dt.tzname(), tzname)
@@ -766,9 +737,7 @@ def test_strptime_single_digit(self):
]
for reason, string, format, target in inputs:
reason = "test single digit " + reason
with self.subTest(
reason=reason, string=string, format=format, target=target
):
with self.subTest(reason=reason, string=string, format=format, target=target):
newdate = strptime(string, format)
self.assertEqual(newdate, target, msg=reason)

@@ -805,9 +774,7 @@ def test_more_timetuple(self):
def test_more_strftime(self):
# This tests fields beyond those tested by the TestDate.test_strftime.
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
self.assertEqual(
t.strftime("%m %d %y %f %S %M %H %j"), "12 31 04 000047 33 22 06 366"
)
self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), "12 31 04 000047 33 22 06 366")
for (s, us), z in [
((33, 123), "33.000123"),
((33, 0), "33"),
@@ -996,9 +963,7 @@ def __new__(cls, *args, **kwargs):
for constr_name, constr_args, expected in test_cases:
for base_obj in (DateTimeSubclass, base_d):
# Test both the classmethod and method
with self.subTest(
base_obj_type=type(base_obj), constr_name=constr_name
):
with self.subTest(base_obj_type=type(base_obj), constr_name=constr_name):
constructor = getattr(base_obj, constr_name)

dt = constructor(*constr_args)
12 changes: 4 additions & 8 deletions tests/test_time.py
Original file line number Diff line number Diff line change
@@ -14,12 +14,12 @@
import sys

sys.path.append("..")
from adafruit_datetime import time as cpy_time
import unittest

# CPython standard implementation
from datetime import time as cpython_time
import unittest

from adafruit_datetime import time as cpy_time

# An arbitrary collection of objects of non-datetime types, for testing
# mixed-type comparisons.
@@ -274,12 +274,8 @@ def test_str(self):
def test_repr(self):
name = "datetime." + self.theclass.__name__
self.assertEqual(repr(self.theclass(1, 2, 3, 4)), "%s(1, 2, 3, 4)" % name)
self.assertEqual(
repr(self.theclass(10, 2, 3, 4000)), "%s(10, 2, 3, 4000)" % name
)
self.assertEqual(
repr(self.theclass(0, 2, 3, 400000)), "%s(0, 2, 3, 400000)" % name
)
self.assertEqual(repr(self.theclass(10, 2, 3, 4000)), "%s(10, 2, 3, 4000)" % name)
self.assertEqual(repr(self.theclass(0, 2, 3, 400000)), "%s(0, 2, 3, 400000)" % name)
self.assertEqual(repr(self.theclass(12, 2, 3, 0)), "%s(12, 2, 3)" % name)
self.assertEqual(repr(self.theclass(23, 15, 0, 0)), "%s(23, 15)" % name)