diff --git a/test/fixtures/setup-py/README.rst b/test/fixtures/setup-py/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..357c3da23035c966961c138d1f302683ed4e4d67 --- /dev/null +++ b/test/fixtures/setup-py/README.rst @@ -0,0 +1,114 @@ +Maya: Datetime for Humansâ„¢ +========================== + +.. image:: https://img.shields.io/pypi/v/maya.svg + :target: https://pypi.python.org/pypi/maya + +.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master + :target: https://travis-ci.org/kennethreitz/maya + +.. image:: https://img.shields.io/badge/SayThanks.io-☼-1EAEDB.svg + :target: https://saythanks.io/to/kennethreitz + + +Datetimes are very frustrating to work with in Python, especially when dealing +with different locales on different systems. This library exists to make the +simple things **much** easier, while admitting that time is an illusion +(timezones doubly so). + +Datetimes should be interacted with via an API written for humans. + +Maya is mostly built around the headaches and use-cases around parsing datetime data from websites. + + +☤ Basic Usage of Maya +--------------------- + +Behold, datetimes for humans! + +.. code-block:: pycon + + >>> now = maya.now() + <MayaDT epoch=1481850660.9> + + >>> tomorrow = maya.when('tomorrow') + <MayaDT epoch=1481919067.23> + + >>> tomorrow.slang_date() + 'tomorrow' + + >>> tomorrow.slang_time() + '23 hours from now' + + >>> tomorrow.iso8601() + '2016-12-16T15:11:30.263350Z' + + >>> tomorrow.rfc2822() + 'Fri, 16 Dec 2016 20:11:30 -0000' + + >>> tomorrow.datetime() + datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>) + + # Automatically parse datetime strings and generate naive datetimes. + >>> scraped = '2016-12-16 18:23:45.423992+00:00' + >>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True) + datetime.datetime(2016, 12, 16, 13, 23, 45, 423992) + + >>> rand_day = maya.when('2011-02-07', timezone='US/Eastern') + <MayaDT epoch=1297036800.0> + + # Note how this is the 6th, not the 7th. + >>> rand_day.day + 6 + + # Always. + >>> rand_day.timezone + UTC + +☤ Why is this useful? +--------------------- + +- All timezone algebra will behave identically on all machines, regardless of system locale. +- Complete symmetric import and export of both ISO 8601 and RFC 2822 datetime stamps. +- Fantastic parsing of both dates written for/by humans and machines (``maya.when()`` vs ``maya.parse()``). +- Support for human slang, both import and export (e.g. `an hour ago`). +- Datetimes can very easily be generated, with or without tzinfo attached. +- This library is based around epoch time, but dates before Jan 1 1970 are indeed supported, via negative integers. +- Maya never panics, and always carries a towel. + + +☤ What about Delorean, Arrow, & Pendulum? +----------------------------------------- + +Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not. + +I simply desire a sane API for datetimes that made sense to me for all the things I'd ever want to do—especially when dealing with timezone algebra. Arrow doesn't do all of the things I need (but it does a lot more!). Maya does do exactly what I need. + +I think these projects complement each-other, personally. Maya is great for parsing websites. For example- Arrow supports floors and ceilings and spans of dates, which Maya does not at all. + + +☤ Installing Maya +----------------- + +Installation is easy, with pip:: + + $ pip install maya + +✨ðŸ°âœ¨ + +☤ Like it? +---------- + +`Say Thanks <https://saythanks.io/to/kennethreitz>`_! + + +How to Contribute +----------------- + +#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. +#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it). +#. Write a test which shows that the bug was fixed or that the feature works as expected. +#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. + +.. _`the repository`: http://github.com/kennethreitz/maya +.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst diff --git a/test/fixtures/setup-py/maya.py b/test/fixtures/setup-py/maya.py new file mode 100644 index 0000000000000000000000000000000000000000..e7323b089e618f0d38e5d341cfb10fd16ef3b860 --- /dev/null +++ b/test/fixtures/setup-py/maya.py @@ -0,0 +1,273 @@ + +# ___ __ ___ _ _ ___ +# || \/ | ||=|| \\// ||=|| +# || | || || // || || + +# Ignore warnings for yaml usage. +import warnings +import ruamel.yaml +warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning) + + +import email.utils +import time +from datetime import datetime as Datetime + +import pytz +import humanize +import dateparser +import iso8601 +import dateutil.parser +from tzlocal import get_localzone + +_EPOCH_START = (1970, 1, 1) + + +def validate_class_type_arguments(operator): + """ + Decorator to validate all the arguments to function + are of the type of calling class + """ + + def inner(function): + def wrapper(self, *args, **kwargs): + for arg in args + tuple(kwargs.values()): + if not isinstance(arg, self.__class__): + raise TypeError('unorderable types: {}() {} {}()'.format( + type(self).__name__, operator, type(arg).__name__)) + return function(self, *args, **kwargs) + + return wrapper + + return inner + + + +class MayaDT(object): + """The Maya Datetime object.""" + + def __init__(self, epoch): + super(MayaDT, self).__init__() + self._epoch = epoch + + def __repr__(self): + return '<MayaDT epoch={}>'.format(self._epoch) + + def __str__(self): + return self.rfc2822() + + def __format__(self, *args, **kwargs): + """Return's the datetime's format""" + return format(self.datetime(), *args, **kwargs) + + + @validate_class_type_arguments('==') + def __eq__(self, maya_dt): + return self._epoch == maya_dt._epoch + + @validate_class_type_arguments('!=') + def __ne__(self, maya_dt): + return self._epoch != maya_dt._epoch + + @validate_class_type_arguments('<') + def __lt__(self, maya_dt): + return self._epoch < maya_dt._epoch + + @validate_class_type_arguments('<=') + def __le__(self, maya_dt): + return self._epoch <= maya_dt._epoch + + @validate_class_type_arguments('>') + def __gt__(self, maya_dt): + return self._epoch > maya_dt._epoch + + @validate_class_type_arguments('>=') + def __ge__(self, maya_dt): + return self._epoch >= maya_dt._epoch + + + # Timezone Crap + # ------------- + + @property + def timezone(self): + """Returns the UTC tzinfo name. It's always UTC. Always.""" + return 'UTC' + + @property + def _tz(self): + """Returns the UTC tzinfo object.""" + return pytz.timezone(self.timezone) + + @property + def local_timezone(self): + """Returns the name of the local timezone, for informational purposes.""" + return self._local_tz.zone + + @property + def _local_tz(self): + """Returns the local timezone.""" + return get_localzone() + + @staticmethod + def __dt_to_epoch(dt): + """Converts a datetime into an epoch.""" + + # Assume UTC if no datetime is provided. + if dt.tzinfo is None: + dt = dt.replace(tzinfo=pytz.utc) + + epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC')) + return (dt - epoch_start).total_seconds() + + # Importers + # --------- + + @classmethod + def from_datetime(klass, dt): + """Returns MayaDT instance from datetime.""" + return klass(klass.__dt_to_epoch(dt)) + + @classmethod + def from_iso8601(klass, string): + """Returns MayaDT instance from iso8601 string.""" + dt = iso8601.parse_date(string) + return klass.from_datetime(dt) + + @staticmethod + def from_rfc2822(string): + """Returns MayaDT instance from rfc2822 string.""" + return parse(string) + + # Exporters + # --------- + + def datetime(self, to_timezone=None, naive=False): + """Returns a timezone-aware datetime... + Defaulting to UTC (as it should). + + Keyword Arguments: + to_timezone {string} -- timezone to convert to (default: None/UTC) + naive {boolean} -- if True, the tzinfo is simply dropped (default: False) + """ + if to_timezone: + dt = self.datetime().astimezone(pytz.timezone(to_timezone)) + else: + dt = Datetime.utcfromtimestamp(self._epoch) + dt.replace(tzinfo=self._tz) + + # Strip the timezone info if requested to do so. + if naive: + return dt.replace(tzinfo=None) + else: + if dt.tzinfo is None: + dt = dt.replace(tzinfo=self._tz) + + return dt + + def iso8601(self): + """Returns an ISO 8601 representation of the MayaDT.""" + # Get a timezone-naive datetime. + dt = self.datetime(naive=True) + return '{}Z'.format(dt.isoformat()) + + def rfc2822(self): + """Returns an RFC 2822 representation of the MayaDT.""" + return email.utils.formatdate(self.epoch, usegmt=True) + + # Properties + # ---------- + + @property + def year(self): + return self.datetime().year + + @property + def month(self): + return self.datetime().month + + @property + def day(self): + return self.datetime().day + + @property + def week(self): + return self.datetime().isocalendar()[1] + + @property + def weekday(self): + """Return the day of the week as an integer. Monday is 1 and Sunday is 7""" + return self.datetime().isoweekday() + + @property + def hour(self): + return self.datetime().hour + + @property + def minute(self): + return self.datetime().minute + + @property + def second(self): + return self.datetime().second + + @property + def microsecond(self): + return self.datetime().microsecond + + @property + def epoch(self): + return self._epoch + + # Human Slang Extras + # ------------------ + + def slang_date(self): + """"Returns human slang representation of date.""" + dt = self.datetime(naive=True, to_timezone=self.local_timezone) + return humanize.naturaldate(dt) + + def slang_time(self): + """"Returns human slang representation of time.""" + dt = self.datetime(naive=True, to_timezone=self.local_timezone) + return humanize.naturaltime(dt) + + + + +def now(): + """Returns a MayaDT instance for this exact moment.""" + epoch = time.time() + return MayaDT(epoch=epoch) + +def when(string, timezone='UTC'): + """"Returns a MayaDT instance for the human moment specified. + + Powered by dateparser. Useful for scraping websites. + + Examples: + 'next week', 'now', 'tomorrow', '300 years ago', 'August 14, 2015' + + Keyword Arguments: + string -- string to be parsed + timezone -- timezone referenced from (default: 'UTC') + + """ + dt = dateparser.parse(string, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'}) + + if dt is None: + raise ValueError('invalid datetime input specified.') + + return MayaDT.from_datetime(dt) + +def parse(string, day_first=False): + """"Returns a MayaDT instance for the machine-produced moment specified. + + Powered by dateutil. Accepts most known formats. Useful for working with data. + + Keyword Arguments: + string -- string to be parsed + day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False) + """ + dt = dateutil.parser.parse(string, dayfirst=day_first) + return MayaDT.from_datetime(dt) diff --git a/test/fixtures/setup-py/setup.py b/test/fixtures/setup-py/setup.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..38389c1d8c0c5bf4d423d1b08195c3fcd22a6ede 100644 --- a/test/fixtures/setup-py/setup.py +++ b/test/fixtures/setup-py/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import codecs + +from setuptools import setup + +try: + # Python 3 + from os import dirname +except ImportError: + # Python 2 + from os.path import dirname + +here = os.path.abspath(dirname(__file__)) + +with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = '\n' + f.read() + + +if sys.argv[-1] == "publish": + os.system("python setup.py sdist bdist_wheel upload") + sys.exit() + +required = [ + 'humanize', + 'pytz', + 'dateparser', + 'iso8601', + 'python-dateutil', + 'ruamel.yaml', + 'tzlocal' +] + +setup( + name='maya', + version='0.1.6', + description='Datetimes for Humans.', + long_description=long_description, + author='Kenneth Reitz', + author_email='me@kennethreitz.com', + url='https://github.com/kennethreitz/maya', + py_modules=['maya'], + install_requires=required, + license='MIT', + classifiers=( + + ), +) diff --git a/test/run b/test/run index c650ea79c94495777b5bff0d25afdab5c8807975..8876e4e61a1d2d030ee61fdfc8d0142a13455bba 100755 --- a/test/run +++ b/test/run @@ -1,5 +1,17 @@ #!/usr/bin/env bash -# See README.md for info on running these tests. + +testNoRequirements() { + compile "no-requirements" + assertCapturedError +} + + +testSetupPy() { + compile "setup-py" + assertCaptured "maya" + assertCapturedSuccess +} + testStandardRequirements() { compile "requirements-standard" @@ -26,6 +38,8 @@ testPython3() { } + + pushd $(dirname 0) >/dev/null popd >/dev/null diff --git a/test/utils b/test/utils index c30544b14474d309e90f380930d77865ce227bd0..c46d242264cc629affbe14dc8aaf90803eb7436e 100644 --- a/test/utils +++ b/test/utils @@ -147,6 +147,11 @@ _assertContains() fi } +debug() +{ + cat $STD_OUT +} + assertContains() { _assertContains "$@" 0 "text"