Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added storage capabilities to pgzero #99

Conversation

gustavooferreira
Copy link
Contributor

Added the code, unittests and documentation.

This PR attempts to add the functionality discussed on Issue #33

Copy link
Owner

@lordmauve lordmauve left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very good - I have some nitpicks but this looks very nearly ready to merge.


.. method:: load()

Loads the data from disk.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should cover what happens if there is no previous save data.

@@ -102,6 +103,8 @@ def prepare_mod(mod):
set before the module globals are run.

"""
fn_hash = hash(mod.__file__) % ((sys.maxsize + 1) * 2)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this hash might need to be stronger than this. Also I don't believe hash() is necessarily stable across Python interpreter versions. I'd suggest using hashlib.sha1().


path = ''

"""Behaves like a dictionary with a few extra functions.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring needs to be the first thing in the class - above path = ''


The name of the file will be the script's name hashed.

NOTE: If two scripts have the same name, they will load/save from/to
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect this only if they have exactly the same path in the filesystem, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting the name from mod.__file__ so basically, if people use the relative path for the script when calling pgzrun intro.py then I would only get intro.py.

I corrected this to check if the path is absolute and if it isn't I convert it to absolute, like so:

    filename = mod.__file__

    if not os.path.isabs(filename):
        filename = os.path.join(os.getcwd(), filename)

    fn_hash = hashlib.sha1(filename.encode('utf-8'))

    Storage.set_app_hash(fn_hash.hexdigest())

# If no file exists, it's fine
logger.debug('No file to load data from.')
except json.JSONDecodeError:
msg = 'Storage is corrupted. Couldn\'t load the data.'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use double-quotes for human readable strings so that you don't have to escape apostrophes :-)

def _get_platform_pgzero_path():
"""Attempts to get the right directory in Windows/Linux.MacOS"""
if platform.system() == 'Windows':
return os.path.join(os.environ['APPDATA'], 'pgzero')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will crash with KeyError if %APPDATA% is not defined. Does that happen? Is there a fallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, I haven't used Windows in years. But I would assume it's always set like $HOME and $SHELL on Linux.

"""Attempts to get the right directory in Windows/Linux.MacOS"""
if platform.system() == 'Windows':
return os.path.join(os.environ['APPDATA'], 'pgzero')
return os.path.expanduser(os.path.join('~', '.config/pgzero/saves'))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks odd. The reason to use os.path.join() is so that you don't have to assume os.sep is /. But the second parameter contains /.

os.path.join() accepts multiple arguments, which will let you remove the /-s from .config/pgzero/saves.

Or you could assume that os.sep is / except on Windows, so just return `os.path.expanduser('~/.config/pgzero/saves').

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, rookie mistake.

exists_mock.return_value = True
self.storage = Storage()

def test_get(self):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're just testing the "happy path" here. You should have tests for the file not existing, or existing but containing invalid JSON.

return result
elif isinstance(obj, dict):
for k, v in obj.items():
result = cls._get_json_error_keys(v, '{}[\'{}\']'.format(json_path, k))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check that k is a string: JSON only supports strings as dict keys.

Also on this line, you're better to write:

'{}[{!r}]'.format(json_path, k)

as the repr of a string containing special characters is more complicated than "'{}'".format(s)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done, need to write a unittest for it.

storage.save()


Following on the previous example, when starting your program, you can load that data back in::
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could you add an example where we define defaults before we call storage.load()?

@r00tr4v3n
Copy link

r00tr4v3n commented Mar 8, 2021

Is this feature included in v1.2? The FlappyBird example uses it but is not executable.
Edit: Sorry, I found the changelog information, master branch is needed.

@lordmauve
Copy link
Owner

lordmauve commented May 15, 2021

This is actually merged, but I can't mark it as such. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants