Skip to content

Commit

Permalink
Reimplement cache class, add _version.py file #29
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Chauvet-Thiry committed Nov 29, 2021
1 parent 5862895 commit c362503
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 24 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
## 🚨🚨 Dev branch 🚨🚨: still not usable, lot of change in the core of beampy, work in progress...

### 🚧 TODO
- Tests for core:
- [ ] Tests for core:
- position operation
- length operation
- group operation
- layer opertation
- define module outside slide and call
- test cache

- Re implement theme system
- Re implement cache system
- Re implement all modules
- Re implement exports

- [x] Re implement theme system
- [ ] Re implement cache system, almost done (need to add global information to cache like version etc...)
- [ ] Re implement all modules
- [ ] Re implement exports

Beampy is a python tool to create slide-show in svg that can be displayed with HTML5
(tested on Firefox and Chromium)
Expand Down
3 changes: 3 additions & 0 deletions beampy/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env python3

__version__ = '1.0.0'
145 changes: 136 additions & 9 deletions beampy/core/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
Manage cache system for slides
"""
import os, sys
from pathlib import Path
import pickle as pkl
import json
from beampy.core.store import Store

import gzip
import hashlib
Expand All @@ -20,30 +23,154 @@ class Cache():
"""Manage cache for slide contents in Beampy
"""

def __init__(self, cache_dir=None):
def __init__(self, cache_dir=None, register=True):
"""Create the cache folder and methods to interact with it.
"""

# Use this dict to save global information on cache
self.data = {}

if cache_dir is None:
cache_dir = create_folder_name()

self.folder = cache_dir
self.index_name = 'data.pklz'
self.index_fullpath = os.path.join(self.folder, self.index_name)
self.index_name = 'data.zip'
self.index_fullpath = self.folder/self.index_name

_log.debug(f'Cache dir: {self.folder}')

if self.folder.exists():
self.data = self.read()
else:
self.folder.mkdir()
_log.debug('create cache dir')

# Do we need to erase cache due to a change in beampy version
if len(self.data) > 0 and self.data['version'] != Store._version:
print('Reset cache because beampy version changed')
self.data = {}
self.remove_files()

# Set the version in cache data
self.data['version'] = Store._version

if register:
Store._cache = self

def read(self):
"""Read the cache file on disk
"""Read the cache index file on disk.
"""
dataout = None
if os.path.exists(self.index_fullpath):
with gzip.open(self.index_full_path, 'rb') as f:
dataout = pkl.load(f)
dataout = {}
if self.index_fullpath.is_file():
with gzip.open(self.index_fullpath, 'rb') as f:
dataout = json.load(f)
else:
_log.debug('Unable to load cache file %s' % str(self.index_fullpath))

return dataout

def is_cached(self, file_id: str) -> bool:
"""Test if a given file_id is cached
"""

if (self.folder/f'{file_id}.json.gz').is_file():
return True

return False

def add(self, content: object):
"""Add a content (beampy.core.content.Content object) to a file.
We split content oject into two parts:
- The content
- The data
"""

# Need to transform content object to a dictionnary

# The content
ct = {}
ct["id"] = content.id
ct["type"] = content.type
ct["name"] = content.name
ct["content"] = content.content
ct["width"] = content.width
ct["height"] = content.height
ct["data_id"] = content._data_id

# The data
data = content.data

if not self.is_cached(content.id):
self.write_file(content.id, ct)
else:
raise Exception("File already exist for %s" % str(content))

if not self.is_cached(ct["data_id"]):
self.write_file(ct["data_id"], data)

fname = self.folder/f'{content.id}.zip'
_log.debug(f"Write {fname} to cache")

def load_from_cache(self, content_id: str) -> dict:
"""Load content and it's data from cached file with the given content_id.
Return a dictionnary width Content informations (id, content, width,
height, data_id)
"""

content = self.read_file(content_id)

# Check if we need to load data or if they are already in the Store.
if not Store.is_data(content["data_id"]):
tmp_data = self.read_file(content["data_id"])
Store.add_data(tmp_data, content["data_id"])

return content

def write_file(self, filename, content):
"""Write a file to cache folder
"""

with gzip.open(self.folder/f'{filename}.json.gz', 'wb') as f:
f.write(json.dumps(content).encode('utf-8'))

def read_file(self, filename):
"""Read a content pikles from file
"""
output = None
fname = self.folder/f'{filename}.json.gz'
if fname.is_file():
with gzip.open(fname, 'rb') as f:
output = json.loads(f.read().decode('utf8'))
else:
raise Exception("Unable to load %s from cache" % filename)

return output

def remove_files(self):
"""Remove all files from cache folder
"""

for f in glob.glob(self.folder+'/*.pklz'):
for f in self.folder.glob('*.json.gz'):
os.remove(f)


def create_folder_name():
"""Create a folder name for the cache. Depends on how beampy is run. Mostly
by calling: "python3 my_presentation.py", we use sys.argv[0] to get my_presentation.py
"""

guess_scriptfilename = Path(sys.argv[0])
if ('ipykernel' in guess_scriptfilename.name or 'ipython' in guess_scriptfilename.name):
cache_folder = guess_scriptfilename.home().joinpath('.beampy_cache_ipython')
print(f"Your in a ipython/notebook session I will put the cache in {cache_folder}")
else:
scriptname = guess_scriptfilename.name.replace(guess_scriptfilename.suffix, '')
cache_folder = Path('./').absolute().joinpath(f'.beampy_cache_{scriptname}')

return cache_folder

class cache_slides():

def __init__(self, cache_dir, document):
Expand Down
48 changes: 47 additions & 1 deletion beampy/core/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys
import hashlib
import logging
import json
from beampy.core.store import Store
_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -101,8 +102,53 @@ def height(self, height):
self._height = height
Store.update_content_size(self)

@property
def is_cached(self):
pass
"""Check if this content is cached on disk
"""

if Store.cache() is not None:
return Store.cache().is_cached(self.id)

return False

def load_from_cache(self):
"""Load a content from cache file
"""
if Store.cache() is not None:
cache_content = Store.cache().load_from_cache(self.id)
# Update the content object
self.content = cache_content["content"]
self._data_id = cache_content["data_id"]

# Add the content to the Store if needed
if not Store.is_content(self.id):
Store.add_content(self)

# Update it's width and height
self.width = cache_content["width"]
self.height = cache_content["height"]

def add_to_cache(self):
"""Add the current object to cache files
"""

if Store.cache() is not None:
Store.cache().add(self)

@property
def json(self):
"""Export content to json
"""
out = {}
out["id"] = self.id
out["content"] = self.content
out["width"] = self.width
out["height"] = self.height
out["data_id"] = self._data_id
out["data"] = self.data

return json.dumps(out)

def __repr__(self):
out = f'Content {self.id}\n'
Expand Down
15 changes: 11 additions & 4 deletions beampy/core/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,16 @@ def add_content(self, content, content_type):
print(f'This module {self.signature} already exist in the Store, I will use {self.content_id}' )
self._content.load_from_store()
else:
# Render the module
self.pre_render()
self.run_render()
# Check if the module is cached
if self._content.is_cached:
# Load from cache
self._content.load_from_cache()
else:
# Render the module
self.pre_render()
self.run_render()
# Add content to cache
self._content.add_to_cache()

def update_signature(self, *args, **kwargs):
"""Create a unique signature of the init method using inspect module.
Expand Down Expand Up @@ -822,7 +829,7 @@ def render_svgdefs(self):
def __repr__(self):
out = 'module: %s\n'%self.name
out += 'Id: %s\n' % self.id
if self._content is not None:
if hasattr(self, '_content') and self._content is not None:
out += 'Content id: %s\n' % self._content.id
out += 'width: %s, height: %s\n' % (str(self.width), str(self.height))
out += 'x: %s, y: %s\n' % (self.x, self.y)
Expand Down
19 changes: 15 additions & 4 deletions beampy/core/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
variable conservation in python
"""
import logging
from .._version import __version__
_log = logging.getLogger(__name__)


class StoreMetaclass(type):
"""
Define a metaclass for store to let define some usefull properties for the
Expand All @@ -33,7 +33,7 @@ def __repr__(self):
out += "- %i slides\n" % (len(self))
out += "- %i modules [%i group]\n" % (len(self._contents), ngroups)
out += f"- current slide: {self._current_slide}\n"
out += f"- current group: {self.group}\n"
out += f"- current group: {self.group()}\n"

return out

Expand Down Expand Up @@ -62,12 +62,18 @@ class Store(metaclass=StoreMetaclass):
# Store slideshow theme
_theme = dict()

# The cache class
_cache = None

# Store the slideshow layout (previously denominated document in Beampy < 1.0)
_layout = None

# Store the current group
_current_group = None

# Beampy version
_version = __version__

@classmethod
def __repr__(cls):
return '%s' % str(cls._slides)
Expand Down Expand Up @@ -237,7 +243,7 @@ def set_group(cls, new_group):

@classmethod
def isgroup(cls):
if isinstance(cls.group, property) or cls.group is None:
if cls.group() is None:
return False

return True
Expand All @@ -249,6 +255,10 @@ def theme(cls, module_name):
else:
raise KeyError(f'Not such module {module_name} defined in theme {cls._theme.keys()}')

@classmethod
def cache(cls):
return cls._cache

@classmethod
def clear_all(cls):
"""
Expand All @@ -262,7 +272,8 @@ def clear_all(cls):
cls._options = dict()
cls._theme = dict()
cls._layout = None
cls.group = None
cls._cache = None
cls._current_group = None

# Some functions to easily manipulate the store
def get_module_position(content_id, slide_id=None):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

from beampy import __version__ as beampy_version
from beampy._version import __version__ as beampy_version

with open("README.md", "r") as fh:
long_description = fh.read()
Expand Down

0 comments on commit c362503

Please sign in to comment.