aiwaf 0.1.9.1.2__py3-none-any.whl → 0.1.9.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiwaf might be problematic. Click here for more details.

@@ -0,0 +1,457 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import re
5
+ from django.core.management.base import BaseCommand
6
+ from django.conf import settings
7
+ import pkg_resources
8
+ import requests
9
+ from packaging import version, specifiers
10
+
11
+
12
+ class Command(BaseCommand):
13
+ help = 'Check project dependencies for updates'
14
+
15
+ def add_arguments(self, parser):
16
+ parser.add_argument(
17
+ '--format',
18
+ choices=['table', 'json'],
19
+ default='table',
20
+ help='Output format (default: table)'
21
+ )
22
+ parser.add_argument(
23
+ '--check-security',
24
+ action='store_true',
25
+ help='Also check for known security vulnerabilities'
26
+ )
27
+ parser.add_argument(
28
+ '--check-compatibility',
29
+ action='store_true',
30
+ default=True,
31
+ help='Check for package compatibility issues (default: True)'
32
+ )
33
+
34
+ def handle(self, *args, **options):
35
+ self.stdout.write(self.style.SUCCESS('🔍 Checking project dependencies...\n'))
36
+
37
+ try:
38
+ dependencies = self.get_project_dependencies()
39
+ if not dependencies:
40
+ self.stdout.write(self.style.WARNING('No dependencies found to check.'))
41
+ return
42
+
43
+ results = []
44
+ for dep_name, current_constraint in dependencies.items():
45
+ result = self.check_package(dep_name, current_constraint)
46
+ results.append(result)
47
+
48
+ self.display_results(results, options['format'])
49
+
50
+ if options['check_compatibility']:
51
+ self.check_compatibility(results)
52
+
53
+ if options['check_security']:
54
+ self.check_security_vulnerabilities(dependencies)
55
+
56
+ except Exception as e:
57
+ self.stdout.write(self.style.ERROR(f'Error checking dependencies: {e}'))
58
+
59
+ def get_project_dependencies(self):
60
+ """Get dependencies from pyproject.toml or requirements.txt"""
61
+ dependencies = {}
62
+
63
+ # Check pyproject.toml first
64
+ pyproject_path = os.path.join(settings.BASE_DIR, 'pyproject.toml')
65
+ if os.path.exists(pyproject_path):
66
+ dependencies.update(self.parse_pyproject_toml(pyproject_path))
67
+
68
+ # Check requirements.txt
69
+ requirements_path = os.path.join(settings.BASE_DIR, 'requirements.txt')
70
+ if os.path.exists(requirements_path):
71
+ dependencies.update(self.parse_requirements_txt(requirements_path))
72
+
73
+ return dependencies
74
+
75
+ def parse_pyproject_toml(self, filepath):
76
+ """Parse dependencies from pyproject.toml"""
77
+ dependencies = {}
78
+ try:
79
+ with open(filepath, 'r') as f:
80
+ content = f.read()
81
+
82
+ # Simple regex to extract dependencies
83
+ import re
84
+ deps_match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
85
+ if deps_match:
86
+ deps_text = deps_match.group(1)
87
+ for line in deps_text.split(','):
88
+ line = line.strip().strip('"\'')
89
+ if line and not line.startswith('#'):
90
+ if '>=' in line or '==' in line or '~=' in line or '<' in line:
91
+ name = re.split(r'[><=~!]', line)[0].strip()
92
+ dependencies[name] = line
93
+ else:
94
+ dependencies[line] = line
95
+ except Exception as e:
96
+ self.stdout.write(self.style.WARNING(f'Could not parse pyproject.toml: {e}'))
97
+
98
+ return dependencies
99
+
100
+ def parse_requirements_txt(self, filepath):
101
+ """Parse dependencies from requirements.txt"""
102
+ dependencies = {}
103
+ try:
104
+ with open(filepath, 'r') as f:
105
+ for line in f:
106
+ line = line.strip()
107
+ if line and not line.startswith('#'):
108
+ if '>=' in line or '==' in line or '~=' in line or '<' in line:
109
+ name = re.split(r'[><=~!]', line)[0].strip()
110
+ dependencies[name] = line
111
+ else:
112
+ dependencies[line] = line
113
+ except Exception as e:
114
+ self.stdout.write(self.style.WARNING(f'Could not parse requirements.txt: {e}'))
115
+
116
+ return dependencies
117
+
118
+ def check_package(self, package_name, constraint):
119
+ """Check a single package for updates"""
120
+ try:
121
+ # Get currently installed version
122
+ try:
123
+ installed_version = pkg_resources.get_distribution(package_name).version
124
+ except pkg_resources.DistributionNotFound:
125
+ return {
126
+ 'name': package_name,
127
+ 'constraint': constraint,
128
+ 'installed': 'Not installed',
129
+ 'latest': 'Unknown',
130
+ 'status': 'not_installed',
131
+ 'update_available': False
132
+ }
133
+
134
+ # Get latest version from PyPI
135
+ latest_version = self.get_latest_version(package_name)
136
+
137
+ # Determine status
138
+ status = 'up_to_date'
139
+ update_available = False
140
+
141
+ if latest_version and version.parse(installed_version) < version.parse(latest_version):
142
+ status = 'outdated'
143
+ update_available = True
144
+ elif latest_version and version.parse(installed_version) > version.parse(latest_version):
145
+ status = 'ahead'
146
+
147
+ return {
148
+ 'name': package_name,
149
+ 'constraint': constraint,
150
+ 'installed': installed_version,
151
+ 'latest': latest_version or 'Unknown',
152
+ 'status': status,
153
+ 'update_available': update_available
154
+ }
155
+
156
+ except Exception as e:
157
+ return {
158
+ 'name': package_name,
159
+ 'constraint': constraint,
160
+ 'installed': 'Error',
161
+ 'latest': 'Error',
162
+ 'status': 'error',
163
+ 'update_available': False,
164
+ 'error': str(e)
165
+ }
166
+
167
+ def get_latest_version(self, package_name):
168
+ """Get latest version from PyPI"""
169
+ try:
170
+ response = requests.get(f'https://pypi.org/pypi/{package_name}/json', timeout=10)
171
+ if response.status_code == 200:
172
+ data = response.json()
173
+ return data['info']['version']
174
+ except Exception:
175
+ pass
176
+ return None
177
+
178
+ def display_results(self, results, format_type):
179
+ """Display results in specified format"""
180
+ if format_type == 'json':
181
+ import json
182
+ self.stdout.write(json.dumps(results, indent=2))
183
+ return
184
+
185
+ # Table format
186
+ up_to_date = [r for r in results if r['status'] == 'up_to_date']
187
+ outdated = [r for r in results if r['status'] == 'outdated']
188
+ not_installed = [r for r in results if r['status'] == 'not_installed']
189
+ errors = [r for r in results if r['status'] == 'error']
190
+
191
+ # Summary
192
+ total = len(results)
193
+ self.stdout.write(f"📊 Summary: {total} packages checked")
194
+ self.stdout.write(f" ✅ Up to date: {len(up_to_date)}")
195
+ self.stdout.write(f" ⚠️ Outdated: {len(outdated)}")
196
+ self.stdout.write(f" ❌ Not installed: {len(not_installed)}")
197
+ self.stdout.write(f" 🔥 Errors: {len(errors)}\n")
198
+
199
+ # Outdated packages
200
+ if outdated:
201
+ self.stdout.write(self.style.WARNING("⚠️ OUTDATED PACKAGES:"))
202
+ self.stdout.write("─" * 80)
203
+ for pkg in outdated:
204
+ self.stdout.write(
205
+ f"📦 {pkg['name']:<20} {pkg['installed']:<12} → {pkg['latest']:<12} "
206
+ f"(constraint: {pkg['constraint']})"
207
+ )
208
+ self.stdout.write("")
209
+
210
+ # Up to date packages
211
+ if up_to_date:
212
+ self.stdout.write(self.style.SUCCESS("✅ UP TO DATE PACKAGES:"))
213
+ self.stdout.write("─" * 80)
214
+ for pkg in up_to_date:
215
+ self.stdout.write(
216
+ f"📦 {pkg['name']:<20} {pkg['installed']:<12} "
217
+ f"(constraint: {pkg['constraint']})"
218
+ )
219
+ self.stdout.write("")
220
+
221
+ # Not installed packages
222
+ if not_installed:
223
+ self.stdout.write(self.style.ERROR("❌ NOT INSTALLED PACKAGES:"))
224
+ self.stdout.write("─" * 80)
225
+ for pkg in not_installed:
226
+ self.stdout.write(f"📦 {pkg['name']:<20} (constraint: {pkg['constraint']})")
227
+ self.stdout.write("")
228
+
229
+ # Errors
230
+ if errors:
231
+ self.stdout.write(self.style.ERROR("🔥 PACKAGES WITH ERRORS:"))
232
+ self.stdout.write("─" * 80)
233
+ for pkg in errors:
234
+ error_msg = pkg.get('error', 'Unknown error')
235
+ self.stdout.write(f"📦 {pkg['name']:<20} Error: {error_msg}")
236
+ self.stdout.write("")
237
+
238
+ # Update command suggestions
239
+ if outdated:
240
+ self.stdout.write(self.style.HTTP_INFO("💡 To update outdated packages, run:"))
241
+ update_cmd = "pip install --upgrade " + " ".join([pkg['name'] for pkg in outdated])
242
+ self.stdout.write(f" {update_cmd}")
243
+
244
+ def check_compatibility(self, results):
245
+ """Check for package compatibility issues"""
246
+ self.stdout.write(self.style.HTTP_INFO("\n🔍 Checking package compatibility..."))
247
+
248
+ # Known compatibility rules
249
+ compatibility_rules = self.get_compatibility_rules()
250
+ conflicts = []
251
+ warnings = []
252
+
253
+ # Check for version conflicts
254
+ for pkg in results:
255
+ if pkg['status'] in ['up_to_date', 'outdated', 'ahead']:
256
+ pkg_name = pkg['name'].lower()
257
+ installed_ver = pkg['installed']
258
+ latest_ver = pkg['latest']
259
+
260
+ # Check against compatibility rules
261
+ for rule in compatibility_rules:
262
+ if rule['package'] == pkg_name:
263
+ conflict = self.check_package_rule(pkg, rule, results)
264
+ if conflict:
265
+ if conflict['severity'] == 'error':
266
+ conflicts.append(conflict)
267
+ else:
268
+ warnings.append(conflict)
269
+
270
+ # Check for missing dependencies of packages
271
+ missing_deps = self.check_missing_dependencies(results)
272
+ conflicts.extend(missing_deps)
273
+
274
+ # Display results
275
+ if conflicts:
276
+ self.stdout.write(self.style.ERROR("\n❌ COMPATIBILITY CONFLICTS:"))
277
+ self.stdout.write("─" * 80)
278
+ for conflict in conflicts:
279
+ self.stdout.write(f"🚨 {conflict['message']}")
280
+ if 'suggestion' in conflict:
281
+ self.stdout.write(f" 💡 {conflict['suggestion']}")
282
+ self.stdout.write("")
283
+
284
+ if warnings:
285
+ self.stdout.write(self.style.WARNING("\n⚠️ COMPATIBILITY WARNINGS:"))
286
+ self.stdout.write("─" * 80)
287
+ for warning in warnings:
288
+ self.stdout.write(f"⚠️ {warning['message']}")
289
+ if 'suggestion' in warning:
290
+ self.stdout.write(f" 💡 {warning['suggestion']}")
291
+ self.stdout.write("")
292
+
293
+ if not conflicts and not warnings:
294
+ self.stdout.write(self.style.SUCCESS("✅ All packages appear to be compatible!"))
295
+
296
+ def get_compatibility_rules(self):
297
+ """Define known compatibility rules between packages"""
298
+ return [
299
+ {
300
+ 'package': 'numpy',
301
+ 'conflicts_with': [
302
+ {
303
+ 'package': 'pandas',
304
+ 'numpy_versions': '>=2.0',
305
+ 'pandas_versions': '<2.1',
306
+ 'message': 'NumPy 2.0+ requires pandas 2.1+ for compatibility',
307
+ 'severity': 'error'
308
+ }
309
+ ]
310
+ },
311
+ {
312
+ 'package': 'scikit-learn',
313
+ 'conflicts_with': [
314
+ {
315
+ 'package': 'numpy',
316
+ 'sklearn_versions': '>=1.3',
317
+ 'numpy_versions': '<1.19',
318
+ 'message': 'scikit-learn 1.3+ requires NumPy 1.19+',
319
+ 'severity': 'error'
320
+ },
321
+ {
322
+ 'package': 'pandas',
323
+ 'sklearn_versions': '>=1.2',
324
+ 'pandas_versions': '<1.0',
325
+ 'message': 'scikit-learn 1.2+ works best with pandas 1.0+',
326
+ 'severity': 'warning'
327
+ }
328
+ ]
329
+ },
330
+ {
331
+ 'package': 'pandas',
332
+ 'conflicts_with': [
333
+ {
334
+ 'package': 'numpy',
335
+ 'pandas_versions': '>=2.0',
336
+ 'numpy_versions': '<1.22',
337
+ 'message': 'pandas 2.0+ requires NumPy 1.22+',
338
+ 'severity': 'error'
339
+ }
340
+ ]
341
+ },
342
+ {
343
+ 'package': 'django',
344
+ 'conflicts_with': [
345
+ {
346
+ 'package': 'pandas',
347
+ 'django_versions': '>=4.0',
348
+ 'pandas_versions': '<1.3',
349
+ 'message': 'Django 4.0+ works better with pandas 1.3+',
350
+ 'severity': 'warning'
351
+ }
352
+ ]
353
+ }
354
+ ]
355
+
356
+ def check_package_rule(self, pkg, rule, all_results):
357
+ """Check a specific package against compatibility rules"""
358
+ pkg_name = pkg['name'].lower()
359
+
360
+ for conflict_rule in rule.get('conflicts_with', []):
361
+ # Find the conflicting package in results
362
+ conflicting_pkg = None
363
+ for other_pkg in all_results:
364
+ if other_pkg['name'].lower() == conflict_rule['package']:
365
+ conflicting_pkg = other_pkg
366
+ break
367
+
368
+ if not conflicting_pkg or conflicting_pkg['status'] == 'not_installed':
369
+ continue
370
+
371
+ # Check version constraints
372
+ pkg_version = pkg['installed']
373
+ other_version = conflicting_pkg['installed']
374
+
375
+ try:
376
+ # Check if this package version matches the conflict rule
377
+ pkg_constraint_key = f"{pkg_name}_versions"
378
+ other_constraint_key = f"{conflict_rule['package']}_versions"
379
+
380
+ pkg_constraint = conflict_rule.get(pkg_constraint_key)
381
+ other_constraint = conflict_rule.get(other_constraint_key)
382
+
383
+ pkg_matches = self.version_matches_constraint(pkg_version, pkg_constraint) if pkg_constraint else True
384
+ other_matches = self.version_matches_constraint(other_version, other_constraint) if other_constraint else True
385
+
386
+ if pkg_matches and other_matches:
387
+ suggestion = f"Consider updating {conflict_rule['package']} or {pkg_name}"
388
+ if conflict_rule['severity'] == 'error':
389
+ suggestion = f"REQUIRED: Update {conflict_rule['package']} or downgrade {pkg_name}"
390
+
391
+ return {
392
+ 'message': f"{pkg_name} {pkg_version} + {conflict_rule['package']} {other_version}: {conflict_rule['message']}",
393
+ 'suggestion': suggestion,
394
+ 'severity': conflict_rule['severity']
395
+ }
396
+ except Exception:
397
+ continue
398
+
399
+ return None
400
+
401
+ def version_matches_constraint(self, version_str, constraint_str):
402
+ """Check if a version matches a constraint"""
403
+ try:
404
+ spec = specifiers.SpecifierSet(constraint_str)
405
+ return version.parse(version_str) in spec
406
+ except Exception:
407
+ return False
408
+
409
+ def check_missing_dependencies(self, results):
410
+ """Check for missing dependencies that packages might need"""
411
+ conflicts = []
412
+
413
+ # Get installed packages
414
+ installed_packages = {pkg['name'].lower(): pkg for pkg in results
415
+ if pkg['status'] in ['up_to_date', 'outdated', 'ahead']}
416
+
417
+ # Check key dependencies
418
+ key_dependencies = {
419
+ 'pandas': ['numpy'],
420
+ 'scikit-learn': ['numpy', 'joblib'],
421
+ 'django': [] # Django has its own dependency management
422
+ }
423
+
424
+ for pkg_name, required_deps in key_dependencies.items():
425
+ if pkg_name in installed_packages:
426
+ for dep in required_deps:
427
+ if dep not in installed_packages:
428
+ conflicts.append({
429
+ 'message': f"{pkg_name} requires {dep} but it's not installed",
430
+ 'suggestion': f"Install {dep}: pip install {dep}",
431
+ 'severity': 'error'
432
+ })
433
+
434
+ return conflicts
435
+
436
+ def check_security_vulnerabilities(self, dependencies):
437
+ """Check for known security vulnerabilities using safety"""
438
+ self.stdout.write(self.style.HTTP_INFO("\n🔒 Checking for security vulnerabilities..."))
439
+ try:
440
+ # Try to use safety if available
441
+ result = subprocess.run(['safety', 'check', '--json'],
442
+ capture_output=True, text=True, timeout=30)
443
+ if result.returncode == 0:
444
+ import json
445
+ vulns = json.loads(result.stdout)
446
+ if vulns:
447
+ self.stdout.write(self.style.ERROR(f"⚠️ Found {len(vulns)} security vulnerabilities!"))
448
+ for vuln in vulns[:5]: # Show first 5
449
+ self.stdout.write(f" 📦 {vuln.get('package')}: {vuln.get('vulnerability')}")
450
+ else:
451
+ self.stdout.write(self.style.SUCCESS("✅ No known security vulnerabilities found"))
452
+ else:
453
+ self.stdout.write(self.style.WARNING("Could not check vulnerabilities (safety not available)"))
454
+ except (subprocess.TimeoutExpired, FileNotFoundError):
455
+ self.stdout.write(self.style.WARNING("Security check skipped (install 'safety' package for vulnerability scanning)"))
456
+ except Exception as e:
457
+ self.stdout.write(self.style.WARNING(f"Security check failed: {e}"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiwaf
3
- Version: 0.1.9.1.2
3
+ Version: 0.1.9.1.3
4
4
  Summary: AI-powered Web Application Firewall
5
5
  Home-page: https://github.com/aayushgauba/aiwaf
6
6
  Author: Aayush Gauba
@@ -14,6 +14,8 @@ Requires-Dist: numpy>=1.21
14
14
  Requires-Dist: pandas>=1.3
15
15
  Requires-Dist: scikit-learn<2.0,>=1.0
16
16
  Requires-Dist: joblib>=1.1
17
+ Requires-Dist: packaging>=21.0
18
+ Requires-Dist: requests>=2.25.0
17
19
  Dynamic: author
18
20
  Dynamic: home-page
19
21
  Dynamic: license-file
@@ -59,7 +61,11 @@ aiwaf/
59
61
  │ └── dynamic_keywords.json # evolves daily
60
62
  ├── management/
61
63
  │ └── commands/
62
- └── detect_and_train.py # `python manage.py detect_and_train`
64
+ ├── detect_and_train.py # `python manage.py detect_and_train`
65
+ │ ├── check_dependencies.py # `python manage.py check_dependencies`
66
+ │ ├── add_ipexemption.py # `python manage.py add_ipexemption`
67
+ │ ├── aiwaf_reset.py # `python manage.py aiwaf_reset`
68
+ │ └── aiwaf_logging.py # `python manage.py aiwaf_logging`
63
69
  └── LICENSE
64
70
  ```
65
71
 
@@ -104,6 +110,20 @@ aiwaf/
104
110
  - **Captures response times** for better anomaly detection
105
111
  - **Zero configuration** - works out of the box
106
112
 
113
+ - **Smart Training System**
114
+ AI trainer automatically uses the best available data source:
115
+ - **Primary**: Configured access log files (`AIWAF_ACCESS_LOG`)
116
+ - **Fallback**: Database RequestLog model when files unavailable
117
+ - **Seamless switching** between data sources
118
+ - **Enhanced compatibility** with exemption system
119
+
120
+ - **Dependency Management**
121
+ Built-in dependency checker ensures package compatibility:
122
+ - **Version compatibility** checking (NumPy 2.0 vs pandas, etc.)
123
+ - **Missing dependency** detection
124
+ - **Security vulnerability** scanning
125
+ - **Smart upgrade suggestions** with compatibility validation
126
+
107
127
 
108
128
  **Exempt Path & IP Awareness**
109
129
 
@@ -189,6 +209,50 @@ python manage.py aiwaf_reset --blacklist-only
189
209
  python manage.py aiwaf_reset --exemptions-only
190
210
  ```
191
211
 
212
+ ### Checking Dependencies
213
+
214
+ Check your project's dependencies for updates and compatibility issues:
215
+
216
+ ```bash
217
+ # Basic dependency check
218
+ python manage.py check_dependencies
219
+
220
+ # JSON format output
221
+ python manage.py check_dependencies --format json
222
+
223
+ # Include security vulnerability scanning
224
+ python manage.py check_dependencies --check-security
225
+ ```
226
+
227
+ **Features:**
228
+ - ✅ **Parses pyproject.toml and requirements.txt**
229
+ - ✅ **Shows current vs latest versions**
230
+ - ✅ **Checks package compatibility** (NumPy 2.0 vs pandas, etc.)
231
+ - ✅ **Detects missing dependencies**
232
+ - ✅ **Security vulnerability scanning** (requires `safety` package)
233
+ - ✅ **Provides upgrade commands**
234
+
235
+ **Example Output:**
236
+ ```
237
+ 🔍 Checking project dependencies...
238
+
239
+ 📊 Summary: 5 packages checked
240
+ ✅ Up to date: 2
241
+ ⚠️ Outdated: 2
242
+ ❌ Not installed: 0
243
+
244
+ ⚠️ OUTDATED PACKAGES:
245
+ ────────────────────────────────────────
246
+ 📦 pandas 1.3.5 → 2.2.2 (constraint: pandas>=1.3)
247
+ 📦 numpy 1.21.0 → 1.26.4 (constraint: numpy>=1.21)
248
+
249
+ 🔍 Checking package compatibility...
250
+ ✅ All packages appear to be compatible!
251
+
252
+ 💡 To update outdated packages, run:
253
+ pip install --upgrade pandas numpy
254
+ ```
255
+
192
256
  This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
193
257
 
194
258
  - **Daily Retraining**
@@ -15,6 +15,7 @@ aiwaf/management/commands/add_ipexemption.py,sha256=sSf3d9hGK9RqqlBYkCrnrd8KZWGT
15
15
  aiwaf/management/commands/aiwaf_diagnose.py,sha256=nXFRhq66N4QC3e4scYJ2sUngJce-0yDxtBO3R2BllRM,6134
16
16
  aiwaf/management/commands/aiwaf_logging.py,sha256=FCIqULn2tii2vD9VxL7vk3PV4k4vr7kaA00KyaCExYY,7692
17
17
  aiwaf/management/commands/aiwaf_reset.py,sha256=0FIBqpZS8xgFFvAKJ-0zAC_-QNQwRkOHpXb8N-OdFr8,3740
18
+ aiwaf/management/commands/check_dependencies.py,sha256=3PFQ6ut3-_I8_P6pfCR9EiSu_lkerJEbAomZoIdSJVs,18978
18
19
  aiwaf/management/commands/clear_cache.py,sha256=cdnuTgxkhKLqT_6k6yTcEBlREovNRQxAE51ceXlGYMA,647
19
20
  aiwaf/management/commands/debug_csv.py,sha256=Lddqp37mIn0zdvHf4GbuNTWYyJ5h8bumDcGmFSAioi0,6801
20
21
  aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMmFEkwwrcDsiM166K0,269
@@ -26,8 +27,8 @@ aiwaf/management/commands/test_exemption_fix.py,sha256=ngyGaHUCmQQ6y--6j4q1viZJt
26
27
  aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
27
28
  aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
29
  aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
29
- aiwaf-0.1.9.1.2.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
30
- aiwaf-0.1.9.1.2.dist-info/METADATA,sha256=_whzaxN1jPkWXyncKhKvrkZqV2GnI34v3lpyBneU4FM,13763
31
- aiwaf-0.1.9.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- aiwaf-0.1.9.1.2.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
33
- aiwaf-0.1.9.1.2.dist-info/RECORD,,
30
+ aiwaf-0.1.9.1.3.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
31
+ aiwaf-0.1.9.1.3.dist-info/METADATA,sha256=3ZoV9qFe4ipGDy9GVFueua_-CuXSbHHRD_XXEGksb0c,16044
32
+ aiwaf-0.1.9.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ aiwaf-0.1.9.1.3.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
34
+ aiwaf-0.1.9.1.3.dist-info/RECORD,,