added ;saveteam, ;showteam, ;showplayer

This commit is contained in:
Sakimori 2020-12-24 04:51:38 -05:00
parent 2ec0efefc9
commit ab8151206b
23 changed files with 3836 additions and 50 deletions

View File

@ -58,12 +58,20 @@ def initialcheck():
strikeouts_taken integer DEFAULT 0 strikeouts_taken integer DEFAULT 0
);""" );"""
teams_table_check_string = """ CREATE TABLE IF NOT EXISTS teams (
counter integer PRIMARY KEY,
name text NOT NULL,
team_json_string text NOT NULL,
timestamp text NOT NULL
); """
if conn is not None: if conn is not None:
c = conn.cursor() c = conn.cursor()
c.execute(soulscream_table_check_string) c.execute(soulscream_table_check_string)
c.execute(player_cache_table_check_string) c.execute(player_cache_table_check_string)
c.execute(player_table_check_string) c.execute(player_table_check_string)
c.execute(player_stats_table_check_string) c.execute(player_stats_table_check_string)
c.execute(teams_table_check_string)
conn.commit() conn.commit()
conn.close() conn.close()
@ -163,7 +171,7 @@ def designate_player(user, player_json):
conn.close() conn.close()
def get_user_player_conn(conn, user): def get_user_player_conn(conn, user):
#try: try:
if conn is not None: if conn is not None:
c = conn.cursor() c = conn.cursor()
c.execute("SELECT player_json_string FROM user_designated_players WHERE user_id=?", (user.id,)) c.execute("SELECT player_json_string FROM user_designated_players WHERE user_id=?", (user.id,))
@ -173,11 +181,41 @@ def get_user_player_conn(conn, user):
return False return False
else: else:
print(conn) print(conn)
#except: except:
#print(conn) print(conn)
def get_user_player(user): def get_user_player(user):
conn = create_connection() conn = create_connection()
player = get_user_player_conn(conn, user) player = get_user_player_conn(conn, user)
conn.close() conn.close()
return player return player
def save_team(name, team_json_string):
conn = create_connection()
try:
if conn is not None:
c = conn.cursor()
store_string = """ INSERT INTO teams(name, team_json_string, timestamp)
VALUES (?,?, ?) """
c.execute(store_string, (name, team_json_string, datetime.datetime.now(datetime.timezone.utc)))
conn.commit()
conn.close()
return True
conn.close()
return False
except:
return False
def get_team(name):
conn = create_connection()
if conn is not None:
c = conn.cursor()
c.execute("SELECT * FROM teams WHERE name=?", (name,))
team = c.fetchone()
conn.close()
print(team[2])
return team[2] #returns a json string
conn.close()
return None

View File

@ -96,3 +96,39 @@ def large_scale_debug(): #massive debug, goes in games.py
# there were {result[9]}, {result[10]}, and {result[11]} strikeouts, respectively. # there were {result[9]}, {result[10]}, and {result[11]} strikeouts, respectively.
# there were {result[12]}, {result[13]}, and {result[14]} groundouts, respectively. # there were {result[12]}, {result[13]}, and {result[14]} groundouts, respectively.
# there were {result[15]}, {result[16]}, and {result[17]} flyouts, respectively.""") # there were {result[15]}, {result[16]}, and {result[17]} flyouts, respectively.""")
def debug_game(): #returns a game object ready to run
average_player = player('{"id" : "average", "name" : "AJ", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}')
average_player2 = player('{"id" : "average", "name" : "Astrid", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}')
average_player3 = player('{"id" : "average", "name" : "xvi", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}')
average_player4 = player('{"id" : "average", "name" : "Fox", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}')
average_player5 = player('{"id" : "average", "name" : "Pigeon", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}')
max_player = player('{"id" : "max", "name" : "max", "batting_stars" : 5, "pitching_stars" : 5, "defense_stars" : 5, "baserunning_stars" : 5}')
min_player = player('{"id" : "min", "name" : "min", "batting_stars" : 1, "pitching_stars" : 1, "defense_stars" : 1, "baserunning_stars" : 1}')
team_avg = team()
team_avg.name = "Arizona Aways"
team_avg.add_lineup(average_player)
team_avg.add_lineup(average_player2)
team_avg.add_lineup(average_player3)
team_avg.add_lineup(average_player4)
team_avg.set_pitcher(average_player5)
team_avg.finalize()
team_avg2 = team()
team_avg2.name = "Houston Homes"
team_avg2.add_lineup(average_player5)
team_avg2.add_lineup(average_player4)
team_avg2.add_lineup(average_player3)
team_avg2.add_lineup(average_player2)
team_avg2.set_pitcher(average_player)
team_avg2.finalize()
team_min = team()
team_min.add_lineup(min_player)
team_min.set_pitcher(min_player)
team_min.finalize()
average_game = game("test", team_avg, team_avg2)
#slugging_game = game(team_max, team_min)
#shutout_game = game(team_min, team_max)
return average_game

View File

@ -1,4 +1,4 @@
import json, random, os, math import json, random, os, math, jsonpickle
from enum import Enum from enum import Enum
import database as db import database as db
@ -82,6 +82,7 @@ class team(object):
self.lineup_position = 0 self.lineup_position = 0
self.pitcher = None self.pitcher = None
self.score = 0 self.score = 0
self.slogan = None
def add_lineup(self, new_player): def add_lineup(self, new_player):
if len(self.lineup) <= 12: if len(self.lineup) <= 12:
@ -97,6 +98,14 @@ class team(object):
def is_ready(self): def is_ready(self):
return (len(self.lineup) >= 1 and self.pitcher is not None) return (len(self.lineup) >= 1 and self.pitcher is not None)
def prepare_for_save(self):
self.lineup_position = 0
self.score = 0
for this_player in self.lineup:
for stat in this_player.game_stats.keys():
this_player.game_stats[stat] = 0
return True
def finalize(self): def finalize(self):
if self.is_ready(): if self.is_ready():
while len(self.lineup) <= 4: while len(self.lineup) <= 4:
@ -124,6 +133,7 @@ class game(object):
self.max_innings = config()["default_length"] self.max_innings = config()["default_length"]
self.bases = {1 : None, 2 : None, 3 : None} self.bases = {1 : None, 2 : None, 3 : None}
def get_batter(self): def get_batter(self):
if self.top_of_inning: if self.top_of_inning:
bat_team = self.teams["away"] bat_team = self.teams["away"]
@ -443,37 +453,20 @@ def random_star_gen(key, player):
# strikeouts_taken # strikeouts_taken
def debug_game(): def get_team(name):
average_player = player('{"id" : "average", "name" : "AJ", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}') #try:
average_player2 = player('{"id" : "average", "name" : "Astrid", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}') team_json = jsonpickle.decode(db.get_team(name), keys=True, classes=team)
average_player3 = player('{"id" : "average", "name" : "xvi", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}') if team_json is not None:
average_player4 = player('{"id" : "average", "name" : "Fox", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}') return team_json
average_player5 = player('{"id" : "average", "name" : "Pigeon", "batting_stars" : 2.5, "pitching_stars" : 2.5, "defense_stars" : 2.5, "baserunning_stars" : 2.5}') return None
max_player = player('{"id" : "max", "name" : "max", "batting_stars" : 5, "pitching_stars" : 5, "defense_stars" : 5, "baserunning_stars" : 5}') # except:
min_player = player('{"id" : "min", "name" : "min", "batting_stars" : 1, "pitching_stars" : 1, "defense_stars" : 1, "baserunning_stars" : 1}') #return None
team_avg = team()
team_avg.name = "Arizona Aways"
team_avg.add_lineup(average_player)
team_avg.add_lineup(average_player2)
team_avg.add_lineup(average_player3)
team_avg.add_lineup(average_player4)
team_avg.set_pitcher(average_player5)
team_avg.finalize()
team_avg2 = team()
team_avg2.name = "Houston Homes"
team_avg2.add_lineup(average_player5)
team_avg2.add_lineup(average_player4)
team_avg2.add_lineup(average_player3)
team_avg2.add_lineup(average_player2)
team_avg2.set_pitcher(average_player)
team_avg2.finalize()
team_min = team()
team_min.add_lineup(min_player)
team_min.set_pitcher(min_player)
team_min.finalize()
average_game = game("test", team_avg, team_avg2) def save_team(this_team):
#slugging_game = game(team_max, team_min) try:
#shutout_game = game(team_min, team_max) this_team.prepare_for_save()
team_json_string = jsonpickle.encode(this_team, keys=True)
return average_game db.save_team(this_team.name, team_json_string)
return True
except:
return None

View File

@ -0,0 +1 @@
pip

View File

@ -0,0 +1,29 @@
Copyright (C) 2008 John Paulett (john -at- paulett.org)
Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,178 @@
Metadata-Version: 2.1
Name: jsonpickle
Version: 1.4.2
Summary: Python library for serializing any arbitrary object graph into JSON
Home-page: https://github.com/jsonpickle/jsonpickle
Author: David Aguilar
Author-email: davvid@gmail.com
License: UNKNOWN
Keywords: json pickle,json,pickle,marshal,serialization,JavaScript Object Notation
Platform: POSIX
Platform: Windows
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: JavaScript
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=2.7
Requires-Dist: importlib-metadata ; python_version < "3.8"
Provides-Extra: docs
Requires-Dist: sphinx ; extra == 'docs'
Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs'
Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
Provides-Extra: testing
Requires-Dist: coverage (<5) ; extra == 'testing'
Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing'
Requires-Dist: pytest-checkdocs (>=1.2.3) ; extra == 'testing'
Requires-Dist: pytest-flake8 ; extra == 'testing'
Requires-Dist: pytest-black-multipy ; extra == 'testing'
Requires-Dist: pytest-cov ; extra == 'testing'
Requires-Dist: ecdsa ; extra == 'testing'
Requires-Dist: feedparser ; extra == 'testing'
Requires-Dist: numpy ; extra == 'testing'
Requires-Dist: pandas ; extra == 'testing'
Requires-Dist: pymongo ; extra == 'testing'
Requires-Dist: sqlalchemy ; extra == 'testing'
Provides-Extra: testing.libs
Requires-Dist: demjson ; extra == 'testing.libs'
Requires-Dist: simplejson ; extra == 'testing.libs'
Requires-Dist: ujson ; extra == 'testing.libs'
Requires-Dist: yajl ; extra == 'testing.libs'
Requires-Dist: enum34 ; (python_version == "2.7") and extra == 'testing'
Requires-Dist: jsonlib ; (python_version == "2.7") and extra == 'testing'
.. image:: https://img.shields.io/pypi/v/jsonpickle.svg
:target: `PyPI link`_
.. image:: https://img.shields.io/pypi/pyversions/jsonpickle.svg
:target: `PyPI link`_
.. _PyPI link: https://pypi.org/project/jsonpickle
.. image:: https://dev.azure.com/jaraco/jsonpickle/_apis/build/status/jaraco.jsonpickle?branchName=master
:target: https://dev.azure.com/jaraco/jsonpickle/_build/latest?definitionId=1&branchName=master
.. image:: https://readthedocs.org/projects/jsonpickle/badge/?version=latest
:target: https://jsonpickle.readthedocs.io/en/latest/?badge=latest
.. image:: https://travis-ci.org/jsonpickle/jsonpickle.svg?branch=master
:target: https://travis-ci.org/jsonpickle/jsonpickle
:alt: travis
.. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
:target: https://github.com/jsonpickle/jsonpickle/blob/master/COPYING
:alt: BSD
jsonpickle
==========
jsonpickle is a library for the two-way conversion of complex Python objects
and `JSON <http://json.org/>`_. jsonpickle builds upon the existing JSON
encoders, such as simplejson, json, and demjson.
For complete documentation, please visit the
`jsonpickle documentation <http://jsonpickle.readthedocs.io/>`_.
Bug reports and merge requests are encouraged at the
`jsonpickle repository on github <https://github.com/jsonpickle/jsonpickle>`_.
jsonpickle supports Python 2.7 and Python 3.4 or greater.
**WARNING**:
jsonpickle can execute arbitrary Python code. Do not load jsonpickles from untrusted / unauthenticated sources.
Why jsonpickle?
===============
Data serialized with python's pickle (or cPickle or dill) is not easily readable outside of python. Using the json format, jsonpickle allows simple data types to be stored in a human-readable format, and more complex data types such as numpy arrays and pandas dataframes, to be machine-readable on any platform that supports json. E.g., unlike pickled data, jsonpickled data stored in an Amazon S3 bucket is indexible by Amazon's Athena.
Install
=======
Install from pip for the latest stable release:
::
pip install jsonpickle
Install from github for the latest changes:
::
pip install git+https://github.com/jsonpickle/jsonpickle.git
If you have the files checked out for development:
::
git clone https://github.com/jsonpickle/jsonpickle.git
cd jsonpickle
python setup.py develop
Numpy Support
=============
jsonpickle includes a built-in numpy extension. If would like to encode
sklearn models, numpy arrays, and other numpy-based data then you must
enable the numpy extension by registering its handlers::
>>> import jsonpickle.ext.numpy as jsonpickle_numpy
>>> jsonpickle_numpy.register_handlers()
Pandas Support
==============
jsonpickle includes a built-in pandas extension. If would like to encode
pandas DataFrame or Series objects then you must enable the pandas extension
by registering its handlers::
>>> import jsonpickle.ext.pandas as jsonpickle_pandas
>>> jsonpickle_pandas.register_handlers()
jsonpickleJS
============
`jsonpickleJS <https://github.com/cuthbertLab/jsonpickleJS>`_
is a javascript implementation of jsonpickle by Michael Scott Cuthbert.
jsonpickleJS can be extremely useful for projects that have parallel data
structures between Python and Javascript.
License
=======
Licensed under the BSD License. See COPYING for details.
See jsonpickleJS/LICENSE for details about the jsonpickleJS license.
Development
===========
Use `make` to run the unit tests::
make test
`pytest` is used to run unit tests internally.
A `tox` target is provided to run tests using tox.
Setting ``multi=1`` tests using all installed and supported Python versions::
make tox
make tox multi=1
`jsonpickle` itself has no dependencies beyond the Python stdlib.
`tox` is required for testing when using the `tox` test runner only.
The testing requirements are specified in `requirements-dev.txt`.
It is recommended to create a virtualenv and run tests from within the
virtualenv, or use a tool such as `vx <https://github.com/davvid/vx/>`_
to activate the virtualenv without polluting the shell environment::
python3 -mvenv env3x
vx env3x pip install --requirement requirements-dev.txt
vx env3x make test
`jsonpickle` supports multiple Python versions, so using a combination of
multiple virtualenvs and `tox` is useful in order to catch compatibility
issues when developing.

View File

@ -0,0 +1,31 @@
jsonpickle-1.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
jsonpickle-1.4.2.dist-info/LICENSE,sha256=x0J1XN545Tr9tDrZCzeskCyA81D41CGSSYCKl2WclqY,1493
jsonpickle-1.4.2.dist-info/METADATA,sha256=m7dcuYs8dSPINpiD6jAFt-AYqtHUNxuWQTifHFFGcP8,6588
jsonpickle-1.4.2.dist-info/RECORD,,
jsonpickle-1.4.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jsonpickle-1.4.2.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110
jsonpickle-1.4.2.dist-info/top_level.txt,sha256=cdRDYlLc4XOT2KzzPurieMn-XW_3YxFzopwdeDqPFVs,11
jsonpickle/__init__.py,sha256=1Az0ZGQbumVkaxKs0VC51yK0lwspN9N-v4menGQeRes,2585
jsonpickle/__pycache__/__init__.cpython-38.pyc,,
jsonpickle/__pycache__/backend.cpython-38.pyc,,
jsonpickle/__pycache__/compat.cpython-38.pyc,,
jsonpickle/__pycache__/handlers.cpython-38.pyc,,
jsonpickle/__pycache__/pickler.cpython-38.pyc,,
jsonpickle/__pycache__/tags.cpython-38.pyc,,
jsonpickle/__pycache__/unpickler.cpython-38.pyc,,
jsonpickle/__pycache__/util.cpython-38.pyc,,
jsonpickle/__pycache__/version.cpython-38.pyc,,
jsonpickle/backend.py,sha256=_OdeFpbnbqOkb6C6MIe95ohNHeDpLBIswVuNTHCmZJE,10312
jsonpickle/compat.py,sha256=5rkux_slQiyqI6dODJUK7VdDM_x5ksfJbgz4A4OS5_Q,1025
jsonpickle/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jsonpickle/ext/__pycache__/__init__.cpython-38.pyc,,
jsonpickle/ext/__pycache__/numpy.cpython-38.pyc,,
jsonpickle/ext/__pycache__/pandas.cpython-38.pyc,,
jsonpickle/ext/numpy.py,sha256=w2YgZqR3FSE6u3hPz9ririDBkYIAtrInjeGlDWHJ3qw,12339
jsonpickle/ext/pandas.py,sha256=LHflgvnRvdUqp1QWO2lBgc7i9sOtsLk5_rvmfEFgljE,7003
jsonpickle/handlers.py,sha256=yvAI7unS9dcRPYvOws9YqUNiRA9BwbNjX2Vx-gB768c,8460
jsonpickle/pickler.py,sha256=v0ahAa62IoHyOqtOLSGSHQbZIXuqMGPdbP5RKddqyqw,26032
jsonpickle/tags.py,sha256=QSrEwfvGvqPJxy6KkTKskDKqstm76RulfX8uBrbfn70,986
jsonpickle/unpickler.py,sha256=yW7dVzyAHnz5ECnl-KKveZAizJLwraRC9h3lqU5SuMU,24807
jsonpickle/util.py,sha256=L4V6l8AXXgk9dsU4nz271wpcV7AjsUX6BbLHwHFeHF8,14089
jsonpickle/version.py,sha256=blvoKVdqIhtMALqv8aFBVj56EYIXUJJMAUkZTec9Ttg,448

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.35.1)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1 @@
jsonpickle

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 John Paulett (john -at- paulett.org)
# Copyright (C) 2009, 2011, 2013 David Aguilar (davvid -at- gmail.com)
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
"""Python library for serializing any arbitrary object graph into JSON.
.. warning::
jsonpickle can execute arbitrary Python code. Do not load jsonpickles from
untrusted / unauthenticated sources.
jsonpickle can take almost any Python object and turn the object into JSON.
Additionally, it can reconstitute the object back into Python.
The object must be accessible globally via a module and must
inherit from object (AKA new-style classes).
Create an object::
class Thing(object):
def __init__(self, name):
self.name = name
obj = Thing('Awesome')
Use jsonpickle to transform the object into a JSON string::
import jsonpickle
frozen = jsonpickle.encode(obj)
Use jsonpickle to recreate a Python object from a JSON string::
thawed = jsonpickle.decode(frozen)
The new object has the same type and data, but essentially is now a copy of
the original.
.. code-block:: python
assert obj.name == thawed.name
If you will never need to load (regenerate the Python class from JSON), you can
pass in the keyword unpicklable=False to prevent extra information from being
added to JSON::
oneway = jsonpickle.encode(obj, unpicklable=False)
result = jsonpickle.decode(oneway)
assert obj.name == result['name'] == 'Awesome'
"""
from __future__ import absolute_import, division, unicode_literals
from .backend import json
from .pickler import encode
from .unpickler import decode
# Export other names not in __all__
from .backend import JSONBackend # noqa: F401
from .version import __version__ # noqa: F401
from .handlers import register # noqa: F401
from .handlers import unregister # noqa: F401
from .pickler import Pickler # noqa: F401
from .unpickler import Unpickler # noqa: F401
__all__ = ('encode', 'decode')
# register built-in handlers
__import__('jsonpickle.handlers', level=0)
# Export specific JSONPluginMgr methods into the jsonpickle namespace
set_preferred_backend = json.set_preferred_backend
set_decoder_options = json.set_decoder_options
set_encoder_options = json.set_encoder_options
load_backend = json.load_backend
remove_backend = json.remove_backend
enable_fallthrough = json.enable_fallthrough
# json.load(), loads(), dump(), dumps() compatibility
dumps = encode
loads = decode

View File

@ -0,0 +1,294 @@
from __future__ import absolute_import, division, unicode_literals
from .compat import string_types
from .compat import PY3_ORDERED_DICT
class JSONBackend(object):
"""Manages encoding and decoding using various backends.
It tries these modules in this order:
simplejson, json, demjson
simplejson is a fast and popular backend and is tried first.
json comes with Python and is tried second.
demjson is the most permissive backend and is tried last.
"""
def __init__(self, fallthrough=True):
# Whether we should fallthrough to the next backend
self._fallthrough = fallthrough
# The names of backends that have been successfully imported
self._backend_names = []
# A dictionary mapping backend names to encode/decode functions
self._encoders = {}
self._decoders = {}
# Options to pass to specific encoders
self._encoder_options = {}
# Options to pass to specific decoders
self._decoder_options = {}
# The exception class that is thrown when a decoding error occurs
self._decoder_exceptions = {}
# Whether we've loaded any backends successfully
self._verified = False
self.load_backend('simplejson')
self.load_backend('json')
self.load_backend('demjson', 'encode', 'decode', 'JSONDecodeError')
self.load_backend('jsonlib', 'write', 'read', 'ReadError')
self.load_backend('yajl')
self.load_backend('ujson')
# Defaults for various encoders
sort = not PY3_ORDERED_DICT
json_opts = ((), {'sort_keys': sort})
self._encoder_options = {
'ujson': ((), {'sort_keys': sort, 'escape_forward_slashes': False}),
'json': json_opts,
'simplejson': json_opts,
'django.util.simplejson': json_opts,
}
def _verify(self):
"""Ensures that we've loaded at least one JSON backend."""
if self._verified:
return
raise AssertionError(
'jsonpickle requires at least one of the '
'following:\n'
' python2.6, simplejson, or demjson'
)
def enable_fallthrough(self, enable):
"""
Disable jsonpickle's fallthrough-on-error behavior
By default, jsonpickle tries the next backend when decoding or
encoding using a backend fails.
This can make it difficult to force jsonpickle to use a specific
backend, and catch errors, because the error will be suppressed and
may not be raised by the subsequent backend.
Calling `enable_backend(False)` will make jsonpickle immediately
re-raise any exceptions raised by the backends.
"""
self._fallthrough = enable
def load_backend(self, name, dumps='dumps', loads='loads', loads_exc=ValueError):
"""Load a JSON backend by name.
This method loads a backend and sets up references to that
backend's loads/dumps functions and exception classes.
:param dumps: is the name of the backend's encode method.
The method should take an object and return a string.
Defaults to 'dumps'.
:param loads: names the backend's method for the reverse
operation -- returning a Python object from a string.
:param loads_exc: can be either the name of the exception class
used to denote decoding errors, or it can be a direct reference
to the appropriate exception class itself. If it is a name,
then the assumption is that an exception class of that name
can be found in the backend module's namespace.
:param load: names the backend's 'load' method.
:param dump: names the backend's 'dump' method.
:rtype bool: True on success, False if the backend could not be loaded.
"""
try:
# Load the JSON backend
mod = __import__(name)
except ImportError:
return False
# Handle submodules, e.g. django.utils.simplejson
try:
for attr in name.split('.')[1:]:
mod = getattr(mod, attr)
except AttributeError:
return False
if not self._store(self._encoders, name, mod, dumps) or not self._store(
self._decoders, name, mod, loads
):
return False
if isinstance(loads_exc, string_types):
# This backend's decoder exception is part of the backend
if not self._store(self._decoder_exceptions, name, mod, loads_exc):
return False
else:
# simplejson uses ValueError
self._decoder_exceptions[name] = loads_exc
# Setup the default args and kwargs for this encoder/decoder
self._encoder_options.setdefault(name, ([], {}))
self._decoder_options.setdefault(name, ([], {}))
# Add this backend to the list of candidate backends
self._backend_names.append(name)
# Indicate that we successfully loaded a JSON backend
self._verified = True
return True
def remove_backend(self, name):
"""Remove all entries for a particular backend."""
self._encoders.pop(name, None)
self._decoders.pop(name, None)
self._decoder_exceptions.pop(name, None)
self._decoder_options.pop(name, None)
self._encoder_options.pop(name, None)
if name in self._backend_names:
self._backend_names.remove(name)
self._verified = bool(self._backend_names)
def encode(self, obj, indent=None, separators=None):
"""
Attempt to encode an object into JSON.
This tries the loaded backends in order and passes along the last
exception if no backend is able to encode the object.
"""
self._verify()
if not self._fallthrough:
name = self._backend_names[0]
return self.backend_encode(name, obj, indent=indent, separators=separators)
for idx, name in enumerate(self._backend_names):
try:
return self.backend_encode(
name, obj, indent=indent, separators=separators
)
except Exception as e:
if idx == len(self._backend_names) - 1:
raise e
# def dumps
dumps = encode
def backend_encode(self, name, obj, indent=None, separators=None):
optargs, optkwargs = self._encoder_options.get(name, ([], {}))
encoder_kwargs = optkwargs.copy()
if indent is not None:
encoder_kwargs['indent'] = indent
if separators is not None:
encoder_kwargs['separators'] = separators
encoder_args = (obj,) + tuple(optargs)
return self._encoders[name](*encoder_args, **encoder_kwargs)
def decode(self, string):
"""
Attempt to decode an object from a JSON string.
This tries the loaded backends in order and passes along the last
exception if no backends are able to decode the string.
"""
self._verify()
if not self._fallthrough:
name = self._backend_names[0]
return self.backend_decode(name, string)
for idx, name in enumerate(self._backend_names):
try:
return self.backend_decode(name, string)
except self._decoder_exceptions[name] as e:
if idx == len(self._backend_names) - 1:
raise e
else:
pass # and try a more forgiving encoder, e.g. demjson
# def loads
loads = decode
def backend_decode(self, name, string):
optargs, optkwargs = self._decoder_options.get(name, ((), {}))
decoder_kwargs = optkwargs.copy()
return self._decoders[name](string, *optargs, **decoder_kwargs)
def set_preferred_backend(self, name):
"""
Set the preferred json backend.
If a preferred backend is set then jsonpickle tries to use it
before any other backend.
For example::
set_preferred_backend('simplejson')
If the backend is not one of the built-in jsonpickle backends
(json/simplejson, or demjson) then you must load the backend
prior to calling set_preferred_backend.
AssertionError is raised if the backend has not been loaded.
"""
if name in self._backend_names:
self._backend_names.remove(name)
self._backend_names.insert(0, name)
else:
errmsg = 'The "%s" backend has not been loaded.' % name
raise AssertionError(errmsg)
def set_encoder_options(self, name, *args, **kwargs):
"""
Associate encoder-specific options with an encoder.
After calling set_encoder_options, any calls to jsonpickle's
encode method will pass the supplied args and kwargs along to
the appropriate backend's encode method.
For example::
set_encoder_options('simplejson', sort_keys=True, indent=4)
set_encoder_options('demjson', compactly=False)
See the appropriate encoder's documentation for details about
the supported arguments and keyword arguments.
"""
self._encoder_options[name] = (args, kwargs)
def set_decoder_options(self, name, *args, **kwargs):
"""
Associate decoder-specific options with a decoder.
After calling set_decoder_options, any calls to jsonpickle's
decode method will pass the supplied args and kwargs along to
the appropriate backend's decode method.
For example::
set_decoder_options('simplejson', encoding='utf8', cls=JSONDecoder)
set_decoder_options('demjson', strict=True)
See the appropriate decoder's documentation for details about
the supported arguments and keyword arguments.
"""
self._decoder_options[name] = (args, kwargs)
def _store(self, dct, backend, obj, name):
try:
dct[backend] = getattr(obj, name)
except AttributeError:
self.remove_backend(backend)
return False
return True
json = JSONBackend()

View File

@ -0,0 +1,39 @@
from __future__ import absolute_import, division, unicode_literals
import sys
import types
import base64
PY_MAJOR = sys.version_info[0]
PY2 = PY_MAJOR == 2
PY3 = PY_MAJOR == 3
PY3_ORDERED_DICT = PY3 and sys.version_info[1] >= 6 # Python 3.6+
class_types = (type,)
iterator_types = (type(iter('')),)
if PY3:
import builtins
import queue
from base64 import encodebytes, decodebytes
from collections.abc import Iterator as abc_iterator
string_types = (str,)
numeric_types = (int, float)
ustr = str
else:
from collections import Iterator as abc_iterator # noqa
builtins = __import__('__builtin__')
class_types += (types.ClassType,)
encodebytes = base64.encodestring
decodebytes = base64.decodestring
string_types = (builtins.basestring,)
numeric_types = (int, float, builtins.long)
queue = __import__('Queue')
ustr = builtins.unicode
def iterator(class_):
if PY2 and hasattr(class_, '__next__'):
class_.next = class_.__next__
return class_

View File

@ -0,0 +1,337 @@
from __future__ import absolute_import
import ast
import sys
import zlib
import warnings
import json
import numpy as np
from ..handlers import BaseHandler, register, unregister
from ..compat import numeric_types
from ..util import b64decode, b64encode
from .. import compat
__all__ = ['register_handlers', 'unregister_handlers']
native_byteorder = '<' if sys.byteorder == 'little' else '>'
def get_byteorder(arr):
"""translate equals sign to native order"""
byteorder = arr.dtype.byteorder
return native_byteorder if byteorder == '=' else byteorder
class NumpyBaseHandler(BaseHandler):
def flatten_dtype(self, dtype, data):
if hasattr(dtype, 'tostring'):
data['dtype'] = dtype.tostring()
else:
dtype = compat.ustr(dtype)
prefix = '(numpy.record, '
if dtype.startswith(prefix):
dtype = dtype[len(prefix) : -1]
data['dtype'] = dtype
def restore_dtype(self, data):
dtype = data['dtype']
if dtype.startswith(('{', '[')):
dtype = ast.literal_eval(dtype)
return np.dtype(dtype)
class NumpyDTypeHandler(NumpyBaseHandler):
def flatten(self, obj, data):
self.flatten_dtype(obj, data)
return data
def restore(self, data):
return self.restore_dtype(data)
class NumpyGenericHandler(NumpyBaseHandler):
def flatten(self, obj, data):
self.flatten_dtype(obj.dtype.newbyteorder('N'), data)
data['value'] = self.context.flatten(obj.tolist(), reset=False)
return data
def restore(self, data):
value = self.context.restore(data['value'], reset=False)
return self.restore_dtype(data).type(value)
class NumpyNDArrayHandler(NumpyBaseHandler):
"""Stores arrays as text representation, without regard for views"""
def flatten_flags(self, obj, data):
if obj.flags.writeable is False:
data['writeable'] = False
def restore_flags(self, data, arr):
if not data.get('writeable', True):
arr.flags.writeable = False
def flatten(self, obj, data):
self.flatten_dtype(obj.dtype.newbyteorder('N'), data)
self.flatten_flags(obj, data)
data['values'] = self.context.flatten(obj.tolist(), reset=False)
if 0 in obj.shape:
# add shape information explicitly as it cannot be
# inferred from an empty list
data['shape'] = obj.shape
return data
def restore(self, data):
values = self.context.restore(data['values'], reset=False)
arr = np.array(
values, dtype=self.restore_dtype(data), order=data.get('order', 'C')
)
shape = data.get('shape', None)
if shape is not None:
arr = arr.reshape(shape)
self.restore_flags(data, arr)
return arr
class NumpyNDArrayHandlerBinary(NumpyNDArrayHandler):
"""stores arrays with size greater than 'size_threshold' as
(optionally) compressed base64
Notes
-----
This would be easier to implement using np.save/np.load, but
that would be less language-agnostic
"""
def __init__(self, size_threshold=16, compression=zlib):
"""
:param size_threshold: nonnegative int or None
valid values for 'size_threshold' are all nonnegative
integers and None
if size_threshold is None, values are always stored as nested lists
:param compression: a compression module or None
valid values for 'compression' are {zlib, bz2, None}
if compresion is None, no compression is applied
"""
self.size_threshold = size_threshold
self.compression = compression
def flatten_byteorder(self, obj, data):
byteorder = obj.dtype.byteorder
if byteorder != '|':
data['byteorder'] = get_byteorder(obj)
def restore_byteorder(self, data, arr):
byteorder = data.get('byteorder', None)
if byteorder:
arr.dtype = arr.dtype.newbyteorder(byteorder)
def flatten(self, obj, data):
"""encode numpy to json"""
if self.size_threshold is None or self.size_threshold >= obj.size:
# encode as text
data = super(NumpyNDArrayHandlerBinary, self).flatten(obj, data)
else:
# encode as binary
if obj.dtype == np.object:
# There's a bug deep in the bowels of numpy that causes a
# segfault when round-tripping an ndarray of dtype object.
# E.g., the following will result in a segfault:
# import numpy as np
# arr = np.array([str(i) for i in range(3)],
# dtype=np.object)
# dtype = arr.dtype
# shape = arr.shape
# buf = arr.tobytes()
# del arr
# arr = np.ndarray(buffer=buf, dtype=dtype,
# shape=shape).copy()
# So, save as a binary-encoded list in this case
buf = json.dumps(obj.tolist()).encode()
elif hasattr(obj, 'tobytes'):
# numpy docstring is lacking as of 1.11.2,
# but this is the option we need
buf = obj.tobytes(order='a')
else:
# numpy < 1.9 compatibility
buf = obj.tostring(order='a')
if self.compression:
buf = self.compression.compress(buf)
data['values'] = b64encode(buf)
data['shape'] = obj.shape
self.flatten_dtype(obj.dtype.newbyteorder('N'), data)
self.flatten_byteorder(obj, data)
self.flatten_flags(obj, data)
if not obj.flags.c_contiguous:
data['order'] = 'F'
return data
def restore(self, data):
"""decode numpy from json"""
values = data['values']
if isinstance(values, list):
# decode text representation
arr = super(NumpyNDArrayHandlerBinary, self).restore(data)
elif isinstance(values, numeric_types):
# single-value array
arr = np.array([values], dtype=self.restore_dtype(data))
else:
# decode binary representation
dtype = self.restore_dtype(data)
buf = b64decode(values)
if self.compression:
buf = self.compression.decompress(buf)
# See note above about segfault bug for numpy dtype object. Those
# are saved as a list to work around that.
if dtype == np.object:
values = json.loads(buf.decode())
arr = np.array(values, dtype=dtype, order=data.get('order', 'C'))
shape = data.get('shape', None)
if shape is not None:
arr = arr.reshape(shape)
else:
arr = np.ndarray(
buffer=buf,
dtype=dtype,
shape=data.get('shape'),
order=data.get('order', 'C'),
).copy() # make a copy, to force the result to own the data
self.restore_byteorder(data, arr)
self.restore_flags(data, arr)
return arr
class NumpyNDArrayHandlerView(NumpyNDArrayHandlerBinary):
"""Pickles references inside ndarrays, or array-views
Notes
-----
The current implementation has some restrictions.
'base' arrays, or arrays which are viewed by other arrays,
must be f-or-c-contiguous.
This is not such a large restriction in practice, because all
numpy array creation is c-contiguous by default.
Relaxing this restriction would be nice though; especially if
it can be done without bloating the design too much.
Furthermore, ndarrays which are views of array-like objects
implementing __array_interface__,
but which are not themselves nd-arrays, are deepcopied with
a warning (by default),
as we cannot guarantee whatever custom logic such classes
implement is correctly reproduced.
"""
def __init__(self, mode='warn', size_threshold=16, compression=zlib):
"""
:param mode: {'warn', 'raise', 'ignore'}
How to react when encountering array-like objects whos
references we cannot safely serialize
:param size_threshold: nonnegative int or None
valid values for 'size_threshold' are all nonnegative
integers and None
if size_threshold is None, values are always stored as nested lists
:param compression: a compression module or None
valid values for 'compression' are {zlib, bz2, None}
if compresion is None, no compression is applied
"""
super(NumpyNDArrayHandlerView, self).__init__(size_threshold, compression)
self.mode = mode
def flatten(self, obj, data):
"""encode numpy to json"""
base = obj.base
if base is None and obj.flags.forc:
# store by value
data = super(NumpyNDArrayHandlerView, self).flatten(obj, data)
# ensure that views on arrays stored as text
# are interpreted correctly
if not obj.flags.c_contiguous:
data['order'] = 'F'
elif isinstance(base, np.ndarray) and base.flags.forc:
# store by reference
data['base'] = self.context.flatten(base, reset=False)
offset = obj.ctypes.data - base.ctypes.data
if offset:
data['offset'] = offset
if not obj.flags.c_contiguous:
data['strides'] = obj.strides
data['shape'] = obj.shape
self.flatten_dtype(obj.dtype.newbyteorder('N'), data)
self.flatten_flags(obj, data)
if get_byteorder(obj) != '|':
byteorder = 'S' if get_byteorder(obj) != get_byteorder(base) else None
if byteorder:
data['byteorder'] = byteorder
if self.size_threshold is None or self.size_threshold >= obj.size:
# not used in restore since base is present, but
# include values for human-readability
super(NumpyNDArrayHandlerBinary, self).flatten(obj, data)
else:
# store a deepcopy or fail
if self.mode == 'warn':
msg = (
"ndarray is defined by reference to an object "
"we do not know how to serialize. "
"A deep copy is serialized instead, breaking "
"memory aliasing."
)
warnings.warn(msg)
elif self.mode == 'raise':
msg = (
"ndarray is defined by reference to an object we do "
"not know how to serialize."
)
raise ValueError(msg)
data = super(NumpyNDArrayHandlerView, self).flatten(obj.copy(), data)
return data
def restore(self, data):
"""decode numpy from json"""
base = data.get('base', None)
if base is None:
# decode array with owndata=True
arr = super(NumpyNDArrayHandlerView, self).restore(data)
else:
# decode array view, which references the data of another array
base = self.context.restore(base, reset=False)
assert (
base.flags.forc
), "Current implementation assumes base is C or F contiguous"
arr = np.ndarray(
buffer=base.data,
dtype=self.restore_dtype(data).newbyteorder(data.get('byteorder', '|')),
shape=data.get('shape'),
offset=data.get('offset', 0),
strides=data.get('strides', None),
)
self.restore_flags(data, arr)
return arr
def register_handlers():
register(np.dtype, NumpyDTypeHandler, base=True)
register(np.generic, NumpyGenericHandler, base=True)
register(np.ndarray, NumpyNDArrayHandlerView(), base=True)
def unregister_handlers():
unregister(np.dtype)
unregister(np.generic)
unregister(np.ndarray)

View File

@ -0,0 +1,228 @@
from __future__ import absolute_import
import pandas as pd
from io import StringIO
import zlib
from .. import encode, decode
from ..handlers import BaseHandler, register, unregister
from ..util import b64decode, b64encode
from .numpy import register_handlers as register_numpy_handlers
from .numpy import unregister_handlers as unregister_numpy_handlers
__all__ = ['register_handlers', 'unregister_handlers']
class PandasProcessor(object):
def __init__(self, size_threshold=500, compression=zlib):
"""
:param size_threshold: nonnegative int or None
valid values for 'size_threshold' are all nonnegative
integers and None. If size_threshold is None,
dataframes are always stored as csv strings
:param compression: a compression module or None
valid values for 'compression' are {zlib, bz2, None}
if compresion is None, no compression is applied
"""
self.size_threshold = size_threshold
self.compression = compression
def flatten_pandas(self, buf, data, meta=None):
if self.size_threshold is not None and len(buf) > self.size_threshold:
if self.compression:
buf = self.compression.compress(buf.encode())
data['comp'] = True
data['values'] = b64encode(buf)
data['txt'] = False
else:
data['values'] = buf
data['txt'] = True
data['meta'] = meta
return data
def restore_pandas(self, data):
if data.get('txt', True):
# It's just text...
buf = data['values']
else:
buf = b64decode(data['values'])
if data.get('comp', False):
buf = self.compression.decompress(buf).decode()
meta = data.get('meta', {})
return (buf, meta)
def make_read_csv_params(meta):
meta_dtypes = meta.get('dtypes', {})
parse_dates = []
converters = {}
dtype = {}
for k, v in meta_dtypes.items():
if v.startswith('datetime'):
parse_dates.append(k)
elif v.startswith('complex'):
converters[k] = complex
else:
dtype[k] = v
return dict(dtype=dtype, parse_dates=parse_dates, converters=converters)
class PandasDfHandler(BaseHandler):
pp = PandasProcessor()
def flatten(self, obj, data):
dtype = obj.dtypes.to_dict()
meta = {'dtypes': {k: str(dtype[k]) for k in dtype}, 'index': encode(obj.index)}
data = self.pp.flatten_pandas(
obj.reset_index(drop=True).to_csv(index=False), data, meta
)
return data
def restore(self, data):
csv, meta = self.pp.restore_pandas(data)
params = make_read_csv_params(meta)
df = (
pd.read_csv(StringIO(csv), **params)
if data['values'].strip()
else pd.DataFrame()
)
df.set_index(decode(meta['index']), inplace=True)
return df
class PandasSeriesHandler(BaseHandler):
pp = PandasProcessor()
def flatten(self, obj, data):
"""Flatten the index and values for reconstruction"""
data['name'] = obj.name
# This relies on the numpy handlers for the inner guts.
data['index'] = self.context.flatten(obj.index, reset=False)
data['values'] = self.context.flatten(obj.values, reset=False)
return data
def restore(self, data):
"""Restore the flattened data"""
name = data['name']
index = self.context.restore(data['index'], reset=False)
values = self.context.restore(data['values'], reset=False)
return pd.Series(values, index=index, name=name)
class PandasIndexHandler(BaseHandler):
pp = PandasProcessor()
index_constructor = pd.Index
def name_bundler(self, obj):
return {'name': obj.name}
def flatten(self, obj, data):
name_bundle = self.name_bundler(obj)
meta = dict(dtype=str(obj.dtype), **name_bundle)
buf = encode(obj.tolist())
data = self.pp.flatten_pandas(buf, data, meta)
return data
def restore(self, data):
buf, meta = self.pp.restore_pandas(data)
dtype = meta.get('dtype', None)
name_bundle = {k: v for k, v in meta.items() if k in {'name', 'names'}}
idx = self.index_constructor(decode(buf), dtype=dtype, **name_bundle)
return idx
class PandasPeriodIndexHandler(PandasIndexHandler):
index_constructor = pd.PeriodIndex
class PandasMultiIndexHandler(PandasIndexHandler):
def name_bundler(self, obj):
return {'names': obj.names}
class PandasTimestampHandler(BaseHandler):
pp = PandasProcessor()
def flatten(self, obj, data):
meta = {'isoformat': obj.isoformat()}
buf = ''
data = self.pp.flatten_pandas(buf, data, meta)
return data
def restore(self, data):
_, meta = self.pp.restore_pandas(data)
isoformat = meta['isoformat']
obj = pd.Timestamp(isoformat)
return obj
class PandasPeriodHandler(BaseHandler):
pp = PandasProcessor()
def flatten(self, obj, data):
meta = {
'start_time': encode(obj.start_time),
'freqstr': obj.freqstr,
}
buf = ''
data = self.pp.flatten_pandas(buf, data, meta)
return data
def restore(self, data):
_, meta = self.pp.restore_pandas(data)
start_time = decode(meta['start_time'])
freqstr = meta['freqstr']
obj = pd.Period(start_time, freqstr)
return obj
class PandasIntervalHandler(BaseHandler):
pp = PandasProcessor()
def flatten(self, obj, data):
meta = {
'left': encode(obj.left),
'right': encode(obj.right),
'closed': obj.closed,
}
buf = ''
data = self.pp.flatten_pandas(buf, data, meta)
return data
def restore(self, data):
_, meta = self.pp.restore_pandas(data)
left = decode(meta['left'])
right = decode(meta['right'])
closed = str(meta['closed'])
obj = pd.Interval(left, right, closed=closed)
return obj
def register_handlers():
register_numpy_handlers()
register(pd.DataFrame, PandasDfHandler, base=True)
register(pd.Series, PandasSeriesHandler, base=True)
register(pd.Index, PandasIndexHandler, base=True)
register(pd.PeriodIndex, PandasPeriodIndexHandler, base=True)
register(pd.MultiIndex, PandasMultiIndexHandler, base=True)
register(pd.Timestamp, PandasTimestampHandler, base=True)
register(pd.Period, PandasPeriodHandler, base=True)
register(pd.Interval, PandasIntervalHandler, base=True)
def unregister_handlers():
unregister_numpy_handlers()
unregister(pd.DataFrame)
unregister(pd.Series)
unregister(pd.Index)
unregister(pd.PeriodIndex)
unregister(pd.MultiIndex)
unregister(pd.Timestamp)
unregister(pd.Period)
unregister(pd.Interval)

View File

@ -0,0 +1,294 @@
"""
Custom handlers may be created to handle other objects. Each custom handler
must derive from :class:`jsonpickle.handlers.BaseHandler` and
implement ``flatten`` and ``restore``.
A handler can be bound to other types by calling
:func:`jsonpickle.handlers.register`.
"""
from __future__ import absolute_import, division, unicode_literals
import array
import copy
import datetime
import io
import re
import sys
import threading
import uuid
from . import compat
from . import util
class Registry(object):
def __init__(self):
self._handlers = {}
self._base_handlers = {}
def get(self, cls_or_name, default=None):
"""
:param cls_or_name: the type or its fully qualified name
:param default: default value, if a matching handler is not found
Looks up a handler by type reference or its fully
qualified name. If a direct match
is not found, the search is performed over all
handlers registered with base=True.
"""
handler = self._handlers.get(cls_or_name)
# attempt to find a base class
if handler is None and util.is_type(cls_or_name):
for cls, base_handler in self._base_handlers.items():
if issubclass(cls_or_name, cls):
return base_handler
return default if handler is None else handler
def register(self, cls, handler=None, base=False):
"""Register the a custom handler for a class
:param cls: The custom object class to handle
:param handler: The custom handler class (if
None, a decorator wrapper is returned)
:param base: Indicates whether the handler should
be registered for all subclasses
This function can be also used as a decorator
by omitting the `handler` argument::
@jsonpickle.handlers.register(Foo, base=True)
class FooHandler(jsonpickle.handlers.BaseHandler):
pass
"""
if handler is None:
def _register(handler_cls):
self.register(cls, handler=handler_cls, base=base)
return handler_cls
return _register
if not util.is_type(cls):
raise TypeError('{!r} is not a class/type'.format(cls))
# store both the name and the actual type for the ugly cases like
# _sre.SRE_Pattern that cannot be loaded back directly
self._handlers[util.importable_name(cls)] = self._handlers[cls] = handler
if base:
# only store the actual type for subclass checking
self._base_handlers[cls] = handler
def unregister(self, cls):
self._handlers.pop(cls, None)
self._handlers.pop(util.importable_name(cls), None)
self._base_handlers.pop(cls, None)
registry = Registry()
register = registry.register
unregister = registry.unregister
get = registry.get
class BaseHandler(object):
def __init__(self, context):
"""
Initialize a new handler to handle a registered type.
:Parameters:
- `context`: reference to pickler/unpickler
"""
self.context = context
def __call__(self, context):
"""This permits registering either Handler instances or classes
:Parameters:
- `context`: reference to pickler/unpickler
"""
self.context = context
return self
def flatten(self, obj, data):
"""
Flatten `obj` into a json-friendly form and write result to `data`.
:param object obj: The object to be serialized.
:param dict data: A partially filled dictionary which will contain the
json-friendly representation of `obj` once this method has
finished.
"""
raise NotImplementedError('You must implement flatten() in %s' % self.__class__)
def restore(self, obj):
"""
Restore an object of the registered type from the json-friendly
representation `obj` and return it.
"""
raise NotImplementedError('You must implement restore() in %s' % self.__class__)
@classmethod
def handles(self, cls):
"""
Register this handler for the given class. Suitable as a decorator,
e.g.::
@MyCustomHandler.handles
class MyCustomClass:
def __reduce__(self):
...
"""
registry.register(cls, self)
return cls
class ArrayHandler(BaseHandler):
"""Flatten and restore array.array objects"""
def flatten(self, obj, data):
data['typecode'] = obj.typecode
data['values'] = self.context.flatten(obj.tolist(), reset=False)
return data
def restore(self, data):
typecode = data['typecode']
values = self.context.restore(data['values'], reset=False)
if typecode == 'c':
values = [bytes(x) for x in values]
return array.array(typecode, values)
ArrayHandler.handles(array.array)
class DatetimeHandler(BaseHandler):
"""Custom handler for datetime objects
Datetime objects use __reduce__, and they generate binary strings encoding
the payload. This handler encodes that payload to reconstruct the
object.
"""
def flatten(self, obj, data):
pickler = self.context
if not pickler.unpicklable:
if hasattr(obj, 'isoformat'):
result = obj.isoformat()
else:
result = compat.ustr(obj)
return result
cls, args = obj.__reduce__()
flatten = pickler.flatten
payload = util.b64encode(args[0])
args = [payload] + [flatten(i, reset=False) for i in args[1:]]
data['__reduce__'] = (flatten(cls, reset=False), args)
return data
def restore(self, data):
cls, args = data['__reduce__']
unpickler = self.context
restore = unpickler.restore
cls = restore(cls, reset=False)
value = util.b64decode(args[0])
params = (value,) + tuple([restore(i, reset=False) for i in args[1:]])
return cls.__new__(cls, *params)
DatetimeHandler.handles(datetime.datetime)
DatetimeHandler.handles(datetime.date)
DatetimeHandler.handles(datetime.time)
class RegexHandler(BaseHandler):
"""Flatten _sre.SRE_Pattern (compiled regex) objects"""
def flatten(self, obj, data):
data['pattern'] = obj.pattern
return data
def restore(self, data):
return re.compile(data['pattern'])
RegexHandler.handles(type(re.compile('')))
class QueueHandler(BaseHandler):
"""Opaquely serializes Queue objects
Queues contains mutex and condition variables which cannot be serialized.
Construct a new Queue instance when restoring.
"""
def flatten(self, obj, data):
return data
def restore(self, data):
return compat.queue.Queue()
QueueHandler.handles(compat.queue.Queue)
class CloneFactory(object):
"""Serialization proxy for collections.defaultdict's default_factory"""
def __init__(self, exemplar):
self.exemplar = exemplar
def __call__(self, clone=copy.copy):
"""Create new instances by making copies of the provided exemplar"""
return clone(self.exemplar)
def __repr__(self):
return '<CloneFactory object at 0x{:x} ({})>'.format(id(self), self.exemplar)
class UUIDHandler(BaseHandler):
"""Serialize uuid.UUID objects"""
def flatten(self, obj, data):
data['hex'] = obj.hex
return data
def restore(self, data):
return uuid.UUID(data['hex'])
UUIDHandler.handles(uuid.UUID)
class LockHandler(BaseHandler):
"""Serialize threading.Lock objects"""
def flatten(self, obj, data):
data['locked'] = obj.locked()
return data
def restore(self, data):
lock = threading.Lock()
if data.get('locked', False):
lock.acquire()
return lock
_lock = threading.Lock()
LockHandler.handles(_lock.__class__)
class TextIOHandler(BaseHandler):
"""Serialize file descriptors as None because we cannot roundtrip"""
def flatten(self, obj, data):
return None
def restore(self, data):
"""Restore should never get called because flatten() returns None"""
raise AssertionError('Restoring IO.TextIOHandler is not supported')
if sys.version_info >= (3, 8):
TextIOHandler.handles(io.TextIOWrapper)

View File

@ -0,0 +1,743 @@
# Copyright (C) 2008 John Paulett (john -at- paulett.org)
# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
from __future__ import absolute_import, division, unicode_literals
import decimal
import warnings
import sys
import types
from itertools import chain, islice
from . import compat
from . import util
from . import tags
from . import handlers
from .backend import json
from .compat import numeric_types, string_types, PY3, PY2
def encode(
value,
unpicklable=True,
make_refs=True,
keys=False,
max_depth=None,
reset=True,
backend=None,
warn=False,
context=None,
max_iter=None,
use_decimal=False,
numeric_keys=False,
use_base85=False,
fail_safe=None,
indent=None,
separators=None,
):
"""Return a JSON formatted representation of value, a Python object.
:param unpicklable: If set to False then the output will not contain the
information necessary to turn the JSON data back into Python objects,
but a simpler JSON stream is produced.
:param max_depth: If set to a non-negative integer then jsonpickle will
not recurse deeper than 'max_depth' steps into the object. Anything
deeper than 'max_depth' is represented using a Python repr() of the
object.
:param make_refs: If set to False jsonpickle's referencing support is
disabled. Objects that are id()-identical won't be preserved across
encode()/decode(), but the resulting JSON stream will be conceptually
simpler. jsonpickle detects cyclical objects and will break the cycle
by calling repr() instead of recursing when make_refs is set False.
:param keys: If set to True then jsonpickle will encode non-string
dictionary keys instead of coercing them into strings via `repr()`.
This is typically what you want if you need to support Integer or
objects as dictionary keys.
:param numeric_keys: Only use this option if the backend supports integer
dict keys natively. This flag tells jsonpickle to leave numeric keys
as-is rather than conforming them to json-friendly strings.
Using ``keys=True`` is the typical solution for integer keys, so only
use this if you have a specific use case where you want to allow the
backend to handle serialization of numeric dict keys.
:param warn: If set to True then jsonpickle will warn when it
returns None for an object which it cannot pickle
(e.g. file descriptors).
:param max_iter: If set to a non-negative integer then jsonpickle will
consume at most `max_iter` items when pickling iterators.
:param use_decimal: If set to True jsonpickle will allow Decimal
instances to pass-through, with the assumption that the simplejson
backend will be used in `use_decimal` mode. In order to use this mode
you will need to configure simplejson::
jsonpickle.set_encoder_options('simplejson',
use_decimal=True, sort_keys=True)
jsonpickle.set_decoder_options('simplejson',
use_decimal=True)
jsonpickle.set_preferred_backend('simplejson')
NOTE: A side-effect of the above settings is that float values will be
converted to Decimal when converting to json.
:param use_base85:
If possible, use base85 to encode binary data. Base85 bloats binary data
by 1/4 as opposed to base64, which expands it by 1/3. This argument is
ignored on Python 2 because it doesn't support it.
:param fail_safe: If set to a function exceptions are ignored when pickling
and if a exception happens the function is called and the return value
is used as the value for the object that caused the error
:param indent: When `indent` is a non-negative integer, then JSON array
elements and object members will be pretty-printed with that indent
level. An indent level of 0 will only insert newlines. ``None`` is
the most compact representation. Since the default item separator is
``(', ', ': ')``, the output might include trailing whitespace when
``indent`` is specified. You can use ``separators=(',', ': ')`` to
avoid this. This value is passed directly to the active JSON backend
library and not used by jsonpickle directly.
:param separators:
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
then it will be used instead of the default ``(', ', ': ')``
separators. ``(',', ':')`` is the most compact JSON representation.
This value is passed directly to the active JSON backend library and
not used by jsonpickle directly.
>>> encode('my string') == '"my string"'
True
>>> encode(36) == '36'
True
>>> encode({'foo': True}) == '{"foo": true}'
True
>>> encode({'foo': [1, 2, [3, 4]]}, max_depth=1)
'{"foo": "[1, 2, [3, 4]]"}'
"""
backend = backend or json
context = context or Pickler(
unpicklable=unpicklable,
make_refs=make_refs,
keys=keys,
backend=backend,
max_depth=max_depth,
warn=warn,
max_iter=max_iter,
numeric_keys=numeric_keys,
use_decimal=use_decimal,
use_base85=use_base85,
fail_safe=fail_safe,
)
return backend.encode(
context.flatten(value, reset=reset), indent=indent, separators=separators
)
class Pickler(object):
def __init__(
self,
unpicklable=True,
make_refs=True,
max_depth=None,
backend=None,
keys=False,
warn=False,
max_iter=None,
numeric_keys=False,
use_decimal=False,
use_base85=False,
fail_safe=None,
):
self.unpicklable = unpicklable
self.make_refs = make_refs
self.backend = backend or json
self.keys = keys
self.warn = warn
self.numeric_keys = numeric_keys
self.use_base85 = use_base85 and (not PY2)
# The current recursion depth
self._depth = -1
# The maximal recursion depth
self._max_depth = max_depth
# Maps id(obj) to reference IDs
self._objs = {}
# Avoids garbage collection
self._seen = []
# maximum amount of items to take from a pickled iterator
self._max_iter = max_iter
# Whether to allow decimals to pass-through
self._use_decimal = use_decimal
if self.use_base85:
self._bytes_tag = tags.B85
self._bytes_encoder = util.b85encode
else:
self._bytes_tag = tags.B64
self._bytes_encoder = util.b64encode
# ignore exceptions
self.fail_safe = fail_safe
def reset(self):
self._objs = {}
self._depth = -1
self._seen = []
def _push(self):
"""Steps down one level in the namespace."""
self._depth += 1
def _pop(self, value):
"""Step up one level in the namespace and return the value.
If we're at the root, reset the pickler's state.
"""
self._depth -= 1
if self._depth == -1:
self.reset()
return value
def _log_ref(self, obj):
"""
Log a reference to an in-memory object.
Return True if this object is new and was assigned
a new ID. Otherwise return False.
"""
objid = id(obj)
is_new = objid not in self._objs
if is_new:
new_id = len(self._objs)
self._objs[objid] = new_id
return is_new
def _mkref(self, obj):
"""
Log a reference to an in-memory object, and return
if that object should be considered newly logged.
"""
is_new = self._log_ref(obj)
# Pretend the object is new
pretend_new = not self.unpicklable or not self.make_refs
return pretend_new or is_new
def _getref(self, obj):
return {tags.ID: self._objs.get(id(obj))}
def flatten(self, obj, reset=True):
"""Takes an object and returns a JSON-safe representation of it.
Simply returns any of the basic builtin datatypes
>>> p = Pickler()
>>> p.flatten('hello world') == 'hello world'
True
>>> p.flatten(49)
49
>>> p.flatten(350.0)
350.0
>>> p.flatten(True)
True
>>> p.flatten(False)
False
>>> r = p.flatten(None)
>>> r is None
True
>>> p.flatten(False)
False
>>> p.flatten([1, 2, 3, 4])
[1, 2, 3, 4]
>>> p.flatten((1,2,))[tags.TUPLE]
[1, 2]
>>> p.flatten({'key': 'value'}) == {'key': 'value'}
True
"""
if reset:
self.reset()
return self._flatten(obj)
def _flatten(self, obj):
#########################################
# if obj is nonrecursive return immediately
# for performance reasons we don't want to do recursive checks
if PY2 and isinstance(obj, types.FileType):
return self._flatten_file(obj)
if util.is_bytes(obj):
return self._flatten_bytestring(obj)
if util.is_primitive(obj):
return obj
# Decimal is a primitive when use_decimal is True
if self._use_decimal and isinstance(obj, decimal.Decimal):
return obj
#########################################
self._push()
return self._pop(self._flatten_obj(obj))
def _max_reached(self):
return self._depth == self._max_depth
def _flatten_obj(self, obj):
self._seen.append(obj)
max_reached = self._max_reached()
try:
in_cycle = _in_cycle(obj, self._objs, max_reached, self.make_refs)
if in_cycle:
# break the cycle
flatten_func = repr
else:
flatten_func = self._get_flattener(obj)
if flatten_func is None:
self._pickle_warning(obj)
return None
return flatten_func(obj)
except (KeyboardInterrupt, SystemExit) as e:
raise e
except Exception as e:
if self.fail_safe is None:
raise e
else:
return self.fail_safe(e)
def _list_recurse(self, obj):
return [self._flatten(v) for v in obj]
def _get_flattener(self, obj):
list_recurse = self._list_recurse
if util.is_list(obj):
if self._mkref(obj):
return list_recurse
else:
self._push()
return self._getref
# We handle tuples and sets by encoding them in a "(tuple|set)dict"
if util.is_tuple(obj):
if not self.unpicklable:
return list_recurse
return lambda obj: {tags.TUPLE: [self._flatten(v) for v in obj]}
if util.is_set(obj):
if not self.unpicklable:
return list_recurse
return lambda obj: {tags.SET: [self._flatten(v) for v in obj]}
if util.is_dictionary(obj):
return self._flatten_dict_obj
if util.is_type(obj):
return _mktyperef
if util.is_object(obj):
return self._ref_obj_instance
if util.is_module_function(obj):
return self._flatten_function
# instance methods, lambdas, old style classes...
self._pickle_warning(obj)
return None
def _ref_obj_instance(self, obj):
"""Reference an existing object or flatten if new"""
if self.unpicklable:
if self._mkref(obj):
# We've never seen this object so return its
# json representation.
return self._flatten_obj_instance(obj)
# We've seen this object before so place an object
# reference tag in the data. This avoids infinite recursion
# when processing cyclical objects.
return self._getref(obj)
else:
max_reached = self._max_reached()
in_cycle = _in_cycle(obj, self._objs, max_reached, False)
if in_cycle:
# A circular becomes None.
return None
self._mkref(obj)
return self._flatten_obj_instance(obj)
def _flatten_file(self, obj):
"""
Special case file objects
"""
assert not PY3 and isinstance(obj, types.FileType)
return None
def _flatten_bytestring(self, obj):
if PY2:
try:
return obj.decode('utf-8')
except UnicodeDecodeError:
pass
return {self._bytes_tag: self._bytes_encoder(obj)}
def _flatten_obj_instance(self, obj):
"""Recursively flatten an instance and return a json-friendly dict"""
data = {}
has_class = hasattr(obj, '__class__')
has_dict = hasattr(obj, '__dict__')
has_slots = not has_dict and hasattr(obj, '__slots__')
has_getnewargs = util.has_method(obj, '__getnewargs__')
has_getnewargs_ex = util.has_method(obj, '__getnewargs_ex__')
has_getinitargs = util.has_method(obj, '__getinitargs__')
has_reduce, has_reduce_ex = util.has_reduce(obj)
# Support objects with __getstate__(); this ensures that
# both __setstate__() and __getstate__() are implemented
has_getstate = hasattr(obj, '__getstate__')
# not using has_method since __getstate__() is handled separately below
if has_class:
cls = obj.__class__
else:
cls = type(obj)
# Check for a custom handler
class_name = util.importable_name(cls)
handler = handlers.get(cls, handlers.get(class_name))
if handler is not None:
if self.unpicklable:
data[tags.OBJECT] = class_name
return handler(self).flatten(obj, data)
reduce_val = None
if self.unpicklable:
if has_reduce and not has_reduce_ex:
try:
reduce_val = obj.__reduce__()
except TypeError:
# A lot of builtin types have a reduce which
# just raises a TypeError
# we ignore those
pass
# test for a reduce implementation, and redirect before
# doing anything else if that is what reduce requests
elif has_reduce_ex:
try:
# we're implementing protocol 2
reduce_val = obj.__reduce_ex__(2)
except TypeError:
# A lot of builtin types have a reduce which
# just raises a TypeError
# we ignore those
pass
if reduce_val and isinstance(reduce_val, string_types):
try:
varpath = iter(reduce_val.split('.'))
# curmod will be transformed by the
# loop into the value to pickle
curmod = sys.modules[next(varpath)]
for modname in varpath:
curmod = getattr(curmod, modname)
# replace obj with value retrieved
return self._flatten(curmod)
except KeyError:
# well, we can't do anything with that, so we ignore it
pass
elif reduce_val:
# at this point, reduce_val should be some kind of iterable
# pad out to len 5
rv_as_list = list(reduce_val)
insufficiency = 5 - len(rv_as_list)
if insufficiency:
rv_as_list += [None] * insufficiency
if getattr(rv_as_list[0], '__name__', '') == '__newobj__':
rv_as_list[0] = tags.NEWOBJ
f, args, state, listitems, dictitems = rv_as_list
# check that getstate/setstate is sane
if not (
state
and hasattr(obj, '__getstate__')
and not hasattr(obj, '__setstate__')
and not isinstance(obj, dict)
):
# turn iterators to iterables for convenient serialization
if rv_as_list[3]:
rv_as_list[3] = tuple(rv_as_list[3])
if rv_as_list[4]:
rv_as_list[4] = tuple(rv_as_list[4])
reduce_args = list(map(self._flatten, rv_as_list))
last_index = len(reduce_args) - 1
while last_index >= 2 and reduce_args[last_index] is None:
last_index -= 1
data[tags.REDUCE] = reduce_args[: last_index + 1]
return data
if has_class and not util.is_module(obj):
if self.unpicklable:
data[tags.OBJECT] = class_name
if has_getnewargs_ex:
data[tags.NEWARGSEX] = list(map(self._flatten, obj.__getnewargs_ex__()))
if has_getnewargs and not has_getnewargs_ex:
data[tags.NEWARGS] = self._flatten(obj.__getnewargs__())
if has_getinitargs:
data[tags.INITARGS] = self._flatten(obj.__getinitargs__())
if has_getstate:
try:
state = obj.__getstate__()
except TypeError:
# Has getstate but it cannot be called, e.g. file descriptors
# in Python3
self._pickle_warning(obj)
return None
else:
return self._getstate(state, data)
if util.is_module(obj):
if self.unpicklable:
data[tags.REPR] = '{name}/{name}'.format(name=obj.__name__)
else:
data = compat.ustr(obj)
return data
if util.is_dictionary_subclass(obj):
self._flatten_dict_obj(obj, data)
return data
if util.is_sequence_subclass(obj):
return self._flatten_sequence_obj(obj, data)
if util.is_iterator(obj):
# force list in python 3
data[tags.ITERATOR] = list(map(self._flatten, islice(obj, self._max_iter)))
return data
if has_dict:
# Support objects that subclasses list and set
if util.is_sequence_subclass(obj):
return self._flatten_sequence_obj(obj, data)
# hack for zope persistent objects; this unghostifies the object
getattr(obj, '_', None)
return self._flatten_dict_obj(obj.__dict__, data)
if has_slots:
return self._flatten_newstyle_with_slots(obj, data)
# catchall return for data created above without a return
# (e.g. __getnewargs__ is not supposed to be the end of the story)
if data:
return data
self._pickle_warning(obj)
return None
def _flatten_function(self, obj):
if self.unpicklable:
data = {tags.FUNCTION: util.importable_name(obj)}
else:
data = None
return data
def _flatten_dict_obj(self, obj, data=None):
"""Recursively call flatten() and return json-friendly dict"""
if data is None:
data = obj.__class__()
# If we allow non-string keys then we have to do a two-phase
# encoding to ensure that the reference IDs are deterministic.
if self.keys:
# Phase 1: serialize regular objects, ignore fancy keys.
flatten = self._flatten_string_key_value_pair
for k, v in util.items(obj):
flatten(k, v, data)
# Phase 2: serialize non-string keys.
flatten = self._flatten_non_string_key_value_pair
for k, v in util.items(obj):
flatten(k, v, data)
else:
# If we have string keys only then we only need a single pass.
flatten = self._flatten_key_value_pair
for k, v in util.items(obj):
flatten(k, v, data)
# the collections.defaultdict protocol
if hasattr(obj, 'default_factory') and callable(obj.default_factory):
factory = obj.default_factory
if util.is_type(factory):
# Reference the class/type
value = _mktyperef(factory)
else:
# The factory is not a type and could reference e.g. functions
# or even the object instance itself, which creates a cycle.
if self._mkref(factory):
# We've never seen this object before so pickle it in-place.
# Create an instance from the factory and assume that the
# resulting instance is a suitable examplar.
value = self._flatten_obj_instance(handlers.CloneFactory(factory()))
else:
# We've seen this object before.
# Break the cycle by emitting a reference.
value = self._getref(factory)
data['default_factory'] = value
# Sub-classes of dict
if hasattr(obj, '__dict__') and self.unpicklable:
dict_data = {}
self._flatten_dict_obj(obj.__dict__, dict_data)
data['__dict__'] = dict_data
return data
def _flatten_obj_attrs(self, obj, attrs, data):
flatten = self._flatten_key_value_pair
ok = False
for k in attrs:
try:
value = getattr(obj, k)
flatten(k, value, data)
except AttributeError:
# The attribute may have been deleted
continue
ok = True
return ok
def _flatten_newstyle_with_slots(self, obj, data):
"""Return a json-friendly dict for new-style objects with __slots__."""
allslots = [
_wrap_string_slot(getattr(cls, '__slots__', tuple()))
for cls in obj.__class__.mro()
]
if not self._flatten_obj_attrs(obj, chain(*allslots), data):
attrs = [
x for x in dir(obj) if not x.startswith('__') and not x.endswith('__')
]
self._flatten_obj_attrs(obj, attrs, data)
return data
def _flatten_key_value_pair(self, k, v, data):
"""Flatten a key/value pair into the passed-in dictionary."""
if not util.is_picklable(k, v):
return data
if k is None:
k = 'null' # for compatibility with common json encoders
if self.numeric_keys and isinstance(k, numeric_types):
pass
elif not isinstance(k, string_types):
try:
k = repr(k)
except Exception:
k = compat.ustr(k)
data[k] = self._flatten(v)
return data
def _flatten_non_string_key_value_pair(self, k, v, data):
"""Flatten only non-string key/value pairs"""
if not util.is_picklable(k, v):
return data
if self.keys and not isinstance(k, string_types):
k = self._escape_key(k)
data[k] = self._flatten(v)
return data
def _flatten_string_key_value_pair(self, k, v, data):
"""Flatten string key/value pairs only."""
if not util.is_picklable(k, v):
return data
if self.keys:
if not isinstance(k, string_types):
return data
elif k.startswith(tags.JSON_KEY):
k = self._escape_key(k)
else:
if k is None:
k = 'null' # for compatibility with common json encoders
if self.numeric_keys and isinstance(k, numeric_types):
pass
elif not isinstance(k, string_types):
try:
k = repr(k)
except Exception:
k = compat.ustr(k)
data[k] = self._flatten(v)
return data
def _flatten_sequence_obj(self, obj, data):
"""Return a json-friendly dict for a sequence subclass."""
if hasattr(obj, '__dict__'):
self._flatten_dict_obj(obj.__dict__, data)
value = [self._flatten(v) for v in obj]
if self.unpicklable:
data[tags.SEQ] = value
else:
return value
return data
def _escape_key(self, k):
return tags.JSON_KEY + encode(
k,
reset=False,
keys=True,
context=self,
backend=self.backend,
make_refs=self.make_refs,
)
def _getstate(self, obj, data):
state = self._flatten(obj)
if self.unpicklable:
data[tags.STATE] = state
else:
data = state
return data
def _pickle_warning(self, obj):
if self.warn:
msg = 'jsonpickle cannot pickle %r: replaced with None' % obj
warnings.warn(msg)
def _in_cycle(obj, objs, max_reached, make_refs):
return (
max_reached or (not make_refs and id(obj) in objs)
) and not util.is_primitive(obj)
def _mktyperef(obj):
"""Return a typeref dictionary
>>> _mktyperef(AssertionError) == {'py/type': 'builtins.AssertionError'}
True
"""
return {tags.TYPE: util.importable_name(obj)}
def _wrap_string_slot(string):
"""Converts __slots__ = 'a' into __slots__ = ('a',)"""
if isinstance(string, string_types):
return (string,)
return string

View File

@ -0,0 +1,52 @@
"""The jsonpickle.tags module provides the custom tags
used for pickling and unpickling Python objects.
These tags are keys into the flattened dictionaries
created by the Pickler class. The Unpickler uses
these custom key names to identify dictionaries
that need to be specially handled.
"""
from __future__ import absolute_import, division, unicode_literals
BYTES = 'py/bytes'
B64 = 'py/b64'
B85 = 'py/b85'
FUNCTION = 'py/function'
ID = 'py/id'
INITARGS = 'py/initargs'
ITERATOR = 'py/iterator'
JSON_KEY = 'json://'
NEWARGS = 'py/newargs'
NEWARGSEX = 'py/newargsex'
NEWOBJ = 'py/newobj'
OBJECT = 'py/object'
REDUCE = 'py/reduce'
REF = 'py/ref'
REPR = 'py/repr'
SEQ = 'py/seq'
SET = 'py/set'
STATE = 'py/state'
TUPLE = 'py/tuple'
TYPE = 'py/type'
# All reserved tag names
RESERVED = {
BYTES,
FUNCTION,
ID,
INITARGS,
ITERATOR,
NEWARGS,
NEWARGSEX,
NEWOBJ,
OBJECT,
REDUCE,
REF,
REPR,
SEQ,
SET,
STATE,
TUPLE,
TYPE,
}

View File

@ -0,0 +1,752 @@
# Copyright (C) 2008 John Paulett (john -at- paulett.org)
# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
from __future__ import absolute_import, division, unicode_literals
import quopri
import sys
from . import compat
from . import util
from . import tags
from . import handlers
from .compat import numeric_types
from .backend import json
def decode(
string, backend=None, context=None, keys=False, reset=True, safe=False, classes=None
):
"""Convert a JSON string into a Python object.
The keyword argument 'keys' defaults to False.
If set to True then jsonpickle will decode non-string dictionary keys
into python objects via the jsonpickle protocol.
The keyword argument 'classes' defaults to None.
If set to a single class, or a sequence (list, set, tuple) of classes,
then the classes will be made available when constructing objects. This
can be used to give jsonpickle access to local classes that are not
available through the global module import scope.
>>> decode('"my string"') == 'my string'
True
>>> decode('36')
36
"""
backend = backend or json
context = context or Unpickler(keys=keys, backend=backend, safe=safe)
data = backend.decode(string)
return context.restore(data, reset=reset, classes=classes)
def _safe_hasattr(obj, attr):
"""Workaround unreliable hasattr() availability on sqlalchemy objects"""
try:
object.__getattribute__(obj, attr)
return True
except AttributeError:
return False
def _is_json_key(key):
"""Has this key a special object that has been encoded to JSON?"""
return isinstance(key, compat.string_types) and key.startswith(tags.JSON_KEY)
class _Proxy(object):
"""Proxies are dummy objects that are later replaced by real instances
The `restore()` function has to solve a tricky problem when pickling
objects with cyclical references -- the parent instance does not yet
exist.
The problem is that `__getnewargs__()`, `__getstate__()`, custom handlers,
and cyclical objects graphs are allowed to reference the yet-to-be-created
object via the referencing machinery.
In other words, objects are allowed to depend on themselves for
construction!
We solve this problem by placing dummy Proxy objects into the referencing
machinery so that we can construct the child objects before constructing
the parent. Objects are initially created with Proxy attribute values
instead of real references.
We collect all objects that contain references to proxies and run
a final sweep over them to swap in the real instance. This is done
at the very end of the top-level `restore()`.
The `instance` attribute below is replaced with the real instance
after `__new__()` has been used to construct the object and is used
when swapping proxies with real instances.
"""
def __init__(self):
self.instance = None
def get(self):
return self.instance
def reset(self, instance):
self.instance = instance
class _IDProxy(_Proxy):
def __init__(self, objs, index):
self._index = index
self._objs = objs
def get(self):
return self._objs[self._index]
def _obj_setattr(obj, attr, proxy):
setattr(obj, attr, proxy.get())
def _obj_setvalue(obj, idx, proxy):
obj[idx] = proxy.get()
class Unpickler(object):
def __init__(self, backend=None, keys=False, safe=False):
self.backend = backend or json
self.keys = keys
self.safe = safe
self.reset()
def reset(self):
"""Resets the object's internal state."""
# Map reference names to object instances
self._namedict = {}
# The stack of names traversed for child objects
self._namestack = []
# Map of objects to their index in the _objs list
self._obj_to_idx = {}
self._objs = []
self._proxies = []
# Extra local classes not accessible globally
self._classes = {}
def restore(self, obj, reset=True, classes=None):
"""Restores a flattened object to its original python state.
Simply returns any of the basic builtin types
>>> u = Unpickler()
>>> u.restore('hello world') == 'hello world'
True
>>> u.restore({'key': 'value'}) == {'key': 'value'}
True
"""
if reset:
self.reset()
if classes:
self.register_classes(classes)
value = self._restore(obj)
if reset:
self._swap_proxies()
return value
def register_classes(self, classes):
"""Register one or more classes
:param classes: sequence of classes or a single class to register
"""
if isinstance(classes, (list, tuple, set)):
for cls in classes:
self.register_classes(cls)
else:
self._classes[util.importable_name(classes)] = classes
def _swap_proxies(self):
"""Replace proxies with their corresponding instances"""
for (obj, attr, proxy, method) in self._proxies:
method(obj, attr, proxy)
self._proxies = []
def _restore(self, obj):
if has_tag(obj, tags.B64):
restore = self._restore_base64
elif has_tag(obj, tags.B85):
restore = self._restore_base85
elif has_tag(obj, tags.BYTES): # Backwards compatibility
restore = self._restore_quopri
elif has_tag(obj, tags.ID):
restore = self._restore_id
elif has_tag(obj, tags.REF): # Backwards compatibility
restore = self._restore_ref
elif has_tag(obj, tags.ITERATOR):
restore = self._restore_iterator
elif has_tag(obj, tags.TYPE):
restore = self._restore_type
elif has_tag(obj, tags.REPR): # Backwards compatibility
restore = self._restore_repr
elif has_tag(obj, tags.REDUCE):
restore = self._restore_reduce
elif has_tag(obj, tags.OBJECT):
restore = self._restore_object
elif has_tag(obj, tags.FUNCTION):
restore = self._restore_function
elif util.is_list(obj):
restore = self._restore_list
elif has_tag(obj, tags.TUPLE):
restore = self._restore_tuple
elif has_tag(obj, tags.SET):
restore = self._restore_set
elif util.is_dictionary(obj):
restore = self._restore_dict
else:
def restore(x):
return x
return restore(obj)
def _restore_base64(self, obj):
return util.b64decode(obj[tags.B64].encode('utf-8'))
def _restore_base85(self, obj):
return util.b85decode(obj[tags.B85].encode('utf-8'))
#: For backwards compatibility with bytes data produced by older versions
def _restore_quopri(self, obj):
return quopri.decodestring(obj[tags.BYTES].encode('utf-8'))
def _restore_iterator(self, obj):
return iter(self._restore_list(obj[tags.ITERATOR]))
def _restore_reduce(self, obj):
"""
Supports restoring with all elements of __reduce__ as per pep 307.
Assumes that iterator items (the last two) are represented as lists
as per pickler implementation.
"""
proxy = _Proxy()
self._mkref(proxy)
reduce_val = list(map(self._restore, obj[tags.REDUCE]))
if len(reduce_val) < 5:
reduce_val.extend([None] * (5 - len(reduce_val)))
f, args, state, listitems, dictitems = reduce_val
if f == tags.NEWOBJ or getattr(f, '__name__', '') == '__newobj__':
# mandated special case
cls = args[0]
if not isinstance(cls, type):
cls = self._restore(cls)
stage1 = cls.__new__(cls, *args[1:])
else:
stage1 = f(*args)
if state:
try:
stage1.__setstate__(state)
except AttributeError:
# it's fine - we'll try the prescribed default methods
try:
# we can't do a straight update here because we
# need object identity of the state dict to be
# preserved so that _swap_proxies works out
for k, v in stage1.__dict__.items():
state.setdefault(k, v)
stage1.__dict__ = state
except AttributeError:
# next prescribed default
try:
for k, v in state.items():
setattr(stage1, k, v)
except Exception:
dict_state, slots_state = state
if dict_state:
stage1.__dict__.update(dict_state)
if slots_state:
for k, v in slots_state.items():
setattr(stage1, k, v)
if listitems:
# should be lists if not None
try:
stage1.extend(listitems)
except AttributeError:
for x in listitems:
stage1.append(x)
if dictitems:
for k, v in dictitems:
stage1.__setitem__(k, v)
proxy.reset(stage1)
self._swapref(proxy, stage1)
return stage1
def _restore_id(self, obj):
try:
idx = obj[tags.ID]
return self._objs[idx]
except IndexError:
return _IDProxy(self._objs, idx)
def _restore_ref(self, obj):
return self._namedict.get(obj[tags.REF])
def _restore_type(self, obj):
typeref = loadclass(obj[tags.TYPE], classes=self._classes)
if typeref is None:
return obj
return typeref
def _restore_repr(self, obj):
if self.safe:
# eval() is not allowed in safe mode
return None
obj = loadrepr(obj[tags.REPR])
return self._mkref(obj)
def _restore_object(self, obj):
class_name = obj[tags.OBJECT]
cls = loadclass(class_name, classes=self._classes)
handler = handlers.get(cls, handlers.get(class_name))
if handler is not None: # custom handler
proxy = _Proxy()
self._mkref(proxy)
instance = handler(self).restore(obj)
proxy.reset(instance)
self._swapref(proxy, instance)
return instance
if cls is None:
return self._mkref(obj)
return self._restore_object_instance(obj, cls)
def _restore_function(self, obj):
return loadclass(obj[tags.FUNCTION], classes=self._classes)
def _loadfactory(self, obj):
try:
default_factory = obj['default_factory']
except KeyError:
return None
del obj['default_factory']
return self._restore(default_factory)
def _restore_object_instance(self, obj, cls):
# This is a placeholder proxy object which allows child objects to
# reference the parent object before it has been instantiated.
proxy = _Proxy()
self._mkref(proxy)
# An object can install itself as its own factory, so load the factory
# after the instance is available for referencing.
factory = self._loadfactory(obj)
if has_tag(obj, tags.NEWARGSEX):
args, kwargs = obj[tags.NEWARGSEX]
else:
args = getargs(obj, classes=self._classes)
kwargs = {}
if args:
args = self._restore(args)
if kwargs:
kwargs = self._restore(kwargs)
is_oldstyle = not (isinstance(cls, type) or getattr(cls, '__meta__', None))
try:
if (not is_oldstyle) and hasattr(cls, '__new__'):
# new style classes
if factory:
instance = cls.__new__(cls, factory, *args, **kwargs)
instance.default_factory = factory
else:
instance = cls.__new__(cls, *args, **kwargs)
else:
instance = object.__new__(cls)
except TypeError: # old-style classes
is_oldstyle = True
if is_oldstyle:
try:
instance = cls(*args)
except TypeError: # fail gracefully
try:
instance = make_blank_classic(cls)
except Exception: # fail gracefully
return self._mkref(obj)
proxy.reset(instance)
self._swapref(proxy, instance)
if isinstance(instance, tuple):
return instance
instance = self._restore_object_instance_variables(obj, instance)
if _safe_hasattr(instance, 'default_factory') and isinstance(
instance.default_factory, _Proxy
):
instance.default_factory = instance.default_factory.get()
return instance
def _restore_from_dict(self, obj, instance, ignorereserved=True):
restore_key = self._restore_key_fn()
method = _obj_setattr
deferred = {}
for k, v in util.items(obj):
# ignore the reserved attribute
if ignorereserved and k in tags.RESERVED:
continue
if isinstance(k, numeric_types):
str_k = k.__str__()
else:
str_k = k
self._namestack.append(str_k)
k = restore_key(k)
# step into the namespace
value = self._restore(v)
if util.is_noncomplex(instance) or util.is_dictionary_subclass(instance):
try:
if k == '__dict__':
setattr(instance, k, value)
else:
instance[k] = value
except TypeError:
# Immutable object, must be constructed in one shot
if k != '__dict__':
deferred[k] = value
self._namestack.pop()
continue
else:
setattr(instance, k, value)
# This instance has an instance variable named `k` that is
# currently a proxy and must be replaced
if isinstance(value, _Proxy):
self._proxies.append((instance, k, value, method))
# step out
self._namestack.pop()
if deferred:
# SQLAlchemy Immutable mappings must be constructed in one shot
instance = instance.__class__(deferred)
return instance
def _restore_object_instance_variables(self, obj, instance):
instance = self._restore_from_dict(obj, instance)
# Handle list and set subclasses
if has_tag(obj, tags.SEQ):
if hasattr(instance, 'append'):
for v in obj[tags.SEQ]:
instance.append(self._restore(v))
elif hasattr(instance, 'add'):
for v in obj[tags.SEQ]:
instance.add(self._restore(v))
if has_tag(obj, tags.STATE):
instance = self._restore_state(obj, instance)
return instance
def _restore_state(self, obj, instance):
state = self._restore(obj[tags.STATE])
has_slots = (
isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict)
)
has_slots_and_dict = has_slots and isinstance(state[0], dict)
if hasattr(instance, '__setstate__'):
instance.__setstate__(state)
elif isinstance(state, dict):
# implements described default handling
# of state for object with instance dict
# and no slots
instance = self._restore_from_dict(state, instance, ignorereserved=False)
elif has_slots:
instance = self._restore_from_dict(state[1], instance, ignorereserved=False)
if has_slots_and_dict:
instance = self._restore_from_dict(
state[0], instance, ignorereserved=False
)
elif not hasattr(instance, '__getnewargs__') and not hasattr(
instance, '__getnewargs_ex__'
):
# __setstate__ is not implemented so that means that the best
# we can do is return the result of __getstate__() rather than
# return an empty shell of an object.
# However, if there were newargs, it's not an empty shell
instance = state
return instance
def _restore_list(self, obj):
parent = []
self._mkref(parent)
children = [self._restore(v) for v in obj]
parent.extend(children)
method = _obj_setvalue
proxies = [
(parent, idx, value, method)
for idx, value in enumerate(parent)
if isinstance(value, _Proxy)
]
self._proxies.extend(proxies)
return parent
def _restore_tuple(self, obj):
return tuple([self._restore(v) for v in obj[tags.TUPLE]])
def _restore_set(self, obj):
return {self._restore(v) for v in obj[tags.SET]}
def _restore_dict(self, obj):
data = {}
# If we are decoding dicts that can have non-string keys then we
# need to do a two-phase decode where the non-string keys are
# processed last. This ensures a deterministic order when
# assigning object IDs for references.
if self.keys:
# Phase 1: regular non-special keys.
for k, v in util.items(obj):
if _is_json_key(k):
continue
if isinstance(k, numeric_types):
str_k = k.__str__()
else:
str_k = k
self._namestack.append(str_k)
data[k] = self._restore(v)
self._namestack.pop()
# Phase 2: object keys only.
for k, v in util.items(obj):
if not _is_json_key(k):
continue
self._namestack.append(k)
k = self._restore_pickled_key(k)
data[k] = result = self._restore(v)
# k is currently a proxy and must be replaced
if isinstance(result, _Proxy):
self._proxies.append((data, k, result, _obj_setvalue))
self._namestack.pop()
else:
# No special keys, thus we don't need to restore the keys either.
for k, v in util.items(obj):
if isinstance(k, numeric_types):
str_k = k.__str__()
else:
str_k = k
self._namestack.append(str_k)
data[k] = self._restore(v)
self._namestack.pop()
return data
def _restore_key_fn(self):
"""Return a callable that restores keys
This function is responsible for restoring non-string keys
when we are decoding with `keys=True`.
"""
# This function is called before entering a tight loop
# where the returned function will be called.
# We return a specific function after checking self.keys
# instead of doing so in the body of the function to
# avoid conditional branching inside a tight loop.
if self.keys:
restore_key = self._restore_pickled_key
else:
def restore_key(key):
return key
return restore_key
def _restore_pickled_key(self, key):
"""Restore a possibly pickled key"""
if _is_json_key(key):
key = decode(
key[len(tags.JSON_KEY) :],
backend=self.backend,
context=self,
keys=True,
reset=False,
)
return key
def _refname(self):
"""Calculates the name of the current location in the JSON stack.
This is called as jsonpickle traverses the object structure to
create references to previously-traversed objects. This allows
cyclical data structures such as doubly-linked lists.
jsonpickle ensures that duplicate python references to the same
object results in only a single JSON object definition and
special reference tags to represent each reference.
>>> u = Unpickler()
>>> u._namestack = []
>>> u._refname() == '/'
True
>>> u._namestack = ['a']
>>> u._refname() == '/a'
True
>>> u._namestack = ['a', 'b']
>>> u._refname() == '/a/b'
True
"""
return '/' + '/'.join(self._namestack)
def _mkref(self, obj):
obj_id = id(obj)
try:
self._obj_to_idx[obj_id]
except KeyError:
self._obj_to_idx[obj_id] = len(self._objs)
self._objs.append(obj)
# Backwards compatibility: old versions of jsonpickle
# produced "py/ref" references.
self._namedict[self._refname()] = obj
return obj
def _swapref(self, proxy, instance):
proxy_id = id(proxy)
instance_id = id(instance)
instance_index = self._obj_to_idx[proxy_id]
self._obj_to_idx[instance_id] = instance_index
del self._obj_to_idx[proxy_id]
self._objs[instance_index] = instance
self._namedict[self._refname()] = instance
def loadclass(module_and_name, classes=None):
"""Loads the module and returns the class.
>>> cls = loadclass('datetime.datetime')
>>> cls.__name__
'datetime'
>>> loadclass('does.not.exist')
>>> loadclass('builtins.int')()
0
"""
# Check if the class exists in a caller-provided scope
if classes:
try:
return classes[module_and_name]
except KeyError:
pass
# Otherwise, load classes from globally-accessible imports
names = module_and_name.split('.')
# First assume that everything up to the last dot is the module name,
# then try other splits to handle classes that are defined within
# classes
for up_to in range(len(names) - 1, 0, -1):
module = util.untranslate_module_name('.'.join(names[:up_to]))
try:
__import__(module)
obj = sys.modules[module]
for class_name in names[up_to:]:
obj = getattr(obj, class_name)
return obj
except (AttributeError, ImportError, ValueError):
continue
return None
def getargs(obj, classes=None):
"""Return arguments suitable for __new__()"""
# Let saved newargs take precedence over everything
if has_tag(obj, tags.NEWARGSEX):
raise ValueError("__newargs_ex__ returns both args and kwargs")
if has_tag(obj, tags.NEWARGS):
return obj[tags.NEWARGS]
if has_tag(obj, tags.INITARGS):
return obj[tags.INITARGS]
try:
seq_list = obj[tags.SEQ]
obj_dict = obj[tags.OBJECT]
except KeyError:
return []
typeref = loadclass(obj_dict, classes=classes)
if not typeref:
return []
if hasattr(typeref, '_fields'):
if len(typeref._fields) == len(seq_list):
return seq_list
return []
class _trivialclassic:
"""
A trivial class that can be instantiated with no args
"""
def make_blank_classic(cls):
"""
Implement the mandated strategy for dealing with classic classes
which cannot be instantiated without __getinitargs__ because they
take parameters
"""
instance = _trivialclassic()
instance.__class__ = cls
return instance
def loadrepr(reprstr):
"""Returns an instance of the object from the object's repr() string.
It involves the dynamic specification of code.
>>> obj = loadrepr('datetime/datetime.datetime.now()')
>>> obj.__class__.__name__
'datetime'
"""
module, evalstr = reprstr.split('/')
mylocals = locals()
localname = module
if '.' in localname:
localname = module.split('.', 1)[0]
mylocals[localname] = __import__(module)
return eval(evalstr)
def has_tag(obj, tag):
"""Helper class that tests to see if the obj is a dictionary
and contains a particular key/tag.
>>> obj = {'test': 1}
>>> has_tag(obj, 'test')
True
>>> has_tag(obj, 'fail')
False
>>> has_tag(42, 'fail')
False
"""
return type(obj) is dict and tag in obj

View File

@ -0,0 +1,562 @@
# Copyright (C) 2008 John Paulett (john -at- paulett.org)
# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
"""Helper functions for pickling and unpickling. Most functions assist in
determining the type of an object.
"""
from __future__ import absolute_import, division, unicode_literals
import base64
import collections
import io
import operator
import time
import types
import inspect
from . import tags
from . import compat
from .compat import (
abc_iterator,
class_types,
iterator_types,
numeric_types,
PY2,
PY3,
PY3_ORDERED_DICT,
)
if PY2:
import __builtin__
SEQUENCES = (list, set, tuple)
SEQUENCES_SET = {list, set, tuple}
PRIMITIVES = {compat.ustr, bool, type(None)} | set(numeric_types)
def is_type(obj):
"""Returns True is obj is a reference to a type.
>>> is_type(1)
False
>>> is_type(object)
True
>>> class Klass: pass
>>> is_type(Klass)
True
"""
# use "isinstance" and not "is" to allow for metaclasses
return isinstance(obj, class_types)
def has_method(obj, name):
# false if attribute doesn't exist
if not hasattr(obj, name):
return False
func = getattr(obj, name)
# builtin descriptors like __getnewargs__
if isinstance(func, types.BuiltinMethodType):
return True
# note that FunctionType has a different meaning in py2/py3
if not isinstance(func, (types.MethodType, types.FunctionType)):
return False
# need to go through __dict__'s since in py3
# methods are essentially descriptors
# __class__ for old-style classes
base_type = obj if is_type(obj) else obj.__class__
original = None
# there is no .mro() for old-style classes
for subtype in inspect.getmro(base_type):
original = vars(subtype).get(name)
if original is not None:
break
# name not found in the mro
if original is None:
return False
# static methods are always fine
if isinstance(original, staticmethod):
return True
# at this point, the method has to be an instancemthod or a classmethod
self_attr = '__self__' if PY3 else 'im_self'
if not hasattr(func, self_attr):
return False
bound_to = getattr(func, self_attr)
# class methods
if isinstance(original, classmethod):
return issubclass(base_type, bound_to)
# bound methods
return isinstance(obj, type(bound_to))
def is_object(obj):
"""Returns True is obj is a reference to an object instance.
>>> is_object(1)
True
>>> is_object(object())
True
>>> is_object(lambda x: 1)
False
"""
return isinstance(obj, object) and not isinstance(
obj, (type, types.FunctionType, types.BuiltinFunctionType)
)
def is_primitive(obj):
"""Helper method to see if the object is a basic data type. Unicode strings,
integers, longs, floats, booleans, and None are considered primitive
and will return True when passed into *is_primitive()*
>>> is_primitive(3)
True
>>> is_primitive([4,4])
False
"""
return type(obj) in PRIMITIVES
def is_dictionary(obj):
"""Helper method for testing if the object is a dictionary.
>>> is_dictionary({'key':'value'})
True
"""
return type(obj) is dict
def is_sequence(obj):
"""Helper method to see if the object is a sequence (list, set, or tuple).
>>> is_sequence([4])
True
"""
return type(obj) in SEQUENCES_SET
def is_list(obj):
"""Helper method to see if the object is a Python list.
>>> is_list([4])
True
"""
return type(obj) is list
def is_set(obj):
"""Helper method to see if the object is a Python set.
>>> is_set(set())
True
"""
return type(obj) is set
def is_bytes(obj):
"""Helper method to see if the object is a bytestring.
>>> is_bytes(b'foo')
True
"""
return type(obj) is bytes
def is_unicode(obj):
"""Helper method to see if the object is a unicode string"""
return type(obj) is compat.ustr
def is_tuple(obj):
"""Helper method to see if the object is a Python tuple.
>>> is_tuple((1,))
True
"""
return type(obj) is tuple
def is_dictionary_subclass(obj):
"""Returns True if *obj* is a subclass of the dict type. *obj* must be
a subclass and not the actual builtin dict.
>>> class Temp(dict): pass
>>> is_dictionary_subclass(Temp())
True
"""
# TODO: add UserDict
return (
hasattr(obj, '__class__')
and issubclass(obj.__class__, dict)
and type(obj) is not dict
)
def is_sequence_subclass(obj):
"""Returns True if *obj* is a subclass of list, set or tuple.
*obj* must be a subclass and not the actual builtin, such
as list, set, tuple, etc..
>>> class Temp(list): pass
>>> is_sequence_subclass(Temp())
True
"""
return (
hasattr(obj, '__class__')
and (issubclass(obj.__class__, SEQUENCES) or is_list_like(obj))
and not is_sequence(obj)
)
def is_noncomplex(obj):
"""Returns True if *obj* is a special (weird) class, that is more complex
than primitive data types, but is not a full object. Including:
* :class:`~time.struct_time`
"""
if type(obj) is time.struct_time:
return True
return False
def is_function(obj):
"""Returns true if passed a function
>>> is_function(lambda x: 1)
True
>>> is_function(locals)
True
>>> def method(): pass
>>> is_function(method)
True
>>> is_function(1)
False
"""
function_types = (
types.FunctionType,
types.MethodType,
types.LambdaType,
types.BuiltinFunctionType,
types.BuiltinMethodType,
)
return type(obj) in function_types
def is_module_function(obj):
"""Return True if `obj` is a module-global function
>>> import os
>>> is_module_function(os.path.exists)
True
>>> is_module_function(lambda: None)
False
"""
return (
hasattr(obj, '__class__')
and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType))
and hasattr(obj, '__module__')
and hasattr(obj, '__name__')
and obj.__name__ != '<lambda>'
)
def is_module(obj):
"""Returns True if passed a module
>>> import os
>>> is_module(os)
True
"""
return isinstance(obj, types.ModuleType)
def is_picklable(name, value):
"""Return True if an object can be pickled
>>> import os
>>> is_picklable('os', os)
True
>>> def foo(): pass
>>> is_picklable('foo', foo)
True
>>> is_picklable('foo', lambda: None)
False
"""
if name in tags.RESERVED:
return False
return is_module_function(value) or not is_function(value)
def is_installed(module):
"""Tests to see if ``module`` is available on the sys.path
>>> is_installed('sys')
True
>>> is_installed('hopefullythisisnotarealmodule')
False
"""
try:
__import__(module)
return True
except ImportError:
return False
def is_list_like(obj):
return hasattr(obj, '__getitem__') and hasattr(obj, 'append')
def is_iterator(obj):
is_file = PY2 and isinstance(obj, __builtin__.file)
return (
isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase) and not is_file
)
def is_collections(obj):
try:
return type(obj).__module__ == 'collections'
except Exception:
return False
def is_reducible(obj):
"""
Returns false if of a type which have special casing,
and should not have their __reduce__ methods used
"""
# defaultdicts may contain functions which we cannot serialise
if is_collections(obj) and not isinstance(obj, collections.defaultdict):
return True
return not (
is_list(obj)
or is_list_like(obj)
or is_primitive(obj)
or is_bytes(obj)
or is_unicode(obj)
or is_dictionary(obj)
or is_sequence(obj)
or is_set(obj)
or is_tuple(obj)
or is_dictionary_subclass(obj)
or is_sequence_subclass(obj)
or is_function(obj)
or is_module(obj)
or isinstance(getattr(obj, '__slots__', None), iterator_types)
or type(obj) is object
or obj is object
or (is_type(obj) and obj.__module__ == 'datetime')
)
def in_dict(obj, key, default=False):
"""
Returns true if key exists in obj.__dict__; false if not in.
If obj.__dict__ is absent, return default
"""
return (key in obj.__dict__) if getattr(obj, '__dict__', None) else default
def in_slots(obj, key, default=False):
"""
Returns true if key exists in obj.__slots__; false if not in.
If obj.__slots__ is absent, return default
"""
return (key in obj.__slots__) if getattr(obj, '__slots__', None) else default
def has_reduce(obj):
"""
Tests if __reduce__ or __reduce_ex__ exists in the object dict or
in the class dicts of every class in the MRO *except object*.
Returns a tuple of booleans (has_reduce, has_reduce_ex)
"""
if not is_reducible(obj) or is_type(obj):
return (False, False)
# in this case, reduce works and is desired
# notwithstanding depending on default object
# reduce
if is_noncomplex(obj):
return (False, True)
has_reduce = False
has_reduce_ex = False
REDUCE = '__reduce__'
REDUCE_EX = '__reduce_ex__'
# For object instance
has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE)
has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX)
# turn to the MRO
for base in type(obj).__mro__:
if is_reducible(base):
has_reduce = has_reduce or in_dict(base, REDUCE)
has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX)
if has_reduce and has_reduce_ex:
return (has_reduce, has_reduce_ex)
# for things that don't have a proper dict but can be
# getattred (rare, but includes some builtins)
cls = type(obj)
object_reduce = getattr(object, REDUCE)
object_reduce_ex = getattr(object, REDUCE_EX)
if not has_reduce:
has_reduce_cls = getattr(cls, REDUCE, False)
if has_reduce_cls is not object_reduce:
has_reduce = has_reduce_cls
if not has_reduce_ex:
has_reduce_ex_cls = getattr(cls, REDUCE_EX, False)
if has_reduce_ex_cls is not object_reduce_ex:
has_reduce_ex = has_reduce_ex_cls
return (has_reduce, has_reduce_ex)
def translate_module_name(module):
"""Rename builtin modules to a consistent module name.
Prefer the more modern naming.
This is used so that references to Python's `builtins` module can
be loaded in both Python 2 and 3. We remap to the "__builtin__"
name and unmap it when importing.
Map the Python2 `exceptions` module to `builtins` because
`builtins` is a superset and contains everything that is
available in `exceptions`, which makes the translation simpler.
See untranslate_module_name() for the reverse operation.
"""
lookup = dict(__builtin__='builtins', exceptions='builtins')
return lookup.get(module, module)
def untranslate_module_name(module):
"""Rename module names mention in JSON to names that we can import
This reverses the translation applied by translate_module_name() to
a module name available to the current version of Python.
"""
module = _0_9_6_compat_untranslate(module)
lookup = dict(builtins='__builtin__') if PY2 else {}
return lookup.get(module, module)
def _0_9_6_compat_untranslate(module):
"""Provide compatibility for pickles created with jsonpickle 0.9.6 and
earlier, remapping `exceptions` and `__builtin__` to `builtins`.
"""
lookup = dict(__builtin__='builtins', exceptions='builtins')
return lookup.get(module, module)
def importable_name(cls):
"""
>>> class Example(object):
... pass
>>> ex = Example()
>>> importable_name(ex.__class__) == 'jsonpickle.util.Example'
True
>>> importable_name(type(25)) == 'builtins.int'
True
>>> importable_name(None.__class__) == 'builtins.NoneType'
True
>>> importable_name(False.__class__) == 'builtins.bool'
True
>>> importable_name(AttributeError) == 'builtins.AttributeError'
True
"""
# Use the fully-qualified name if available (Python >= 3.3)
name = getattr(cls, '__qualname__', cls.__name__)
module = translate_module_name(cls.__module__)
return '{}.{}'.format(module, name)
def b64encode(data):
"""
Encode binary data to ascii text in base64. Data must be bytes.
"""
return base64.b64encode(data).decode('ascii')
def b64decode(payload):
"""
Decode payload - must be ascii text.
"""
return base64.b64decode(payload)
def b85encode(data):
"""
Encode binary data to ascii text in base85. Data must be bytes.
"""
if PY2:
raise NotImplementedError("Python 2 can't encode data in base85.")
return base64.b85encode(data).decode('ascii')
def b85decode(payload):
"""
Decode payload - must be ascii text.
"""
if PY2:
raise NotImplementedError("Python 2 can't decode base85-encoded data.")
return base64.b85decode(payload)
def itemgetter(obj, getter=operator.itemgetter(0)):
return compat.ustr(getter(obj))
def items(obj):
"""Iterate over dicts in a deterministic order
Python2 does not guarantee dict ordering, so this function
papers over the difference in behavior. Python3 does guarantee
dict order, without use of OrderedDict, so no sorting is needed there.
"""
if PY3_ORDERED_DICT:
for k, v in obj.items():
yield k, v
else:
for k, v in sorted(obj.items(), key=itemgetter):
yield k, v

View File

@ -0,0 +1,21 @@
import sys
try:
if sys.version_info < (3, 8):
import importlib_metadata as metadata
else:
from importlib import metadata
except (ImportError, OSError):
metadata = None
def _get_version():
default_version = '0.0.0-alpha'
try:
version = metadata.version('jsonpickle')
except (AttributeError, ImportError, OSError):
version = default_version
return version
__version__ = _get_version()

View File

@ -108,6 +108,11 @@ async def on_message(msg):
except: except:
await msg.channel.send("We can't find your idol. Looked everywhere, too.") await msg.channel.send("We can't find your idol. Looked everywhere, too.")
elif command.startswith("showplayer "):
player_name = json.loads(ono.get_stats(command.split(" ",1)[1]))
await msg.channel.send(embed=build_star_embed(player_name))
elif command == "startgame" and msg.author.id in config()["owners"]: elif command == "startgame" and msg.author.id in config()["owners"]:
game_task = asyncio.create_task(watch_game(msg.channel)) game_task = asyncio.create_task(watch_game(msg.channel))
@ -118,11 +123,23 @@ async def on_message(msg):
if game[0].name == msg.author.name: if game[0].name == msg.author.name:
await msg.channel.send("There's already an active game with that name.") await msg.channel.send("There's already an active game with that name.")
return return
try:
game_task = asyncio.create_task(setup_game(msg.channel, msg.author, games.game(msg.author.name, games.team(), games.team()))) inningmax = int(command.split("setupgame ")[1])
except:
inningmax = 3
game_task = asyncio.create_task(setup_game(msg.channel, msg.author, games.game(msg.author.name, games.team(), games.team(), length=inningmax)))
await game_task await game_task
elif command.startswith("saveteam\n") and msg.author.id in config()["owners"]:
save_task = asyncio.create_task(save_team_batch(msg, command))
await save_task
elif command.startswith("showteam "):
team = games.get_team(command.split(" ",1)[1])
if team is not None:
await msg.channel.send(embed=build_team_embed(team))
else:
await msg.channel.send("Can't find that team, boss. Typo?")
elif command == "credit": elif command == "credit":
await msg.channel.send("Our avatar was graciously provided to us, with permission, by @HetreaSky on Twitter.") await msg.channel.send("Our avatar was graciously provided to us, with permission, by @HetreaSky on Twitter.")
@ -276,6 +293,8 @@ async def watch_game(channel, game):
first_base = discord.utils.get(client.emojis, id = 790899850320543745) first_base = discord.utils.get(client.emojis, id = 790899850320543745)
second_base = discord.utils.get(client.emojis, id = 790900139656740865) second_base = discord.utils.get(client.emojis, id = 790900139656740865)
third_base = discord.utils.get(client.emojis, id = 790900156597403658) third_base = discord.utils.get(client.emojis, id = 790900156597403658)
out_emoji = discord.utils.get(client.emojis, id = 791578957241778226)
in_emoji = discord.utils.get(client.emojis, id = 791578957244792832)
newgame = game newgame = game
embed = await channel.send("Play ball!") embed = await channel.send("Play ball!")
@ -301,7 +320,7 @@ async def watch_game(channel, game):
new_embed.add_field(name="Inning:", value=f"🔼 {newgame.inning}", inline=True) new_embed.add_field(name="Inning:", value=f"🔼 {newgame.inning}", inline=True)
else: else:
new_embed.add_field(name="Inning:", value=f"🔽 {newgame.inning}", inline=True) new_embed.add_field(name="Inning:", value=f"🔽 {newgame.inning}", inline=True)
new_embed.add_field(name="Outs:", value=newgame.outs, inline=True) new_embed.add_field(name="Outs:", value=f"{str(out_emoji)*newgame.outs+str(in_emoji)*(2-newgame.outs)}", inline=True)
new_embed.add_field(name="Pitcher:", value=newgame.get_pitcher(), inline=False) new_embed.add_field(name="Pitcher:", value=newgame.get_pitcher(), inline=False)
new_embed.add_field(name="Batter:", value=newgame.get_batter(), inline=False) new_embed.add_field(name="Batter:", value=newgame.get_batter(), inline=False)
@ -351,8 +370,16 @@ async def watch_game(channel, game):
gamesarray.pop(gamesarray.index((newgame,use_emoji_names))) #cleanup is important! gamesarray.pop(gamesarray.index((newgame,use_emoji_names))) #cleanup is important!
del newgame del newgame
def build_team_embed(team):
embed = discord.Embed(color=discord.Color.purple(), title=team.name)
lineup_string = ""
for player in team.lineup:
lineup_string += f"{player.name} {player.star_string('batting_stars')}\n"
embed.add_field(name="Pitcher:", value=f"{team.pitcher.name} {team.pitcher.star_string('pitching_stars')}.", inline = False)
embed.add_field(name="Lineup:", value=lineup_string, inline = False)
embed.set_footer(text=team.slogan)
return embed
def build_star_embed(player_json): def build_star_embed(player_json):
starkeys = {"batting_stars" : "Batting", "pitching_stars" : "Pitching", "baserunning_stars" : "Baserunning", "defense_stars" : "Defense"} starkeys = {"batting_stars" : "Batting", "pitching_stars" : "Pitching", "baserunning_stars" : "Baserunning", "defense_stars" : "Defense"}
@ -373,5 +400,44 @@ def build_star_embed(player_json):
return embed return embed
async def save_team_batch(message, command):
newteam = games.team()
#try:
roster = command.split("\n",1)[1].split("\n")
newteam.name = roster[0] #first line is team name
newteam.slogan = roster[1] #second line is slogan
for rosternum in range(2,len(roster)-1):
if roster[rosternum] != "":
newteam.add_lineup(games.player(ono.get_stats(roster[rosternum])))
newteam.set_pitcher(games.player(ono.get_stats(roster[len(roster)-1]))) #last line is pitcher name
if len(newteam.name) > 30:
await message.send("Team names have to be less than 30 characters! Try again.")
return
elif len(newteam.slogan) > 100:
await message.send("We've given you 100 characters for the slogan. Discord puts limits on us and thus, we put limits on you. C'est la vie.")
await message.channel.send(embed=build_team_embed(newteam))
checkmsg = await message.channel.send("Does this look good to you, boss?")
await checkmsg.add_reaction("👍")
await checkmsg.add_reaction("👎")
def react_check(react, user):
return user == message.author and react.message == checkmsg
try:
react, user = await client.wait_for('reaction_add', timeout=20.0, check=react_check)
if react.emoji == "👍":
await message.channel.send("You got it, chief. Saving now.")
games.save_team(newteam)
await message.channel.send("Saved! Thank you for flying Air Matteo. We hope you had a pleasant data entry.")
return
elif react.emoji == "👎":
await message.channel.send("Message received. Pumping brakes, turning this car around. Try again, chief.")
return
except asyncio.TimeoutError:
await message.channel.send("Look, I don't have all day. 20 seconds is long enough, right? Try again.")
return
#except:
#await message.channel.send("uh.")
client.run(config()["token"]) client.run(config()["token"])