You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importrequestsimportsemverimporttimeimportjsonfromdatetimeimportdatetime, timedeltafrompathlibimportPathfrom ...__pack__import__version__, __name__from ..loggerimportloggerclassVersionControl:
"""Class to manage version control and caching for the library."""CACHE_DURATION=timedelta(days=1)
def__init__(self):
self.current_version=__version__self.package_name=__name__# Store cache in the same directory as the modulemodule_dir=Path(__file__).parentself.cache_file=module_dir/"cache.json"self._load_initial_cache()
def_load_initial_cache(self):
"""Load initial cache values."""self.cached_version, self.cache_time=self._load_cache()
def_fetch_latest_version(self) ->str:
"""Fetch the latest version from PyPI."""url=f"https://pypi.org/pypi/{self.package_name}/json"response=requests.get(url)
ifresponse.status_code==200:
data=response.json()
returndata['info']['version']
return"0.0.0"def_load_cache(self) ->tuple[str, datetime]:
"""Load the cached version and timestamp from JSON."""ifself.cache_file.exists():
try:
withopen(self.cache_file, 'r') asf:
cache_data=json.load(f)
# Verify cache data is validif (notisinstance(cache_data, dict) or'version'notincache_dataor'timestamp'notincache_data):
return"0.0.0", datetime.minreturn (
cache_data['version'],
datetime.fromtimestamp(cache_data['timestamp'])
)
except (json.JSONDecodeError, KeyError, ValueError):
# If cache file is corrupted or invalid, return default valuesreturn"0.0.0", datetime.minreturn"0.0.0", datetime.mindef_save_cache(self, version: str):
"""Save the version and current timestamp to JSON cache."""cache_data= {
'version': version,
'timestamp': time.time(),
'package': self.package_name,
'last_checked': datetime.now().isoformat()
}
withopen(self.cache_file, 'w') asf:
json.dump(cache_data, f, indent=2)
# Update instance variables after savingself.cached_version=versionself.cache_time=datetime.fromtimestamp(cache_data['timestamp'])
defcheck_for_update(self):
"""Check if there is a newer version available."""current_time=datetime.now()
cache_age=current_time-self.cache_timeifcache_age>self.CACHE_DURATION:
latest_version=self._fetch_latest_version()
self._save_cache(latest_version)
logger.debug(f"[DEBUG] Fetched latest version: {latest_version}")
else:
latest_version=self.cached_versionlogger.debug(f"[DEBUG] Using cached version: {latest_version}")
logger.debug(f"[DEBUG] Current version: {self.current_version}")
ifsemver.compare(latest_version, self.current_version) >0:
logger.warning(
f"🚀 A new version {latest_version} is available! "f"You are using {self.current_version}. Consider updating."
)
else:
logger.debug(
f"✅ You are using the latest version: {self.current_version}."
)
code for running databases, deleting databases and killing processes
importsubprocessimportosfrompathlibimportPathimportclickimportsocketimportsignalimportsysimporttimeimportcolorlogimportloggingimportpsutilimportquestionaryimportwebbrowserfromtypingimportOptional, List, Tuple, Dict# Configure colored logginghandler=colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter(
'%(log_color)s%(asctime)s %(reset)s %(message)s',
datefmt='%H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
}
))
logger=logging.getLogger('datasette_viewer')
logger.addHandler(handler)
logger.setLevel(logging.INFO)
DB_DIR=Path(__file__).parent.parent/"db"MODELS_DB=DB_DIR/"models.db"USAGE_DB=DB_DIR/"usage.db"SESSIONS_DB=DB_DIR/"sessions.db"# Global list to track running processesrunning_processes: List[subprocess.Popen] = []
# Database configurationsDB_CONFIGS: Dict[str, Dict] = {
'Models Database': {'path': MODELS_DB, 'port': 8881},
'Usage Database': {'path': USAGE_DB, 'port': 8882},
'Sessions Database': {'path': SESSIONS_DB, 'port': 8883},
'Delete Database': {'action': 'delete_db'},
'Kill Process on Port': {'action': 'kill_port'},
'Exit': {'action': 'exit'}
}
defdelete_database(db_path: Path) ->bool:
""" Safely delete a database file. Args: db_path: Path to the database file Returns: bool: True if database was deleted, False otherwise """try:
ifnotdb_path.exists():
logger.warning(f"Database {db_path} does not exist")
returnFalse# Delete the database fileos.remove(db_path)
logger.info(f"Successfully deleted database: {db_path}")
returnTrueexceptExceptionase:
logger.error(f"Error deleting database {db_path}: {e}")
returnFalsedefkill_port(port: int) ->bool:
""" Kill process running on specified port. Args: port: Port number to kill Returns: bool: True if process was killed, False otherwise """try:
# Use lsof to find process using the portcmd=f"lsof -ti tcp:{port}"process=subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error=process.communicate()
ifoutput:
pid=int(output.decode().strip())
try:
proc=psutil.Process(pid)
proc.terminate()
proc.wait(timeout=3)
logger.info(f"Successfully killed process {pid} on port {port}")
returnTrueexceptpsutil.NoSuchProcess:
logger.warning(f"Process {pid} on port {port} already terminated")
returnTrueexceptpsutil.TimeoutExpired:
proc.kill()
logger.info(f"Force killed process {pid} on port {port}")
returnTrueelse:
logger.warning(f"No process found on port {port}")
returnFalseexceptExceptionase:
logger.error(f"Error killing port {port}: {e}")
returnFalsedefcleanup_processes():
"""Terminate all running processes."""forprocessinrunning_processes:
try:
process.terminate()
process.wait(timeout=5)
exceptsubprocess.TimeoutExpired:
process.kill()
exceptExceptionase:
logger.error(f"Error while terminating process: {e}")
running_processes.clear()
defsignal_handler(signum, frame):
"""Handle termination signals."""print("\n🛑 Shutting down database viewers...")
cleanup_processes()
sys.exit(0)
# Register signal handlerssignal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
defformat_url(port: int) ->str:
"""Format URL with ANSI colors for terminal."""returnf"\033[1;94mhttp://localhost:{port}\033[0m"defstart_datasette(
db_path: str,
start_port: int,
max_attempts: int=10
) ->Tuple[Optional[subprocess.Popen], Optional[int]]:
"""Try to start datasette on an available port with retries."""forportinrange(start_port, start_port+max_attempts):
# Try to kill any existing process on this portkill_port(port)
time.sleep(1) # Give the system time to free the porttry:
process=subprocess.Popen(
[
"datasette", "serve", db_path,
"--port", str(port),
"--cors",
"--setting", "truncate_cells_html", "0"
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# Give the process a moment to start and bind to the porttime.sleep(1)
# Check if process is still runningifprocess.poll() isNone:
# Try to connect to verify the server is upwithsocket.socket(socket.AF_INET, socket.SOCK_STREAM) ass:
try:
s.connect(('localhost', port))
returnprocess, portexceptsocket.error:
process.terminate()
continueelse:
# Process failed to start, clean up and try next port_, stderr=process.communicate()
ifb"address already in use"notinstderr:
print(f"Failed to start datasette: {stderr.decode()}")
process.terminate()
exceptExceptionase:
print(f"Error starting datasette on port {port}: {e}")
returnNone, Nonedeflaunch_viewer(db_path: str, db_name: str, start_port: int) ->bool:
"""Launch a database viewer and return success status."""print(f"🚀 Starting {db_name} database viewer...")
process, port=start_datasette(db_path, start_port)
ifnotprocessornotport:
print(f"Failed to start {db_name} database viewer")
returnFalserunning_processes.append(process)
url=f"http://localhost:{port}"print(f"✓ {db_name} DB viewer: {format_url(port)}")
# Open browser after a short delay to ensure server is readytime.sleep(1)
try:
webbrowser.open(url)
exceptExceptionase:
print(f"Note: Could not open browser automatically: {e}")
print(f"Please open {url} manually in your browser")
returnTruedefshow_interactive_menu():
"""Show interactive menu for database selection."""whileTrue:
choice=questionary.select(
"Select an action:",
choices=list(DB_CONFIGS.keys())
).ask()
ifnotchoice:
breakconfig=DB_CONFIGS[choice]
if'action'inconfig:
if config['action'] =='kill_port':
port=questionary.text("Enter port number to kill:").ask()
ifport:
try:
port=int(port)
ifkill_port(port):
print(f"✓ Successfully killed process on port {port}")
else:
print(f"✗ No process found on port {port}")
exceptValueError:
print("✗ Invalid port number")
elifconfig['action'] =='delete_db':
db_choice=questionary.select(
"Select database to delete:",
choices=[
'Models Database',
'Usage Database',
'Sessions Database',
'Cancel'
]
).ask()
ifdb_choice=='Cancel':
continuedb_path=DB_CONFIGS[db_choice]['path']
confirm=questionary.confirm(
f"Are you sure you want to delete {db_choice}? This action cannot be undone.",
default=False
).ask()
ifconfirm:
ifdelete_database(db_path):
print(f"✓ Successfully deleted {db_choice}")
else:
print(f"✗ Failed to delete {db_choice}")
elifconfig['action'] =='exit':
breakelse:
iflaunch_viewer(config['path'], choice, config['port']):
print("\n🔍 Press Ctrl+C to stop the server")
running_processes[0].wait()
break@click.command()@click.option('--db', is_flag=True, help='Launch interactive database viewer')defcli(db):
"""Pydantic2 CLI tool for database viewing"""try:
ifdb:
show_interactive_menu()
else:
print("Use --db to launch the interactive database viewer")
exceptExceptionase:
print(f"Error: {str(e)}")
cleanup_processes()
raiseclick.Abort()
if__name__=='__main__':
try:
cli()
finally:
cleanup_processes()
The documentation breaches Pydantic trademark by using the Pydantic logo and the PSF trademark by using the Python logo:
Contact and additional research
We repeated asked the creator to take down the package on github, he deleted our issues then deleted the repo or made it private. Screenshot of issue before it was deleted:
Code of Conduct
I agree to follow the PSF Code of Conduct
The text was updated successfully, but these errors were encountered:
Project to be claimed
pydantic2
: https://pypi.org/project/pydantic2Your PyPI username
markolofsen
: https://pypi.org/user/markolofsen/Reasons for the request
This package is AI generate garbage, it's not an honest attempt to develop an open source library.
None of the code could run as expected due to the repo structure and that no dependencies are included.
The code looks very dangerous and potentially malicous, e.g. it tries to auto-update itself, run databases and delete them, see below.
The name is extremely confusing at best since the library has nothing to do with the widely used
pydantic
package, which is currently on V2.At worst this is deliberate typo squatting.
The user has done the same with numerous other packages:
auto-update code
code for running databases, deleting databases and killing processes
This is all from code downloaded from the whl at https://pypi.org/project/pydantic2/1.1.15/#files
Maintenance or replacement?
Replacement
Source code repositories URLs
Repo links to https://github.com/markolofsen/pydantic2 (goes to 404)
The documentation is here: https://pydantic.reforms.ai/getting-started/installation/
The documentation breaches Pydantic trademark by using the Pydantic logo and the PSF trademark by using the Python logo:
Contact and additional research
We repeated asked the creator to take down the package on github, he deleted our issues then deleted the repo or made it private. Screenshot of issue before it was deleted:
Code of Conduct
The text was updated successfully, but these errors were encountered: