Commit graph

50 commits

Author SHA1 Message Date
lukpueh
974de44ce9
Merge pull request #1323 from MVrachev/fix-version-comment
New API: Fix exception message for version
2021-03-24 15:07:16 +01:00
Martin Vrachev
cbc814ffa8 New API: Fix exception message for version
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2021-03-24 15:55:29 +02:00
Martin Vrachev
901b7f4491 Remove additional version settup in Signed
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2021-03-24 15:38:11 +02:00
Jussi Kukkonen
ab56344a53 metadata: Make isort happy and bundle imports
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
2021-03-19 17:10:45 +02:00
Jussi Kukkonen
4575637efd imports: Make 'formats' imports vendoring-compatible
Use "from tuf import <module>" instead of "import tuf.<module>": this
makes it possible for vendoring tool to vendor tuf. Fix all references
to <module> in the code.

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
2021-03-19 16:56:47 +02:00
Jussi Kukkonen
4b078b0975 imports: Make 'exceptions' imports vendoring-compatible
Use "from tuf import <module>" instead of "import tuf.<module>": this
makes it possible for vendoring tool to vendor tuf. Fix all references
to <module> in the code.

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
2021-03-19 16:54:39 +02:00
Lukas Puehringer
be0cef067c Manually standardize quotes where black cannot
Black standardizes single to double quotes where feasible.
However, it doesn't seem to change double to single quotes nor adds
escape characters, as a consequence it skips standardization on
strings with mixed quotes.

Unfortunately, pylint's quote consistency check also doesn't detect
this, so the onus will remain on the reviewer in these cases.

**Unrelated changes**:
The commit still enables pylint's "check-quote-consistency" just in
case it can detect something the black doesn't.

The commit also fixes a syntax inconsistency in pylintrc.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-17 11:57:45 +01:00
Lukas Puehringer
4648fbfadd Update tuf/api/pylintrc for new code
The updated pylintrc is based on the Google Python Style Guide
pylint configuration at
https://google.github.io/styleguide/pylintrc with the following
differences:
- We don't list defaults which are applied anyway.
- We don't configure checks that seem unrelated to the code style
  guide.
- We don't disable any checks that are not in conflict with the
  current code or code style guide.

This has the advantage of a minimal configuration file which should
be easy to maintain and extend as required, e.g. if conflicting
code is added, or linting time becomes too long, due to unnecessary
checks.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-12 12:27:59 +01:00
Lukas Puehringer
42a797b4a1 Re-format tuf/api/* using black and isort
Use black and isort to reformat new code in tuf/api/*, like so:

```
black --line-length 80 api
isort --line-length 80 --profile black api
```

Besides downsizing the default line length to fit our Code Style
Guide no extra configuration is required.

Unified format according to black and isort will be enforced by
CI/CD in a future commit.

**Changes include:**
- Use double quotes instead of single quotes where feasible
- Re-wrap and re-indent long lines such as dict literals, function
  signatures and function calls, using hanging indent
  This will require an update in our Code Style Guide, which the
  benefits of using black seem worth.
  https://github.com/secure-systems-lab/code-style-guidelines/blob/master/python.md#indentation-and-line-continuation
- Update vertical and horizontal spacing
- Sort and wrap imports

See black and isort docs for details:
https://black.readthedocs.io/en/stable/the_black_code_style.html
https://pycqa.github.io/isort/docs/configuration/black_compatibility/

NOTE: If desired I can split commits by change and/or configure git
for this repo to ignore the corresponding revision(s) in git-blame.
https://github.com/psf/black#migrating-your-code-style-without-ruining-git-blame

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-12 11:51:55 +01:00
Martin Vrachev
49aa0fc167 Make new API compatible with the Signing interface
In the securesystemslib pr https://github.com/secure-systems-lab/securesystemslib/pull/319
I added a new Signer interface with the purpose of supporting multiple
signing implementations.
Additionally, I added the SSlibSigner implementation of that interface
which implements the signing operation for rsa, ed25519 and ecdsa
schemes.
With this commit, I integrate the SSlibSigner into the new API in tuf.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2021-03-10 10:57:45 +01:00
Lukas Puehringer
ef91964db0 Call mixin-style parent methods on cls/self
Call an instance method and a static method that are only defined
in a parent class from child instances using self (instance) and
cls (static) instead of super().

While this doesn't make a practical difference, the new syntax is
probably less confusing to the reader.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
a53d68b91d Re-word api.serializer.json docstrings
- Make class docstrings wording consistent.
- Emphasize that we use the OLPC Canonical JSON specification.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
bd94f6d8d1 Remove py2 compat from api.serialization package
tuf.api is not designed for Python 2 compatibility. This commit
removes the following stray compatibility constructs in its
serialization subpackage:

- '__metaclass__ = abc.ABCMeta'
- six.raise_from

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
ab92ba257f Fix inconsistent returns in json serializers
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
326d2af7c4 Fix blank lines in tuf.api as per styleguide
https://github.com/google/styleguide/blob/gh-pages/pyguide.md#35-blank-lines

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
ace25e4ad3 Demystify from/to_dict methods in Signed baseclass
Prior to this commit the (abstract) 'Signed' base class implemented
from/to_dict methods, to be used by any subclass in addition to
or instead of a custom from/to_dict method. The design led to some
confusion, especially in 'Signed.from_dict' factories, which
instantiated subclass objects when called on a subclass, which
didn't implement its own 'from_dict' method.

This commit demystifies the design, by implementing from/to_dict
on all 'Signed' subclasses, and moving common from/to_dict tasks
to helper functions in the 'Signed' class.

The newly gained clarity and explicitness comes at the cost of
slightly more lines of code.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
f8fc5e263b Reduce JSON-bias in metadata class model
Clarify that the TUF metadata class model is not bound to a JSON
wireline format by:

- re-wording module, class and method docstrings and code comments
  to add details about custom and default serialization and the
  purpose of from/to_dict methods, and

- removing the 'JsonDict' type annotation -- instead we use
  generic Mapping[str, Any] for method arguments and strict
  Dict[str, Any] as return value as suggested in
  https://docs.python.org/3/library/typing.html#typing.Dict

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
aba6ba3f30 Use named argument instead of clarifying comment
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:38 +01:00
Lukas Puehringer
d823c8fc01 Rename a few variables in tuf.api
- Rename _dict to json_dict to avoid wrong semantics of leading
  underscore. (leading underscore was initially chosen to avoid name
  shadowing)

- Rename 'serializer' argument of type 'SignedSerializer' to
  'signed_serializer', to distinguish from 'serializer' argument of
  type 'MetadataSerializer'.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:30 +01:00
Lukas Puehringer
aa8225cb07 Mark kwargs in metadata API methods as Optional
Use typing.Optional for optional kwargs that default to None.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:09 +01:00
Lukas Puehringer
2b4085718b Re-word serialization cyclic import code comments
- Try to clarify purpose and remove unimportant TODO note
- Use pylint block-level control for shorter lines, see
  http://pylint.pycqa.org/en/latest/user_guide/message-control.html#block-disables

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-10 09:44:03 +01:00
Lukas Puehringer
2f57eb8ed7 Add SPDX style license and copyright boilerplate
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 16:26:34 +01:00
Lukas Puehringer
8e9afc96f9 Revert "Move to/from_dict metadata API methods..."
Revert an earlier commit that moved to/from_dict metadata class
model methods to a util module of the serialization sub-package.

We keep to/from_dict methods on the metadata classes because:
- It seems **idiomatic** (see e.g. 3rd-party libaries such as attrs,
pydantic, marshmallow, or built-ins that provide default or
customizable dict representation for higher-level objects).
The idiomatic choice should make usage more intuitive.
- It feels better **structured** when each method is encapsulated
within the corresponding class, which in turn should make
maintaining/modifying/extending the class model easier.
- It allows us to remove function-scope imports (see subsequent
commit).

Caveat:
Now that "the meat" of the sub-packaged JSON serializer is
implemented on the class, it might make it harder to create a
non-dict based serializer by copy-paste-amending the JSON
serializer.

However, the benefits from above seem to outweigh the disadvantage.

See option 5 of ADR0006 for further details (#1270).

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 12:46:16 +01:00
Lukas Puehringer
e1be085c3c Move to/from_dict metadata API methods to util
Add tuf.api.serialization.util module with functions to
convert between TUF metadata class model and the corresponding
dictionary representation. These functions replace the
corresponding to/from_dict classmethods.

Configure api/pylintrc to exempt '_type' from protected member
access warning, because the underscore prefix here is only used to
avoid name shadowing.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 12:33:28 +01:00
Lukas Puehringer
240fb547af Use custom errors in serializer.json sub-package
Re-raise all errors that happen during de/serialization as custom
De/SerializationError.

Whilelist 'e', which is idiomatic for error, in api/pylintrc, and
inline exempt broad-except, which are okay if re-raised.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 12:33:28 +01:00
Lukas Puehringer
499f1c858e Adopt serialization sub-package in metadata API
- Rename Metadata methods:
  - to_json_file -> to_file
  - from_json_file -> from_file
- Remove Metadata.from_json/to_json
- Remove Signed.to_canonical_bytes
- Accept optional de/serializer arguments:
  - from_file (default: JSONDeserializer)
  - to_file (default: JSONSerializer)
  - sign, verify (default: CanonicalJSONSerializer)
- inline disable pylint cyclic-import checks

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 12:33:18 +01:00
Lukas Puehringer
4a22b4a578 Add concrete de/serializer implementations
Add serializer.json module with implementations to serialize and
deserialize TUF role metadata to and from the JSON wireline format
for transportation, and to serialize the 'signed' part of TUF role
metadata to the OLPC Canonical JSON format for signature generation
and verification.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-04 10:51:37 +01:00
Lukas Puehringer
3d8cade471 Add metadata serialization sub-package
Add sub-package with 3 abstract base classes to:
- serialize Metadata objects to bytes (transport)
- deserialize Metadata objects from bytes (transport)
- serialize Signed objects to bytes (signatures)

pylint notes:
- configure tox to use api/pylintrc
- configure api/pylintrc to allow classes without public methods
  (default was 2)

Design considerations
---------------------
- Why not implement de/serialization on metadata classes?
  -> See ADR0006.

- Why use separate classes for serialization and deserialization?
  -> Some users might only need either one, e.g. client only needs
     Deserializer. Maybe there are use cases where different
     implementations are used to serialize and deserialize.

- Why use separate classes for Metadata- and Signed-Serialization?
  -> They require different concrete types, i.e. Metadata and
     Signed as parameters, and using these specific types seems to
     make the interface stronger.

- Why are de/serialize methods not class/staticmethods?
  -> In reality we only use classes to namespace and define a
     type annotated interface, thus it would be enough to make the
     methods classmethods. However, to keep the de/serialize
     interface minimal, we move any custom format configuration to
     the constructor. (See e.g. "compact" for JSONSerializer in
     subsequent commit).

Naming considerations
---------------------
- Why de/serialize?
  -> Implies byte stream as input or output to the function, which
     is what our interface needs.
- Why not marshaling?
  -> Synonym for serialize but implies transport, would be okay.
- Why not encoding?
  -> Too abstract and too many connotations (character, a/v).
- Why not parse?
  -> Too abstract and no good opposite terms (unparse, write,
     dump?)

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2021-03-03 15:09:48 +01:00
Teodora Sechkova
82726359da
Add root metadata class
Add root metadata class to tuf.api.metadata module and implement
(de)serialisation and modification methods.

Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
2020-11-09 11:37:55 +02:00
Joshua Lock
a53d4ec475 Disable an instance of too-many-arguments error
The Targets constructor takes seven arguments, which violates pylints
default value of five for max-arguments:

R0913: Too many arguments (7/5) (too-many-arguments)

As this feels like a coding style decision that should be made and
documented disable that test for only the Targets constructor until
a coding style decision has been made and documented as a decision
record.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-10-15 14:35:14 +01:00
Joshua Lock
f205e98851 Remove else after raise usage in api.metadata
Using an else after a raise results in a refactor message from pylint:

R1720: Unnecessary "elif" after "raise" (no-else-raise)

This is because the raise will exit the block, and pylint suggests that
explicit if's, rather than an if-elif-else, are clearer style. Update the
style of Metadata.verify() to match pylint expectations.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-10-15 14:35:14 +01:00
Joshua Lock
fea52b54ee Remove use of single letter variable
A single letter variable name of 'f' causes pylint to throw a coding style
convention warning:

C0103: Variable name "f" doesn't conform to snake_case naming style
(invalid-name)

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-10-15 14:35:14 +01:00
Joshua Lock
3877667ff4 Remove unused import from tuf.api.metadata
The logging module is not used in metadata, therefore remove it

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-10-15 14:35:14 +01:00
Joshua Lock
f91ce395e5 Add minimal pylintrc for new code in tuf/api
Add a minimal pylintrc to lint for new code being developed in tuf/api and
update the tox configuration to ignore tuf/api with the default pylintrc
and run an extra invocation of pylint for just the modules in tuf/api.

Signed-off-by: Joshua Lock <jlock@vmware.com>
2020-10-15 14:35:14 +01:00
William Woodruff
05b1609786 tuf/api: Expose tuf.api as a package
Signed-off-by: William Woodruff <william@trailofbits.com>
2020-10-15 14:35:14 +01:00
Jussi Kukkonen
2f69986e2b Remove iso8601 dependency
Our 'expires' strings are constrained by the ISO8601_DATETIME_SCHEMA
which matches regex '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z'. This can be
parsed with just a datetime.strptime(): iso8601 module is not needed.

* Add formats.expiry_string_to_datetime() helper function
* Modify the 3 locations that used iso8601 and the api/metadata.py usage
  of datetime.strptime()
* Remove related unnecessary logger setup
* Add the missing exception documentation to relevant functions (in many
  cases the exception is rather unlikely as the schema has been verified
  many times before this though...)

Fixes #1065

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
2020-10-13 20:06:14 +03:00
Lukas Puehringer
f106435aa5 Remove iso8601 dependency from simple metadata api
Use builtin datetime instead of external iso6801 for simple
datetime string parsing. Also see
https://github.com/theupdateframework/tuf/issues/1065

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
228a4c72e0 Ticketize doc header todo items
See:
Add root metadata class to new TUF metadata model #1137
Add classes for complex metadata fields #1139
Add input validation to simple metadata api #1140

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
73dd72d54d Raise on bad signature count in Metadata.verify
Change Metadata.verify(key) behavior to raise an exception if
none or multiple signatures for the passed key are found on the
Metadata object.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
f9a4ebe1ea Re-order metadata methods logically and add vspace
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
387169fc11 Add from_json metadata convenience wrapper
Add convenience wrapper that takes a json string and passes it
to from_dict to create a Metadata object.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
f63dce6ddd Refactor metadata constructors and add factory
This commit better separates the Metadata class model from the
Metadata wireline format, by tailoring the constructors
towards class-based parameters and adding an additional
factory classmethod that creates Metadata objects based on the
wireline json/dictionary metadata representation. (pythonic
way of constructor overloading).

This 'from_dict' factory method recurses into the 'from_dict'
methods of each contained complex field/attribute that is also
represented by a class. Currently 'signed' is the only such
attribute.

This commit further:
- Changes optional constructor keyword arguments to mandatory
positional arguments: Reduces code and simplifies usage by
restricting it. For now, users are unlikely to call
constructor directly anyway, but the 'from_dict' factory (or
its 'from_json_file' wrapper) instead.

- Removes Signed.__expiration (datetime) vs. Signed.expires
(datestring) dichotomy: Keeping only one representation of the
same attribute in memory makes the interface simpler and less
ambiguous. We choose the datetime object, because it is more
convenient to modify. Transformation from and to the string
format required by the tuf wireline format is performed in the
corresponding metadata de/serialization methods, i.e.
('to_dict' and 'from_dict').

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
f738ea0273 Rename tuf metadata interface methods
Consistenly rename de/serialization interface methods, using
a 'from_' and 'to_' prefix.

read_from_json -> from_json_file
write_to_json  -> to_json_file
as_json        -> to_json
as_dict        -> to_dict
signed_bytes   -> to_canonical_bytes

The latter is also changed from a property to a method for
consistency with the other serialization methods.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
e61ae1bea3 Remove Signed.read_from_json metadata method
Remove metadata factory on Signed class, for the sake of API
simplicity/non-ambiguity, i.e. it's enough to have one
way of loading any Metadata, that is:
Metadata.read_from_json

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:28 +02:00
Lukas Puehringer
08bdc171e4 Add simple sign + verify Metadata methods (+tests)
Add simple methods to create or verify signatures of the
canonical_signed property of a Metadata object.

See corresponding docstrings for behavior and design
considerations.

The commit also adds tests and updates the test setup to load
some test keys into memory.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:18:19 +02:00
Lukas Puehringer
5cc73353fa Add metadata model class and method docstrings
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 16:09:22 +02:00
Lukas Puehringer
0d7e2680f2 Simplifies Timestamp.update method
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:10 +02:00
Lukas Puehringer
088e94055f Replace _get_written_metadata with as_json method.
Add simple as_json Metadata method and use it instead of repository
lib's internal _get_written_metadata function in write_to_json.

This commit further adds code documentation and the possibility to
write compact json by excluding whitespace to write_to_json, and
also removes a call to the sign method from write_to_json.

The commit also adds tests.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:10 +02:00
Lukas Puehringer
e997097d1c Add generic Metadata.read_from_json class method
Add generic read from json class method that returns a Metadata
object with a signed field that contains the appropriate Signed
subclass, based on the signed._type field of the read metadata.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-09-10 15:59:10 +02:00
Lukas Puehringer
17f08ad200 Add simple TUF role metadata model (WIP)
Add metadata module with container classes for TUF role metadata, including
methods to read/serialize/write from and to JSON, perform TUF-compliant
metadata updates, and create and verify signatures.

The 'Metadata' class provides a container for inner TUF metadata objects (Root,
Timestamp, Snapshot, Targets) (i.e. OOP composition)

The 'Signed' class provides a base class to aggregate common attributes (i.e.
version, expires, spec_version) of the inner metadata classes. (i.e. OOP
inheritance). The name of the class also aligns with the 'signed' field of
the outer metadata container.

Based on prior observations in TUF's sister project in-toto, this architecture
seems to well represent the metadata model as it is defined in the
specification (see in-toto/in-toto#98 and in-toto/in-toto#142 for related
discussions).

This commits also adds tests.

**TODO: See doc header TODO list**

**Additional design considerations**
(also in regards to prior sketches of this module)

 - Aims at simplicity, brevity and recognizability of the wireline metadata
   format.

 - All attributes that correspond to fields in TUF JSON metadata are public.
   There doesn't seem to be a good reason to protect them with leading
   underscores and use setters/getters instead, it just adds more code, and
   impedes recognizability of the wireline metadata format.

 - Although, it might be convenient to have short-cuts on the Metadata class
   that point to methods and attributes that are common to all subclasses of
   the contained Signed class (e.g. Metadata.version instead of
   Metadata.signed.version, etc.), this also conflicts with goal of
   recognizability of the wireline metadata. Thus we won't add such short-cuts
   for now. See:
   https://github.com/theupdateframework/tuf/pull/1060#discussion_r452906629

 - Signing keys and a 'consistent_snapshot' boolean are not on the targets
   metadata class. They are a better fit for management code. See:
   https://github.com/theupdateframework/tuf/pull/1060#issuecomment-660056376,
   and #660.

 - Does not use sslib schema checks (see TODO notes about validation in
   doc header)

 - Does not use existing tuf utils, such as make_metadata_fileinfo,
   build_dict_conforming_to_schema, if it is easy and more explicit to
   just re-implement the desired behavior on the metadata classes.

 - All datetime's are treated as UTC. Since timezone info is not captured in
   the wireline metadata format it should not be captured in the internal
   representation either.

 - Does not use 3rd-party dateutil package, in order to minimize dependency
   footprint, which is especially important for update clients which often have
   to vendor their dependencies.
   However, compatibility between the more advanced dateutil.relativedelta (e.g
   handles leap years automatically) and timedelta is tested.

 - Uses PEP8 indentation (4 space) and Google-style doc string instead of
   sslab-style. See
   https://github.com/secure-systems-lab/code-style-guidelines/issues/20

 - Does not support Python =< 3.5

Co-authored-by: Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com>
Co-authored-by: Joshua Lock <jlock@vmware.com>
Co-authored-by: Teodora Sechkova <tsechkova@vmware.com>
Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
2020-08-20 12:14:40 +02:00