ducky-python-module 0.1.5__tar.gz → 1.0.1__tar.gz
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.
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/PKG-INFO +2 -2
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/README.md +1 -1
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/pyproject.toml +1 -1
- ducky_python_module-1.0.1/src/ducky/__init__.py +2 -0
- ducky_python_module-1.0.1/src/ducky/backend.py +16 -0
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky/main.py +321 -9
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/PKG-INFO +2 -2
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/SOURCES.txt +1 -0
- ducky_python_module-0.1.5/src/ducky/__init__.py +0 -7
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/LICENSE.txt +0 -0
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/setup.cfg +0 -0
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/dependency_links.txt +0 -0
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/requires.txt +0 -0
- {ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducky-python-module
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Ducky Python module
|
|
5
5
|
Author: Tom
|
|
6
6
|
License: Creative Commons Attribution-NonCommercial 4.0 International Public
|
|
@@ -345,7 +345,7 @@ Requires-Dist: numpy>=1.26.0
|
|
|
345
345
|
Dynamic: license-file
|
|
346
346
|
|
|
347
347
|
# Ducky-Python-Module
|
|
348
|
-
A collection of tools for python
|
|
348
|
+
A collection of QOL and specialised tools for python
|
|
349
349
|
Still in progress and will be releasing fully soon
|
|
350
350
|
Credit:
|
|
351
351
|
Turtle library has been used and modified. I did not make it and I did include the text from the original library explaining rights to the Turtle program which I am not allowed to remove.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Ducky-Python-Module
|
|
2
|
-
A collection of tools for python
|
|
2
|
+
A collection of QOL and specialised tools for python
|
|
3
3
|
Still in progress and will be releasing fully soon
|
|
4
4
|
Credit:
|
|
5
5
|
Turtle library has been used and modified. I did not make it and I did include the text from the original library explaining rights to the Turtle program which I am not allowed to remove.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ducky-python-module"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "1.0.1" #changed version number already. Last pypi release uses 1.0.0
|
|
8
8
|
description = "Ducky Python module"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import numpy as _numpy
|
|
2
|
+
_backend = _numpy
|
|
3
|
+
def set_backend(name: str):
|
|
4
|
+
global _backend
|
|
5
|
+
if name == "numpy":
|
|
6
|
+
import numpy
|
|
7
|
+
_backend = numpy
|
|
8
|
+
elif name == "cupy":
|
|
9
|
+
import cupy
|
|
10
|
+
_backend = cupy
|
|
11
|
+
|
|
12
|
+
else:
|
|
13
|
+
raise ValueError("Backend must be numpy or cupy")
|
|
14
|
+
|
|
15
|
+
def backend():
|
|
16
|
+
return _backend
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import random, time, os, nltk, tkinter as TK, types, math, inspect, sys, requests, json,
|
|
1
|
+
import random, time, os, nltk, tkinter as TK, types, math, inspect, sys, requests, json, ast, logging, abc
|
|
2
2
|
from os.path import isfile, split, join
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from tkinter import simpledialog
|
|
5
5
|
from turtle import Turtle
|
|
6
6
|
from nltk.tokenize import word_tokenize
|
|
7
|
+
from ducky.backend import backend
|
|
7
8
|
from nltk.stem import LancasterStemmer
|
|
8
|
-
logging.basicConfig(level=logging.INFO)
|
|
9
9
|
|
|
10
10
|
#matrix functions
|
|
11
11
|
|
|
12
12
|
def matrix(x,y,fill=0):
|
|
13
|
-
mat = [[fill for
|
|
13
|
+
mat = [[fill for _ in range(x)] for _ in range(y)]
|
|
14
14
|
return mat
|
|
15
15
|
|
|
16
16
|
def draw(mat,x,y,val=1,errors=True): #draws a certain value to all specified positions
|
|
@@ -72,7 +72,34 @@ def render(mat):
|
|
|
72
72
|
print("------------------------------------------------------")
|
|
73
73
|
|
|
74
74
|
#my functions
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
def download_nltk():
|
|
77
|
+
nltk.download('punkt', quiet=True)
|
|
78
|
+
|
|
79
|
+
def download_to_path(url, path, file_name, quiet=False):
|
|
80
|
+
try:
|
|
81
|
+
r = requests.get(url)
|
|
82
|
+
r.raise_for_status()
|
|
83
|
+
|
|
84
|
+
with open(f"{path}/{file_name}", "wb") as f:
|
|
85
|
+
f.write(r.content)
|
|
86
|
+
|
|
87
|
+
except requests.exceptions.RequestException as e:
|
|
88
|
+
if not quiet:
|
|
89
|
+
print("Download failed:", e)
|
|
90
|
+
|
|
91
|
+
def low_ram_download(url,path,file_name,quiet=False):
|
|
92
|
+
try:
|
|
93
|
+
with requests.get(url, stream=True) as r:
|
|
94
|
+
r.raise_for_status()
|
|
95
|
+
with open(f"{path}/{file_name}", "wb") as f:
|
|
96
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
97
|
+
if chunk:
|
|
98
|
+
f.write(chunk)
|
|
99
|
+
except requests.exceptions.RequestException as e:
|
|
100
|
+
if not quiet:
|
|
101
|
+
print("Download failed:", e)
|
|
102
|
+
|
|
76
103
|
def rand_int(minimum,maximum):
|
|
77
104
|
return random.randint(minimum,maximum)
|
|
78
105
|
|
|
@@ -91,7 +118,7 @@ def current_minute():
|
|
|
91
118
|
def current_second():
|
|
92
119
|
return time.localtime()[5]
|
|
93
120
|
|
|
94
|
-
def
|
|
121
|
+
def tokenise(string):
|
|
95
122
|
tokens = word_tokenize(string)
|
|
96
123
|
return tokens
|
|
97
124
|
|
|
@@ -100,16 +127,16 @@ def stem(string):
|
|
|
100
127
|
return lancaster.stem(string)
|
|
101
128
|
|
|
102
129
|
def array(lst):
|
|
103
|
-
return
|
|
130
|
+
return backend().array(lst)
|
|
104
131
|
|
|
105
132
|
def dot_product(mat1,mat2):
|
|
106
|
-
return
|
|
133
|
+
return backend().dot(mat1,mat2)
|
|
107
134
|
|
|
108
135
|
def scale_matrices(mat,val):
|
|
109
136
|
return mat*val
|
|
110
137
|
|
|
111
138
|
def average_matrices(mat):
|
|
112
|
-
return
|
|
139
|
+
return backend().average(mat)
|
|
113
140
|
|
|
114
141
|
def calc(param):
|
|
115
142
|
try:
|
|
@@ -397,7 +424,7 @@ class Chatbot:
|
|
|
397
424
|
|
|
398
425
|
def reply(self,reply_input):
|
|
399
426
|
score = [0, 0, 0, 0, 0]
|
|
400
|
-
tokens =
|
|
427
|
+
tokens = tokenise(reply_input)
|
|
401
428
|
stemmed_words = []
|
|
402
429
|
for token in tokens:
|
|
403
430
|
stemmed_words.append((stem(token)))
|
|
@@ -432,6 +459,291 @@ class Chatbot:
|
|
|
432
459
|
return 1
|
|
433
460
|
return None
|
|
434
461
|
|
|
462
|
+
|
|
463
|
+
#AI class templates
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
import abc
|
|
467
|
+
import random
|
|
468
|
+
|
|
469
|
+
import ducky
|
|
470
|
+
import numpy
|
|
471
|
+
import numpy
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class ActivationFunctions:
|
|
475
|
+
TANH = 0
|
|
476
|
+
|
|
477
|
+
@staticmethod
|
|
478
|
+
def __tanh(x: numpy.ndarray):
|
|
479
|
+
return numpy.tanh(x)
|
|
480
|
+
|
|
481
|
+
@staticmethod
|
|
482
|
+
def __tanh_derived(x: numpy.ndarray):
|
|
483
|
+
return 1 - (numpy.tanh(x) ** 2)
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def use_tanh():
|
|
487
|
+
return ActivationFunctions.TANH, ActivationFunctions.__tanh, ActivationFunctions.__tanh_derived
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class CostFunctions:
|
|
491
|
+
MSE = 0
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
def __mse(actual: numpy.ndarray, prediction: numpy.ndarray):
|
|
495
|
+
return numpy.mean(numpy.power(actual - prediction, 2))
|
|
496
|
+
|
|
497
|
+
@staticmethod
|
|
498
|
+
def __mse_derived(actual: numpy.ndarray, prediction: numpy.ndarray):
|
|
499
|
+
return (2 * (prediction - actual)) / actual.size
|
|
500
|
+
|
|
501
|
+
@staticmethod
|
|
502
|
+
def use_mse():
|
|
503
|
+
return CostFunctions.MSE, CostFunctions.__mse, CostFunctions.__mse_derived
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class Layer(abc.ABC):
|
|
507
|
+
def __init__(self):
|
|
508
|
+
self.input = None
|
|
509
|
+
self.output = None
|
|
510
|
+
|
|
511
|
+
@abc.abstractmethod
|
|
512
|
+
def feed_forward(self, input_values: numpy.ndarray):
|
|
513
|
+
pass
|
|
514
|
+
|
|
515
|
+
@abc.abstractmethod
|
|
516
|
+
def back_propagate(self, output_error: numpy.ndarray, learning_rate: float):
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
@abc.abstractmethod
|
|
520
|
+
def copy(self):
|
|
521
|
+
pass
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class FullyConnectedLayer(Layer):
|
|
525
|
+
def __init__(self, input_size: int, output_size: int):
|
|
526
|
+
Layer.__init__(self)
|
|
527
|
+
random_bound = 1.0 / numpy.sqrt(input_size)
|
|
528
|
+
self.weights = numpy.random.uniform(-random_bound, random_bound, (input_size, output_size))
|
|
529
|
+
self.bias = numpy.zeros((1, output_size))
|
|
530
|
+
self.input_size = input_size
|
|
531
|
+
self.output_size = output_size
|
|
532
|
+
|
|
533
|
+
def feed_forward(self, input_values):
|
|
534
|
+
self.input = input_values
|
|
535
|
+
self.output = numpy.dot(self.input, self.weights) + self.bias
|
|
536
|
+
return self.output
|
|
537
|
+
|
|
538
|
+
def back_propagate(self, output_error, learning_rate):
|
|
539
|
+
input_error = numpy.dot(output_error, self.weights.T)
|
|
540
|
+
weights_error = numpy.dot(self.input.T, output_error)
|
|
541
|
+
self.weights -= (learning_rate * weights_error)
|
|
542
|
+
self.bias -= (learning_rate * numpy.mean(output_error, axis=0, keepdims=True))
|
|
543
|
+
return input_error
|
|
544
|
+
|
|
545
|
+
def copy(self):
|
|
546
|
+
copied_layer = FullyConnectedLayer(self.input_size, self.output_size)
|
|
547
|
+
weights = self.weights.copy()
|
|
548
|
+
bias = self.bias.copy()
|
|
549
|
+
copied_layer.weights = weights
|
|
550
|
+
copied_layer.bias = bias
|
|
551
|
+
return copied_layer
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class ActivationLayer(Layer):
|
|
555
|
+
def __init__(self, activation_id, activation, activation_derived):
|
|
556
|
+
Layer.__init__(self)
|
|
557
|
+
self.activation_id = activation_id
|
|
558
|
+
self.activation = activation
|
|
559
|
+
self.activation_derived = activation_derived
|
|
560
|
+
|
|
561
|
+
def feed_forward(self, input_values):
|
|
562
|
+
self.input = input_values
|
|
563
|
+
self.output = self.activation(self.input)
|
|
564
|
+
return self.output
|
|
565
|
+
|
|
566
|
+
def back_propagate(self, output_error, learning_rate):
|
|
567
|
+
return self.activation_derived(self.input) * output_error
|
|
568
|
+
|
|
569
|
+
def copy(self):
|
|
570
|
+
copied_layer = ActivationLayer(self.activation_id, self.activation, self.activation_derived)
|
|
571
|
+
return copied_layer
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
class NeuralNetwork:
|
|
575
|
+
def __init__(self, cost_function_id, cost_function, cost_function_derived):
|
|
576
|
+
self.layers = []
|
|
577
|
+
self.cost_function_id = cost_function_id
|
|
578
|
+
self.cost_function = cost_function
|
|
579
|
+
self.cost_function_derived = cost_function_derived
|
|
580
|
+
|
|
581
|
+
def add_layer(self, layer):
|
|
582
|
+
self.layers.append(layer)
|
|
583
|
+
|
|
584
|
+
def predict(self, input_data):
|
|
585
|
+
output = input_data
|
|
586
|
+
for layer in self.layers:
|
|
587
|
+
output = layer.feed_forward(output)
|
|
588
|
+
return output
|
|
589
|
+
|
|
590
|
+
def train(self, train_input, train_output, n_epochs, batch_size, learning_rate):
|
|
591
|
+
for epoch in range(n_epochs):
|
|
592
|
+
batch_index = 0
|
|
593
|
+
while batch_index < train_input.shape[0]:
|
|
594
|
+
if batch_index + batch_size < train_input.shape[0]:
|
|
595
|
+
output = train_input[batch_index:batch_index + batch_size, ]
|
|
596
|
+
actual = train_output[batch_index:batch_index + batch_size, ]
|
|
597
|
+
else:
|
|
598
|
+
output = train_input[batch_index:, ]
|
|
599
|
+
actual = train_output[batch_index:, ]
|
|
600
|
+
for layer in self.layers:
|
|
601
|
+
output = layer.feed_forward(output)
|
|
602
|
+
cost = self.cost_function(actual, output)
|
|
603
|
+
error = self.cost_function_derived(actual, output)
|
|
604
|
+
for layer in reversed(self.layers):
|
|
605
|
+
error = layer.back_propagate(error, learning_rate)
|
|
606
|
+
print(f"Epoch {epoch + 1}/{n_epochs} | Batch = {(batch_index + 1) * batch_size} | Cost = {cost}")
|
|
607
|
+
batch_index += batch_size
|
|
608
|
+
|
|
609
|
+
def save(self, file_path):
|
|
610
|
+
with open(f"{file_path}.nn", "w") as save_file:
|
|
611
|
+
save_file.write(f"{self.cost_function_id}\n")
|
|
612
|
+
for index, layer in enumerate(self.layers):
|
|
613
|
+
if isinstance(layer, FullyConnectedLayer):
|
|
614
|
+
save_file.write(f"{index}|F|{layer.weights.shape}|{layer.bias.shape}\n")
|
|
615
|
+
numpy.save(f"{file_path}_{index}_w", layer.weights)
|
|
616
|
+
numpy.save(f"{file_path}_{index}_b", layer.bias)
|
|
617
|
+
elif isinstance(layer, ActivationLayer):
|
|
618
|
+
save_file.write(f"{index}|A|{layer.activation_id}\n")
|
|
619
|
+
|
|
620
|
+
@staticmethod
|
|
621
|
+
def load_from_file(file_path):
|
|
622
|
+
with open(f"{file_path}.nn", "r") as load_file:
|
|
623
|
+
lines = load_file.readlines()
|
|
624
|
+
cost_function_id = int(lines[0])
|
|
625
|
+
if cost_function_id == CostFunctions.MSE:
|
|
626
|
+
neural_network = NeuralNetwork(*CostFunctions.use_mse())
|
|
627
|
+
else:
|
|
628
|
+
neural_network = NeuralNetwork(*CostFunctions.use_mse())
|
|
629
|
+
for line in lines[1:]:
|
|
630
|
+
if line != "":
|
|
631
|
+
parts = line.split("|")
|
|
632
|
+
if parts[1] == "F":
|
|
633
|
+
weights = numpy.load(f"{file_path}_{int(parts[0])}_w.npy")
|
|
634
|
+
bias = numpy.load(f"{file_path}_{int(parts[0])}_b.npy")
|
|
635
|
+
layer = FullyConnectedLayer(weights.shape[0], weights.shape[1])
|
|
636
|
+
layer.weights = weights
|
|
637
|
+
layer.bias = bias
|
|
638
|
+
neural_network.add_layer(layer)
|
|
639
|
+
elif parts[1] == "A":
|
|
640
|
+
activation_id = int(parts[2])
|
|
641
|
+
if activation_id == ActivationFunctions.TANH:
|
|
642
|
+
neural_network.add_layer(ActivationLayer(*ActivationFunctions.use_tanh()))
|
|
643
|
+
else:
|
|
644
|
+
neural_network.add_layer(ActivationLayer(*ActivationFunctions.use_tanh()))
|
|
645
|
+
return neural_network
|
|
646
|
+
|
|
647
|
+
def copy_network(self):
|
|
648
|
+
copied_network = NeuralNetwork(self.cost_function_id, self.cost_function, self.cost_function_derived)
|
|
649
|
+
for layer in self.layers:
|
|
650
|
+
copied_network.add_layer(layer.copy())
|
|
651
|
+
return copied_network
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class Agent:
|
|
655
|
+
def __init__(self):
|
|
656
|
+
self.gamma = 0.99
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class QTable:
|
|
660
|
+
def __init__(self, state, actions):
|
|
661
|
+
self.table = numpy.zeros((state,actions))
|
|
662
|
+
self.actions = actions
|
|
663
|
+
self.state = state
|
|
664
|
+
|
|
665
|
+
def update_table(self, state_row, action, value):
|
|
666
|
+
self.table[state_row, action] += value
|
|
667
|
+
|
|
668
|
+
def get_action(self, state_row):
|
|
669
|
+
return numpy.argmax(self.table[state_row])
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
class QTableAgent(Agent):
|
|
673
|
+
def __init__(self, table, num_possible_values):
|
|
674
|
+
Agent.__init__(self)
|
|
675
|
+
self.table = table
|
|
676
|
+
self.num_possible_values = num_possible_values
|
|
677
|
+
self.epsilon = 1
|
|
678
|
+
|
|
679
|
+
def state_to_row(self,state):
|
|
680
|
+
return int("".join([str(s) for s in state.astype(int)]), self.num_possible_values)
|
|
681
|
+
|
|
682
|
+
def predict(self, state):
|
|
683
|
+
self.epsilon *= 0.99
|
|
684
|
+
if random.random() > self.epsilon:
|
|
685
|
+
return self.table.get_action(self.state_to_row(state))
|
|
686
|
+
else:
|
|
687
|
+
return random.randint(0,self.table.actions-1)
|
|
688
|
+
|
|
689
|
+
def train(self, state, action, reward):
|
|
690
|
+
self.table.update_table(self.state_to_row(state), action, reward)
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
class DeepQAgent(Agent):
|
|
694
|
+
def __init__(self, layer_sizes):
|
|
695
|
+
Agent.__init__(self)
|
|
696
|
+
self.memory = Memory()
|
|
697
|
+
self.counter = 0
|
|
698
|
+
try:
|
|
699
|
+
self.neural_network = NeuralNetwork.load_from_file("deepq-nn")
|
|
700
|
+
except FileNotFoundError:
|
|
701
|
+
self.neural_network = NeuralNetwork(*CostFunctions.use_mse())
|
|
702
|
+
for i, size in enumerate(layer_sizes[:-1]):
|
|
703
|
+
self.neural_network.add_layer(FullyConnectedLayer(size, layer_sizes[i+1]))
|
|
704
|
+
self.neural_network.add_layer(ActivationLayer(*ActivationFunctions.use_tanh()))
|
|
705
|
+
|
|
706
|
+
def predict(self, state):
|
|
707
|
+
return int(self.neural_network.predict(state)[0].argmax())
|
|
708
|
+
|
|
709
|
+
def train(self, discount_rate):
|
|
710
|
+
training_input_data = []
|
|
711
|
+
training_output_data = []
|
|
712
|
+
for i in range(len(self.memory.states)):
|
|
713
|
+
prediction = self.neural_network.predict(self.memory.states[i])
|
|
714
|
+
prediction[0] += self.memory.rewards[i - 1]
|
|
715
|
+
if i > 0:
|
|
716
|
+
prediction[0] += self.memory.rewards[i-1] * discount_rate
|
|
717
|
+
training_input_data = numpy.array(training_input_data)
|
|
718
|
+
training_output_data = numpy.array(training_output_data)
|
|
719
|
+
self.neural_network.train(training_input_data, training_output_data, n_epochs=500, batch_size=10, learning_rate=0.1)
|
|
720
|
+
if self.counter >= 10:
|
|
721
|
+
self.neural_network.save("deepq-nn")
|
|
722
|
+
self.counter = 0
|
|
723
|
+
else:
|
|
724
|
+
self.counter += 1
|
|
725
|
+
|
|
726
|
+
def update_memory(self, state, action, reward):
|
|
727
|
+
self.memory.update(state, action, reward)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
class Memory:
|
|
731
|
+
def __init__(self):
|
|
732
|
+
self.states = []
|
|
733
|
+
self.actions = []
|
|
734
|
+
self.rewards = []
|
|
735
|
+
|
|
736
|
+
def update(self, state, action, reward):
|
|
737
|
+
self.states.append(state)
|
|
738
|
+
self.actions.append(action)
|
|
739
|
+
self.rewards.append(reward)
|
|
740
|
+
|
|
741
|
+
def clear(self):
|
|
742
|
+
self.states = []
|
|
743
|
+
self.actions = []
|
|
744
|
+
self.rewards = []
|
|
745
|
+
|
|
746
|
+
|
|
435
747
|
#turtlel
|
|
436
748
|
|
|
437
749
|
def dots(turt_name, number, random_colours=True, colours=None, minimum=20, maximum=100):
|
{ducky_python_module-0.1.5 → ducky_python_module-1.0.1}/src/ducky_python_module.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducky-python-module
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Ducky Python module
|
|
5
5
|
Author: Tom
|
|
6
6
|
License: Creative Commons Attribution-NonCommercial 4.0 International Public
|
|
@@ -345,7 +345,7 @@ Requires-Dist: numpy>=1.26.0
|
|
|
345
345
|
Dynamic: license-file
|
|
346
346
|
|
|
347
347
|
# Ducky-Python-Module
|
|
348
|
-
A collection of tools for python
|
|
348
|
+
A collection of QOL and specialised tools for python
|
|
349
349
|
Still in progress and will be releasing fully soon
|
|
350
350
|
Credit:
|
|
351
351
|
Turtle library has been used and modified. I did not make it and I did include the text from the original library explaining rights to the Turtle program which I am not allowed to remove.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|