How to properly implement method cascading - chaining in Python? - python

I have a python code roughly like this:
class SomeClass():
def get_date(self):
date = # read the data from the database in YYYY-MM-DD HH:MM:SS format
return date
now, I will use the returned value in an API query, which accepts the input as a unix timestamp. Actually I can give the returned date to another function to have it converted to a timestamp, but I thought it would be a good opportunity to learn about function cascading / chaining, which I've always wondered about in Python.
Now, what I basically want to achieve is something like this:
If I just execute SomeClass.get_date(), I want to have the date in the same format that has been returned from the database, which is YYYY-MM-DD HH:MM:SS
If I execute SomeClass.get_date().as_timestamp(), I should get it as timestamp.
If not possible, I would settle for having it like SomeClass.get_date().as_datetime() and SomeClass.get_date().as_timestamp()
I might use the second function (.as_timestamp()) for more than one primary function (there are multiple datetime columns that I may need to be converted into timestamp).
I've looked into some examples of function cascading. They are mostly saying that key to this is returning the self, but I could not find any info about how to implement it in the way I need to.
I basically need the return value to be fed into the second function (I guess), but not sure if it is possible, and don't know how to do it. Any ideas?

What you're looking for looks more like this:
import time
import calendar
class DateClass():
def get_date(self):
self = # read the data from the database in YYYY-MM-DD HH:MM:SS format
return self
def as_timestamp(self):
mysql_time = time.strptime(self, '%Y-%m-%d %H:%M:%S')
self = calendar.timegm(mysql_time)
return self
d = DateClass()
print(d.get_date())
print(d.get_date().as_timestamp())
print(d)
d.get_date()
print(d)
The first output would be the MySQL datetime, and the second would be the Unix timestamp, the third would also be the unix timestamp and the fourth would be the datetime. get_date() and as_timestamp() mutate the instance d when called on it.
In this configuration, as_timestamp() requires get_date(), which seems to be in line with your question, but you could change that by assigning self from the database in as_timestamp() like this:
def as_timestamp(self):
self = # read the data from the database in YYYY-MM-DD HH:MM:SS format
mysql_time = time.strptime(self, '%Y-%m-%d %H:%M:%S')
self = calendar.timegm(mysql_time)
return self
which will make it possible to call as_timestamp() independent of get_date(). You can also then add d.as_timestamp() to mutate any iteration of d so that d is a timestamp.

Related

checking when a command was last used

I have a discord bot which has a command that requires a cooldown of a week. I considered using something like #commands.cooldown, but the issue is that if the bot goes offline, that cooldown resets itself. So, a member could simply watch for when the bot goes down and try to exploit it after it returns.
Since I can't keep the bot running 24x7x365, the solution I came up with was to record the date a user last used the command in a database, and when they use the command next, to check if seven days have passed.
For example's sake, this is what the command would look like:
#bot.command()
async def datetest(ctx,date):
today = datetime.date.today()
print(today)
someday = datetime.date.strptime(date,"%y-%m-%d")
diff = someday - today
await ctx.send(diff + "days.")
In this example, I'm using "date" as a parameter to just test if the command returns the difference of days.
(in the actual command, rather than take a date argument, it'll get a date stored in the DB)
However, the issue I'm facing is that:
someday = datetime.date.strptime(date,"%y-%m-%d")
doesn't run. What am I doing wrong here?
1: To format a date it's strftime 'f' for 'format' not strptime, 'parse'. strptime is used to turn a string object into a datetime object.
2: If you are working with the object you don't need to also pass that object to the methods of that class. i.e. you don't need to pass date to the object method 'strftime'
This is because, as with all object methods, it's done internally using the implicit 'self' parameter.
3: To get the actual date object you need to call a method of the datetime class which produces that. e.g. datetime.now()
Your code line should simply read something like:
import datetime.datetime as datetime
# or from datetime import datetime
someday = datetime.now().date().strftime("%y-%m-%d")
or
import datetime
someday = datetime.datetime.now().date().strftime("%y-%m-%d")
(Reading your post not sure what your intent is, so adding this for completeness)
Going the other way, getting a value from your database and using that to generate a datetime.
I don't know what database interface you are using so this is a general answer, as for many Python DB interfaces this is not necessary as the date field select() call will return a type for that field that is a datetime object.
# Paraphrasing a DB interface
dt_col = db.select("select max(date) from lastrun_dates;").execute()
# returns ["2022-10-3",]
date = datetime.datetime.strptime(dt_col[0], "%Y-m-%d")
# note the uppercase 'Y' for a 4 digit date
However, don't do this!
I don't recommend saving dates/times in a DB (or anywhere else for that matter) as a string as it has a DB schema, geographical, localle and daylight saving interpretation, which results in many ways of creating future issues. Instead it is recommended to use the UTC datetime from the epoch as an integer or float.
datetime.datetime.utcnow().timestamp()
# or
datetime.datetime.utcnow().toordinal()
Ok, so I figured out the issue after digging deeper into how datetime subtractions work.
One is that I needed to use someday = datetime.datetime.strptime().
Two is that I needed to use %Y instead of %y to match the time format passed into the date parameter (yyyy-mm-dd). Console pointed this out after a few tests.
Third is that I needed to extract the number of days out of the timedelta answer, because in the end, I'm trying to see how many days passed from the time the command was last used.
So, the new code looks like:
someday = datetime.datetime.strptime(date, "%Y-%m-%d")
diff = someday - today
print(diff.days)

How to cast integer (epoch) column to datetime in sqlalchemy?

I'm using sqlalchemy to query a database and have a Table.c.Field object from inspect, where Field is of type integer (e.g., 1596657600). How do I cast that field to a datetime? In the query I want to cast Field as datetime, then later extract and groupby dayofweek (or some other aspect of datetime). But first I need to cast Field as a date or datetime.
I have tried a few ways, but all fail. In the code below I first import sqlalchemy as sa.
I tried
sa.func.to_timestamp(Table.c.Field / 1.0).cast(sa.Date)
as suggested here. But it produces the error: no such function: to_timestamp.
I tried
Table.c.Field.cast(sa.DateTime)
as suggested here. But as in that link it produces the error: Couldn't parse datetime string '1596657600' - value is not a string. If it helps, the database I am querying is also Sqlite, but I would like my solution to work for any integer field from any database.
I also tried
sa.cast(Table.c.Field, SQA.Interval)
but it produced the same error: Couldn't parse datetime string '1596657600' - value is not a string.
If the solution is to create some special function (e.g., Fx) to do the conversion, to be used as
(Table.c.Field).Fx()
what would that function look like? Or what kind of function would do the trick? I cannot write to the database, and need to do all calculations (casting, grouping, filtering, etc.) via a single sqlalchemy query.
I found a solution in the docs, but I wasn't sure at first how to apply it in my case.
First, create a new type that accepts integers, then simply cast Field to that type. I first import sqlalchemy as SQA and import tzinfo and datetime and timedelta from datetime.
class MyEpochType(SQA.types.TypeDecorator):
impl = SQA.types.Integer
epoch = datetime(1970, 1, 1, 0, 0, 0, tzinfo=pytz.timezone('UTC'))
def process_bind_param(self, value, dialect):
pass # not needed in my use case
def process_result_value(self, value, dialect):
# either of the two returns below, or similar ones
#return self.epoch + timedelta(seconds=value )
return datetime.fromtimestamp(value).isoformat()
Table.c.Field.cast(MyEpochType)
The above solution will not allow for a group_by on the transformed values (e.g., a datetime or string). For this, a better solution is to use the native functions of the underlying database. In my case it is Sqlite, and would be called similar to that below:
SQA.func.strftime('%H%M',
SQA.func.datetime(Table.c.Field, 'unixepoch')).label('someLabel')
Here as an example I am calling two different Sqlite functions and giving the result a label. Each database has its own set of particular functions that can be called.

How to get the value of a DateTimeField in peewee

class Test(Model):
time = DateTimeField()
# ...
row = Test.select()[0]
test.time
This returns a string that looks like this: 2017-01-23 01:01:39+01:00. How can I get it as a datetime object instead? Do I have to parse it manually?
Also I would be interested if there is any documentation on how to use the DateTimeField. The official documentation doesn't have anything on it.
Are you using SQLite? If so, SQLite doesn't have a dedicated datetime type, so datetimes are stored as strings in the DB. What peewee will do is recognize certain datetime formats coming out of the DB and convert them to datetime objects. What you need to do is ensure that either:
When you create/save your object, that you assign a datetime object to the field.
When reading back pre-existing data, that the data is in a recognized format.
The formats peewee supports out-of-the-box for datetime field are:
YYYY-mm-dd HH:MM:SS.ffffff
YYYY-mm-dd HH:MM:SS
YYYY-mm-dd
It looks like your has zone info. I'd suggest converting to UTC and dropping the zone info. That should fix it.
Have you tried adding a default like this?
time = DateTimeField(default=datetime.datetime.now())
Or when adding an entry add it as a datetime.datetime object directly:
test = Test(....., time=datetime.datetime.strptime("2018-3-15", '%Y-%m-%d'))
In the second case you don't need to specify anything in the class definition...

MySQL data type for movie times

I have chapter times in the form of HH:MM:SS. I am parsing them from a document, and I will have times as a string in the format of '00:12:14'. How would I store this in a mysql column, and then retrieve it in the required format to be able to:
1) order by time;
2) convert to a string in the above format.
I suggest you look at the MySQL time type. It will allow you to sort and format as you wish.
http://dev.mysql.com/doc/refman/5.0/en/time.html
Use the TIME type.
It allows "time values to be represented in several formats, such as quoted strings or as numbers, depending on the exact type of the value and other factors." In addition, you can perform various functions to manipulate the time.
If I have such a simple task, I choose a simple solution: I would choose the python datetime.time module (see: datetime.time) and store a TIME object using strftime.
Loading it back in is a little painful as you would have to split your string at : and then pass the values to the time constructor. Example:
def load(timestr):
hours,minutes,seconds = timestr.split(":")
return datetime.time(hours,minutes,seconds)
Hope this helps.

Modify column output for sqlform.grid() in Web2py

I have started using web2py for a web application and try to use SQLFORM.grid(...) to display a paginated listing of one of my db-table's data like in the following minimal example.
grid=SQLFORM.grid(query,
links=links,
fields=[db.example.date,db.example.foo, db.example.bar])
The db.example.date field contains a Python datetime.datetime object in UTC. At the moment it is displayed just plainly like that. However, I want to have more control about the actual output in a way that I can set the local timezone and modify the output string to have something like "2 hours ago".
As seen in another question[0] I can use the links to insert new columns. Unfortunately I can't seem to sort the rows by a field I have inserted in such way. Also, they are inserted on the right instead of actually replacing my first column. So that does not seem to be a solution.
To sum it up: How do I gain control about the way db.example.date is printed out in the end?
[0] Calculated Fields in web2py sqlgrid
You can achieve your goal when you define the table in your model. The represent parameter in the Field constructor that you used in define_table will be recognized by the SQLFORM.grid. For example, if you wanted to just print the date with the month name you could put the following in your model.
Field('a_date', type='date', represent=lambda x, row: x.strftime("%B %d, %Y")),
your function could also convert to local time.
You need to use prettydate to change the datetime arid format in a humanized string, and call it in the represent parameter of your Field() descriptor. For example :
from gluon.tools import prettydate
db.example.date.represent = lambda v,r: prettydate(r.date)
That way, any display of the db.example.date would be displayed humanized, including through SQLFORM.grid
If you don't want to have the date always represented in this way as per David Nehme's answer. Just before your grid creation, you can set the db.table.field.represent in the controller.
db.example.date.represent = lambda value, row: value.strftime("%B %d, %Y")
followed by.
grid = SQLFORM.grid(query,....
I use this often when I join tables. If there is a row.field in the represent from the model file it breaks because it then must be more specific, row.table.field.

Categories