diff --git a/README.md b/README.md index 01c8189..138c045 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ HateSonar allows you to detect hate speech and offensive language in text, witho ## Feature Support * Hate speech and offensive language detection -HateSonar officially supports Python 2.7 & 3.4–3.6 +HateSonar officially supports Python 3.9+ ## Installation To install HateSonar, simply use `pip`: diff --git a/hatesonar/api.py b/hatesonar/api.py index f34146e..3ec496e 100644 --- a/hatesonar/api.py +++ b/hatesonar/api.py @@ -2,17 +2,13 @@ Model API. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import joblib import numpy as np -class Sonar(object): +class Sonar: def __init__(self): BASE_DIR = os.path.join(os.path.dirname(__file__), './data') @@ -49,7 +45,7 @@ def get_class_idx(): return i class_idx = get_class_idx() - features = self.preprocessor.get_feature_names() + features = self.preprocessor.get_feature_names_out() weights = self.estimator.coef_[class_idx] word2weight = {f: w for f, w in zip(features, weights)} tokenize = self.preprocessor.build_analyzer() diff --git a/hatesonar/crawler/blog.py b/hatesonar/crawler/blog.py index 728f37e..ef126cc 100644 --- a/hatesonar/crawler/blog.py +++ b/hatesonar/crawler/blog.py @@ -4,7 +4,7 @@ import requests -class BlogCrawler(object): +class BlogCrawler: def __init__(self): pass diff --git a/hatesonar/crawler/twitter.py b/hatesonar/crawler/twitter.py index c64f58c..3db79db 100644 --- a/hatesonar/crawler/twitter.py +++ b/hatesonar/crawler/twitter.py @@ -20,7 +20,7 @@ def load_keys(): return consumer_key, consumer_secret, access_token, access_token_secret -class TwitterAPI(object): +class TwitterAPI: def __init__(self, consumer_key, consumer_secret, access_token, access_token_secret): auth = tweepy.OAuthHandler(consumer_key, consumer_secret) diff --git a/hatesonar/data/model.joblib b/hatesonar/data/model.joblib index dc95cb3..855d84b 100644 Binary files a/hatesonar/data/model.joblib and b/hatesonar/data/model.joblib differ diff --git a/hatesonar/data/preprocess.joblib b/hatesonar/data/preprocess.joblib index f459e0a..ce9d4c7 100644 Binary files a/hatesonar/data/preprocess.joblib and b/hatesonar/data/preprocess.joblib differ diff --git a/hatesonar/trainer/task.py b/hatesonar/trainer/task.py index ed76e8a..b53cfeb 100644 --- a/hatesonar/trainer/task.py +++ b/hatesonar/trainer/task.py @@ -2,15 +2,11 @@ Baseline model. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import argparse import os +import joblib import pandas as pd -from sklearn.externals import joblib from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report @@ -28,7 +24,7 @@ def main(args): x_train = vectorizer.fit_transform(x_train) print('Fitting...') - clf = LogisticRegression(penalty='l1') + clf = LogisticRegression(penalty='l1', solver='saga', max_iter=1000) clf.fit(x_train, y_train) print('Saving...') @@ -48,8 +44,8 @@ def main(args): SAVE_DIR = os.path.join(os.path.dirname(__file__), '../data') parser = argparse.ArgumentParser(description='Training a classifier') parser.add_argument('--dataset', default=os.path.join(DATA_DIR, 'labeled_data.csv'), help='dataset') - parser.add_argument('--model_file', default=os.path.join(SAVE_DIR, 'model.pkl'), help='model file') - parser.add_argument('--preprocessor', default=os.path.join(SAVE_DIR, 'preprocess.pkl'), help='preprocessor') + parser.add_argument('--model_file', default=os.path.join(SAVE_DIR, 'model.joblib'), help='model file') + parser.add_argument('--preprocessor', default=os.path.join(SAVE_DIR, 'preprocess.joblib'), help='preprocessor') parser.add_argument('--test_size', type=float, default=0.3, help='test data size') args = parser.parse_args() main(args) diff --git a/requirements.txt b/requirements.txt index e803e6b..23c0124 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -click==7.1.2 -Flask==1.1.2 -gunicorn==20.1.0 -itsdangerous==1.1.0 -Jinja2==3.0.3 -joblib==1.0.1 -MarkupSafe==1.1.1 -numpy==1.22.2 -pandas==1.2.4 -python-dateutil==2.8.1 -pytz==2021.1 -scikit-learn==0.24.2 -scipy==1.8.0 -six==1.16.0 -threadpoolctl==2.1.0 -Werkzeug==2.0.3 +click>=8.0.0 +Flask>=2.0.0 +gunicorn>=20.1.0 +itsdangerous>=2.0.0 +Jinja2>=3.0.0 +joblib>=1.0.0 +MarkupSafe>=2.0.0 +numpy>=1.19.0 +pandas>=1.1.0 +python-dateutil>=2.8.0 +pytz>=2021.1 +scikit-learn>=0.24.0 +scipy>=1.5.0 +six>=1.15.0 +threadpoolctl>=2.1.0 +Werkzeug>=2.0.0 diff --git a/setup.py b/setup.py index 1135a13..4c93c08 100644 --- a/setup.py +++ b/setup.py @@ -25,12 +25,12 @@ sys.exit() required = [ - 'numpy>=1.14.0', 'pandas>=0.22.0', 'scikit-learn>=0.19.1', 'scipy>=1.0.0', 'joblib>=0.16.0' + 'numpy>=1.19.0', 'pandas>=1.1.0', 'scikit-learn>=0.24.0', 'scipy>=1.5.0', 'joblib>=1.0.0' ] setup( name=NAME, - version='0.0.7', + version='0.0.8', description=DESCRIPTION, long_description=long_description, long_description_content_type='text/markdown', @@ -41,14 +41,16 @@ install_requires=required, include_package_data=True, license=LICENSE, + python_requires='>=3.9', classifiers=[ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy' ], ) diff --git a/tox.ini b/tox.ini index ae5b19d..dd0af90 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py36 +envlist = py39,py310,py311,py312 [testenv] deps=pytest diff --git a/verify_migration.py b/verify_migration.py new file mode 100644 index 0000000..4423c09 --- /dev/null +++ b/verify_migration.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +""" +Verification script for Python 3.9+ migration. +Run this after installing dependencies to verify the migration was successful. +""" + +import sys + +def check_python_version(): + """Check if Python version is 3.9 or higher.""" + print(f"Python version: {sys.version}") + if sys.version_info < (3, 9): + print("❌ ERROR: Python 3.9+ is required!") + return False + print("✅ Python version OK") + return True + +def check_imports(): + """Check if all required imports work.""" + try: + import joblib + print(f"✅ joblib {joblib.__version__}") + except ImportError as e: + print(f"❌ Failed to import joblib: {e}") + return False + + try: + import numpy + print(f"✅ numpy {numpy.__version__}") + except ImportError as e: + print(f"❌ Failed to import numpy: {e}") + return False + + try: + import pandas + print(f"✅ pandas {pandas.__version__}") + except ImportError as e: + print(f"❌ Failed to import pandas: {e}") + return False + + try: + import sklearn + print(f"✅ scikit-learn {sklearn.__version__}") + except ImportError as e: + print(f"❌ Failed to import sklearn: {e}") + return False + + try: + import scipy + print(f"✅ scipy {scipy.__version__}") + except ImportError as e: + print(f"❌ Failed to import scipy: {e}") + return False + + return True + +def check_hatesonar(): + """Check if hatesonar can be imported and works.""" + try: + from hatesonar import Sonar + print("✅ HateSonar imported successfully") + + # Try to create an instance + sonar = Sonar() + print("✅ Sonar instance created successfully") + + # Try a simple prediction + result = sonar.ping(text="This is a test") + print("✅ Sonar.ping() works correctly") + print(f" Result keys: {result.keys()}") + + return True + except Exception as e: + print(f"❌ Failed to test HateSonar: {e}") + import traceback + traceback.print_exc() + return False + +def main(): + """Run all verification checks.""" + print("=" * 60) + print("HateSonar Python 3.9+ Migration Verification") + print("=" * 60) + print() + + print("Step 1: Checking Python version...") + version_ok = check_python_version() + print() + + if not version_ok: + print("Please install Python 3.9 or higher and try again.") + sys.exit(1) + + print("Step 2: Checking dependencies...") + imports_ok = check_imports() + print() + + if not imports_ok: + print("Please install dependencies: pip install -e .") + sys.exit(1) + + print("Step 3: Testing HateSonar...") + hatesonar_ok = check_hatesonar() + print() + + if hatesonar_ok: + print("=" * 60) + print("✅ All checks passed! Migration successful!") + print("=" * 60) + else: + print("=" * 60) + print("❌ Some checks failed. Please review the errors above.") + print("=" * 60) + sys.exit(1) + +if __name__ == "__main__": + main()