Added support link to download game client, link for addons.
Fixed an issue that prevented the password reset tokens from working. Added email templates for password reset success and new account creation. Added more dynamic email template support.
This commit is contained in:
parent
536fc5cdb6
commit
9bbeb35c08
2741 changed files with 466974 additions and 1023 deletions
16
.idea/.gitignore
vendored
16
.idea/.gitignore
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
# Editor-based HTTP Client requests
|
# Editor-based HTTP Client requests
|
||||||
/httpRequests/
|
/httpRequests/
|
||||||
# Datasource local storage ignored files
|
# Datasource local storage ignored files
|
||||||
/dataSources/
|
/dataSources/
|
||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="Flask">
|
<component name="Flask">
|
||||||
<option name="enabled" value="true" />
|
<option name="enabled" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="TemplatesService">
|
<component name="TemplatesService">
|
||||||
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
||||||
<option name="TEMPLATE_FOLDERS">
|
<option name="TEMPLATE_FOLDERS">
|
||||||
<list>
|
<list>
|
||||||
<option value="$MODULE_DIR$/Webserver/templates" />
|
<option value="$MODULE_DIR$/Webserver/templates" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
<version value="1.0" />
|
<version value="1.0" />
|
||||||
</settings>
|
</settings>
|
||||||
</component>
|
</component>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.10 (AzerothCore)" />
|
<option name="sdkName" value="Python 3.10 (AzerothCore)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (AzerothCore)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (AzerothCore)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/AzerothCore.iml" filepath="$PROJECT_DIR$/.idea/AzerothCore.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/AzerothCore.iml" filepath="$PROJECT_DIR$/.idea/AzerothCore.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
176
README.md
176
README.md
|
|
@ -1,88 +1,88 @@
|
||||||
# AzerothCore Account Management
|
# AzerothCore Account Management
|
||||||
|
|
||||||
This application allows users to create and manage accounts for the World of Warcraft: Wrath of the Lich King private server. It provides features such as account creation, password reset, and email notifications.
|
This application allows users to create and manage accounts for the World of Warcraft: Wrath of the Lich King private server. It provides features such as account creation, password reset, and email notifications.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Account Creation**: Create new accounts with username, email, password, and expansion details.
|
- **Account Creation**: Create new accounts with username, email, password, and expansion details.
|
||||||
- **Password Reset**: Reset account passwords through email verification.
|
- **Password Reset**: Reset account passwords through email verification.
|
||||||
- **Secure Communication**: Utilizes Gmail App Passwords for secure email communication.
|
- **Secure Communication**: Utilizes Gmail App Passwords for secure email communication.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Python 3.8+**
|
- **Python 3.8+**
|
||||||
- **MySQL**: Database for storing user data
|
- **MySQL**: Database for storing user data
|
||||||
- **Gmail App Passwords**: For sending emails securely
|
- **Gmail App Passwords**: For sending emails securely
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. **Clone the repository:**
|
1. **Clone the repository:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/BeardedInfoSec/AzerothCore-website.git
|
git clone https://github.com/BeardedInfoSec/AzerothCore-website.git
|
||||||
cd AzerothCore-website
|
cd AzerothCore-website
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Configure the application:**
|
2. **Configure the application:**
|
||||||
|
|
||||||
Ensure the `config.json` file in the root directory has the following structure and update it with your details:
|
Ensure the `config.json` file in the root directory has the following structure and update it with your details:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"USERNAME": "acore",
|
"USERNAME": "acore",
|
||||||
"PASSWORD": "password",
|
"PASSWORD": "password",
|
||||||
"SERVER_IP": "127.0.0.1",
|
"SERVER_IP": "127.0.0.1",
|
||||||
"MYSQL_PORT": 3306,
|
"MYSQL_PORT": 3306,
|
||||||
"DATABASE": "acore_auth",
|
"DATABASE": "acore_auth",
|
||||||
"SMTP_EMAIL_ADDRESS": "your_email@gmail.com",
|
"SMTP_EMAIL_ADDRESS": "your_email@gmail.com",
|
||||||
"SMTP_EMAIL_PASSWORD": "your_app_password"
|
"SMTP_EMAIL_PASSWORD": "your_app_password"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Ensure you create a [Gmail App Password](https://myaccount.google.com/apppasswords) and enable [2-Step Verification](https://support.google.com/accounts/answer/185833?hl=en) for your Google account.
|
**Note**: Ensure you create a [Gmail App Password](https://myaccount.google.com/apppasswords) and enable [2-Step Verification](https://support.google.com/accounts/answer/185833?hl=en) for your Google account.
|
||||||
|
|
||||||
## Running the Application
|
## Running the Application
|
||||||
|
|
||||||
1. **Start the Flask application:**
|
1. **Start the Flask application:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python website.py
|
python website.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The application will be available at `http://127.0.0.1:5000/`.
|
The application will be available at `http://127.0.0.1:5000/`.
|
||||||
|
|
||||||
**Note**: The SQLite database for password reset tokens will be auto-initialized when the website is run.
|
**Note**: The SQLite database for password reset tokens will be auto-initialized when the website is run.
|
||||||
|
|
||||||
## Configuration Notes
|
## Configuration Notes
|
||||||
|
|
||||||
### HTTP vs. HTTPS
|
### HTTP vs. HTTPS
|
||||||
|
|
||||||
- **HTTP**: Sends web traffic in plain text, making it potentially vulnerable to interception and attacks. It is **not secure**.
|
- **HTTP**: Sends web traffic in plain text, making it potentially vulnerable to interception and attacks. It is **not secure**.
|
||||||
- **HTTPS**: Encrypts web traffic, ensuring data is securely transmitted between the client and server. It is **recommended** for all web applications to protect sensitive data.
|
- **HTTPS**: Encrypts web traffic, ensuring data is securely transmitted between the client and server. It is **recommended** for all web applications to protect sensitive data.
|
||||||
|
|
||||||
To secure your application:
|
To secure your application:
|
||||||
|
|
||||||
- Open ports 80 (HTTP) and 443 (HTTPS) on your server.
|
- Open ports 80 (HTTP) and 443 (HTTPS) on your server.
|
||||||
- Configure your firewall to allow traffic on these ports and point to your server's IP address or domain.
|
- Configure your firewall to allow traffic on these ports and point to your server's IP address or domain.
|
||||||
- Obtain and install an SSL/TLS certificate to enable HTTPS.
|
- Obtain and install an SSL/TLS certificate to enable HTTPS.
|
||||||
|
|
||||||
### Email Configuration
|
### Email Configuration
|
||||||
|
|
||||||
- **Important**: The password reset functionality will not work without having the Gmail account configured, as it sends the reset link through email.
|
- **Important**: The password reset functionality will not work without having the Gmail account configured, as it sends the reset link through email.
|
||||||
|
|
||||||
## Security Best Practices
|
## Security Best Practices
|
||||||
|
|
||||||
- **Disable Debug Mode**: Ensure `debug=False` in your app configuration.
|
- **Disable Debug Mode**: Ensure `debug=False` in your app configuration.
|
||||||
- **Use Environment Variables**: Store sensitive data in environment variables.
|
- **Use Environment Variables**: Store sensitive data in environment variables.
|
||||||
- **Enable HTTPS**: Secure your application with HTTPS.
|
- **Enable HTTPS**: Secure your application with HTTPS.
|
||||||
- **Set Secure Headers**: Use libraries like `Flask-Talisman` to set secure headers.
|
- **Set Secure Headers**: Use libraries like `Flask-Talisman` to set secure headers.
|
||||||
- **Rate Limiting**: Implement rate limiting to protect against brute force attacks.
|
- **Rate Limiting**: Implement rate limiting to protect against brute force attacks.
|
||||||
- **Input Validation**: Always validate and sanitize input data.
|
- **Input Validation**: Always validate and sanitize input data.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
For any issues or questions, please contact [thesoargoat@gmail.com].
|
For any issues or questions, please contact [thesoargoat@gmail.com].
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
This README provides comprehensive instructions for setting up and running your AzerothCore account management application securely.
|
This README provides comprehensive instructions for setting up and running your AzerothCore account management application securely.
|
||||||
|
|
|
||||||
BIN
__pycache__/website.cpython-312.pyc
Normal file
BIN
__pycache__/website.cpython-312.pyc
Normal file
Binary file not shown.
18
config.json
18
config.json
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"USERNAME" : "acore",
|
"USERNAME" : "acore",
|
||||||
"PASSWORD" : "password",
|
"PASSWORD" : "acore",
|
||||||
"SERVER_IP" : "127.0.0.1",
|
"SERVER_IP" : "127.0.0.1",
|
||||||
"MYSQL_PORT" : 3306,
|
"MYSQL_PORT" : 3306,
|
||||||
"DATABASE" : "acore_auth",
|
"DATABASE" : "acore_auth",
|
||||||
"SMTP_EMAIL_ADDRESS" : "gmail_address",
|
"SMTP_EMAIL_ADDRESS" : "email_address",
|
||||||
"SMTP_EMAIL_PASSWORD" : "gmail_app_password"
|
"SMTP_EMAIL_PASSWORD" : "email_app_password"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Flask==2.3.3
|
Flask==2.3.3
|
||||||
mysql-connector-python==8.1.0
|
mysql-connector-python==8.1.0
|
||||||
email-validator==2.0.0.post2
|
email-validator==2.0.0.post2
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
cryptography==41.0.2
|
cryptography==41.0.2
|
||||||
|
|
|
||||||
4
restart_website.sh
Normal file
4
restart_website.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Restart the website systemd service
|
||||||
|
sudo systemctl restart website.service
|
||||||
Binary file not shown.
BIN
scripts/__pycache__/createAccount.cpython-312.pyc
Normal file
BIN
scripts/__pycache__/createAccount.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
scripts/__pycache__/initialize_db.cpython-312.pyc
Normal file
BIN
scripts/__pycache__/initialize_db.cpython-312.pyc
Normal file
Binary file not shown.
|
|
@ -1,85 +1,85 @@
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
def sha1(data):
|
def sha1(data):
|
||||||
return hashlib.sha1(data).digest()
|
return hashlib.sha1(data).digest()
|
||||||
|
|
||||||
def generate_salt():
|
def generate_salt():
|
||||||
return os.urandom(32)
|
return os.urandom(32)
|
||||||
|
|
||||||
def calculate_verifier(username, password, salt):
|
def calculate_verifier(username, password, salt):
|
||||||
g = 7
|
g = 7
|
||||||
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
||||||
|
|
||||||
username = username.upper()
|
username = username.upper()
|
||||||
password = password.upper()
|
password = password.upper()
|
||||||
|
|
||||||
h1 = sha1(f"{username}:{password}".encode())
|
h1 = sha1(f"{username}:{password}".encode())
|
||||||
h2 = sha1(salt + h1)
|
h2 = sha1(salt + h1)
|
||||||
|
|
||||||
h2_int = int.from_bytes(h2, 'little')
|
h2_int = int.from_bytes(h2, 'little')
|
||||||
verifier_int = pow(g, h2_int, N)
|
verifier_int = pow(g, h2_int, N)
|
||||||
|
|
||||||
verifier = verifier_int.to_bytes((verifier_int.bit_length() + 7) // 8, 'little')
|
verifier = verifier_int.to_bytes((verifier_int.bit_length() + 7) // 8, 'little')
|
||||||
return verifier
|
return verifier
|
||||||
|
|
||||||
def create_account(account_name, email, passwd1, passwd2, expansion):
|
def create_account(account_name, email, passwd1, passwd2, expansion):
|
||||||
if passwd1 != passwd2:
|
if passwd1 != passwd2:
|
||||||
return "Passwords do not match."
|
return "Passwords do not match."
|
||||||
|
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
config_path = script_dir / "../config.json"
|
config_path = script_dir / "../config.json"
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
with open(config_path) as config_file:
|
||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
|
|
||||||
USERNAME = config["USERNAME"]
|
USERNAME = config["USERNAME"]
|
||||||
PASSWORD = config["PASSWORD"]
|
PASSWORD = config["PASSWORD"]
|
||||||
SERVER_IP = config["SERVER_IP"]
|
SERVER_IP = config["SERVER_IP"]
|
||||||
PORT = config["MYSQL_PORT"]
|
PORT = config["MYSQL_PORT"]
|
||||||
DATABASE = config["DATABASE"]
|
DATABASE = config["DATABASE"]
|
||||||
|
|
||||||
conn = None
|
conn = None
|
||||||
cursor = None
|
cursor = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = mysql.connector.connect(
|
conn = mysql.connector.connect(
|
||||||
host=SERVER_IP,
|
host=SERVER_IP,
|
||||||
user=USERNAME,
|
user=USERNAME,
|
||||||
password=PASSWORD,
|
password=PASSWORD,
|
||||||
database=DATABASE,
|
database=DATABASE,
|
||||||
port=PORT
|
port=PORT
|
||||||
)
|
)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Check if the username already exists
|
# Check if the username already exists
|
||||||
cursor.execute("SELECT id FROM account WHERE username = %s", (account_name,))
|
cursor.execute("SELECT id FROM account WHERE username = %s", (account_name,))
|
||||||
if cursor.fetchone():
|
if cursor.fetchone():
|
||||||
return "Username already taken."
|
return "Username already taken."
|
||||||
|
|
||||||
cursor.execute("SELECT MAX(id) FROM account")
|
cursor.execute("SELECT MAX(id) FROM account")
|
||||||
max_id = cursor.fetchone()[0]
|
max_id = cursor.fetchone()[0]
|
||||||
new_id = max_id + 1 if max_id else 1
|
new_id = max_id + 1 if max_id else 1
|
||||||
|
|
||||||
salt = generate_salt()
|
salt = generate_salt()
|
||||||
verifier = calculate_verifier(account_name, passwd1, salt)
|
verifier = calculate_verifier(account_name, passwd1, salt)
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO account (id, username, salt, verifier, email) VALUES (%s, %s, %s, %s, %s)",
|
"INSERT INTO account (id, username, salt, verifier, email) VALUES (%s, %s, %s, %s, %s)",
|
||||||
(new_id, account_name, salt, verifier, email)
|
(new_id, account_name, salt, verifier, email)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return "Account created successfully!"
|
return "Account created successfully!"
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
return f"Error: {err}"
|
return f"Error: {err}"
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if cursor is not None:
|
if cursor is not None:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
if conn is not None:
|
if conn is not None:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
def initialize_db():
|
def initialize_db():
|
||||||
conn = sqlite3.connect('tokens.db')
|
conn = sqlite3.connect('tokens.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
token TEXT NOT NULL,
|
token TEXT NOT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
|
||||||
|
|
@ -1,212 +1,212 @@
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from flask import Flask, request, render_template, jsonify, url_for
|
from flask import Flask, request, render_template, jsonify, url_for
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
config_path = script_dir / "../config.json"
|
config_path = script_dir / "../config.json"
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
with open(config_path) as config_file:
|
||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
|
|
||||||
return mysql.connector.connect(
|
return mysql.connector.connect(
|
||||||
host=config["SERVER_IP"],
|
host=config["SERVER_IP"],
|
||||||
user=config["USERNAME"],
|
user=config["USERNAME"],
|
||||||
password=config["PASSWORD"],
|
password=config["PASSWORD"],
|
||||||
database=config["DATABASE"],
|
database=config["DATABASE"],
|
||||||
port=config["MYSQL_PORT"]
|
port=config["MYSQL_PORT"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
config_path = script_dir / "../config.json"
|
config_path = script_dir / "../config.json"
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
with open(config_path) as config_file:
|
||||||
return json.load(config_file)
|
return json.load(config_file)
|
||||||
|
|
||||||
@app.route('/resetpassword')
|
@app.route('/resetpassword')
|
||||||
def reset_password():
|
def reset_password():
|
||||||
return render_template('resetpassword.html')
|
return render_template('resetpassword.html')
|
||||||
|
|
||||||
@app.route('/reset_password', methods=['POST'])
|
@app.route('/reset_password', methods=['POST'])
|
||||||
def handle_reset_password():
|
def handle_reset_password():
|
||||||
data = request.json
|
data = request.json
|
||||||
email = data.get('email')
|
email = data.get('email')
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
return jsonify({'success': False, 'message': 'Email is required.'}), 400
|
return jsonify({'success': False, 'message': 'Email is required.'}), 400
|
||||||
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor(dictionary=True)
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("SELECT id FROM account WHERE email = %s", (email,))
|
cursor.execute("SELECT id FROM account WHERE email = %s", (email,))
|
||||||
account = cursor.fetchone()
|
account = cursor.fetchone()
|
||||||
|
|
||||||
if not account:
|
if not account:
|
||||||
return jsonify({'success': False, 'message': 'Email not found.'}), 404
|
return jsonify({'success': False, 'message': 'Email not found.'}), 404
|
||||||
|
|
||||||
token = os.urandom(24).hex()
|
token = os.urandom(24).hex()
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO password_reset_tokens (account_id, token) VALUES (%s, %s)",
|
"INSERT INTO password_reset_tokens (account_id, token) VALUES (%s, %s)",
|
||||||
(account['id'], token)
|
(account['id'], token)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
reset_link = url_for('reset_password_token', token=token, _external=True)
|
reset_link = url_for('reset_password_token', token=token, _external=True)
|
||||||
disable_link = url_for('disable_token', token=token, email=email, _external=True)
|
disable_link = url_for('disable_token', token=token, email=email, _external=True)
|
||||||
|
|
||||||
send_email(email, reset_link, disable_link, 'Azerothcore Password Reset Request')
|
send_email(email, reset_link, disable_link, 'Azerothcore Password Reset Request')
|
||||||
|
|
||||||
return jsonify({'success': True, 'message': 'Password reset link has been sent to your email.'})
|
return jsonify({'success': True, 'message': 'Password reset link has been sent to your email.'})
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
return jsonify({'success': False, 'message': str(err)}), 500
|
return jsonify({'success': False, 'message': str(err)}), 500
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def send_email(to_email, reset_link, disable_link, subject):
|
def send_email(to_email, reset_link, disable_link, subject):
|
||||||
config = get_config()
|
config = get_config()
|
||||||
from_email = config["SMTP_EMAIL_ADDRESS"]
|
from_email = config["SMTP_EMAIL_ADDRESS"]
|
||||||
from_password = config["SMTP_EMAIL_PASSWORD"]
|
from_password = config["SMTP_EMAIL_PASSWORD"]
|
||||||
|
|
||||||
msg = MIMEMultipart('alternative')
|
msg = MIMEMultipart('alternative')
|
||||||
msg['From'] = from_email
|
msg['From'] = from_email
|
||||||
msg['To'] = to_email
|
msg['To'] = to_email
|
||||||
msg['Subject'] = subject
|
msg['Subject'] = subject
|
||||||
|
|
||||||
text_content = f'Click the link to reset your password: {reset_link}'
|
text_content = f'Click the link to reset your password: {reset_link}'
|
||||||
html_content = f"""
|
html_content = f"""
|
||||||
<html>
|
<html>
|
||||||
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
|
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
|
||||||
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
|
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
|
||||||
<h2 style="color: #333;">Password Reset Request</h2>
|
<h2 style="color: #333;">Password Reset Request</h2>
|
||||||
<p>Click the button below to reset your password:</p>
|
<p>Click the button below to reset your password:</p>
|
||||||
<a href="{reset_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #007bff; text-decoration: none; border-radius: 5px;">Reset Password</a>
|
<a href="{reset_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #007bff; text-decoration: none; border-radius: 5px;">Reset Password</a>
|
||||||
<p>If you did not request this email, click the button below to disable the token:</p>
|
<p>If you did not request this email, click the button below to disable the token:</p>
|
||||||
<a href="{disable_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #ff0000; text-decoration: none; border-radius: 5px;">Disable Token</a>
|
<a href="{disable_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #ff0000; text-decoration: none; border-radius: 5px;">Disable Token</a>
|
||||||
<p style="color: #999; margin-top: 20px;">If you have any questions, feel free to contact our support team.</p>
|
<p style="color: #999; margin-top: 20px;">If you have any questions, feel free to contact our support team.</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
part1 = MIMEText(text_content, 'plain')
|
part1 = MIMEText(text_content, 'plain')
|
||||||
part2 = MIMEText(html_content, 'html')
|
part2 = MIMEText(html_content, 'html')
|
||||||
|
|
||||||
msg.attach(part1)
|
msg.attach(part1)
|
||||||
msg.attach(part2)
|
msg.attach(part2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with smtplib.SMTP('smtp.gmail.com', 587) as server:
|
with smtplib.SMTP('smtp.gmail.com', 587) as server:
|
||||||
server.starttls()
|
server.starttls()
|
||||||
server.login(from_email, from_password)
|
server.login(from_email, from_password)
|
||||||
server.sendmail(from_email, to_email, msg.as_string())
|
server.sendmail(from_email, to_email, msg.as_string())
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send email: {e}")
|
print(f"Failed to send email: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@app.route('/disable_token', methods=['GET'])
|
@app.route('/disable_token', methods=['GET'])
|
||||||
def disable_token():
|
def disable_token():
|
||||||
token = request.args.get('token')
|
token = request.args.get('token')
|
||||||
email = request.args.get('email')
|
email = request.args.get('email')
|
||||||
|
|
||||||
if not token or not email:
|
if not token or not email:
|
||||||
return jsonify({'message': 'Invalid request.'}), 400
|
return jsonify({'message': 'Invalid request.'}), 400
|
||||||
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s AND email = %s", (token, email))
|
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s AND email = %s", (token, email))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
if cursor.rowcount == 0:
|
if cursor.rowcount == 0:
|
||||||
return jsonify({'message': 'Token not found or already disabled.'}), 404
|
return jsonify({'message': 'Token not found or already disabled.'}), 404
|
||||||
|
|
||||||
return jsonify({'message': 'Token disabled successfully.'}), 200
|
return jsonify({'message': 'Token disabled successfully.'}), 200
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
return jsonify({'message': str(err)}), 500
|
return jsonify({'message': str(err)}), 500
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
|
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
|
||||||
def reset_password_token(token):
|
def reset_password_token(token):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
data = request.form
|
data = request.form
|
||||||
password = data.get('password')
|
password = data.get('password')
|
||||||
confirm_password = data.get('confirm_password')
|
confirm_password = data.get('confirm_password')
|
||||||
|
|
||||||
if not password or not confirm_password:
|
if not password or not confirm_password:
|
||||||
return render_template('resetpassword.html', message='All fields are required.')
|
return render_template('resetpassword.html', message='All fields are required.')
|
||||||
|
|
||||||
if password != confirm_password:
|
if password != confirm_password:
|
||||||
return render_template('resetpassword.html', message='Passwords do not match.')
|
return render_template('resetpassword.html', message='Passwords do not match.')
|
||||||
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("SELECT account_id FROM password_reset_tokens WHERE token = %s", (token,))
|
cursor.execute("SELECT account_id FROM password_reset_tokens WHERE token = %s", (token,))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return render_template('resetpassword.html', message='Invalid or expired token.')
|
return render_template('resetpassword.html', message='Invalid or expired token.')
|
||||||
|
|
||||||
account_id = result[0]
|
account_id = result[0]
|
||||||
salt = os.urandom(32)
|
salt = os.urandom(32)
|
||||||
verifier = calculate_verifier("USERNAME", password, salt) # Update USERNAME appropriately
|
verifier = calculate_verifier("USERNAME", password, salt) # Update USERNAME appropriately
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"UPDATE account SET salt = %s, verifier = %s WHERE id = %s",
|
"UPDATE account SET salt = %s, verifier = %s WHERE id = %s",
|
||||||
(salt, verifier, account_id)
|
(salt, verifier, account_id)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s", (token,))
|
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s", (token,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return redirect(url_for('success'))
|
return redirect(url_for('success'))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return render_template('resetpassword.html')
|
return render_template('resetpassword.html')
|
||||||
|
|
||||||
@app.route('/success')
|
@app.route('/success')
|
||||||
def success():
|
def success():
|
||||||
return render_template('success.html')
|
return render_template('success.html')
|
||||||
|
|
||||||
def calculate_verifier(username, password, salt):
|
def calculate_verifier(username, password, salt):
|
||||||
g = 7
|
g = 7
|
||||||
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
||||||
|
|
||||||
username = username.upper()
|
username = username.upper()
|
||||||
password = password.upper()
|
password = password.upper()
|
||||||
|
|
||||||
h1 = hashlib.sha1(f"{username}:{password}".encode()).digest()
|
h1 = hashlib.sha1(f"{username}:{password}".encode()).digest()
|
||||||
h2 = hashlib.sha1(salt + h1).digest()
|
h2 = hashlib.sha1(salt + h1).digest()
|
||||||
|
|
||||||
h2_int = int.from_bytes(h2, 'little')
|
h2_int = int.from_bytes(h2, 'little')
|
||||||
verifier_int = pow(g, h2_int, N)
|
verifier_int = pow(g, h2_int, N)
|
||||||
|
|
||||||
verifier = verifier_int.to_bytes((verifier_int.bit_length() + 7) // 8, 'little')
|
verifier = verifier_int.to_bytes((verifier_int.bit_length() + 7) // 8, 'little')
|
||||||
return verifier
|
return verifier
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
|
||||||
7
start_virtualenv.sh
Normal file
7
start_virtualenv.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Navigate to the project directory
|
||||||
|
cd /home/wotlk_webserver/AzerothCore-website
|
||||||
|
|
||||||
|
# Activate the virtual environment
|
||||||
|
source venv/bin/activate
|
||||||
13
start_website.sh
Normal file
13
start_website.sh
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Navigate to the project directory
|
||||||
|
cd /home/wotlk_webserver/AzerothCore-website
|
||||||
|
|
||||||
|
# Activate the virtual environment
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Run the Flask app
|
||||||
|
python3 website.py
|
||||||
|
|
||||||
|
# Deactivate the virtual environment when done
|
||||||
|
deactivate
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
.form {
|
.form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field input,.input-field select {
|
.input-field input,.input-field select {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field label {
|
.input-field label {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-container {
|
.btn-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
@ -1,80 +1,80 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="heading">Create Account</div>
|
<div class="heading">Create Account</div>
|
||||||
<form id="accountForm" class="form" onsubmit="event.preventDefault(); createAccount();">
|
<form id="accountForm" class="form" onsubmit="event.preventDefault(); createAccount();">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="accountName">Account Name</label>
|
<label for="accountName">Account Name</label>
|
||||||
<input type="text" id="accountName" maxlength="20" required autocomplete="off" />
|
<input type="text" id="accountName" maxlength="20" required autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="email">Email Address</label>
|
<label for="email">Email Address</label>
|
||||||
<input type="email" id="email" required autocomplete="off" />
|
<input type="email" id="email" required autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="passwd1">Password</label>
|
<label for="passwd1">Password</label>
|
||||||
<input type="password" id="passwd1" minlength="8" required autocomplete="off" />
|
<input type="password" id="passwd1" minlength="8" required autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="passwd2">Re-enter Password</label>
|
<label for="passwd2">Re-enter Password</label>
|
||||||
<input type="password" id="passwd2" minlength="8" required autocomplete="off" />
|
<input type="password" id="passwd2" minlength="8" required autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="exp">Expansion</label>
|
<label for="exp">Expansion</label>
|
||||||
<select name="expansion" id="exp" required>
|
<select name="expansion" id="exp" required>
|
||||||
<option value="2">Wrath of the Lich King</option>
|
<option value="2">Wrath of the Lich King</option>
|
||||||
<option value="1">The Burning Crusade</option>
|
<option value="1">The Burning Crusade</option>
|
||||||
<option value="0">World of Warcraft (Classic)</option>
|
<option value="0">World of Warcraft (Classic)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button type="submit" class="btn">Create Account</button>
|
<button type="submit" class="btn">Create Account</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p id="result"></p>
|
<p id="result"></p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function createAccount() {
|
function createAccount() {
|
||||||
const accountName = document.getElementById('accountName').value;
|
const accountName = document.getElementById('accountName').value;
|
||||||
const email = document.getElementById('email').value;
|
const email = document.getElementById('email').value;
|
||||||
const passwd1 = document.getElementById('passwd1').value;
|
const passwd1 = document.getElementById('passwd1').value;
|
||||||
const passwd2 = document.getElementById('passwd2').value;
|
const passwd2 = document.getElementById('passwd2').value;
|
||||||
const expansion = document.getElementById('exp').value;
|
const expansion = document.getElementById('exp').value;
|
||||||
|
|
||||||
if (passwd1.length < 8) {
|
if (passwd1.length < 8) {
|
||||||
document.getElementById('result').innerText = 'Password must be at least 8 characters long.';
|
document.getElementById('result').innerText = 'Password must be at least 8 characters long.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwd1 !== passwd2) {
|
if (passwd1 !== passwd2) {
|
||||||
document.getElementById('result').innerText = 'Passwords do not match.';
|
document.getElementById('result').innerText = 'Passwords do not match.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/create_account', {
|
fetch('/create_account', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accountName: accountName,
|
accountName: accountName,
|
||||||
email: email,
|
email: email,
|
||||||
passwd1: passwd1,
|
passwd1: passwd1,
|
||||||
passwd2: passwd2,
|
passwd2: passwd2,
|
||||||
expansion: expansion
|
expansion: expansion
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
document.getElementById('result').innerText = data.message;
|
document.getElementById('result').innerText = data.message;
|
||||||
if (data.message === "Account created successfully!") {
|
if (data.message === "Account created successfully!") {
|
||||||
document.getElementById('accountForm').reset();
|
document.getElementById('accountForm').reset();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('home') }}">Account Creation</a></li>
|
<li><a href="{{ url_for('home') }}">Account Creation</a></li>
|
||||||
|
<li><a href="https://drive.google.com/file/d/1W42A5Th1z470A-3Cz_CJVzKOTGhgjI01/view?usp=sharing">Download</a></li>
|
||||||
|
<li><a href="https://legacy-wow.com/wotlk-addons/">Add-ons</a></li>
|
||||||
<li><a href="{{ url_for('reset_password') }}">Reset Password</a></li>
|
<li><a href="{{ url_for('reset_password') }}">Reset Password</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,84 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="heading">Set New Password</div>
|
<div class="heading">Set New Password</div>
|
||||||
<form class="form" onsubmit="event.preventDefault(); updatePassword();">
|
<form class="form" onsubmit="event.preventDefault(); updatePassword();">
|
||||||
<input type="hidden" id="email" value="{{ email }}">
|
<input type="hidden" id="email" value="{{ email }}">
|
||||||
<input type="hidden" id="token" value="{{ token }}">
|
<input type="hidden" id="token" value="{{ token }}">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="passwd1">New Password</label>
|
<label for="passwd1">New Password</label>
|
||||||
<input type="password" id="passwd1" minlength="8" required>
|
<input type="password" id="passwd1" minlength="8" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="passwd2">Confirm New Password</label>
|
<label for="passwd2">Confirm New Password</label>
|
||||||
<input type="password" id="passwd2" minlength="8" required>
|
<input type="password" id="passwd2" minlength="8" required>
|
||||||
</div>
|
</div>
|
||||||
<div id="passwordMatchMessage" style="color: red;"></div>
|
<div id="passwordMatchMessage" style="color: red;"></div>
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button type="submit" class="btn" id="updatePasswordBtn" disabled>Update Password</button>
|
<button type="submit" class="btn" id="updatePasswordBtn" disabled>Update Password</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p id="result"></p>
|
<p id="result"></p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function validatePasswords() {
|
function validatePasswords() {
|
||||||
const passwd1 = document.getElementById('passwd1').value;
|
const passwd1 = document.getElementById('passwd1').value;
|
||||||
const passwd2 = document.getElementById('passwd2').value;
|
const passwd2 = document.getElementById('passwd2').value;
|
||||||
const passwordMatchMessage = document.getElementById('passwordMatchMessage');
|
const passwordMatchMessage = document.getElementById('passwordMatchMessage');
|
||||||
const updatePasswordBtn = document.getElementById('updatePasswordBtn');
|
const updatePasswordBtn = document.getElementById('updatePasswordBtn');
|
||||||
|
|
||||||
if (passwd1.length >= 8 && passwd2.length >= 8 && passwd1 === passwd2) {
|
if (passwd1.length >= 8 && passwd2.length >= 8 && passwd1 === passwd2) {
|
||||||
passwordMatchMessage.innerText = '';
|
passwordMatchMessage.innerText = '';
|
||||||
updatePasswordBtn.disabled = false;
|
updatePasswordBtn.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
if (passwd1 !== passwd2) {
|
if (passwd1 !== passwd2) {
|
||||||
passwordMatchMessage.innerText = 'Passwords do not match.';
|
passwordMatchMessage.innerText = 'Passwords do not match.';
|
||||||
} else {
|
} else {
|
||||||
passwordMatchMessage.innerText = '';
|
passwordMatchMessage.innerText = '';
|
||||||
}
|
}
|
||||||
updatePasswordBtn.disabled = true;
|
updatePasswordBtn.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePassword() {
|
function updatePassword() {
|
||||||
const email = document.getElementById('email').value;
|
const email = document.getElementById('email').value;
|
||||||
const token = document.getElementById('token').value;
|
const token = document.getElementById('token').value;
|
||||||
const passwd1 = document.getElementById('passwd1').value;
|
const passwd1 = document.getElementById('passwd1').value;
|
||||||
const passwd2 = document.getElementById('passwd2').value;
|
const passwd2 = document.getElementById('passwd2').value;
|
||||||
const result = document.getElementById('result');
|
const result = document.getElementById('result');
|
||||||
|
|
||||||
if (passwd1 !== passwd2) {
|
if (passwd1 !== passwd2) {
|
||||||
result.innerText = 'Passwords do not match.';
|
result.innerText = 'Passwords do not match.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/update_password', {
|
fetch('/update_password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
token: token,
|
token: token,
|
||||||
password: passwd1
|
password: passwd1
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.message === 'Password updated successfully!') {
|
if (data.message === 'Password updated successfully!') {
|
||||||
window.location.href = '/success';
|
window.location.href = '/success';
|
||||||
} else {
|
} else {
|
||||||
result.innerText = data.message;
|
result.innerText = data.message;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
result.innerText = 'An error occurred. Please try again.';
|
result.innerText = 'An error occurred. Please try again.';
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('passwd1').addEventListener('input', validatePasswords);
|
document.getElementById('passwd1').addEventListener('input', validatePasswords);
|
||||||
document.getElementById('passwd2').addEventListener('input', validatePasswords);
|
document.getElementById('passwd2').addEventListener('input', validatePasswords);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,39 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="heading">Reset Password</div>
|
<div class="heading">Reset Password</div>
|
||||||
<form id="resetForm" class="form" onsubmit="event.preventDefault(); resetPassword();">
|
<form id="resetForm" class="form" onsubmit="event.preventDefault(); resetPassword();">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<label for="email">Email Address</label>
|
<label for="email">Email Address</label>
|
||||||
<input type="email" id="email" required autocomplete="off" />
|
<input type="email" id="email" required autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button type="submit" class="btn">Reset Password</button>
|
<button type="submit" class="btn">Reset Password</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p id="result" style="color: white;"></p>
|
<p id="result" style="color: white;"></p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function resetPassword() {
|
function resetPassword() {
|
||||||
const email = document.getElementById('email').value;
|
const email = document.getElementById('email').value;
|
||||||
|
|
||||||
fetch('/reset_password', {
|
fetch('/reset_password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ email: email }),
|
body: JSON.stringify({ email: email }),
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const resultElement = document.getElementById('result');
|
const resultElement = document.getElementById('result');
|
||||||
resultElement.innerText = data.message;
|
resultElement.innerText = data.message;
|
||||||
resultElement.style.color = data.success ? 'red' : 'white';
|
resultElement.style.color = data.success ? 'red' : 'white';
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="heading">Success</div>
|
<div class="heading">Success</div>
|
||||||
<p>Your password has been updated successfully!</p>
|
<p>Your password has been updated successfully!</p>
|
||||||
<a href="/" class="btn">Return to Home</a>
|
<a href="/" class="btn">Return to Home</a>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
247
venv/bin/Activate.ps1
Normal file
247
venv/bin/Activate.ps1
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Activate a Python virtual environment for the current PowerShell session.
|
||||||
|
|
||||||
|
.Description
|
||||||
|
Pushes the python executable for a virtual environment to the front of the
|
||||||
|
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||||
|
in a Python virtual environment. Makes use of the command line switches as
|
||||||
|
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||||
|
|
||||||
|
.Parameter VenvDir
|
||||||
|
Path to the directory that contains the virtual environment to activate. The
|
||||||
|
default value for this is the parent of the directory that the Activate.ps1
|
||||||
|
script is located within.
|
||||||
|
|
||||||
|
.Parameter Prompt
|
||||||
|
The prompt prefix to display when this virtual environment is activated. By
|
||||||
|
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||||
|
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Verbose
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and shows extra information about the activation as it executes.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||||
|
Activates the Python virtual environment located in the specified location.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Prompt "MyPython"
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and prefixes the current prompt with the specified string (surrounded in
|
||||||
|
parentheses) while the virtual environment is active.
|
||||||
|
|
||||||
|
.Notes
|
||||||
|
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||||
|
execution policy for the user. You can do this by issuing the following PowerShell
|
||||||
|
command:
|
||||||
|
|
||||||
|
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
|
||||||
|
For more information on Execution Policies:
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||||
|
|
||||||
|
#>
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$VenvDir,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$Prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
<# Function declarations --------------------------------------------------- #>
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Remove all shell session elements added by the Activate script, including the
|
||||||
|
addition of the virtual environment's Python executable from the beginning of
|
||||||
|
the PATH variable.
|
||||||
|
|
||||||
|
.Parameter NonDestructive
|
||||||
|
If present, do not remove this function from the global namespace for the
|
||||||
|
session.
|
||||||
|
|
||||||
|
#>
|
||||||
|
function global:deactivate ([switch]$NonDestructive) {
|
||||||
|
# Revert to original values
|
||||||
|
|
||||||
|
# The prior prompt:
|
||||||
|
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||||
|
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||||
|
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PYTHONHOME:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PATH:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the VIRTUAL_ENV altogether:
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||||
|
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||||
|
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Leave deactivate function in the global namespace if requested:
|
||||||
|
if (-not $NonDestructive) {
|
||||||
|
Remove-Item -Path function:deactivate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Description
|
||||||
|
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||||
|
given folder, and returns them in a map.
|
||||||
|
|
||||||
|
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||||
|
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||||
|
then it is considered a `key = value` line. The left hand string is the key,
|
||||||
|
the right hand is the value.
|
||||||
|
|
||||||
|
If the value starts with a `'` or a `"` then the first and last character is
|
||||||
|
stripped from the value before being captured.
|
||||||
|
|
||||||
|
.Parameter ConfigDir
|
||||||
|
Path to the directory that contains the `pyvenv.cfg` file.
|
||||||
|
#>
|
||||||
|
function Get-PyVenvConfig(
|
||||||
|
[String]
|
||||||
|
$ConfigDir
|
||||||
|
) {
|
||||||
|
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||||
|
|
||||||
|
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||||
|
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||||
|
|
||||||
|
# An empty map will be returned if no config file is found.
|
||||||
|
$pyvenvConfig = @{ }
|
||||||
|
|
||||||
|
if ($pyvenvConfigPath) {
|
||||||
|
|
||||||
|
Write-Verbose "File exists, parse `key = value` lines"
|
||||||
|
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||||
|
|
||||||
|
$pyvenvConfigContent | ForEach-Object {
|
||||||
|
$keyval = $PSItem -split "\s*=\s*", 2
|
||||||
|
if ($keyval[0] -and $keyval[1]) {
|
||||||
|
$val = $keyval[1]
|
||||||
|
|
||||||
|
# Remove extraneous quotations around a string value.
|
||||||
|
if ("'""".Contains($val.Substring(0, 1))) {
|
||||||
|
$val = $val.Substring(1, $val.Length - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
$pyvenvConfig[$keyval[0]] = $val
|
||||||
|
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pyvenvConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<# Begin Activate script --------------------------------------------------- #>
|
||||||
|
|
||||||
|
# Determine the containing directory of this script
|
||||||
|
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||||
|
|
||||||
|
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||||
|
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||||
|
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||||
|
|
||||||
|
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||||
|
# First, get the location of the virtual environment, it might not be
|
||||||
|
# VenvExecDir if specified on the command line.
|
||||||
|
if ($VenvDir) {
|
||||||
|
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||||
|
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||||
|
Write-Verbose "VenvDir=$VenvDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||||
|
# as `prompt`.
|
||||||
|
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||||
|
|
||||||
|
# Next, set the prompt from the command line, or the config file, or
|
||||||
|
# just use the name of the virtual environment folder.
|
||||||
|
if ($Prompt) {
|
||||||
|
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||||
|
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||||
|
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||||
|
$Prompt = $pyvenvCfg['prompt'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||||
|
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||||
|
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Prompt = '$Prompt'"
|
||||||
|
Write-Verbose "VenvDir='$VenvDir'"
|
||||||
|
|
||||||
|
# Deactivate any currently active virtual environment, but leave the
|
||||||
|
# deactivate function in place.
|
||||||
|
deactivate -nondestructive
|
||||||
|
|
||||||
|
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||||
|
# that there is an activated venv.
|
||||||
|
$env:VIRTUAL_ENV = $VenvDir
|
||||||
|
|
||||||
|
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||||
|
|
||||||
|
Write-Verbose "Setting prompt to '$Prompt'"
|
||||||
|
|
||||||
|
# Set the prompt to include the env name
|
||||||
|
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||||
|
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||||
|
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||||
|
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||||
|
|
||||||
|
function global:prompt {
|
||||||
|
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||||
|
_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear PYTHONHOME
|
||||||
|
if (Test-Path -Path Env:PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
Remove-Item -Path Env:PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add the venv to the PATH
|
||||||
|
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||||
|
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||||
70
venv/bin/activate
Normal file
70
venv/bin/activate
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# You cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
unset VIRTUAL_ENV_PROMPT
|
||||||
|
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||||
|
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||||
|
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||||
|
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||||
|
export VIRTUAL_ENV=$(cygpath "/home/wotlk_webserver/AzerothCore-website/venv")
|
||||||
|
else
|
||||||
|
# use the path as-is
|
||||||
|
export VIRTUAL_ENV="/home/wotlk_webserver/AzerothCore-website/venv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
PS1="(venv) ${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
VIRTUAL_ENV_PROMPT="(venv) "
|
||||||
|
export VIRTUAL_ENV_PROMPT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
27
venv/bin/activate.csh
Normal file
27
venv/bin/activate.csh
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||||
|
# You cannot run it directly.
|
||||||
|
|
||||||
|
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||||
|
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||||
|
|
||||||
|
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV "/home/wotlk_webserver/AzerothCore-website/venv"
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
set prompt = "(venv) $prompt"
|
||||||
|
setenv VIRTUAL_ENV_PROMPT "(venv) "
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
69
venv/bin/activate.fish
Normal file
69
venv/bin/activate.fish
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||||
|
# (https://fishshell.com/). You cannot run it directly.
|
||||||
|
|
||||||
|
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||||
|
# reset old environment variables
|
||||||
|
if test -n "$_OLD_VIRTUAL_PATH"
|
||||||
|
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||||
|
set -e _OLD_VIRTUAL_PATH
|
||||||
|
end
|
||||||
|
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||||
|
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
# prevents error when using nested fish instances (Issue #93858)
|
||||||
|
if functions -q _old_fish_prompt
|
||||||
|
functions -e fish_prompt
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
set -e VIRTUAL_ENV_PROMPT
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self-destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV "/home/wotlk_webserver/AzerothCore-website/venv"
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# With the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command.
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
# Output the original/"old" prompt.
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
set -gx VIRTUAL_ENV_PROMPT "(venv) "
|
||||||
|
end
|
||||||
8
venv/bin/email_validator
Normal file
8
venv/bin/email_validator
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from email_validator.__main__ import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/flask
Normal file
8
venv/bin/flask
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from flask.cli import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/gunicorn
Normal file
8
venv/bin/gunicorn
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from gunicorn.app.wsgiapp import run
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(run())
|
||||||
8
venv/bin/normalizer
Normal file
8
venv/bin/normalizer
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from charset_normalizer.cli import cli_detect
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli_detect())
|
||||||
8
venv/bin/pip
Normal file
8
venv/bin/pip
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/pip3
Normal file
8
venv/bin/pip3
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/pip3.12
Normal file
8
venv/bin/pip3.12
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/wotlk_webserver/AzerothCore-website/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
BIN
venv/bin/python
Normal file
BIN
venv/bin/python
Normal file
Binary file not shown.
BIN
venv/bin/python3
Normal file
BIN
venv/bin/python3
Normal file
Binary file not shown.
BIN
venv/bin/python3.12
Normal file
BIN
venv/bin/python3.12
Normal file
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2007 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Jinja2
|
||||||
|
Version: 3.1.2
|
||||||
|
Summary: A very fast and expressive template engine.
|
||||||
|
Home-page: https://palletsprojects.com/p/jinja/
|
||||||
|
Author: Armin Ronacher
|
||||||
|
Author-email: armin.ronacher@active-4.com
|
||||||
|
Maintainer: Pallets
|
||||||
|
Maintainer-email: contact@palletsprojects.com
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Documentation, https://jinja.palletsprojects.com/
|
||||||
|
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
|
||||||
|
Project-URL: Source Code, https://github.com/pallets/jinja/
|
||||||
|
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
|
||||||
|
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||||
|
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||||
|
Requires-Python: >=3.7
|
||||||
|
Description-Content-Type: text/x-rst
|
||||||
|
License-File: LICENSE.rst
|
||||||
|
Requires-Dist: MarkupSafe (>=2.0)
|
||||||
|
Provides-Extra: i18n
|
||||||
|
Requires-Dist: Babel (>=2.7) ; extra == 'i18n'
|
||||||
|
|
||||||
|
Jinja
|
||||||
|
=====
|
||||||
|
|
||||||
|
Jinja is a fast, expressive, extensible templating engine. Special
|
||||||
|
placeholders in the template allow writing code similar to Python
|
||||||
|
syntax. Then the template is passed data to render the final document.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
|
||||||
|
- Template inheritance and inclusion.
|
||||||
|
- Define and import macros within templates.
|
||||||
|
- HTML templates can use autoescaping to prevent XSS from untrusted
|
||||||
|
user input.
|
||||||
|
- A sandboxed environment can safely render untrusted templates.
|
||||||
|
- AsyncIO support for generating templates and calling async
|
||||||
|
functions.
|
||||||
|
- I18N support with Babel.
|
||||||
|
- Templates are compiled to optimized Python code just-in-time and
|
||||||
|
cached, or can be compiled ahead-of-time.
|
||||||
|
- Exceptions point to the correct line in templates to make debugging
|
||||||
|
easier.
|
||||||
|
- Extensible filters, tests, functions, and even syntax.
|
||||||
|
|
||||||
|
Jinja's philosophy is that while application logic belongs in Python if
|
||||||
|
possible, it shouldn't make the template designer's job difficult by
|
||||||
|
restricting functionality too much.
|
||||||
|
|
||||||
|
|
||||||
|
Installing
|
||||||
|
----------
|
||||||
|
|
||||||
|
Install and update using `pip`_:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip install -U Jinja2
|
||||||
|
|
||||||
|
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||||
|
|
||||||
|
|
||||||
|
In A Nutshell
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Members{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<ul>
|
||||||
|
{% for user in users %}
|
||||||
|
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
Donate
|
||||||
|
------
|
||||||
|
|
||||||
|
The Pallets organization develops and supports Jinja and other popular
|
||||||
|
packages. In order to grow the community of contributors and users, and
|
||||||
|
allow the maintainers to devote more time to the projects, `please
|
||||||
|
donate today`_.
|
||||||
|
|
||||||
|
.. _please donate today: https://palletsprojects.com/donate
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Documentation: https://jinja.palletsprojects.com/
|
||||||
|
- Changes: https://jinja.palletsprojects.com/changes/
|
||||||
|
- PyPI Releases: https://pypi.org/project/Jinja2/
|
||||||
|
- Source Code: https://github.com/pallets/jinja/
|
||||||
|
- Issue Tracker: https://github.com/pallets/jinja/issues/
|
||||||
|
- Website: https://palletsprojects.com/p/jinja/
|
||||||
|
- Twitter: https://twitter.com/PalletsTeam
|
||||||
|
- Chat: https://discord.gg/pallets
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
Jinja2-3.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
Jinja2-3.1.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
||||||
|
Jinja2-3.1.2.dist-info/METADATA,sha256=PZ6v2SIidMNixR7MRUX9f7ZWsPwtXanknqiZUmRbh4U,3539
|
||||||
|
Jinja2-3.1.2.dist-info/RECORD,,
|
||||||
|
Jinja2-3.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
Jinja2-3.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
||||||
|
Jinja2-3.1.2.dist-info/entry_points.txt,sha256=zRd62fbqIyfUpsRtU7EVIFyiu1tPwfgO7EvPErnxgTE,59
|
||||||
|
Jinja2-3.1.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
||||||
|
jinja2/__init__.py,sha256=8vGduD8ytwgD6GDSqpYc2m3aU-T7PKOAddvVXgGr_Fs,1927
|
||||||
|
jinja2/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/_identifier.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/async_utils.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/bccache.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/compiler.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/constants.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/debug.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/defaults.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/environment.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/exceptions.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/ext.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/filters.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/idtracking.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/lexer.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/loaders.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/meta.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/nativetypes.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/nodes.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/optimizer.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/parser.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/runtime.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/sandbox.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/tests.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/utils.cpython-312.pyc,,
|
||||||
|
jinja2/__pycache__/visitor.cpython-312.pyc,,
|
||||||
|
jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958
|
||||||
|
jinja2/async_utils.py,sha256=dHlbTeaxFPtAOQEYOGYh_PHcDT0rsDaUJAFDl_0XtTg,2472
|
||||||
|
jinja2/bccache.py,sha256=mhz5xtLxCcHRAa56azOhphIAe19u1we0ojifNMClDio,14061
|
||||||
|
jinja2/compiler.py,sha256=Gs-N8ThJ7OWK4-reKoO8Wh1ZXz95MVphBKNVf75qBr8,72172
|
||||||
|
jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433
|
||||||
|
jinja2/debug.py,sha256=iWJ432RadxJNnaMOPrjIDInz50UEgni3_HKuFXi2vuQ,6299
|
||||||
|
jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267
|
||||||
|
jinja2/environment.py,sha256=6uHIcc7ZblqOMdx_uYNKqRnnwAF0_nzbyeMP9FFtuh4,61349
|
||||||
|
jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
|
||||||
|
jinja2/ext.py,sha256=ivr3P7LKbddiXDVez20EflcO3q2aHQwz9P_PgWGHVqE,31502
|
||||||
|
jinja2/filters.py,sha256=9js1V-h2RlyW90IhLiBGLM2U-k6SCy2F4BUUMgB3K9Q,53509
|
||||||
|
jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704
|
||||||
|
jinja2/lexer.py,sha256=DW2nX9zk-6MWp65YR2bqqj0xqCvLtD-u9NWT8AnFRxQ,29726
|
||||||
|
jinja2/loaders.py,sha256=BfptfvTVpClUd-leMkHczdyPNYFzp_n7PKOJ98iyHOg,23207
|
||||||
|
jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
|
||||||
|
jinja2/nativetypes.py,sha256=DXgORDPRmVWgy034H0xL8eF7qYoK3DrMxs-935d0Fzk,4226
|
||||||
|
jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550
|
||||||
|
jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
|
||||||
|
jinja2/parser.py,sha256=nHd-DFHbiygvfaPtm9rcQXJChZG7DPsWfiEsqfwKerY,39595
|
||||||
|
jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
jinja2/runtime.py,sha256=5CmD5BjbEJxSiDNTFBeKCaq8qU4aYD2v6q2EluyExms,33476
|
||||||
|
jinja2/sandbox.py,sha256=Y0xZeXQnH6EX5VjaV2YixESxoepnRbW_3UeQosaBU3M,14584
|
||||||
|
jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
|
||||||
|
jinja2/utils.py,sha256=u9jXESxGn8ATZNVolwmkjUVu4SA-tLgV0W7PcSfPfdQ,23965
|
||||||
|
jinja2/visitor.py,sha256=MH14C6yq24G_KVtWzjwaI7Wg14PCJIYlWW1kpkxYak0,3568
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.37.1)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[babel.extractors]
|
||||||
|
jinja2 = jinja2.ext:babel_extract[i18n]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
jinja2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: MarkupSafe
|
||||||
|
Version: 2.1.5
|
||||||
|
Summary: Safely add untrusted strings to HTML/XML markup.
|
||||||
|
Home-page: https://palletsprojects.com/p/markupsafe/
|
||||||
|
Maintainer: Pallets
|
||||||
|
Maintainer-email: contact@palletsprojects.com
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
||||||
|
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
||||||
|
Project-URL: Source Code, https://github.com/pallets/markupsafe/
|
||||||
|
Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||||
|
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||||
|
Requires-Python: >=3.7
|
||||||
|
Description-Content-Type: text/x-rst
|
||||||
|
License-File: LICENSE.rst
|
||||||
|
|
||||||
|
MarkupSafe
|
||||||
|
==========
|
||||||
|
|
||||||
|
MarkupSafe implements a text object that escapes characters so it is
|
||||||
|
safe to use in HTML and XML. Characters that have special meanings are
|
||||||
|
replaced so that they display as the actual characters. This mitigates
|
||||||
|
injection attacks, meaning untrusted user input can safely be displayed
|
||||||
|
on a page.
|
||||||
|
|
||||||
|
|
||||||
|
Installing
|
||||||
|
----------
|
||||||
|
|
||||||
|
Install and update using `pip`_:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
pip install -U MarkupSafe
|
||||||
|
|
||||||
|
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> from markupsafe import Markup, escape
|
||||||
|
|
||||||
|
>>> # escape replaces special characters and wraps in Markup
|
||||||
|
>>> escape("<script>alert(document.cookie);</script>")
|
||||||
|
Markup('<script>alert(document.cookie);</script>')
|
||||||
|
|
||||||
|
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
||||||
|
>>> Markup("<strong>Hello</strong>")
|
||||||
|
Markup('<strong>hello</strong>')
|
||||||
|
|
||||||
|
>>> escape(Markup("<strong>Hello</strong>"))
|
||||||
|
Markup('<strong>hello</strong>')
|
||||||
|
|
||||||
|
>>> # Markup is a str subclass
|
||||||
|
>>> # methods and operators escape their arguments
|
||||||
|
>>> template = Markup("Hello <em>{name}</em>")
|
||||||
|
>>> template.format(name='"World"')
|
||||||
|
Markup('Hello <em>"World"</em>')
|
||||||
|
|
||||||
|
|
||||||
|
Donate
|
||||||
|
------
|
||||||
|
|
||||||
|
The Pallets organization develops and supports MarkupSafe and other
|
||||||
|
popular packages. In order to grow the community of contributors and
|
||||||
|
users, and allow the maintainers to devote more time to the projects,
|
||||||
|
`please donate today`_.
|
||||||
|
|
||||||
|
.. _please donate today: https://palletsprojects.com/donate
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Documentation: https://markupsafe.palletsprojects.com/
|
||||||
|
- Changes: https://markupsafe.palletsprojects.com/changes/
|
||||||
|
- PyPI Releases: https://pypi.org/project/MarkupSafe/
|
||||||
|
- Source Code: https://github.com/pallets/markupsafe/
|
||||||
|
- Issue Tracker: https://github.com/pallets/markupsafe/issues/
|
||||||
|
- Chat: https://discord.gg/pallets
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
||||||
|
MarkupSafe-2.1.5.dist-info/METADATA,sha256=2dRDPam6OZLfpX0wg1JN5P3u9arqACxVSfdGmsJU7o8,3003
|
||||||
|
MarkupSafe-2.1.5.dist-info/RECORD,,
|
||||||
|
MarkupSafe-2.1.5.dist-info/WHEEL,sha256=vJMp7mUkE-fMIYyE5xJ9Q2cYPnWVgHf20clVdwMSXAg,152
|
||||||
|
MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
||||||
|
markupsafe/__init__.py,sha256=r7VOTjUq7EMQ4v3p4R1LoVOGJg6ysfYRncLr34laRBs,10958
|
||||||
|
markupsafe/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
markupsafe/__pycache__/_native.cpython-312.pyc,,
|
||||||
|
markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713
|
||||||
|
markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083
|
||||||
|
markupsafe/_speedups.cpython-312-x86_64-linux-gnu.so,sha256=Y2jIPiSLPZlb82iRu9UUj27sbTui5o7SSoi-2SIXEUg,54072
|
||||||
|
markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229
|
||||||
|
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.42.0)
|
||||||
|
Root-Is-Purelib: false
|
||||||
|
Tag: cp312-cp312-manylinux_2_17_x86_64
|
||||||
|
Tag: cp312-cp312-manylinux2014_x86_64
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
markupsafe
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright 2010 Jason Kirtland
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: blinker
|
||||||
|
Version: 1.8.2
|
||||||
|
Summary: Fast, simple object-to-object and broadcast signaling
|
||||||
|
Author: Jason Kirtland
|
||||||
|
Maintainer-email: Pallets Ecosystem <contact@palletsprojects.com>
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Project-URL: Documentation, https://blinker.readthedocs.io
|
||||||
|
Project-URL: Source, https://github.com/pallets-eco/blinker/
|
||||||
|
|
||||||
|
# Blinker
|
||||||
|
|
||||||
|
Blinker provides a fast dispatching system that allows any number of
|
||||||
|
interested parties to subscribe to events, or "signals".
|
||||||
|
|
||||||
|
|
||||||
|
## Pallets Community Ecosystem
|
||||||
|
|
||||||
|
> [!IMPORTANT]\
|
||||||
|
> This project is part of the Pallets Community Ecosystem. Pallets is the open
|
||||||
|
> source organization that maintains Flask; Pallets-Eco enables community
|
||||||
|
> maintenance of related projects. If you are interested in helping maintain
|
||||||
|
> this project, please reach out on [the Pallets Discord server][discord].
|
||||||
|
>
|
||||||
|
> [discord]: https://discord.gg/pallets
|
||||||
|
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Signal receivers can subscribe to specific senders or receive signals
|
||||||
|
sent by any sender.
|
||||||
|
|
||||||
|
```pycon
|
||||||
|
>>> from blinker import signal
|
||||||
|
>>> started = signal('round-started')
|
||||||
|
>>> def each(round):
|
||||||
|
... print(f"Round {round}")
|
||||||
|
...
|
||||||
|
>>> started.connect(each)
|
||||||
|
|
||||||
|
>>> def round_two(round):
|
||||||
|
... print("This is round two.")
|
||||||
|
...
|
||||||
|
>>> started.connect(round_two, sender=2)
|
||||||
|
|
||||||
|
>>> for round in range(1, 4):
|
||||||
|
... started.send(round)
|
||||||
|
...
|
||||||
|
Round 1!
|
||||||
|
Round 2!
|
||||||
|
This is round two.
|
||||||
|
Round 3!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
blinker-1.8.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
blinker-1.8.2.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054
|
||||||
|
blinker-1.8.2.dist-info/METADATA,sha256=3tEx40hm9IEofyFqDPJsDPE9MAIEhtifapoSp7FqzuA,1633
|
||||||
|
blinker-1.8.2.dist-info/RECORD,,
|
||||||
|
blinker-1.8.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||||
|
blinker/__init__.py,sha256=ymyJY_PoTgBzaPgdr4dq-RRsGh7D-sYQIGMNp8Rx4qc,1577
|
||||||
|
blinker/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
blinker/__pycache__/_utilities.cpython-312.pyc,,
|
||||||
|
blinker/__pycache__/base.cpython-312.pyc,,
|
||||||
|
blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675
|
||||||
|
blinker/base.py,sha256=nIZJEtXQ8LLZZJrwVp2wQcdfCzDixvAHR9VpSWiyVcQ,22574
|
||||||
|
blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: flit 3.9.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
60
venv/lib/python3.12/site-packages/blinker/__init__.py
Normal file
60
venv/lib/python3.12/site-packages/blinker/__init__.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from .base import ANY
|
||||||
|
from .base import default_namespace
|
||||||
|
from .base import NamedSignal
|
||||||
|
from .base import Namespace
|
||||||
|
from .base import Signal
|
||||||
|
from .base import signal
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ANY",
|
||||||
|
"default_namespace",
|
||||||
|
"NamedSignal",
|
||||||
|
"Namespace",
|
||||||
|
"Signal",
|
||||||
|
"signal",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(name: str) -> t.Any:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if name == "__version__":
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"The '__version__' attribute is deprecated and will be removed in"
|
||||||
|
" Blinker 1.9.0. Use feature detection or"
|
||||||
|
" 'importlib.metadata.version(\"blinker\")' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return importlib.metadata.version("blinker")
|
||||||
|
|
||||||
|
if name == "receiver_connected":
|
||||||
|
from .base import _receiver_connected
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"The global 'receiver_connected' signal is deprecated and will be"
|
||||||
|
" removed in Blinker 1.9. Use 'Signal.receiver_connected' and"
|
||||||
|
" 'Signal.receiver_disconnected' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _receiver_connected
|
||||||
|
|
||||||
|
if name == "WeakNamespace":
|
||||||
|
from .base import _WeakNamespace
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
|
||||||
|
" Use 'Namespace' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _WeakNamespace
|
||||||
|
|
||||||
|
raise AttributeError(name)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
64
venv/lib/python3.12/site-packages/blinker/_utilities.py
Normal file
64
venv/lib/python3.12/site-packages/blinker/_utilities.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections.abc as c
|
||||||
|
import inspect
|
||||||
|
import typing as t
|
||||||
|
from weakref import ref
|
||||||
|
from weakref import WeakMethod
|
||||||
|
|
||||||
|
T = t.TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class Symbol:
|
||||||
|
"""A constant symbol, nicer than ``object()``. Repeated calls return the
|
||||||
|
same instance.
|
||||||
|
|
||||||
|
>>> Symbol('foo') is Symbol('foo')
|
||||||
|
True
|
||||||
|
>>> Symbol('foo')
|
||||||
|
foo
|
||||||
|
"""
|
||||||
|
|
||||||
|
symbols: t.ClassVar[dict[str, Symbol]] = {}
|
||||||
|
|
||||||
|
def __new__(cls, name: str) -> Symbol:
|
||||||
|
if name in cls.symbols:
|
||||||
|
return cls.symbols[name]
|
||||||
|
|
||||||
|
obj = super().__new__(cls)
|
||||||
|
cls.symbols[name] = obj
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __getnewargs__(self) -> tuple[t.Any, ...]:
|
||||||
|
return (self.name,)
|
||||||
|
|
||||||
|
|
||||||
|
def make_id(obj: object) -> c.Hashable:
|
||||||
|
"""Get a stable identifier for a receiver or sender, to be used as a dict
|
||||||
|
key or in a set.
|
||||||
|
"""
|
||||||
|
if inspect.ismethod(obj):
|
||||||
|
# The id of a bound method is not stable, but the id of the unbound
|
||||||
|
# function and instance are.
|
||||||
|
return id(obj.__func__), id(obj.__self__)
|
||||||
|
|
||||||
|
if isinstance(obj, (str, int)):
|
||||||
|
# Instances with the same value always compare equal and have the same
|
||||||
|
# hash, even if the id may change.
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# Assume other types are not hashable but will always be the same instance.
|
||||||
|
return id(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]:
|
||||||
|
if inspect.ismethod(obj):
|
||||||
|
return WeakMethod(obj, callback) # type: ignore[arg-type, return-value]
|
||||||
|
|
||||||
|
return ref(obj, callback)
|
||||||
621
venv/lib/python3.12/site-packages/blinker/base.py
Normal file
621
venv/lib/python3.12/site-packages/blinker/base.py
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections.abc as c
|
||||||
|
import typing as t
|
||||||
|
import warnings
|
||||||
|
import weakref
|
||||||
|
from collections import defaultdict
|
||||||
|
from contextlib import AbstractContextManager
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from functools import cached_property
|
||||||
|
from inspect import iscoroutinefunction
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
|
from ._utilities import make_id
|
||||||
|
from ._utilities import make_ref
|
||||||
|
from ._utilities import Symbol
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
F = t.TypeVar("F", bound=c.Callable[..., t.Any])
|
||||||
|
|
||||||
|
ANY = Symbol("ANY")
|
||||||
|
"""Symbol for "any sender"."""
|
||||||
|
|
||||||
|
ANY_ID = 0
|
||||||
|
|
||||||
|
|
||||||
|
class Signal:
|
||||||
|
"""A notification emitter.
|
||||||
|
|
||||||
|
:param doc: The docstring for the signal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ANY = ANY
|
||||||
|
"""An alias for the :data:`~blinker.ANY` sender symbol."""
|
||||||
|
|
||||||
|
set_class: type[set[t.Any]] = set
|
||||||
|
"""The set class to use for tracking connected receivers and senders.
|
||||||
|
Python's ``set`` is unordered. If receivers must be dispatched in the order
|
||||||
|
they were connected, an ordered set implementation can be used.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def receiver_connected(self) -> Signal:
|
||||||
|
"""Emitted at the end of each :meth:`connect` call.
|
||||||
|
|
||||||
|
The signal sender is the signal instance, and the :meth:`connect`
|
||||||
|
arguments are passed through: ``receiver``, ``sender``, and ``weak``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.2
|
||||||
|
"""
|
||||||
|
return Signal(doc="Emitted after a receiver connects.")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def receiver_disconnected(self) -> Signal:
|
||||||
|
"""Emitted at the end of each :meth:`disconnect` call.
|
||||||
|
|
||||||
|
The sender is the signal instance, and the :meth:`disconnect` arguments
|
||||||
|
are passed through: ``receiver`` and ``sender``.
|
||||||
|
|
||||||
|
This signal is emitted **only** when :meth:`disconnect` is called
|
||||||
|
explicitly. This signal cannot be emitted by an automatic disconnect
|
||||||
|
when a weakly referenced receiver or sender goes out of scope, as the
|
||||||
|
instance is no longer be available to be used as the sender for this
|
||||||
|
signal.
|
||||||
|
|
||||||
|
An alternative approach is available by subscribing to
|
||||||
|
:attr:`receiver_connected` and setting up a custom weakref cleanup
|
||||||
|
callback on weak receivers and senders.
|
||||||
|
|
||||||
|
.. versionadded:: 1.2
|
||||||
|
"""
|
||||||
|
return Signal(doc="Emitted after a receiver disconnects.")
|
||||||
|
|
||||||
|
def __init__(self, doc: str | None = None) -> None:
|
||||||
|
if doc:
|
||||||
|
self.__doc__ = doc
|
||||||
|
|
||||||
|
self.receivers: dict[
|
||||||
|
t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any]
|
||||||
|
] = {}
|
||||||
|
"""The map of connected receivers. Useful to quickly check if any
|
||||||
|
receivers are connected to the signal: ``if s.receivers:``. The
|
||||||
|
structure and data is not part of the public API, but checking its
|
||||||
|
boolean value is.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.is_muted: bool = False
|
||||||
|
self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
|
||||||
|
self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
|
||||||
|
self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {}
|
||||||
|
|
||||||
|
def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F:
|
||||||
|
"""Connect ``receiver`` to be called when the signal is sent by
|
||||||
|
``sender``.
|
||||||
|
|
||||||
|
:param receiver: The callable to call when :meth:`send` is called with
|
||||||
|
the given ``sender``, passing ``sender`` as a positional argument
|
||||||
|
along with any extra keyword arguments.
|
||||||
|
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
||||||
|
called when :meth:`send` is called with this sender. If ``ANY``, the
|
||||||
|
receiver will be called for any sender. A receiver may be connected
|
||||||
|
to multiple senders by calling :meth:`connect` multiple times.
|
||||||
|
:param weak: Track the receiver with a :mod:`weakref`. The receiver will
|
||||||
|
be automatically disconnected when it is garbage collected. When
|
||||||
|
connecting a receiver defined within a function, set to ``False``,
|
||||||
|
otherwise it will be disconnected when the function scope ends.
|
||||||
|
"""
|
||||||
|
receiver_id = make_id(receiver)
|
||||||
|
sender_id = ANY_ID if sender is ANY else make_id(sender)
|
||||||
|
|
||||||
|
if weak:
|
||||||
|
self.receivers[receiver_id] = make_ref(
|
||||||
|
receiver, self._make_cleanup_receiver(receiver_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.receivers[receiver_id] = receiver
|
||||||
|
|
||||||
|
self._by_sender[sender_id].add(receiver_id)
|
||||||
|
self._by_receiver[receiver_id].add(sender_id)
|
||||||
|
|
||||||
|
if sender is not ANY and sender_id not in self._weak_senders:
|
||||||
|
# store a cleanup for weakref-able senders
|
||||||
|
try:
|
||||||
|
self._weak_senders[sender_id] = make_ref(
|
||||||
|
sender, self._make_cleanup_sender(sender_id)
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers:
|
||||||
|
try:
|
||||||
|
self.receiver_connected.send(
|
||||||
|
self, receiver=receiver, sender=sender, weak=weak
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
# TODO no explanation or test for this
|
||||||
|
self.disconnect(receiver, sender)
|
||||||
|
raise
|
||||||
|
|
||||||
|
if _receiver_connected.receivers and self is not _receiver_connected:
|
||||||
|
try:
|
||||||
|
_receiver_connected.send(
|
||||||
|
self, receiver_arg=receiver, sender_arg=sender, weak_arg=weak
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
self.disconnect(receiver, sender)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return receiver
|
||||||
|
|
||||||
|
def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]:
|
||||||
|
"""Connect the decorated function to be called when the signal is sent
|
||||||
|
by ``sender``.
|
||||||
|
|
||||||
|
The decorated function will be called when :meth:`send` is called with
|
||||||
|
the given ``sender``, passing ``sender`` as a positional argument along
|
||||||
|
with any extra keyword arguments.
|
||||||
|
|
||||||
|
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
||||||
|
called when :meth:`send` is called with this sender. If ``ANY``, the
|
||||||
|
receiver will be called for any sender. A receiver may be connected
|
||||||
|
to multiple senders by calling :meth:`connect` multiple times.
|
||||||
|
:param weak: Track the receiver with a :mod:`weakref`. The receiver will
|
||||||
|
be automatically disconnected when it is garbage collected. When
|
||||||
|
connecting a receiver defined within a function, set to ``False``,
|
||||||
|
otherwise it will be disconnected when the function scope ends.=
|
||||||
|
|
||||||
|
.. versionadded:: 1.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(fn: F) -> F:
|
||||||
|
self.connect(fn, sender, weak)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def connected_to(
|
||||||
|
self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY
|
||||||
|
) -> c.Generator[None, None, None]:
|
||||||
|
"""A context manager that temporarily connects ``receiver`` to the
|
||||||
|
signal while a ``with`` block executes. When the block exits, the
|
||||||
|
receiver is disconnected. Useful for tests.
|
||||||
|
|
||||||
|
:param receiver: The callable to call when :meth:`send` is called with
|
||||||
|
the given ``sender``, passing ``sender`` as a positional argument
|
||||||
|
along with any extra keyword arguments.
|
||||||
|
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
||||||
|
called when :meth:`send` is called with this sender. If ``ANY``, the
|
||||||
|
receiver will be called for any sender.
|
||||||
|
|
||||||
|
.. versionadded:: 1.1
|
||||||
|
"""
|
||||||
|
self.connect(receiver, sender=sender, weak=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield None
|
||||||
|
finally:
|
||||||
|
self.disconnect(receiver)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def muted(self) -> c.Generator[None, None, None]:
|
||||||
|
"""A context manager that temporarily disables the signal. No receivers
|
||||||
|
will be called if the signal is sent, until the ``with`` block exits.
|
||||||
|
Useful for tests.
|
||||||
|
"""
|
||||||
|
self.is_muted = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield None
|
||||||
|
finally:
|
||||||
|
self.is_muted = False
|
||||||
|
|
||||||
|
def temporarily_connected_to(
|
||||||
|
self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY
|
||||||
|
) -> AbstractContextManager[None]:
|
||||||
|
"""Deprecated alias for :meth:`connected_to`.
|
||||||
|
|
||||||
|
.. deprecated:: 1.1
|
||||||
|
Renamed to ``connected_to``. Will be removed in Blinker 1.9.
|
||||||
|
|
||||||
|
.. versionadded:: 0.9
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"'temporarily_connected_to' is renamed to 'connected_to'. The old name is"
|
||||||
|
" deprecated and will be removed in Blinker 1.9.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.connected_to(receiver, sender)
|
||||||
|
|
||||||
|
def send(
|
||||||
|
self,
|
||||||
|
sender: t.Any | None = None,
|
||||||
|
/,
|
||||||
|
*,
|
||||||
|
_async_wrapper: c.Callable[
|
||||||
|
[c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any]
|
||||||
|
]
|
||||||
|
| None = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
|
||||||
|
"""Call all receivers that are connected to the given ``sender``
|
||||||
|
or :data:`ANY`. Each receiver is called with ``sender`` as a positional
|
||||||
|
argument along with any extra keyword arguments. Return a list of
|
||||||
|
``(receiver, return value)`` tuples.
|
||||||
|
|
||||||
|
The order receivers are called is undefined, but can be influenced by
|
||||||
|
setting :attr:`set_class`.
|
||||||
|
|
||||||
|
If a receiver raises an exception, that exception will propagate up.
|
||||||
|
This makes debugging straightforward, with an assumption that correctly
|
||||||
|
implemented receivers will not raise.
|
||||||
|
|
||||||
|
:param sender: Call receivers connected to this sender, in addition to
|
||||||
|
those connected to :data:`ANY`.
|
||||||
|
:param _async_wrapper: Will be called on any receivers that are async
|
||||||
|
coroutines to turn them into sync callables. For example, could run
|
||||||
|
the receiver with an event loop.
|
||||||
|
:param kwargs: Extra keyword arguments to pass to each receiver.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
Added the ``_async_wrapper`` argument.
|
||||||
|
"""
|
||||||
|
if self.is_muted:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for receiver in self.receivers_for(sender):
|
||||||
|
if iscoroutinefunction(receiver):
|
||||||
|
if _async_wrapper is None:
|
||||||
|
raise RuntimeError("Cannot send to a coroutine function.")
|
||||||
|
|
||||||
|
result = _async_wrapper(receiver)(sender, **kwargs)
|
||||||
|
else:
|
||||||
|
result = receiver(sender, **kwargs)
|
||||||
|
|
||||||
|
results.append((receiver, result))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
async def send_async(
|
||||||
|
self,
|
||||||
|
sender: t.Any | None = None,
|
||||||
|
/,
|
||||||
|
*,
|
||||||
|
_sync_wrapper: c.Callable[
|
||||||
|
[c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]
|
||||||
|
]
|
||||||
|
| None = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
|
||||||
|
"""Await all receivers that are connected to the given ``sender``
|
||||||
|
or :data:`ANY`. Each receiver is called with ``sender`` as a positional
|
||||||
|
argument along with any extra keyword arguments. Return a list of
|
||||||
|
``(receiver, return value)`` tuples.
|
||||||
|
|
||||||
|
The order receivers are called is undefined, but can be influenced by
|
||||||
|
setting :attr:`set_class`.
|
||||||
|
|
||||||
|
If a receiver raises an exception, that exception will propagate up.
|
||||||
|
This makes debugging straightforward, with an assumption that correctly
|
||||||
|
implemented receivers will not raise.
|
||||||
|
|
||||||
|
:param sender: Call receivers connected to this sender, in addition to
|
||||||
|
those connected to :data:`ANY`.
|
||||||
|
:param _sync_wrapper: Will be called on any receivers that are sync
|
||||||
|
callables to turn them into async coroutines. For example,
|
||||||
|
could call the receiver in a thread.
|
||||||
|
:param kwargs: Extra keyword arguments to pass to each receiver.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
"""
|
||||||
|
if self.is_muted:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for receiver in self.receivers_for(sender):
|
||||||
|
if not iscoroutinefunction(receiver):
|
||||||
|
if _sync_wrapper is None:
|
||||||
|
raise RuntimeError("Cannot send to a non-coroutine function.")
|
||||||
|
|
||||||
|
result = await _sync_wrapper(receiver)(sender, **kwargs)
|
||||||
|
else:
|
||||||
|
result = await receiver(sender, **kwargs)
|
||||||
|
|
||||||
|
results.append((receiver, result))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def has_receivers_for(self, sender: t.Any) -> bool:
|
||||||
|
"""Check if there is at least one receiver that will be called with the
|
||||||
|
given ``sender``. A receiver connected to :data:`ANY` will always be
|
||||||
|
called, regardless of sender. Does not check if weakly referenced
|
||||||
|
receivers are still live. See :meth:`receivers_for` for a stronger
|
||||||
|
search.
|
||||||
|
|
||||||
|
:param sender: Check for receivers connected to this sender, in addition
|
||||||
|
to those connected to :data:`ANY`.
|
||||||
|
"""
|
||||||
|
if not self.receivers:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._by_sender[ANY_ID]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if sender is ANY:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return make_id(sender) in self._by_sender
|
||||||
|
|
||||||
|
def receivers_for(
|
||||||
|
self, sender: t.Any
|
||||||
|
) -> c.Generator[c.Callable[..., t.Any], None, None]:
|
||||||
|
"""Yield each receiver to be called for ``sender``, in addition to those
|
||||||
|
to be called for :data:`ANY`. Weakly referenced receivers that are not
|
||||||
|
live will be disconnected and skipped.
|
||||||
|
|
||||||
|
:param sender: Yield receivers connected to this sender, in addition
|
||||||
|
to those connected to :data:`ANY`.
|
||||||
|
"""
|
||||||
|
# TODO: test receivers_for(ANY)
|
||||||
|
if not self.receivers:
|
||||||
|
return
|
||||||
|
|
||||||
|
sender_id = make_id(sender)
|
||||||
|
|
||||||
|
if sender_id in self._by_sender:
|
||||||
|
ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
|
||||||
|
else:
|
||||||
|
ids = self._by_sender[ANY_ID].copy()
|
||||||
|
|
||||||
|
for receiver_id in ids:
|
||||||
|
receiver = self.receivers.get(receiver_id)
|
||||||
|
|
||||||
|
if receiver is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(receiver, weakref.ref):
|
||||||
|
strong = receiver()
|
||||||
|
|
||||||
|
if strong is None:
|
||||||
|
self._disconnect(receiver_id, ANY_ID)
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield strong
|
||||||
|
else:
|
||||||
|
yield receiver
|
||||||
|
|
||||||
|
def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None:
|
||||||
|
"""Disconnect ``receiver`` from being called when the signal is sent by
|
||||||
|
``sender``.
|
||||||
|
|
||||||
|
:param receiver: A connected receiver callable.
|
||||||
|
:param sender: Disconnect from only this sender. By default, disconnect
|
||||||
|
from all senders.
|
||||||
|
"""
|
||||||
|
sender_id: c.Hashable
|
||||||
|
|
||||||
|
if sender is ANY:
|
||||||
|
sender_id = ANY_ID
|
||||||
|
else:
|
||||||
|
sender_id = make_id(sender)
|
||||||
|
|
||||||
|
receiver_id = make_id(receiver)
|
||||||
|
self._disconnect(receiver_id, sender_id)
|
||||||
|
|
||||||
|
if (
|
||||||
|
"receiver_disconnected" in self.__dict__
|
||||||
|
and self.receiver_disconnected.receivers
|
||||||
|
):
|
||||||
|
self.receiver_disconnected.send(self, receiver=receiver, sender=sender)
|
||||||
|
|
||||||
|
def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None:
|
||||||
|
if sender_id == ANY_ID:
|
||||||
|
if self._by_receiver.pop(receiver_id, None) is not None:
|
||||||
|
for bucket in self._by_sender.values():
|
||||||
|
bucket.discard(receiver_id)
|
||||||
|
|
||||||
|
self.receivers.pop(receiver_id, None)
|
||||||
|
else:
|
||||||
|
self._by_sender[sender_id].discard(receiver_id)
|
||||||
|
self._by_receiver[receiver_id].discard(sender_id)
|
||||||
|
|
||||||
|
def _make_cleanup_receiver(
|
||||||
|
self, receiver_id: c.Hashable
|
||||||
|
) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]:
|
||||||
|
"""Create a callback function to disconnect a weakly referenced
|
||||||
|
receiver when it is garbage collected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None:
|
||||||
|
self._disconnect(receiver_id, ANY_ID)
|
||||||
|
|
||||||
|
return cleanup
|
||||||
|
|
||||||
|
def _make_cleanup_sender(
|
||||||
|
self, sender_id: c.Hashable
|
||||||
|
) -> c.Callable[[weakref.ref[t.Any]], None]:
|
||||||
|
"""Create a callback function to disconnect all receivers for a weakly
|
||||||
|
referenced sender when it is garbage collected.
|
||||||
|
"""
|
||||||
|
assert sender_id != ANY_ID
|
||||||
|
|
||||||
|
def cleanup(ref: weakref.ref[t.Any]) -> None:
|
||||||
|
self._weak_senders.pop(sender_id, None)
|
||||||
|
|
||||||
|
for receiver_id in self._by_sender.pop(sender_id, ()):
|
||||||
|
self._by_receiver[receiver_id].discard(sender_id)
|
||||||
|
|
||||||
|
return cleanup
|
||||||
|
|
||||||
|
def _cleanup_bookkeeping(self) -> None:
|
||||||
|
"""Prune unused sender/receiver bookkeeping. Not threadsafe.
|
||||||
|
|
||||||
|
Connecting & disconnecting leaves behind a small amount of bookkeeping
|
||||||
|
data. Typical workloads using Blinker, for example in most web apps,
|
||||||
|
Flask, CLI scripts, etc., are not adversely affected by this
|
||||||
|
bookkeeping.
|
||||||
|
|
||||||
|
With a long-running process performing dynamic signal routing with high
|
||||||
|
volume, e.g. connecting to function closures, senders are all unique
|
||||||
|
object instances. Doing all of this over and over may cause memory usage
|
||||||
|
to grow due to extraneous bookkeeping. (An empty ``set`` for each stale
|
||||||
|
sender/receiver pair.)
|
||||||
|
|
||||||
|
This method will prune that bookkeeping away, with the caveat that such
|
||||||
|
pruning is not threadsafe. The risk is that cleanup of a fully
|
||||||
|
disconnected receiver/sender pair occurs while another thread is
|
||||||
|
connecting that same pair. If you are in the highly dynamic, unique
|
||||||
|
receiver/sender situation that has lead you to this method, that failure
|
||||||
|
mode is perhaps not a big deal for you.
|
||||||
|
"""
|
||||||
|
for mapping in (self._by_sender, self._by_receiver):
|
||||||
|
for ident, bucket in list(mapping.items()):
|
||||||
|
if not bucket:
|
||||||
|
mapping.pop(ident, None)
|
||||||
|
|
||||||
|
def _clear_state(self) -> None:
|
||||||
|
"""Disconnect all receivers and senders. Useful for tests."""
|
||||||
|
self._weak_senders.clear()
|
||||||
|
self.receivers.clear()
|
||||||
|
self._by_sender.clear()
|
||||||
|
self._by_receiver.clear()
|
||||||
|
|
||||||
|
|
||||||
|
_receiver_connected = Signal(
|
||||||
|
"""\
|
||||||
|
Sent by a :class:`Signal` after a receiver connects.
|
||||||
|
|
||||||
|
:argument: the Signal that was connected to
|
||||||
|
:keyword receiver_arg: the connected receiver
|
||||||
|
:keyword sender_arg: the sender to connect to
|
||||||
|
:keyword weak_arg: true if the connection to receiver_arg is a weak reference
|
||||||
|
|
||||||
|
.. deprecated:: 1.2
|
||||||
|
Individual signals have their own :attr:`~Signal.receiver_connected` and
|
||||||
|
:attr:`~Signal.receiver_disconnected` signals with a slightly simplified
|
||||||
|
call signature. This global signal will be removed in Blinker 1.9.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NamedSignal(Signal):
|
||||||
|
"""A named generic notification emitter. The name is not used by the signal
|
||||||
|
itself, but matches the key in the :class:`Namespace` that it belongs to.
|
||||||
|
|
||||||
|
:param name: The name of the signal within the namespace.
|
||||||
|
:param doc: The docstring for the signal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, doc: str | None = None) -> None:
|
||||||
|
super().__init__(doc)
|
||||||
|
|
||||||
|
#: The name of this signal.
|
||||||
|
self.name: str = name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
base = super().__repr__()
|
||||||
|
return f"{base[:-1]}; {self.name!r}>" # noqa: E702
|
||||||
|
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
|
||||||
|
class PNamespaceSignal(t.Protocol):
|
||||||
|
def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ...
|
||||||
|
|
||||||
|
# Python < 3.9
|
||||||
|
_NamespaceBase = dict[str, NamedSignal] # type: ignore[misc]
|
||||||
|
else:
|
||||||
|
_NamespaceBase = dict
|
||||||
|
|
||||||
|
|
||||||
|
class Namespace(_NamespaceBase):
|
||||||
|
"""A dict mapping names to signals."""
|
||||||
|
|
||||||
|
def signal(self, name: str, doc: str | None = None) -> NamedSignal:
|
||||||
|
"""Return the :class:`NamedSignal` for the given ``name``, creating it
|
||||||
|
if required. Repeated calls with the same name return the same signal.
|
||||||
|
|
||||||
|
:param name: The name of the signal.
|
||||||
|
:param doc: The docstring of the signal.
|
||||||
|
"""
|
||||||
|
if name not in self:
|
||||||
|
self[name] = NamedSignal(name, doc)
|
||||||
|
|
||||||
|
return self[name]
|
||||||
|
|
||||||
|
|
||||||
|
class _WeakNamespace(WeakValueDictionary): # type: ignore[type-arg]
|
||||||
|
"""A weak mapping of names to signals.
|
||||||
|
|
||||||
|
Automatically cleans up unused signals when the last reference goes out
|
||||||
|
of scope. This namespace implementation provides similar behavior to Blinker
|
||||||
|
<= 1.2.
|
||||||
|
|
||||||
|
.. deprecated:: 1.3
|
||||||
|
Will be removed in Blinker 1.9.
|
||||||
|
|
||||||
|
.. versionadded:: 1.3
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
warnings.warn(
|
||||||
|
"'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
|
||||||
|
" Use 'Namespace' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def signal(self, name: str, doc: str | None = None) -> NamedSignal:
|
||||||
|
"""Return the :class:`NamedSignal` for the given ``name``, creating it
|
||||||
|
if required. Repeated calls with the same name return the same signal.
|
||||||
|
|
||||||
|
:param name: The name of the signal.
|
||||||
|
:param doc: The docstring of the signal.
|
||||||
|
"""
|
||||||
|
if name not in self:
|
||||||
|
self[name] = NamedSignal(name, doc)
|
||||||
|
|
||||||
|
return self[name] # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
|
default_namespace: Namespace = Namespace()
|
||||||
|
"""A default :class:`Namespace` for creating named signals. :func:`signal`
|
||||||
|
creates a :class:`NamedSignal` in this namespace.
|
||||||
|
"""
|
||||||
|
|
||||||
|
signal: PNamespaceSignal = default_namespace.signal
|
||||||
|
"""Return a :class:`NamedSignal` in :data:`default_namespace` with the given
|
||||||
|
``name``, creating it if required. Repeated calls with the same name return the
|
||||||
|
same signal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(name: str) -> t.Any:
|
||||||
|
if name == "receiver_connected":
|
||||||
|
warnings.warn(
|
||||||
|
"The global 'receiver_connected' signal is deprecated and will be"
|
||||||
|
" removed in Blinker 1.9. Use 'Signal.receiver_connected' and"
|
||||||
|
" 'Signal.receiver_disconnected' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _receiver_connected
|
||||||
|
|
||||||
|
if name == "WeakNamespace":
|
||||||
|
warnings.warn(
|
||||||
|
"'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
|
||||||
|
" Use 'Namespace' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _WeakNamespace
|
||||||
|
|
||||||
|
raise AttributeError(name)
|
||||||
0
venv/lib/python3.12/site-packages/blinker/py.typed
Normal file
0
venv/lib/python3.12/site-packages/blinker/py.typed
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
This package contains a modified version of ca-bundle.crt:
|
||||||
|
|
||||||
|
ca-bundle.crt -- Bundle of CA Root Certificates
|
||||||
|
|
||||||
|
This is a bundle of X.509 certificates of public Certificate Authorities
|
||||||
|
(CA). These were automatically extracted from Mozilla's root certificates
|
||||||
|
file (certdata.txt). This file can be found in the mozilla source tree:
|
||||||
|
https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||||
|
It contains the certificates in PEM format and therefore
|
||||||
|
can be directly used with curl / libcurl / php_curl, or with
|
||||||
|
an Apache+mod_ssl webserver for SSL client authentication.
|
||||||
|
Just configure this file as the SSLCACertificateFile.#
|
||||||
|
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
||||||
|
one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: certifi
|
||||||
|
Version: 2024.8.30
|
||||||
|
Summary: Python package for providing Mozilla's CA Bundle.
|
||||||
|
Home-page: https://github.com/certifi/python-certifi
|
||||||
|
Author: Kenneth Reitz
|
||||||
|
Author-email: me@kennethreitz.com
|
||||||
|
License: MPL-2.0
|
||||||
|
Project-URL: Source, https://github.com/certifi/python-certifi
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
||||||
|
Classifier: Natural Language :: English
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3 :: Only
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Requires-Python: >=3.6
|
||||||
|
License-File: LICENSE
|
||||||
|
|
||||||
|
Certifi: Python SSL Certificates
|
||||||
|
================================
|
||||||
|
|
||||||
|
Certifi provides Mozilla's carefully curated collection of Root Certificates for
|
||||||
|
validating the trustworthiness of SSL certificates while verifying the identity
|
||||||
|
of TLS hosts. It has been extracted from the `Requests`_ project.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
``certifi`` is available on PyPI. Simply install it with ``pip``::
|
||||||
|
|
||||||
|
$ pip install certifi
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To reference the installed certificate authority (CA) bundle, you can use the
|
||||||
|
built-in function::
|
||||||
|
|
||||||
|
>>> import certifi
|
||||||
|
|
||||||
|
>>> certifi.where()
|
||||||
|
'/usr/local/lib/python3.7/site-packages/certifi/cacert.pem'
|
||||||
|
|
||||||
|
Or from the command line::
|
||||||
|
|
||||||
|
$ python -m certifi
|
||||||
|
/usr/local/lib/python3.7/site-packages/certifi/cacert.pem
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
|
|
||||||
|
.. _`Requests`: https://requests.readthedocs.io/en/master/
|
||||||
|
|
||||||
|
Addition/Removal of Certificates
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Certifi does not support any addition/removal or other modification of the
|
||||||
|
CA trust store content. This project is intended to provide a reliable and
|
||||||
|
highly portable root of trust to python deployments. Look to upstream projects
|
||||||
|
for methods to use alternate trust.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
certifi-2024.8.30.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
certifi-2024.8.30.dist-info/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989
|
||||||
|
certifi-2024.8.30.dist-info/METADATA,sha256=GhBHRVUN6a4ZdUgE_N5wmukJfyuoE-QyIl8Y3ifNQBM,2222
|
||||||
|
certifi-2024.8.30.dist-info/RECORD,,
|
||||||
|
certifi-2024.8.30.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
||||||
|
certifi-2024.8.30.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8
|
||||||
|
certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94
|
||||||
|
certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243
|
||||||
|
certifi/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
certifi/__pycache__/__main__.cpython-312.pyc,,
|
||||||
|
certifi/__pycache__/core.cpython-312.pyc,,
|
||||||
|
certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427
|
||||||
|
certifi/core.py,sha256=qRDDFyXVJwTB_EmoGppaXU_R9qCZvhl-EzxPMuV3nTA,4426
|
||||||
|
certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: setuptools (74.0.0)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
certifi
|
||||||
4
venv/lib/python3.12/site-packages/certifi/__init__.py
Normal file
4
venv/lib/python3.12/site-packages/certifi/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .core import contents, where
|
||||||
|
|
||||||
|
__all__ = ["contents", "where"]
|
||||||
|
__version__ = "2024.08.30"
|
||||||
12
venv/lib/python3.12/site-packages/certifi/__main__.py
Normal file
12
venv/lib/python3.12/site-packages/certifi/__main__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from certifi import contents, where
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("-c", "--contents", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.contents:
|
||||||
|
print(contents())
|
||||||
|
else:
|
||||||
|
print(where())
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
4929
venv/lib/python3.12/site-packages/certifi/cacert.pem
Normal file
4929
venv/lib/python3.12/site-packages/certifi/cacert.pem
Normal file
File diff suppressed because it is too large
Load diff
114
venv/lib/python3.12/site-packages/certifi/core.py
Normal file
114
venv/lib/python3.12/site-packages/certifi/core.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
"""
|
||||||
|
certifi.py
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
This module returns the installation location of cacert.pem or its contents.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
def exit_cacert_ctx() -> None:
|
||||||
|
_CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
|
||||||
|
from importlib.resources import as_file, files
|
||||||
|
|
||||||
|
_CACERT_CTX = None
|
||||||
|
_CACERT_PATH = None
|
||||||
|
|
||||||
|
def where() -> str:
|
||||||
|
# This is slightly terrible, but we want to delay extracting the file
|
||||||
|
# in cases where we're inside of a zipimport situation until someone
|
||||||
|
# actually calls where(), but we don't want to re-extract the file
|
||||||
|
# on every call of where(), so we'll do it once then store it in a
|
||||||
|
# global variable.
|
||||||
|
global _CACERT_CTX
|
||||||
|
global _CACERT_PATH
|
||||||
|
if _CACERT_PATH is None:
|
||||||
|
# This is slightly janky, the importlib.resources API wants you to
|
||||||
|
# manage the cleanup of this file, so it doesn't actually return a
|
||||||
|
# path, it returns a context manager that will give you the path
|
||||||
|
# when you enter it and will do any cleanup when you leave it. In
|
||||||
|
# the common case of not needing a temporary file, it will just
|
||||||
|
# return the file system location and the __exit__() is a no-op.
|
||||||
|
#
|
||||||
|
# We also have to hold onto the actual context manager, because
|
||||||
|
# it will do the cleanup whenever it gets garbage collected, so
|
||||||
|
# we will also store that at the global level as well.
|
||||||
|
_CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem"))
|
||||||
|
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
||||||
|
atexit.register(exit_cacert_ctx)
|
||||||
|
|
||||||
|
return _CACERT_PATH
|
||||||
|
|
||||||
|
def contents() -> str:
|
||||||
|
return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii")
|
||||||
|
|
||||||
|
elif sys.version_info >= (3, 7):
|
||||||
|
|
||||||
|
from importlib.resources import path as get_path, read_text
|
||||||
|
|
||||||
|
_CACERT_CTX = None
|
||||||
|
_CACERT_PATH = None
|
||||||
|
|
||||||
|
def where() -> str:
|
||||||
|
# This is slightly terrible, but we want to delay extracting the
|
||||||
|
# file in cases where we're inside of a zipimport situation until
|
||||||
|
# someone actually calls where(), but we don't want to re-extract
|
||||||
|
# the file on every call of where(), so we'll do it once then store
|
||||||
|
# it in a global variable.
|
||||||
|
global _CACERT_CTX
|
||||||
|
global _CACERT_PATH
|
||||||
|
if _CACERT_PATH is None:
|
||||||
|
# This is slightly janky, the importlib.resources API wants you
|
||||||
|
# to manage the cleanup of this file, so it doesn't actually
|
||||||
|
# return a path, it returns a context manager that will give
|
||||||
|
# you the path when you enter it and will do any cleanup when
|
||||||
|
# you leave it. In the common case of not needing a temporary
|
||||||
|
# file, it will just return the file system location and the
|
||||||
|
# __exit__() is a no-op.
|
||||||
|
#
|
||||||
|
# We also have to hold onto the actual context manager, because
|
||||||
|
# it will do the cleanup whenever it gets garbage collected, so
|
||||||
|
# we will also store that at the global level as well.
|
||||||
|
_CACERT_CTX = get_path("certifi", "cacert.pem")
|
||||||
|
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
||||||
|
atexit.register(exit_cacert_ctx)
|
||||||
|
|
||||||
|
return _CACERT_PATH
|
||||||
|
|
||||||
|
def contents() -> str:
|
||||||
|
return read_text("certifi", "cacert.pem", encoding="ascii")
|
||||||
|
|
||||||
|
else:
|
||||||
|
import os
|
||||||
|
import types
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
Package = Union[types.ModuleType, str]
|
||||||
|
Resource = Union[str, "os.PathLike"]
|
||||||
|
|
||||||
|
# This fallback will work for Python versions prior to 3.7 that lack the
|
||||||
|
# importlib.resources module but relies on the existing `where` function
|
||||||
|
# so won't address issues with environments like PyOxidizer that don't set
|
||||||
|
# __file__ on modules.
|
||||||
|
def read_text(
|
||||||
|
package: Package,
|
||||||
|
resource: Resource,
|
||||||
|
encoding: str = 'utf-8',
|
||||||
|
errors: str = 'strict'
|
||||||
|
) -> str:
|
||||||
|
with open(where(), encoding=encoding) as data:
|
||||||
|
return data.read()
|
||||||
|
|
||||||
|
# If we don't have importlib.resources, then we will just do the old logic
|
||||||
|
# of assuming we're on the filesystem and munge the path directly.
|
||||||
|
def where() -> str:
|
||||||
|
f = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
return os.path.join(f, "cacert.pem")
|
||||||
|
|
||||||
|
def contents() -> str:
|
||||||
|
return read_text("certifi", "cacert.pem", encoding="ascii")
|
||||||
0
venv/lib/python3.12/site-packages/certifi/py.typed
Normal file
0
venv/lib/python3.12/site-packages/certifi/py.typed
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
Except when otherwise stated (look for LICENSE files in directories or
|
||||||
|
information at the beginning of each file) all software and
|
||||||
|
documentation is licensed as follows:
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: cffi
|
||||||
|
Version: 1.17.1
|
||||||
|
Summary: Foreign Function Interface for Python calling C code.
|
||||||
|
Home-page: http://cffi.readthedocs.org
|
||||||
|
Author: Armin Rigo, Maciej Fijalkowski
|
||||||
|
Author-email: python-cffi@googlegroups.com
|
||||||
|
License: MIT
|
||||||
|
Project-URL: Documentation, http://cffi.readthedocs.org/
|
||||||
|
Project-URL: Source Code, https://github.com/python-cffi/cffi
|
||||||
|
Project-URL: Issue Tracker, https://github.com/python-cffi/cffi/issues
|
||||||
|
Project-URL: Changelog, https://cffi.readthedocs.io/en/latest/whatsnew.html
|
||||||
|
Project-URL: Downloads, https://github.com/python-cffi/cffi/releases
|
||||||
|
Project-URL: Contact, https://groups.google.com/forum/#!forum/python-cffi
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
License-File: LICENSE
|
||||||
|
Requires-Dist: pycparser
|
||||||
|
|
||||||
|
|
||||||
|
CFFI
|
||||||
|
====
|
||||||
|
|
||||||
|
Foreign Function Interface for Python calling C code.
|
||||||
|
Please see the `Documentation <http://cffi.readthedocs.org/>`_.
|
||||||
|
|
||||||
|
Contact
|
||||||
|
-------
|
||||||
|
|
||||||
|
`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
_cffi_backend.cpython-312-x86_64-linux-gnu.so,sha256=-fK60bkCudr6tjAHt4dA3x_CHaOWgVs_Lb2J0JGO3Po,1114632
|
||||||
|
cffi-1.17.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
cffi-1.17.1.dist-info/LICENSE,sha256=BLgPWwd7vtaICM_rreteNSPyqMmpZJXFh72W3x6sKjM,1294
|
||||||
|
cffi-1.17.1.dist-info/METADATA,sha256=u6nuvP_qPJKu2zvIbi2zkGzVu7KjnnRIYUFyIrOY3j4,1531
|
||||||
|
cffi-1.17.1.dist-info/RECORD,,
|
||||||
|
cffi-1.17.1.dist-info/WHEEL,sha256=h7F_RlbsFAwUaa98BSEEv6RQhdTqVo2FhuJDzTSKXxc,151
|
||||||
|
cffi-1.17.1.dist-info/entry_points.txt,sha256=y6jTxnyeuLnL-XJcDv8uML3n6wyYiGRg8MTp_QGJ9Ho,75
|
||||||
|
cffi-1.17.1.dist-info/top_level.txt,sha256=rE7WR3rZfNKxWI9-jn6hsHCAl7MDkB-FmuQbxWjFehQ,19
|
||||||
|
cffi/__init__.py,sha256=H6t_ebva6EeHpUuItFLW1gbRp94eZRNJODLaWKdbx1I,513
|
||||||
|
cffi/__pycache__/__init__.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/_imp_emulation.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/_shimmed_dist_utils.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/api.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/backend_ctypes.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/cffi_opcode.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/commontypes.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/cparser.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/error.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/ffiplatform.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/lock.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/model.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/pkgconfig.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/recompiler.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/setuptools_ext.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/vengine_cpy.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/vengine_gen.cpython-312.pyc,,
|
||||||
|
cffi/__pycache__/verifier.cpython-312.pyc,,
|
||||||
|
cffi/_cffi_errors.h,sha256=zQXt7uR_m8gUW-fI2hJg0KoSkJFwXv8RGUkEDZ177dQ,3908
|
||||||
|
cffi/_cffi_include.h,sha256=Exhmgm9qzHWzWivjfTe0D7Xp4rPUkVxdNuwGhMTMzbw,15055
|
||||||
|
cffi/_embedding.h,sha256=EDKw5QrLvQoe3uosXB3H1xPVTYxsn33eV3A43zsA_Fw,18787
|
||||||
|
cffi/_imp_emulation.py,sha256=RxREG8zAbI2RPGBww90u_5fi8sWdahpdipOoPzkp7C0,2960
|
||||||
|
cffi/_shimmed_dist_utils.py,sha256=Bjj2wm8yZbvFvWEx5AEfmqaqZyZFhYfoyLLQHkXZuao,2230
|
||||||
|
cffi/api.py,sha256=alBv6hZQkjpmZplBphdaRn2lPO9-CORs_M7ixabvZWI,42169
|
||||||
|
cffi/backend_ctypes.py,sha256=h5ZIzLc6BFVXnGyc9xPqZWUS7qGy7yFSDqXe68Sa8z4,42454
|
||||||
|
cffi/cffi_opcode.py,sha256=JDV5l0R0_OadBX_uE7xPPTYtMdmpp8I9UYd6av7aiDU,5731
|
||||||
|
cffi/commontypes.py,sha256=7N6zPtCFlvxXMWhHV08psUjdYIK2XgsN3yo5dgua_v4,2805
|
||||||
|
cffi/cparser.py,sha256=0qI3mEzZSNVcCangoyXOoAcL-RhpQL08eG8798T024s,44789
|
||||||
|
cffi/error.py,sha256=v6xTiS4U0kvDcy4h_BDRo5v39ZQuj-IMRYLv5ETddZs,877
|
||||||
|
cffi/ffiplatform.py,sha256=avxFjdikYGJoEtmJO7ewVmwG_VEVl6EZ_WaNhZYCqv4,3584
|
||||||
|
cffi/lock.py,sha256=l9TTdwMIMpi6jDkJGnQgE9cvTIR7CAntIJr8EGHt3pY,747
|
||||||
|
cffi/model.py,sha256=W30UFQZE73jL5Mx5N81YT77us2W2iJjTm0XYfnwz1cg,21797
|
||||||
|
cffi/parse_c_type.h,sha256=OdwQfwM9ktq6vlCB43exFQmxDBtj2MBNdK8LYl15tjw,5976
|
||||||
|
cffi/pkgconfig.py,sha256=LP1w7vmWvmKwyqLaU1Z243FOWGNQMrgMUZrvgFuOlco,4374
|
||||||
|
cffi/recompiler.py,sha256=sim4Tm7lamt2Jn8uzKN0wMYp6ODByk3g7of47-h9LD4,65367
|
||||||
|
cffi/setuptools_ext.py,sha256=-ebj79lO2_AUH-kRcaja2pKY1Z_5tloGwsJgzK8P3Cc,8871
|
||||||
|
cffi/vengine_cpy.py,sha256=8UagT6ZEOZf6Dju7_CfNulue8CnsHLEzJYhnqUhoF04,43752
|
||||||
|
cffi/vengine_gen.py,sha256=DUlEIrDiVin1Pnhn1sfoamnS5NLqfJcOdhRoeSNeJRg,26939
|
||||||
|
cffi/verifier.py,sha256=oX8jpaohg2Qm3aHcznidAdvrVm5N4sQYG0a3Eo5mIl4,11182
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: setuptools (74.1.1)
|
||||||
|
Root-Is-Purelib: false
|
||||||
|
Tag: cp312-cp312-manylinux_2_17_x86_64
|
||||||
|
Tag: cp312-cp312-manylinux2014_x86_64
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[distutils.setup_keywords]
|
||||||
|
cffi_modules = cffi.setuptools_ext:cffi_modules
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
_cffi_backend
|
||||||
|
cffi
|
||||||
14
venv/lib/python3.12/site-packages/cffi/__init__.py
Normal file
14
venv/lib/python3.12/site-packages/cffi/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError',
|
||||||
|
'FFIError']
|
||||||
|
|
||||||
|
from .api import FFI
|
||||||
|
from .error import CDefError, FFIError, VerificationError, VerificationMissing
|
||||||
|
from .error import PkgConfigError
|
||||||
|
|
||||||
|
__version__ = "1.17.1"
|
||||||
|
__version_info__ = (1, 17, 1)
|
||||||
|
|
||||||
|
# The verifier module file names are based on the CRC32 of a string that
|
||||||
|
# contains the following version number. It may be older than __version__
|
||||||
|
# if nothing is clearly incompatible.
|
||||||
|
__version_verifier_modules__ = "0.8.6"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue