Skip to content

Commit

Permalink
refaktor code
Browse files Browse the repository at this point in the history
  • Loading branch information
Am K committed Sep 16, 2024
1 parent c71d87e commit 527c0c5
Show file tree
Hide file tree
Showing 16 changed files with 814 additions and 7 deletions.
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

setup(
name='zcache',
version='v1.0.2',
version='v1.0.3',
packages=['zcache',],
license='MIT',
author="guangrei",
author_email="[email protected]",
description="PyZCache is dependency free python key value cache based file storage and json serialize",
description="Key Value Database/Cache with multiple storage and plugins",
long_description=long_description,
long_description_content_type='text/markdown',
keywords="cache key value file json",
url="https://github.com/guangrei/PyZCache",
url="https://github.com/guangrei/zcache",
)
129 changes: 129 additions & 0 deletions zcache/Class/Database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# -*-coding:utf8;-*-
from zcache.Storage.BaseFileStorage import BaseFileStorage
from zcache.Interface.Storage import Storage as StorageInterface
from zcache.Interface.Plugins import Plugins as PluginsInterface
import time


class Database:

def __init__(
self,
path=None,
limit=0,
storage=BaseFileStorage,
plugins=None,
StorageArgs=None,
):
if plugins is not None and not issubclass(plugins, PluginsInterface):
raise NotImplementedError
self.plugins = plugins
if not issubclass(storage, StorageInterface):
raise NotImplementedError
if path is not None:
path = path
else:
path = "zcache.json"
if StorageArgs is not None:
if isinstance(StorageArgs, dict):
self.storage = storage(path, **StorageArgs)
else:
raise TypeError
else:
self.storage = storage(path)
self.__limit = limit

def __updatefile(self):
self.storage.save(self.databases)

def __loadfile(self):
self.databases = self.storage.load()
self.databases["limit"] = self.__limit

def __exists(self, key):
try:
t = self.databases["data"][key]
if t["ttl"] != 0:
sisa = int(time.time()) - t["time"]
if sisa >= t["ttl"]:
if self.plugins is not None:
self.plugins.on_expired(self, key)
del self.databases["data"][key]
self.__updatefile()
return False, False
else:
return True, t["content"]
else:
return True, t["content"]
except KeyError:
return False, False

def __set(self, key, value, ttl=0):
if self.plugins is not None:
value = self.plugins.on_write(self, key, value)
data = {}
data["time"] = int(time.time())
data["ttl"] = int(ttl)
data["content"] = value
self.databases["data"][key] = data
self.__updatefile()

def has(self, key):
if not isinstance(key, str):
raise TypeError
self.__loadfile()
r, v = self.__exists(key)
return r

def get(self, key):
if not isinstance(key, str):
raise TypeError
self.__loadfile()
r, v = self.__exists(key)
if r:
if self.plugins is not None:
return self.plugins.on_read(self, key, v)
return v
else:
return None

def set(self, key, value, ttl=0):
if not isinstance(key, str):
raise TypeError
# to optimize, __loadfile() not called here because already called in size()
size = self.size()
if self.databases["limit"] != 0:
if self.databases["limit"] == size:
if self.plugins is not None:
self.plugins.on_limit(self, key, value, ttl)
return False
else:
self.__set(key, value, ttl)
return True
else:
self.__set(key, value, ttl)
return True

def delete(self, key):
if not isinstance(key, str):
raise TypeError
# to optimize, __loadfile() not called here because already called in has()
check = self.has(key)
if check:
if self.plugins is not None:
self.plugins.on_delete(self, key)
del self.databases["data"][key]
self.__updatefile()
return True
else:
return False

def size(self):
self.__loadfile()
ret = len(self.databases["data"])
return ret

def reset(self):
self.__loadfile()
self.databases["data"] = {}
self.__updatefile()
77 changes: 77 additions & 0 deletions zcache/Class/Queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*-coding:utf8;-*-
from zcache.Storage.BaseFileStorage import BaseFileStorage
from zcache.Class.Database import Database
import uuid


class Queue:
"""Implementasi FIFO Queue.
Methods:
- put(item): Menambahkan item ke dalam queue.
- get(): Menghapus dan mengembalikan item pertama dari queue.
- peek(): Melihat item pertama tanpa menghapusnya.
- empty(): Mengecek apakah queue kosong.
- size(): Mendapatkan jumlah item dalam queue.
"""

def __init__(self, path="queue.json", storage=BaseFileStorage):
self.q = Database(path=path, storage=BaseFileStorage)
self._stack_load()

def put(self, item, id=str(uuid.uuid4())):
"""Menambahkan item ke dalam queue."""
if not isinstance(id, str):
raise TypeError
if id == "__queue__":
raise ValueError
queue = self._stack_load()
queue.append(id)
self.q.set(id, item)
self._stack_update(queue)
return id

def get(self):
"""Menghapus dan mengembalikan item pertama dari queue."""
queue = self._stack_load()
if len(queue) > 0:
id = queue.pop(0)
ret = self.q.get(id)
self.q.delete(id)
self._stack_update(queue)
return ret
else:
return None

def peek(self):
"""Melihat item pertama tanpa menghapusnya."""
queue = self._stack_load()
if len(queue) > 0:
id = queue[0]
return self.q.get(id)

def _stack_load(self):
if not self.q.has("__queue__"):
self.q.set("__queue__", [])
return []
return self.q.get("__queue__")

def _stack_update(self, data):
self.q.set("__queue__", data)

def empty(self):
"""Mengecek apakah queue kosong."""
queue = self._stack_load()
return len(queue) == 0

def size(self):
"""Mendapatkan jumlah item dalam queue."""
queue = self._stack_load()
return len(queue)

def exists(self, id):
"""Mengecek id queue"""
if not isinstance(id, str):
raise TypeError
queue = self._stack_load()
return id in queue
75 changes: 75 additions & 0 deletions zcache/Class/SmartRequest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2022 PyZCache https://github.com/guangrei/PyZCache
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from zcache.Class.Database import Database
from zcache.Plugins.BytesCachePlugins import BytesCachePlugins
from urllib import request
import os


class SmartRequest:
"""
A class for making Smart HTTP requests with caching capabilities using PyZCache.
"""

def __init__(self, url, cache_path=None, cache_time=120, offline_ttl=604800):
if not isinstance(url, str):
cache_name = url.url
else:
cache_name = url
cache = Database(path=cache_path, plugins=BytesCachePlugins)
if cache.has(cache_name):
self.response = cache.get(cache_name)
self.is_loaded_from_cache = True
else:
r = self._makeRequest(url, cache_name, cache)
if r != False:
cache.set(cache_name, r, ttl=cache_time)
cache.set(cache_name+"_offline", r, ttl=offline_ttl)
self.response = r
self.is_loaded_from_cache = False
else:
self.response = cache.get(cache_name+"_offline")
self.is_loaded_from_cache = True

def _makeRequest(self, url, cache_name, cache):
if not isinstance(url, str):
try:
headers, body = url.get()
assert type(body) == str
return {"headers": headers, "body": body}
except BaseException as e:
if cache.has(cache_name+"_offline"):
return False
else:
raise Exception(e)
try:
response = request.urlopen(url)
headers, body = (dict(response.info()), response.read())
return {"headers": headers, "body": body.decode('utf-8')}
except BaseException as e:
if cache.has(cache_name+"_offline"):
return False
else:
raise Exception(e)
Empty file added zcache/Class/__init__.py
Empty file.
45 changes: 45 additions & 0 deletions zcache/Interface/Plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*-coding:utf8;-*-
from abc import ABC, abstractmethod


class Plugins(ABC):

@abstractmethod
def on_write(db, key, value):
"""
trigger at Database.__set()
return will affect on data content before write.
"""
pass

@abstractmethod
def on_read(db, key, value):
"""
trigger at Database.get()
return will affect on data content after read.
"""
pass

@abstractmethod
def on_limit(db, key, value, ttl):
"""
trigger when Database limit reached on Database.set()
return will not affect anything.
"""
pass

@abstractmethod
def on_expired(db, key):
"""
trigger when Database key time to live limit reached.
return will not affect anything.
"""
pass

@abstractmethod
def on_delete(db, key):
"""
trigger on Database.delete()
return will not affect anything.
"""
pass
16 changes: 16 additions & 0 deletions zcache/Interface/Storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*-coding:utf8;-*-
from abc import ABC, abstractmethod


class Storage(ABC):
@abstractmethod
def __init__(self, path):
pass

@abstractmethod
def load(self):
pass

@abstractmethod
def save(self, data):
pass
Empty file added zcache/Interface/__init__.py
Empty file.
Loading

0 comments on commit 527c0c5

Please sign in to comment.